Directory seek support.

Define the seek behaviour on the stored cache of dirents.

Signed-off-by: Bharata B Rao <[EMAIL PROTECTED]>
---
 fs/read_write.c       |   11 ---
 fs/union.c            |  171 +++++++++++++++++++++++++++++++++++++++++++++++++-
 include/linux/fs.h    |    8 ++
 include/linux/union.h |   25 +++++++
 4 files changed, 205 insertions(+), 10 deletions(-)

--- a/fs/read_write.c
+++ b/fs/read_write.c
@@ -16,6 +16,7 @@
 #include <linux/syscalls.h>
 #include <linux/pagemap.h>
 #include <linux/splice.h>
+#include <linux/union.h>
 #include "read_write.h"
 
 #include <asm/uaccess.h>
@@ -116,15 +117,7 @@ EXPORT_SYMBOL(default_llseek);
 
 loff_t vfs_llseek(struct file *file, loff_t offset, int origin)
 {
-       loff_t (*fn)(struct file *, loff_t, int);
-
-       fn = no_llseek;
-       if (file->f_mode & FMODE_LSEEK) {
-               fn = default_llseek;
-               if (file->f_op && file->f_op->llseek)
-                       fn = file->f_op->llseek;
-       }
-       return fn(file, offset, origin);
+       return do_llseek(file, offset, origin);
 }
 EXPORT_SYMBOL(vfs_llseek);
 
--- a/fs/union.c
+++ b/fs/union.c
@@ -614,6 +614,7 @@ static int rdcache_add_entry(struct rdst
        this->dtype = d_type;
        INIT_LIST_HEAD(&this->list);
        list_add_tail(&this->list, list);
+       r->cur_dirent = this;
        return 0;
 }
 
@@ -636,18 +637,96 @@ static int filldir_union(void *buf, cons
        if (rdcache_find_entry(&r->dirent_cache, name, namlen))
                return 0;
 
-       err =  cb->filldir(cb->buf, name, namlen, r->cur_off,
+       /* We come here with NULL cb->filldir from lseek path */
+       if (cb->filldir)
+               err =  cb->filldir(cb->buf, name, namlen, r->cur_off,
                                ino, d_type);
        if (err >= 0) {
                rdcache_add_entry(r, &r->dirent_cache,
                        name, namlen, offset, ino, d_type);
                r->cur_off = ++r->last_off;
                r->nr_dirents++;
+               if (r->cur_off == r->fill_off) {
+                       /* We filled up to the required seek offset */
+                       r->fill_off = 0;
+                       err = -EINVAL;
+               }
        }
        cb->error = err;
        return err;
 }
 
+/*
+ * This is called when current offset in rdcache gets changed and when
+ * we need to change the current underlying directory in the rdstate
+ * to match the current offset.
+ */
+static void update_rdstate(struct file *file)
+{
+       struct rdstate *r = file->f_rdstate;
+       loff_t off = r->cur_off;
+       struct union_mount *um;
+
+       if (!(r->flags & RDSTATE_NEED_UPDATE))
+               return;
+
+       spin_lock(&union_lock);
+       um = union_lookup(file->f_path.dentry, file->f_path.mnt);
+       spin_unlock(&union_lock);
+       if (!um)
+               goto out;
+       off -= um->nr_dirents;
+       path_put(&r->cur_path);
+       r->cur_path = file->f_path;
+       path_get(&r->cur_path);
+
+       while (off > 0) {
+               spin_lock(&union_lock);
+               um = union_lookup(r->cur_path.dentry, r->cur_path.mnt);
+               spin_unlock(&union_lock);
+               if (!um)
+                       goto out;
+               off -= um->nr_dirents;
+               path_put(&r->cur_path);
+               r->cur_path = um->u_next;
+               path_get(&r->cur_path);
+       }
+out:
+       r->file_off = r->cur_dirent->off;
+}
+
+/*
+ * Returns dirents from rdcache to userspace.
+ */
+static int readdir_rdcache(struct file *file, struct rdcache_callback *cb)
+{
+       struct rdstate *r = cb->rdstate;
+       struct rdcache_entry *tmp = r->cur_dirent;
+       int err = 0;
+
+       BUG_ON(r->cur_off > r->last_off);
+
+       /* If offsets already uptodate, just return */
+       if (likely(r->cur_off == r->last_off))
+               return 0;
+
+       /*
+        * return the entries from cur_off till last_off from rdcache to
+        * user space.
+        */
+       list_for_each_entry_from(tmp, &r->dirent_cache, list) {
+               err =  cb->filldir(cb->buf, tmp->name.name, tmp->name.len,
+                               r->cur_off, tmp->ino, tmp->dtype);
+               r->cur_dirent = tmp;
+               if (err < 0)
+                       break;
+               r->cur_off++;
+               r->flags |= RDSTATE_NEED_UPDATE;
+       }
+       update_rdstate(file);
+       return err;
+}
+
 /* Called from last fput() */
 void put_rdstate(struct rdstate *rdstate)
 {
@@ -710,6 +789,10 @@ int readdir_union(struct file *file, voi
        cb.rdstate = rdstate;
        cb.error = 0;
 
+       err = readdir_rdcache(file, &cb);
+       if (err)
+               return err;
+
        offset = rdstate->file_off;
 
        /* Read from the topmost directory */
@@ -796,6 +879,92 @@ out:
        return err;
 }
 
+static void rdcache_rewind(struct file *file, struct rdstate *r, loff_t offset)
+{
+       struct rdcache_entry *tmp = r->cur_dirent;
+
+       list_for_each_entry_reverse_from(tmp, &r->dirent_cache, list) {
+               if (r->cur_off == offset)
+                       break;
+               r->cur_dirent = tmp;
+               r->cur_off--;
+               r->flags |= RDSTATE_NEED_UPDATE;
+       }
+       update_rdstate(file);
+}
+
+static void rdcache_forward(struct file *file, struct rdstate *r, loff_t 
offset)
+{
+       struct rdcache_entry *tmp = r->cur_dirent;
+
+       list_for_each_entry_continue(tmp, &r->dirent_cache, list) {
+               if (r->cur_off == offset)
+                       break;
+               r->cur_dirent = tmp;
+               r->cur_off++;
+               r->flags |= RDSTATE_NEED_UPDATE;
+       }
+       update_rdstate(file);
+}
+
+loff_t llseek_union(struct file *file, loff_t offset, int origin)
+{
+       loff_t err;
+       struct rdstate *r = file->f_rdstate;
+       loff_t orig_off = file->f_rdstate->cur_off;
+
+       if (!r)
+               return -EINVAL;
+
+       switch (origin) {
+       case SEEK_END:
+               /*
+                * To support this, we need to know the end of the directory,
+                * which may need reading _all_ the entries from the filesystem
+                * to readdir cache, which can be expensive.
+                *
+                * After we have the last entry in the cache, do
+                * offset += r->last_off;
+                */
+               err = -EINVAL;
+               goto out;
+       case SEEK_CUR:
+               offset += r->cur_off;
+               break;
+       }
+       err = -EINVAL;
+       if (offset < 0)
+               goto out;
+
+       if (offset > r->cur_off) {
+               /* Seek forward into the cache */
+               rdcache_forward(file, r, offset);
+               if (offset > r->cur_off) {
+                       /*
+                        * Need to seek beyond the cache, read dirents from into
+                        * underlying directories into rdcache.
+                        */
+                       r->fill_off = offset;
+                       err = readdir_union(file, NULL, NULL);
+                       if (err < 0)
+                               goto out;
+
+                       /* readdir() failed, restore the original offset. */
+                       if (offset != r->cur_off) {
+                               rdcache_rewind(file, r, orig_off);
+                               err = -EINVAL;
+                               goto out;
+                       }
+                       err = offset;
+               }
+       } else {
+               rdcache_rewind(file, r, offset);
+               err = offset;
+       }
+out:
+       return err;
+}
+
 /*
  * Union mount copyup support
  */
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -803,9 +803,17 @@ struct rdstate {
        loff_t last_off;        /* Offset to last dirent in rdcache */
        loff_t nr_dirents;      /* Number of entries from current underlying
                                   directory in rdcache */
+       loff_t fill_off;        /* Fill cache upto this offset. Used during
+                                  seek */
        struct list_head dirent_cache;  /* cache of directory entries */
+       struct rdcache_entry *cur_dirent;       /* pointer to current directory
+                               entry in rdcache which corresponds to cur_off */
+       int flags;
 };
 
+#define RDSTATE_STALE          0x01
+#define RDSTATE_NEED_UPDATE    0x02
+
 extern void put_rdstate(struct rdstate *rdstate);
 
 #else
--- a/include/linux/union.h
+++ b/include/linux/union.h
@@ -55,6 +55,7 @@ extern int attach_mnt_union(struct vfsmo
                            struct dentry *);
 extern void detach_mnt_union(struct vfsmount *);
 extern int readdir_union(struct file *, void *, filldir_t);
+extern loff_t llseek_union(struct file *, loff_t, int);
 extern int last_union_is_root(struct path *);
 extern int is_dir_unioned(struct path *);
 extern int union_relookup_topmost(struct nameidata *, int);
@@ -110,5 +111,29 @@ static inline int do_readdir(struct file
        return res;
 }
 
+static inline loff_t do_llseek(struct file *file, loff_t offset, int origin)
+{
+       long long res;
+#ifdef CONFIG_UNION_MOUNT
+       if (IS_MNT_UNION(file->f_path.mnt) && is_dir_unioned(&file->f_path)) {
+               mutex_lock(&union_rdmutex);
+               res = llseek_union(file, offset, origin);
+               mutex_unlock(&union_rdmutex);
+       } else
+#endif
+       {
+               loff_t (*fn)(struct file *, loff_t, int);
+
+               fn = no_llseek;
+               if (file->f_mode & FMODE_LSEEK) {
+                       fn = default_llseek;
+                       if (file->f_op && file->f_op->llseek)
+                               fn = file->f_op->llseek;
+               }
+               res = fn(file, offset, origin);
+       }
+       return res;
+}
+
 #endif /* __KERNEL__ */
 #endif /* __LINUX_UNION_H */
--
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