9
0
Fork 0
barebox/fs/bpkfs.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->inode = 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->inode;
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->inode;
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->inode;
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);