[RFC PATCH 4/5] Directory seek support

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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/

[Index of Archives]     [Kernel Newbies]     [Netfilter]     [Bugtraq]     [Photo]     [Stuff]     [Gimp]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Video 4 Linux]     [Linux for the blind]     [Linux Resources]
  Powered by Linux