Newer
Older
barebox / fs / squashfs / squashfs.c
#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 <linux/mtd/ubi.h>
#include <linux/mtd/mtd.h>

#include "squashfs_fs.h"
#include "squashfs_fs_sb.h"
#include "squashfs_fs_i.h"
#include "squashfs.h"

struct ubi_volume_desc;

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 void squashfs_set_rootarg(struct squashfs_priv *priv,
					struct fs_device_d *fsdev)
{
	struct ubi_volume_desc *ubi_vol;
	struct ubi_volume_info vi = {};
	struct ubi_device_info di = {};
	struct mtd_info *mtd;
	char *str;

	if (!IS_ENABLED(CONFIG_MTD_UBI))
		return;

	ubi_vol = ubi_open_volume_cdev(fsdev->cdev, UBI_READONLY);

	if (IS_ERR(ubi_vol))
		return;

	ubi_get_volume_info(ubi_vol, &vi);
	ubi_get_device_info(vi.ubi_num, &di);
	mtd = di.mtd;

	str = basprintf("root=/dev/ubiblock%d_%d ubi.mtd=%s ubi.block=%d,%d rootfstype=squashfs",
			vi.ubi_num, vi.vol_id, mtd->cdev.partname, vi.ubi_num, vi.vol_id);

	fsdev_set_linux_rootarg(fsdev, str);

	free(str);
}

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;
	}

	squashfs_set_rootarg(priv, fsdev);

	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,
	.type		= filetype_squashfs,
	.drv = {
		.probe = squashfs_probe,
		.remove = squashfs_remove,
		.name = "squashfs",
	}
};

static int squashfs_init(void)
{
	return register_fs_driver(&squashfs_driver);
}

device_initcall(squashfs_init);