Our asprintf and vasprintf have different prototypes than the glibc functions. This causes trouble when we want to share barebox code with userspace code. Change the prototypes for (v)asprintf to match the glibc prototypes. Since the current (v)asprintf are convenient to use change the existing functions to b(v)asprintf. Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
515 lines
10 KiB
C
515 lines
10 KiB
C
/*
|
|
* Copyright (c) 2013 Jean-Christophe PLAGNIOL-VILLARD <plagnioj@jcrosoft.com>
|
|
*
|
|
* Simple update file format developed for Somfy, tools and library are
|
|
* available under LGPLv2 (https://www.gitorious.org/libbpk).
|
|
*
|
|
* under GPLv2 ONLY
|
|
*/
|
|
|
|
#include <common.h>
|
|
#include <driver.h>
|
|
#include <fs.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <malloc.h>
|
|
#include <init.h>
|
|
#include <crc.h>
|
|
#include <linux/stat.h>
|
|
#include <linux/err.h>
|
|
#include <bpkfs.h>
|
|
#include <libgen.h>
|
|
|
|
static bool bpkfs_is_crc_file(struct bpkfs_handle_data *d)
|
|
{
|
|
return d->type & (1 << 31);
|
|
}
|
|
|
|
const char* bpkfs_type_to_str(uint32_t type)
|
|
{
|
|
switch (type) {
|
|
case BPKFS_TYPE_BL:
|
|
return "bootloader";
|
|
case BPKFS_TYPE_BLV:
|
|
return "bootloader_version";
|
|
case BPKFS_TYPE_DSC:
|
|
return "description.gz";
|
|
case BPKFS_TYPE_KER:
|
|
return "kernel";
|
|
case BPKFS_TYPE_RFS:
|
|
return "rootfs";
|
|
case BPKFS_TYPE_FMV:
|
|
return "firmware_version";
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static struct bpkfs_handle_hw *bpkfs_get_by_hw_id(
|
|
struct bpkfs_handle *handle, uint32_t hw_id)
|
|
{
|
|
struct bpkfs_handle_hw *h;
|
|
|
|
list_for_each_entry(h, &handle->list, list_hw_id) {
|
|
if (h->hw_id == hw_id)
|
|
return h;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static struct bpkfs_handle_hw *bpkfs_hw_id_get_by_name(
|
|
struct bpkfs_handle *handle, const char *name)
|
|
{
|
|
struct bpkfs_handle_hw *h;
|
|
|
|
if (!name)
|
|
return NULL;
|
|
|
|
list_for_each_entry(h, &handle->list, list_hw_id) {
|
|
if (strcmp(h->name, name) == 0)
|
|
return h;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static struct bpkfs_handle_data *bpkfs_data_get_by_name(
|
|
struct bpkfs_handle_hw *h, const char *name)
|
|
{
|
|
struct bpkfs_handle_data *d;
|
|
|
|
if (!name)
|
|
return NULL;
|
|
|
|
list_for_each_entry(d, &h->list_data, list) {
|
|
if (strcmp(d->name, name) == 0)
|
|
return d;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static struct bpkfs_handle_hw *bpkfs_get_or_add_hw_id(
|
|
struct bpkfs_handle *handle, uint32_t hw_id)
|
|
{
|
|
struct bpkfs_handle_hw *h;
|
|
|
|
h = bpkfs_get_by_hw_id(handle, hw_id);
|
|
if (h)
|
|
return h;
|
|
|
|
h = xzalloc(sizeof(*h));
|
|
|
|
INIT_LIST_HEAD(&h->list_data);
|
|
h->hw_id = hw_id;
|
|
h->name = basprintf("hw_id_%x", hw_id);
|
|
list_add_tail(&h->list_hw_id, &handle->list);
|
|
|
|
return h;
|
|
}
|
|
|
|
static struct bpkfs_handle_data *bpkfs_get_by_type(
|
|
struct bpkfs_handle *handle, uint32_t hw_id, uint32_t type)
|
|
{
|
|
struct bpkfs_handle_data *d;
|
|
struct bpkfs_handle_hw *h;
|
|
|
|
h = bpkfs_get_by_hw_id(handle, hw_id);
|
|
if (!h)
|
|
return NULL;
|
|
|
|
list_for_each_entry(d, &h->list_data, list) {
|
|
if (d->type == type)
|
|
return d;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static int bpkfs_open(struct device_d *dev, FILE *f, const char *filename)
|
|
{
|
|
struct bpkfs_handle *priv = dev->priv;
|
|
struct bpkfs_handle_data *d;
|
|
struct bpkfs_handle_hw *h;
|
|
char *dir, *file;
|
|
int ret = -EINVAL;
|
|
char *tmp = xstrdup(filename);
|
|
char *tmp2 = xstrdup(filename);
|
|
|
|
dir = dirname(tmp);
|
|
|
|
if (dir[0] == '/')
|
|
dir++;
|
|
|
|
h = bpkfs_hw_id_get_by_name(priv, dir);
|
|
if (!h)
|
|
goto out;
|
|
|
|
file = basename(tmp2);
|
|
d = bpkfs_data_get_by_name(h, file);
|
|
if (!d)
|
|
goto out;
|
|
|
|
if (!bpkfs_is_crc_file(d)) {
|
|
d->fd = open(priv->filename, O_RDONLY);
|
|
if (d->fd < 0) {
|
|
ret = d->fd;
|
|
goto out;
|
|
}
|
|
|
|
lseek(d->fd, d->offset, SEEK_SET);
|
|
}
|
|
|
|
f->size = d->size;
|
|
f->priv = d;
|
|
ret = 0;
|
|
|
|
out:
|
|
free(tmp);
|
|
free(tmp2);
|
|
return ret;
|
|
}
|
|
|
|
static int bpkfs_close(struct device_d *dev, FILE *file)
|
|
{
|
|
struct bpkfs_handle_data *d = file->priv;
|
|
|
|
close(d->fd);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int bpkfs_read(struct device_d *dev, FILE *file, void *buf, size_t insize)
|
|
{
|
|
struct bpkfs_handle_data *d = file->priv;
|
|
|
|
if (bpkfs_is_crc_file(d)) {
|
|
memcpy(buf, &d->data[d->pos], insize);
|
|
return insize;
|
|
} else {
|
|
return read(d->fd, buf, insize);
|
|
}
|
|
}
|
|
|
|
static loff_t bpkfs_lseek(struct device_d *dev, FILE *file, loff_t pos)
|
|
{
|
|
struct bpkfs_handle_data *d = file->priv;
|
|
|
|
if (!bpkfs_is_crc_file(d))
|
|
lseek(d->fd, d->offset + pos, SEEK_SET);
|
|
|
|
d->pos = pos;
|
|
|
|
return pos;
|
|
}
|
|
|
|
struct somfy_readdir {
|
|
struct bpkfs_handle_hw *h;
|
|
struct bpkfs_handle_data *d;
|
|
|
|
DIR dir;
|
|
};
|
|
|
|
static DIR *bpkfs_opendir(struct device_d *dev, const char *pathname)
|
|
{
|
|
struct bpkfs_handle *priv = dev->priv;
|
|
struct somfy_readdir *sdir;
|
|
DIR *dir;
|
|
|
|
sdir = xzalloc(sizeof(*sdir));
|
|
dir = &sdir->dir;
|
|
dir->priv = sdir;
|
|
|
|
if (pathname[0] == '/')
|
|
pathname++;
|
|
|
|
if (!strlen(pathname)) {
|
|
if (list_empty(&priv->list))
|
|
return dir;
|
|
|
|
sdir->h = list_first_entry(&priv->list,
|
|
struct bpkfs_handle_hw, list_hw_id);
|
|
} else {
|
|
sdir->h = bpkfs_hw_id_get_by_name(priv, pathname);
|
|
if (!sdir->h || list_empty(&sdir->h->list_data))
|
|
return dir;
|
|
|
|
sdir->d = list_first_entry(&sdir->h->list_data,
|
|
struct bpkfs_handle_data, list);
|
|
}
|
|
|
|
return dir;
|
|
}
|
|
|
|
static struct dirent *bpkfs_readdir(struct device_d *dev, DIR *dir)
|
|
{
|
|
struct bpkfs_handle *priv = dev->priv;
|
|
struct somfy_readdir *sdir = dir->priv;
|
|
struct bpkfs_handle_hw *h = sdir->h;
|
|
struct bpkfs_handle_data *d = sdir->d;
|
|
|
|
if (!h)
|
|
return NULL;
|
|
|
|
if (!d) {
|
|
if (&h->list_hw_id == &priv->list)
|
|
return NULL;
|
|
|
|
strcpy(dir->d.d_name, h->name);
|
|
sdir->h = list_entry(h->list_hw_id.next, struct bpkfs_handle_hw, list_hw_id);
|
|
} else {
|
|
if (&d->list == &h->list_data)
|
|
return NULL;
|
|
|
|
strcpy(dir->d.d_name, d->name);
|
|
sdir->d = list_entry(d->list.next, struct bpkfs_handle_data, list);
|
|
}
|
|
|
|
return &dir->d;
|
|
}
|
|
|
|
static int bpkfs_closedir(struct device_d *dev, DIR *dir)
|
|
{
|
|
struct somfy_readdir *sdir = dir->priv;
|
|
|
|
free(sdir);
|
|
return 0;
|
|
}
|
|
|
|
static int bpkfs_stat(struct device_d *dev, const char *filename, struct stat *s)
|
|
{
|
|
struct bpkfs_handle *priv = dev->priv;
|
|
struct bpkfs_handle_data *d;
|
|
struct bpkfs_handle_hw *h;
|
|
char *dir, *file;
|
|
int ret = -EINVAL;
|
|
char *tmp = xstrdup(filename);
|
|
char *tmp2 = xstrdup(filename);
|
|
|
|
dir = dirname(tmp);
|
|
|
|
if (filename[0] == '/')
|
|
filename++;
|
|
|
|
if (dir[0] == '/')
|
|
dir++;
|
|
|
|
if (!strlen(dir)) {
|
|
h = bpkfs_hw_id_get_by_name(priv, filename);
|
|
if (!h)
|
|
goto out;
|
|
|
|
s->st_size = strlen(filename);
|
|
s->st_mode = S_IFDIR | S_IRWXU | S_IRWXG | S_IRWXO;
|
|
ret = 0;
|
|
goto out;
|
|
}
|
|
h = bpkfs_hw_id_get_by_name(priv, dir);
|
|
if (!h)
|
|
goto out;
|
|
|
|
file = basename(tmp2);
|
|
d = bpkfs_data_get_by_name(h, file);
|
|
if (!d)
|
|
goto out;
|
|
|
|
s->st_size = d->size;
|
|
s->st_mode = S_IFREG | S_IRWXU | S_IRWXG | S_IRWXO;
|
|
|
|
ret = 0;
|
|
|
|
out:
|
|
free(tmp);
|
|
free(tmp2);
|
|
return ret;
|
|
}
|
|
|
|
static void bpkfs_remove_data(struct bpkfs_handle_hw *h)
|
|
{
|
|
struct bpkfs_handle_data *d, *tmp;
|
|
|
|
list_for_each_entry_safe(d, tmp, &h->list_data, list) {
|
|
free(d->name);
|
|
free(d);
|
|
}
|
|
}
|
|
|
|
static void bpkfs_remove(struct device_d *dev)
|
|
{
|
|
struct bpkfs_handle *priv = dev->priv;
|
|
struct bpkfs_handle_hw *h, *tmp;
|
|
|
|
list_for_each_entry_safe(h, tmp, &priv->list, list_hw_id) {
|
|
bpkfs_remove_data(h);
|
|
free(h->name);
|
|
free(h);
|
|
}
|
|
|
|
free(priv);
|
|
}
|
|
|
|
static int bpkfs_probe(struct device_d *dev)
|
|
{
|
|
struct fs_device_d *fsdev = dev_to_fs_device(dev);
|
|
struct bpkfs_handle *priv;
|
|
struct bpkfs_header *header;
|
|
struct bpkfs_data_header data_header;
|
|
int ret = 0;
|
|
uint32_t checksum, crc;
|
|
uint64_t size;
|
|
int i;
|
|
size_t offset = 0;
|
|
char *buf;
|
|
int fd;
|
|
|
|
priv = xzalloc(sizeof(struct bpkfs_handle));
|
|
INIT_LIST_HEAD(&priv->list);
|
|
buf = xmalloc(2048);
|
|
dev->priv = priv;
|
|
|
|
priv->filename = fsdev->backingstore;
|
|
dev_dbg(dev, "mount: %s\n", fsdev->backingstore);
|
|
|
|
fd = open(fsdev->backingstore, O_RDONLY);
|
|
if (fd < 0) {
|
|
ret = fd;
|
|
goto err;
|
|
}
|
|
|
|
header = &priv->header;
|
|
|
|
ret = read(fd, header, sizeof(*header));
|
|
if (ret < 0) {
|
|
dev_err(dev, "could not read: %s (ret = %d)\n", errno_str(), ret);
|
|
goto err;
|
|
}
|
|
|
|
dev_dbg(dev, "header.magic = 0x%x\n", be32_to_cpu(header->magic));
|
|
dev_dbg(dev, "header.version = 0x%x\n", be32_to_cpu(header->version));
|
|
dev_dbg(dev, "header.crc = 0x%x\n", be32_to_cpu(header->crc));
|
|
dev_dbg(dev, "header.size = %llu\n", be64_to_cpu(header->size));
|
|
dev_dbg(dev, "header.spare = %llu\n", be64_to_cpu(header->spare));
|
|
|
|
size = be64_to_cpu(header->size);
|
|
offset += sizeof(*header);
|
|
size -= sizeof(*header);
|
|
|
|
checksum = be32_to_cpu(header->crc);
|
|
header->crc = 0;
|
|
|
|
crc = crc32(0, header, sizeof(*header));
|
|
|
|
for (i = 0; size; i++) {
|
|
struct bpkfs_handle_data *d;
|
|
struct bpkfs_handle_hw *h;
|
|
const char *type;
|
|
|
|
ret = read(fd, &data_header, sizeof(data_header));
|
|
if (ret < 0) {
|
|
dev_err(dev, "could not read: %s\n", errno_str());
|
|
goto err;
|
|
} else if (ret == 0) {
|
|
dev_err(dev, "EOF: to_read %llu\n", size);
|
|
goto err;
|
|
}
|
|
|
|
d = xzalloc(sizeof(*d));
|
|
|
|
crc = crc32(crc, &data_header, sizeof(data_header));
|
|
offset += sizeof(data_header);
|
|
size -= sizeof(data_header);
|
|
|
|
d->type = be32_to_cpu(data_header.type);
|
|
d->hw_id = be32_to_cpu(data_header.hw_id);
|
|
d->size = be64_to_cpu(data_header.size);
|
|
d->offset = offset;
|
|
d->crc = be32_to_cpu(data_header.crc);
|
|
type = bpkfs_type_to_str(d->type);
|
|
|
|
h = bpkfs_get_or_add_hw_id(priv, d->hw_id);
|
|
|
|
if (!type) {
|
|
type = "unknown";
|
|
d->name = basprintf("%s_%08x", type, d->type);
|
|
} else {
|
|
d->name = xstrdup(type);
|
|
}
|
|
|
|
dev_dbg(dev, "%d: type = 0x%x => %s\n", i, d->type, d->name);
|
|
dev_dbg(dev, "%d: size = %llu\n", i, d->size);
|
|
dev_dbg(dev, "%d: offset = %zu\n", i, d->offset);
|
|
|
|
dev_dbg(dev, "%d: hw_id = 0x%x => %s\n", i, h->hw_id, h->name);
|
|
|
|
offset += d->size;
|
|
size -= d->size;
|
|
|
|
if (bpkfs_get_by_type(priv, d->hw_id, d->type)) {
|
|
dev_info(dev, "ignore data %d type %s already present, ignored\n",
|
|
i, type);
|
|
free(d);
|
|
continue;
|
|
}
|
|
|
|
list_add_tail(&d->list, &h->list_data);
|
|
priv->nb_data_entries++;
|
|
|
|
ret = lseek(fd, d->size, SEEK_CUR);
|
|
if (ret < 0) {
|
|
dev_err(dev, "could not seek: %s\n", errno_str());
|
|
goto err;
|
|
}
|
|
|
|
type = d->name;
|
|
d = xzalloc(sizeof(*d));
|
|
d->type = be32_to_cpu(data_header.type);
|
|
d->name = basprintf("%s.crc", type);
|
|
d->type |= (1 << 31);
|
|
d->size = 8;
|
|
sprintf(d->data, "%08x", be32_to_cpu(data_header.crc));
|
|
list_add_tail(&d->list, &h->list_data);
|
|
}
|
|
|
|
if (crc != checksum) {
|
|
dev_err(dev, "invalid crc (0x%x != 0x%x)\n", checksum, crc);
|
|
goto err;
|
|
}
|
|
|
|
close(fd);
|
|
free(buf);
|
|
|
|
return 0;
|
|
|
|
err:
|
|
close(fd);
|
|
free(buf);
|
|
bpkfs_remove(dev);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static struct fs_driver_d bpkfs_driver = {
|
|
.open = bpkfs_open,
|
|
.close = bpkfs_close,
|
|
.read = bpkfs_read,
|
|
.lseek = bpkfs_lseek,
|
|
.opendir = bpkfs_opendir,
|
|
.readdir = bpkfs_readdir,
|
|
.closedir = bpkfs_closedir,
|
|
.stat = bpkfs_stat,
|
|
.flags = 0,
|
|
.type = filetype_bpk,
|
|
.drv = {
|
|
.probe = bpkfs_probe,
|
|
.remove = bpkfs_remove,
|
|
.name = "bpkfs",
|
|
}
|
|
};
|
|
|
|
static int bpkfs_init(void)
|
|
{
|
|
return register_fs_driver(&bpkfs_driver);
|
|
}
|
|
coredevice_initcall(bpkfs_init);
|