6977292a34
Because that's what it is. 'inode' will become confusing once we support real inodes. Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
514 lines
10 KiB
C
514 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 <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 = asprintf("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 = asprintf("%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 = asprintf("%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);
|