On Wed, Jun 20, 2007 at 11:24:18AM +0530, Bharata B Rao wrote:
> From: Bharata B Rao <[EMAIL PROTECTED]>
> Subject: Directory listing support for union mounted directories.
> 
> Modifies readdir()/getdents() to support union mounted directories.
> 
> This patch adds support to readdir()/getdents() to read directory entries
> from all the directories of the union stack, and present a merged view to
> the userspace. This is achieved by maintaining a cache of read entries.
> 
> TODO: Breaks lseek on union mounted directories, Fix this.

Btw, to avoid dealing with file structs representing multiple objects
->readdir should be changed to an inode_operation and not take a struct
file * but rather struct inode + loff_t *ppos.  This sounds like a nice
preparatory patch for any kind of union mount implementation and is
acceptable standing on it's own.

> 
> Signed-off-by: Bharata B Rao <[EMAIL PROTECTED]>
> ---
>  fs/file_table.c    |    1 
>  fs/readdir.c       |  242 
> ++++++++++++++++++++++++++++++++++++++++++++++++++++-
>  include/linux/fs.h |   12 ++
>  3 files changed, 254 insertions(+), 1 deletion(-)
> 
> --- a/fs/file_table.c
> +++ b/fs/file_table.c
> @@ -174,6 +174,7 @@ void fastcall __fput(struct file *file)
>       if (file->f_mode & FMODE_WRITE)
>               put_write_access(inode);
>       put_pid(file->f_owner.pid);
> +     put_rdstate(file->f_rdstate);
>       file_kill(file);
>       file->f_path.dentry = NULL;
>       file->f_path.mnt = NULL;
> --- a/fs/readdir.c
> +++ b/fs/readdir.c
> @@ -19,6 +19,8 @@
>  
>  #include <asm/uaccess.h>
>  
> +int readdir_union(struct file *file, void *buf, filldir_t filler);
> +
>  int vfs_readdir(struct file *file, filldir_t filler, void *buf)
>  {
>       struct inode *inode = file->f_path.dentry->d_inode;
> @@ -33,7 +35,10 @@ int vfs_readdir(struct file *file, filld
>       mutex_lock(&inode->i_mutex);
>       res = -ENOENT;
>       if (!IS_DEADDIR(inode)) {
> -             res = file->f_op->readdir(file, buf, filler);
> +             if (IS_MNT_UNION(file->f_path.mnt))
> +                     res = readdir_union(file, buf, filler);
> +             else
> +                     res = file->f_op->readdir(file, buf, filler);
>               file_accessed(file);
>       }
>       mutex_unlock(&inode->i_mutex);
> @@ -303,3 +308,238 @@ out_putf:
>  out:
>       return error;
>  }
> +
> +/*
> + * NOTE: Some of the code below which maintains a list of dirents as a cache
> + * is from Jan Blunck's original union mount readdir patch.
> + */
> +
> +/* The readdir cache object */
> +struct rdcache_entry {
> +     struct list_head list;
> +     struct qstr name;
> +};
> +
> +struct rdcache_callback {
> +     void *buf;                      /* original callback buffer */
> +     filldir_t filldir;              /* the filldir() we should call */
> +     int error;                      /* stores filldir error */
> +     struct rdstate *rdstate;        /* readdir state */
> +};
> +
> +static int rdcache_add_entry(struct list_head *list,
> +                              const char *name, int namelen)
> +{
> +     struct rdcache_entry *this;
> +     char *tmp_name;
> +
> +     this = kmalloc(sizeof(*this), GFP_KERNEL);
> +     if (!this) {
> +             printk(KERN_CRIT "rdcache_add_entry(): out of kernel memory\n");
> +             return -ENOMEM;
> +     }
> +
> +     tmp_name = kmalloc(namelen + 1, GFP_KERNEL);
> +     if (!tmp_name) {
> +             printk(KERN_CRIT "rdcache_add_entry(): out of kernel memory\n");
> +             kfree(this);
> +             return -ENOMEM;
> +     }
> +
> +     this->name.name = tmp_name;
> +     this->name.len = namelen;
> +     this->name.hash = 0;
> +     memcpy(tmp_name, name, namelen);
> +     tmp_name[namelen] = 0;
> +     INIT_LIST_HEAD(&this->list);
> +     list_add(&this->list, list);
> +     return 0;
> +}
> +
> +static void rdcache_free(struct list_head *list)
> +{
> +     struct list_head *p;
> +     struct list_head *ptmp;
> +     int count = 0;
> +
> +     list_for_each_safe(p, ptmp, list) {
> +             struct rdcache_entry *this;
> +
> +             this = list_entry(p, struct rdcache_entry, list);
> +             list_del_init(&this->list);
> +             kfree(this->name.name);
> +             kfree(this);
> +             count++;
> +     }
> +     return;
> +}
> +
> +static int rdcache_find_entry(struct list_head *uc_list,
> +                               const char *name, int namelen)
> +{
> +     struct rdcache_entry *p;
> +     int ret = 0;
> +
> +     list_for_each_entry(p, uc_list, list) {
> +             if (p->name.len != namelen)
> +                     continue;
> +             if (strncmp(p->name.name, name, namelen) == 0) {
> +                     ret = 1;
> +                     break;
> +             }
> +     }
> +     return ret;
> +}
> +
> +/*
> + * filldir routine for union mounted directories.
> + * Handles duplicate elimination by building a readdir cache.
> + * TODO: readdir cache is a linked list. Check if this can be designed
> + * more efficiently.
> + */
> +static int filldir_union(void *buf, const char *name, int namlen,
> +                        loff_t offset, u64 ino, unsigned int d_type)
> +{
> +     struct rdcache_callback *cb = buf;
> +     int err;
> +
> +     if (rdcache_find_entry(&cb->rdstate->dirent_cache, name, namlen))
> +             return 0;
> +
> +     err =  cb->filldir(cb->buf, name, namlen, offset, ino, d_type);
> +     if (err >= 0)
> +             rdcache_add_entry(&cb->rdstate->dirent_cache, name, namlen);
> +     cb->error = err;
> +     return err;
> +}
> +
> +/* Called from last fput() */
> +void put_rdstate(struct rdstate *rdstate)
> +{
> +     if (!rdstate)
> +             return;
> +
> +     mntput(rdstate->mnt);
> +     dput(rdstate->dentry);
> +     rdcache_free(&rdstate->dirent_cache);
> +     kfree(rdstate);
> +}
> +
> +static struct rdstate *get_rdstate(struct file *file)
> +{
> +     if (file->f_rdstate)
> +             return file->f_rdstate;
> +
> +     /*
> +      * We have read the dirents from this earlier but now don't have a
> +      * corresponding rdstate. This shouldn't happen.
> +      */
> +     if (file->f_pos)
> +             return ERR_PTR(-EINVAL);
> +
> +     file->f_rdstate = kmalloc(sizeof(struct rdstate), GFP_KERNEL);
> +     if (!file->f_rdstate)
> +             return ERR_PTR(-ENOMEM);
> +
> +     file->f_rdstate->off = 0;
> +     file->f_rdstate->mnt = mntget(file->f_path.mnt);
> +     file->f_rdstate->dentry = dget(file->f_path.dentry);
> +     INIT_LIST_HEAD(&file->f_rdstate->dirent_cache);
> +     return file->f_rdstate;
> +}
> +
> +int readdir_union(struct file *file, void *buf, filldir_t filler)
> +{
> +     struct dentry *topmost = file->f_path.dentry;
> +     struct rdstate *rdstate;
> +     struct nameidata nd;
> +     loff_t offset = 0;
> +     struct rdcache_callback cb;
> +     int err = 0;
> +
> +     /*
> +      * If this is not a union mount point or not a unioned directory
> +      * within the union mount point, do readdir in the normal way.
> +      */
> +     nd.mnt = file->f_path.mnt;
> +     nd.dentry = file->f_path.dentry;
> +     if (!next_union_mount_exists(nd.mnt, nd.dentry))
> +             return file->f_op->readdir(file, buf, filler);
> +
> +     rdstate = get_rdstate(file);
> +     if (IS_ERR(rdstate)) {
> +             err = PTR_ERR(rdstate);
> +             return err;
> +     }
> +
> +     cb.buf = buf;
> +     cb.filldir = filler;
> +     cb.rdstate = rdstate;
> +
> +     offset = rdstate->off;
> +
> +     /* Read from the topmost directory */
> +     if (rdstate->dentry == topmost) {
> +             file->f_pos = offset;
> +             err = file->f_op->readdir(file, &cb, filldir_union);
> +             rdstate->off = file->f_pos;
> +             if (err < 0 || cb.error < 0)
> +                     goto out;
> +
> +             /*
> +              * Reading from topmost dir complete, start reading the lower
> +              * dir from the beginning.
> +              */
> +             offset = 0;
> +     } else {
> +             nd.mnt = rdstate->mnt;
> +             nd.dentry = rdstate->dentry;
> +             goto read_lower;
> +     }
> +
> +     if (!next_union_mount(&nd))
> +             return err;
> +
> +read_lower:
> +     do {
> +             struct vfsmount *mnt_tmp = mntget(nd.mnt);
> +             struct dentry *dentry_tmp = dget(nd.dentry);
> +             struct file *ftmp = dentry_open(nd.dentry, nd.mnt,
> +                                             file->f_flags);
> +             if (IS_ERR(ftmp)) {
> +                     mntput(mnt_tmp);
> +                     dput(dentry_tmp);
> +                     err = PTR_ERR(ftmp);
> +                     goto out;
> +             }
> +
> +             mutex_lock(&nd.dentry->d_inode->i_mutex);
> +             ftmp->f_pos = offset;
> +
> +             err = ftmp->f_op->readdir(ftmp, &cb, filldir_union);
> +             file_accessed(ftmp);
> +             rdstate->off = ftmp->f_pos;
> +             mutex_unlock(&nd.dentry->d_inode->i_mutex);
> +             if (nd.mnt != rdstate->mnt) {
> +                     mntput(rdstate->mnt);
> +                     rdstate->mnt = mntget(nd.mnt);
> +             }
> +             if (nd.dentry != rdstate->dentry) {
> +                     dput(rdstate->dentry);
> +                     rdstate->dentry = dget(nd.dentry);
> +             }
> +             fput(ftmp);
> +             if (err < 0 || cb.error < 0)
> +                     goto out;
> +
> +             /*
> +              * Reading from a lower dir complete, start reading the
> +              * next lower dir from the beginning.
> +              */
> +             offset = 0;
> +     } while (next_union_mount(&nd));
> +
> +     return 0;
> +out:
> +     return err;
> +}
> --- a/include/linux/fs.h
> +++ b/include/linux/fs.h
> @@ -841,6 +841,14 @@ static inline void ra_set_size(struct fi
>  unsigned long ra_submit(struct file_ra_state *ra,
>                      struct address_space *mapping, struct file *filp);
>  
> +/* Direct cache for readdir, used for union mounted directories. */
> +struct rdstate {
> +     struct vfsmount *mnt;
> +     struct dentry *dentry;
> +     loff_t off;
> +     struct list_head dirent_cache;
> +};
> +
>  struct file {
>       /*
>        * fu_list becomes invalid after file_free is called and queued via
> @@ -875,6 +883,7 @@ struct file {
>       spinlock_t              f_ep_lock;
>  #endif /* #ifdef CONFIG_EPOLL */
>       struct address_space    *f_mapping;
> +     struct rdstate          *f_rdstate;
>  };
>  extern spinlock_t files_lock;
>  #define file_list_lock() spin_lock(&files_lock);
> @@ -2119,5 +2128,8 @@ static inline void free_secdata(void *se
>  { }
>  #endif       /* CONFIG_SECURITY */
>  
> +/* fs/readdir.c */
> +void put_rdstate(struct rdstate *rdstate);
> +
>  #endif /* __KERNEL__ */
>  #endif /* _LINUX_FS_H */
> -
> To unsubscribe from this list: send the line "unsubscribe linux-fsdevel" in
> the body of a message to [EMAIL PROTECTED]
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
---end quoted text---
-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to [EMAIL PROTECTED]
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

Reply via email to