diff options
Diffstat (limited to 'fs/squashfs/squashfs.c')
-rw-r--r-- | fs/squashfs/squashfs.c | 368 |
1 files changed, 368 insertions, 0 deletions
diff --git a/fs/squashfs/squashfs.c b/fs/squashfs/squashfs.c new file mode 100644 index 0000000000..d00dee6caa --- /dev/null +++ b/fs/squashfs/squashfs.c @@ -0,0 +1,368 @@ +#include <common.h> +#include <malloc.h> +#include <driver.h> +#include <init.h> +#include <errno.h> +#include <fs.h> +#include <xfuncs.h> + +#include <linux/fs.h> +#include <linux/stat.h> +#include <linux/pagemap.h> + +#include "squashfs_fs.h" +#include "squashfs_fs_sb.h" +#include "squashfs_fs_i.h" +#include "squashfs.h" + +char *squashfs_devread(struct squashfs_sb_info *fs, int byte_offset, + int byte_len) +{ + ssize_t size; + char *buf; + + buf = malloc(byte_len); + if (buf == NULL) + return NULL; + + size = cdev_read(fs->cdev, buf, byte_len, byte_offset, 0); + if (size < 0) { + dev_err(fs->dev, "read error: %s\n", + strerror(-size)); + return NULL; + } + + return buf; +} + +static struct inode *duplicate_inode(struct inode *inode) +{ + struct squashfs_inode_info *ei; + ei = malloc(sizeof(struct squashfs_inode_info)); + if (ei == NULL) { + ERROR("Error allocating memory for inode\n"); + return NULL; + } + memcpy(ei, squashfs_i(inode), + sizeof(struct squashfs_inode_info)); + + return &ei->vfs_inode; +} + +static struct inode *squashfs_findfile(struct super_block *sb, + const char *filename, char *buf) +{ + char *next; + char fpath[128]; + char *name = fpath; + struct inode *inode; + struct inode *t_inode = NULL; + + strcpy(fpath, filename); + + /* Remove all leading slashes */ + while (*name == '/') + name++; + + inode = duplicate_inode(sb->s_root->d_inode); + + /* + * Handle root-directory ('/') + */ + if (!name || *name == '\0') + return inode; + + for (;;) { + /* Extract the actual part from the pathname. */ + next = strchr(name, '/'); + if (next) { + /* Remove all leading slashes. */ + while (*next == '/') + *(next++) = '\0'; + } + + t_inode = squashfs_lookup(inode, name, 0); + if (t_inode == NULL) + break; + + /* + * Check if directory with this name exists + */ + + /* Found the node! */ + if (!next || *next == '\0') { + if (buf != NULL) + sprintf(buf, "%s", name); + + free(squashfs_i(inode)); + return t_inode; + } + + name = next; + + free(squashfs_i(inode)); + inode = t_inode; + } + + free(squashfs_i(inode)); + return NULL; +} + +static int squashfs_probe(struct device_d *dev) +{ + struct fs_device_d *fsdev; + struct squashfs_priv *priv; + int ret; + + fsdev = dev_to_fs_device(dev); + + priv = xzalloc(sizeof(struct squashfs_priv)); + dev->priv = priv; + + ret = fsdev_open_cdev(fsdev); + if (ret) + goto err_out; + + + ret = squashfs_mount(fsdev, 0); + if (ret) { + dev_err(dev, "no valid squashfs found\n"); + goto err_out; + } + + return 0; + +err_out: + free(priv); + + return ret; +} + +static void squashfs_remove(struct device_d *dev) +{ + struct squashfs_priv *priv = dev->priv; + + squashfs_put_super(&priv->sb); + free(priv); +} + +static int squashfs_open(struct device_d *dev, FILE *file, const char *filename) +{ + struct squashfs_priv *priv = dev->priv; + struct inode *inode; + struct squashfs_page *page; + int i; + + inode = squashfs_findfile(&priv->sb, filename, NULL); + if (!inode) + return -ENOENT; + + page = malloc(sizeof(struct squashfs_page)); + page->buf = calloc(32, sizeof(*page->buf)); + for (i = 0; i < 32; i++) { + page->buf[i] = malloc(PAGE_CACHE_SIZE); + if (page->buf[i] == NULL) { + dev_err(dev, "error allocation read buffer\n"); + goto error; + } + } + + page->data_block = 0; + page->idx = 0; + page->real_page.inode = inode; + file->size = inode->i_size; + file->priv = page; + + return 0; + +error: + for (; i > 0; --i) + free(page->buf[i]); + + free(page->buf); + free(page); + + return -ENOMEM; +} + +static int squashfs_close(struct device_d *dev, FILE *f) +{ + struct squashfs_page *page = f->priv; + int i; + + for (i = 0; i < 32; i++) + free(page->buf[i]); + + free(page->buf); + free(squashfs_i(page->real_page.inode)); + free(page); + + return 0; +} + +static int squashfs_read_buf(struct squashfs_page *page, int pos, void **buf) +{ + unsigned int data_block = pos / (32 * PAGE_CACHE_SIZE); + unsigned int data_block_pos = pos % (32 * PAGE_CACHE_SIZE); + unsigned int idx = data_block_pos / PAGE_CACHE_SIZE; + + if (data_block != page->data_block || page->idx == 0) { + page->idx = 0; + page->real_page.index = data_block * 32; + squashfs_readpage(NULL, &page->real_page); + page->data_block = data_block; + } + + *buf = page->buf[idx]; + + return 0; +} + +static int squashfs_read(struct device_d *_dev, FILE *f, void *buf, + size_t insize) +{ + unsigned int size = insize; + unsigned int pos = f->pos; + unsigned int ofs; + unsigned int now; + void *pagebuf; + struct squashfs_page *page = f->priv; + + /* Read till end of current buffer page */ + ofs = pos % PAGE_CACHE_SIZE; + if (ofs) { + squashfs_read_buf(page, pos, &pagebuf); + + now = min(size, PAGE_CACHE_SIZE - ofs); + memcpy(buf, pagebuf + ofs, now); + + size -= now; + pos += now; + buf += now; + } + + /* Do full buffer pages */ + while (size >= PAGE_CACHE_SIZE) { + squashfs_read_buf(page, pos, &pagebuf); + + memcpy(buf, pagebuf, PAGE_CACHE_SIZE); + size -= PAGE_CACHE_SIZE; + pos += PAGE_CACHE_SIZE; + buf += PAGE_CACHE_SIZE; + } + + /* And the rest */ + if (size) { + squashfs_read_buf(page, pos, &pagebuf); + memcpy(buf, pagebuf, size); + size = 0; + } + + return insize; +} + +static loff_t squashfs_lseek(struct device_d *dev, FILE *f, loff_t pos) +{ + f->pos = pos; + + return pos; +} + +struct squashfs_dir { + struct file file; + struct dentry dentry; + struct dentry root_dentry; + struct inode inode; + struct qstr nm; + DIR dir; + char d_name[256]; + char root_d_name[256]; +}; + +static DIR *squashfs_opendir(struct device_d *dev, const char *pathname) +{ + struct squashfs_priv *priv = dev->priv; + struct inode *inode; + struct squashfs_dir *dir; + char buf[256]; + + inode = squashfs_findfile(&priv->sb, pathname, buf); + if (!inode) + return NULL; + + dir = xzalloc(sizeof(struct squashfs_dir)); + dir->dir.priv = dir; + + dir->root_dentry.d_inode = inode; + + sprintf(dir->d_name, "%s", buf); + sprintf(dir->root_d_name, "%s", buf); + + return &dir->dir; +} + +static struct dirent *squashfs_readdir(struct device_d *dev, DIR *_dir) +{ + struct squashfs_dir *dir = _dir->priv; + struct dentry *root_dentry = &dir->root_dentry; + + if (squashfs_lookup_next(root_dentry->d_inode, + dir->root_d_name, + dir->d_name)) + return NULL; + + strcpy(_dir->d.d_name, dir->d_name); + + return &_dir->d; +} + +static int squashfs_closedir(struct device_d *dev, DIR *_dir) +{ + struct squashfs_dir *dir = _dir->priv; + + free(squashfs_i(dir->root_dentry.d_inode)); + free(dir); + + return 0; +} + +static int squashfs_stat(struct device_d *dev, const char *filename, + struct stat *s) +{ + struct squashfs_priv *priv = dev->priv; + struct inode *inode; + + inode = squashfs_findfile(&priv->sb, filename, NULL); + if (!inode) + return -ENOENT; + + s->st_size = inode->i_size; + s->st_mode = inode->i_mode; + + free(squashfs_i(inode)); + + return 0; +} + +static struct fs_driver_d squashfs_driver = { + .open = squashfs_open, + .close = squashfs_close, + .read = squashfs_read, + .lseek = squashfs_lseek, + .opendir = squashfs_opendir, + .readdir = squashfs_readdir, + .closedir = squashfs_closedir, + .stat = squashfs_stat, + .drv = { + .probe = squashfs_probe, + .remove = squashfs_remove, + .name = "squashfs", + } +}; + +static int squashfs_init(void) +{ + return register_fs_driver(&squashfs_driver); +} + +device_initcall(squashfs_init); |