[PATCH 05/18] UBIFS: file operations
Sascha Hauer
s.hauer at pengutronix.de
Tue Dec 4 17:53:39 EST 2012
Hi Renaud,
First of all, thank you for working in this. Nice ;)
On Mon, Dec 03, 2012 at 06:08:21PM +0000, Renaud Barbier wrote:
> This file defines all the files/directories operations.
> It also initializes the driver.
>
> Signed-off-by: Renaud Barbier <renaud.barbier at ge.com>
> ---
> fs/ubifs/ubifs.c | 942 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
> 1 files changed, 942 insertions(+), 0 deletions(-)
> create mode 100644 fs/ubifs/ubifs.c
>
> +/*
> + * ubifsls...
> + */
> +static int filldir(struct ubifs_info *c, const char *name, int namlen,
> + u64 ino, unsigned int d_type)
> +{
> + struct inode *inode;
> +
> + inode = ubifs_iget(c->vfs_sb, ino);
> + if (IS_ERR(inode)) {
> + printf("%s: Error in ubifs_iget(), ino=%lld ret=%p!\n",
> + __func__, ino, inode);
> + return -1;
No -1 as error code please. -1 means -EPERM.
> + }
> + /*ctime_r((time_t *)&inode->i_mtime, filetime);
> + printf("%9lld %24.24s ", inode->i_size, filetime); */
> + ubifs_iput(inode);
> +
> +
> + return 0;
> +}
> +
> +
> +
> +static int ubifs_doreaddir(struct file *file, struct dirent *dirent)
> +{
> + int err, over = 0;
> + struct qstr nm;
> + union ubifs_key key;
> + struct ubifs_dent_node *dent;
> +
> + struct inode *dir = file->f_path.dentry->d_inode;
> +
> + struct ubifs_info *c = dir->i_sb->s_fs_info;
> +
> + dbg_gen("dir ino %lu, f_pos %#llx", dir->i_ino, file->f_pos);
> +
> + memset(dirent->d_name, 0, sizeof(dirent->d_name));
> +
> + if (file->f_pos > UBIFS_S_KEY_HASH_MASK || file->f_pos == 2) {
> + /*
> + * The directory was seek'ed to a senseless position or there
> + * are no more entries.
> + */
> + return 0;
> + }
> +
> +
> + if (file->f_pos == 1) {
> + /* Find the first entry in TNC and save it */
> + lowest_dent_key(c, &key, dir->i_ino);
> + nm.name = NULL;
> + dent = ubifs_tnc_next_ent(c, &key, &nm);
> + if (IS_ERR(dent)) {
> + err = PTR_ERR(dent);
> + goto out;
> + }
> +
> + file->f_pos = key_hash_flash(c, &dent->key);
> + file->private_data = dent;
> + }
> +
> + dent = file->private_data;
> + if (!dent) {
> + /*
> + * The directory was seek'ed to and is now readdir'ed.
> + * Find the entry corresponding to @file->f_pos or the
> + * closest one.
> + */
> + dent_key_init_hash(c, &key, dir->i_ino, file->f_pos);
> + nm.name = NULL;
> + dent = ubifs_tnc_next_ent(c, &key, &nm);
> + if (IS_ERR(dent)) {
> + err = PTR_ERR(dent);
> + goto out;
> + }
> + file->f_pos = key_hash_flash(c, &dent->key);
> + file->private_data = dent;
> + }
> +
> + while (1) {
> + dbg_gen("feed '%s', ino %llu, new f_pos %#x",
> + dent->name, (unsigned long long)le64_to_cpu(dent->inum),
> + key_hash_flash(c, &dent->key));
> + ubifs_assert(le64_to_cpu(dent->ch.sqnum) > ubifs_inode(dir)->creat_sqnum);
> +
> + nm.len = le16_to_cpu(dent->nlen);
> + /* Need to remove this */
> + over = filldir(c, (char *)dent->name, nm.len,
> + le64_to_cpu(dent->inum), dent->type);
> + if (over) {
> + return 0;
> + } else {
> + memcpy(dirent, dent->name, strlen(dent->name));
> +
> + /* Switch to the next entry */
> + key_read(c, &dent->key, &key);
> + nm.name = (char *)dent->name;
> + dent = ubifs_tnc_next_ent(c, &key, &nm);
> + if (IS_ERR(dent)) {
> + err = PTR_ERR(dent);
> + goto out;
> + }
> +
> + kfree(file->private_data);
> + file->f_pos = key_hash_flash(c, &dent->key);
> + file->private_data = dent;
> + cond_resched();
> + return 1;
> + }
> + }
> +
> +out:
> + if (err != -ENOENT) {
> + ubifs_err("cannot find next direntry, error %d", err);
> + return 0;
> + }
> +
> + kfree(file->private_data);
> + file->private_data = NULL;
> + file->f_pos = 2;
> + return 1;
> +}
> +
> +static struct dirent *ubifs_readdir(struct device_d *dev, DIR *dir)
> +{
> + struct ubifs_priv *priv = (struct ubifs_priv *)dev->priv;
> + struct file *file;
> + int ret;
> +
> + file = priv->file;
> + ret = ubifs_doreaddir(file, &dir->d);
> +
> + if (ret)
> + return &dir->d;
> + else
> + return NULL;
> +
> +
> +}
> +
> +static int ubifs_finddir(struct super_block *sb, char *dirname,
> + unsigned long root_inum, unsigned long *inum)
> +{
> + int err;
> + struct qstr nm;
> + union ubifs_key key;
> + struct ubifs_dent_node *dent;
> + struct ubifs_info *c;
> + struct file *file;
> + struct dentry *dentry;
> + struct inode *dir;
> +
> + file = kzalloc(sizeof(struct file), 0);
> + dentry = kzalloc(sizeof(struct dentry), 0);
> + dir = kzalloc(sizeof(struct inode), 0);
> + if (!file || !dentry || !dir) {
> + printf("%s: Error, no memory for malloc!\n", __func__);
> + err = -ENOMEM;
> + goto out;
> + }
> +
> + dir->i_sb = sb;
> + file->f_path.dentry = dentry;
> + file->f_path.dentry->d_parent = dentry;
> + file->f_path.dentry->d_inode = dir;
> + file->f_path.dentry->d_inode->i_ino = root_inum;
> + c = sb->s_fs_info;
> +
> + dbg_gen("finddir: dir ino %lu, f_pos %#llx", dir->i_ino, file->f_pos);
> +
> + /* Find the first entry in TNC and save it */
> + lowest_dent_key(c, &key, dir->i_ino);
> + nm.name = NULL;
> + dent = ubifs_tnc_next_ent(c, &key, &nm);
> + if (IS_ERR(dent)) {
> + err = PTR_ERR(dent);
> + goto out;
> + }
> +
> + file->f_pos = key_hash_flash(c, &dent->key);
> + file->private_data = dent;
> +
> + while (1) {
> + dbg_gen("feed '%s', ino %llu, new f_pos %#x",
> + dent->name, (unsigned long long)le64_to_cpu(dent->inum),
> + key_hash_flash(c, &dent->key));
> + ubifs_assert(le64_to_cpu(dent->ch.sqnum) > ubifs_inode(dir)->creat_sqnum);
> +
> + nm.len = le16_to_cpu(dent->nlen);
> + if ((strncmp(dirname, (char *)dent->name, nm.len) == 0) &&
> + (strlen(dirname) == nm.len)) {
> + *inum = le64_to_cpu(dent->inum);
> + return 1;
> + }
> +
> + /* Switch to the next entry */
> + key_read(c, &dent->key, &key);
> + nm.name = (char *)dent->name;
> + dent = ubifs_tnc_next_ent(c, &key, &nm);
> + if (IS_ERR(dent)) {
> + err = PTR_ERR(dent);
> + goto out;
> + }
> +
> + kfree(file->private_data);
> + file->f_pos = key_hash_flash(c, &dent->key);
> + file->private_data = dent;
> + cond_resched();
> + }
> +
> +out:
> + if (err != -ENOENT) {
> + ubifs_err("cannot find next direntry, error %d", err);
> + return err;
> + }
> +
> + if (file->private_data)
> + kfree(file->private_data);
> + if (file)
> + free(file);
> + if (dentry)
> + free(dentry);
> + if (dir)
> + free(dir);
No need to check for valid pointers, free() does this.
> +
> + return 0;
Please return 0 for success and a negative error code otherwise.
Everything else is quite confusing.
> +}
> +
> +static unsigned long ubifs_findfile(struct super_block *sb,
> + const char *filename)
ditto. Since the result of this function seems to be an inode or block number
you could pass this in as a pointer instead of returning it.
> +{
> + int ret;
> + char *next;
> + char fpath[128];
> + char symlinkpath[128];
> + char *name = fpath;
> + unsigned long root_inum = 1;
> + unsigned long inum;
> + int symlink_count = 0; /* Don't allow symlink recursion */
> + char link_name[64];
> +
> + strcpy(fpath, filename);
> +
> + /* Remove all leading slashes */
> + while (*name == '/')
> + name++;
> +
> + /*
> + * Handle root-direcoty ('/')
> + */
> + inum = root_inum;
> + if (!name || *name == '\0')
> + return inum;
> +
> + for (;;) {
> + struct inode *inode;
> + struct ubifs_inode *ui;
> +
> + /* Extract the actual part from the pathname. */
> + next = strchr(name, '/');
> + if (next) {
> + /* Remove all leading slashes. */
> + while (*next == '/')
> + *(next++) = '\0';
> + }
You don't need this. the barebox fs implementation removes all double
slashes.
> +
> + ret = ubifs_finddir(sb, name, root_inum, &inum);
> + if (!ret)
> + return 0;
> + inode = ubifs_iget(sb, inum);
> +
> + if (!inode)
> + return 0;
> + ui = ubifs_inode(inode);
> +
> + if ((inode->i_mode & S_IFMT) == S_IFLNK) {
barebox has link handling. You can just remove link handling here and
implement .readlink.
> + char buf[128];
> +
> + /* We have some sort of symlink recursion, bail out */
> + if (symlink_count++ > 8) {
> + printf("Symlink recursion, aborting\n");
> + return 0;
> + }
> + memcpy(link_name, ui->data, ui->data_len);
> + link_name[ui->data_len] = '\0';
> +
> + if (link_name[0] == '/') {
> + /* Absolute path, redo everything without
> + * the leading slash */
> + next = name = link_name + 1;
> + root_inum = 1;
> + continue;
> + }
> + /* Relative to cur dir */
> + sprintf(buf, "%s/%s",
> + link_name, next == NULL ? "" : next);
> + memcpy(symlinkpath, buf, sizeof(buf));
> + next = name = symlinkpath;
> + continue;
> + }
> +
> + /*
> + * Check if directory with this name exists
> + */
> +
> + /* Found the node! */
> + if (!next || *next == '\0')
> + return inum;
> +
> + root_inum = inum;
> + name = next;
> + }
> +
> + return 0;
> +}
> +
> +static int ubifs_open(struct device_d *dev, FILE *file, const char *filename)
> +{
> + struct ubifs_priv *priv = (struct ubifs_priv *)dev->priv;
> + struct ubifs_info *c = priv->sb->s_fs_info;
> + struct ubifs_inode *ui;
> + struct inode *inode;
> + unsigned long inum;
> +
> +
> + c->ubi = ubi_open_volume(c->vi.ubi_num, c->vi.vol_id, UBI_READONLY);
Is this necessary? I would expect this happens at mount time.
> +
> + inum = ubifs_findfile(priv->sb, filename);
> + if (!inum) {
> + printf("ubifs_open: file %s not found\n", filename);
> + ubi_close_volume(c->ubi);
> + return -ENOENT;
> + }
> +
> + /*
> + * Read file inode
> + */
> + inode = ubifs_iget(c->vfs_sb, inum);
> +
> + if (!inode) {
> + ubi_close_volume(c->ubi);
> + return -ENOENT;
> + }
> +
> + ui = ubifs_inode(inode);
> +
> + file->inode = inode;
> + file->size = ui->ui_size;
> +
> + return 0;
> +}
> +
> +static int ubifs_read(struct device_d *dev, FILE *f, void *buf, size_t size)
> +{
> + struct ubifs_priv *priv = (struct ubifs_priv *)dev->priv;
> + struct ubifs_info *c = priv->sb->s_fs_info;
> + struct inode *inode = f->inode;
> + struct page page;
> + int last_block_size = 0;
> + int count, ix;
> + int outsize = 0;
> + int err;
> +
> + if (f->pos + size > inode->i_size)
> + size = inode->i_size - f->pos;
> +
> + count = (size + UBIFS_BLOCK_SIZE - 1) >> UBIFS_BLOCK_SHIFT;
> + page.addr = (void *)buf;
> + page.index = f->pos >> UBIFS_BLOCK_SHIFT;
> + page.inode = inode;
> +
> + for (ix = 0; ix < count; ix++) {
> + /*
> + * Make sure to not read beyond the requested size
> + */
> + if (((ix + 1) == count) && (size < inode->i_size)) {
> + last_block_size = size - (ix * PAGE_SIZE);
> + outsize += last_block_size;
> + } else
> + outsize += PAGE_SIZE;
> +
> + err = do_readpage(c, inode, &page, last_block_size);
> + if (err)
> + break;
> + page.addr += PAGE_SIZE;
> + page.index++;
> + }
> +
> + if (err)
> + return err;
> +
> + return outsize;
> +}
This function needs more work. It correctly handles partial page reads
when f->pos + size is not page aligned, but it doesn't handle the case
when you enter this function with f->pos not page aligned. The usage of
do_readpage is artificial, barebox has no idea of pages. Instead you
should use read_block directly here and drop do_readpage completely.
I think ubifs_read could look like:
u8 tmpbuf[UBIFS_BLOCK_SIZE];
block = f->pos >> UBIFS_BLOCK_SHIFT;
part = f->pos & (UBIFS_BLOCK_SIZE - 1));
if (part) {
/* partial start block */
int now = min(size, UBIFS_BLOCK_SIZE - part);
read_block(inode, tmpbuf, block, dn);
memcpy(buf, tmpbuf + part, now);
buf += now;
size -= now;
block++;
}
while (size >= UBIFS_BLOCK_SIZE) {
/* full blocks */
read_block(inode, buf, block, dn);
size -= UBIFS_BLOCK_SIZE;
buf += UBIFS_BLOCK_SIZE;
block++;
}
if (size) {
/* remaining partial block */
read_block(inode, tmpbuf, block, dn);
memcpy(buf, tmpbuf, size);
}
A further optimization step would be to store tmpbuf along with the
information which block number is stored in the files private data.
This will give a better performance when ubifs_read is continuously
called with not block aligned file offsets. This isn't necessary for now
though.
> +/*
> + * ubifs_probe: allocate private data and mount volume
> + */
> +static int ubifs_probe(struct device_d *dev)
> +{
> + struct fs_device_d *fsdev;
> + struct ubifs_priv *priv;
> + char *backingstore;
> +
> + priv = xzalloc(sizeof(struct ubifs_priv));
> +
> + fsdev = dev_to_fs_device(dev);
> + dev->priv = priv;
> + backingstore = fsdev->backingstore;
> +
> + /* Tested only with /dev/ubi0 */
> + if (strncmp(backingstore, "/dev/ubi", 8))
> + return -ENODEV;
> +
> + backingstore += 5;
> +
> + priv->cdev = cdev_by_name(backingstore);
> + if (!priv->cdev)
> + return -ENODEV;
> +
> + /* Change volune name from ubiX.NAME to ubiX:NAME */
> + backingstore[4] = ':';
You should modify the copy you make below, not the original string.
> +
> + priv->vol_name = strdup(backingstore);
> +
Sascha
--
Pengutronix e.K. | |
Industrial Linux Solutions | http://www.pengutronix.de/ |
Peiner Str. 6-8, 31137 Hildesheim, Germany | Phone: +49-5121-206917-0 |
Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-5555 |
More information about the barebox
mailing list