mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Jan Blunck <jblunck@suse.de>
To: linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org
Cc: Bharata B Rao <bharata@linux.vnet.ibm.com>
Subject: [RFC 20/26] union-mount: Simple union-mount readdir implementation
Date: Mon, 30 Jul 2007 18:13:43 +0200	[thread overview]
Message-ID: <20070730161325.004086646@weierstrass.suse.de> (raw)
In-Reply-To: <20070730161323.100048969@weierstrass.suse.de>

[-- Attachment #1: um/union-mount-readdir.diff --]
[-- Type: text/plain, Size: 11470 bytes --]

This is a very simple union mount readdir implementation. It modifies the
readdir routine to merge the entries of union mounted directories and
eliminate duplicates while walking the union stack.

  FIXME:
  This patch needs to be reworked! At the moment this only works for ext2 and
  tmpfs. All kind of index directories that return d_off > i_size don't work
  with this.

The directory entries are read starting from the top layer and they are
maintained in a cache. Subsequently when the entries from the bottom layers
of the union stack are read they are checked for duplicates (in the cache)
before being passed out to the user space. There can be multiple calls
to readdir/getdents routines for reading the entries of a single directory.
But union directory cache is not maitained across these calls. Instead
for every call, the previously read entries are re-read into the cache
and newly read entires are compared against these for duplicates before
being they are returned to user space.

Signed-off-by: Jan Blunck <jblunck@suse.de>
Signed-off-by: Bharata B Rao <bharata@linux.vnet.ibm.com>
---
 fs/readdir.c          |   11 -
 fs/union.c            |  336 ++++++++++++++++++++++++++++++++++++++++++++++++++
 include/linux/union.h |   25 +++
 3 files changed, 364 insertions(+), 8 deletions(-)

--- a/fs/readdir.c
+++ b/fs/readdir.c
@@ -16,13 +16,14 @@
 #include <linux/security.h>
 #include <linux/syscalls.h>
 #include <linux/unistd.h>
+#include <linux/union.h>
 
 #include <asm/uaccess.h>
 
 int vfs_readdir(struct file *file, filldir_t filler, void *buf)
 {
-	struct inode *inode = file->f_path.dentry->d_inode;
 	int res = -ENOTDIR;
+
 	if (!file->f_op || !file->f_op->readdir)
 		goto out;
 
@@ -30,13 +31,7 @@ int vfs_readdir(struct file *file, filld
 	if (res)
 		goto out;
 
-	mutex_lock(&inode->i_mutex);
-	res = -ENOENT;
-	if (!IS_DEADDIR(inode)) {
-		res = file->f_op->readdir(file, buf, filler);
-		file_accessed(file);
-	}
-	mutex_unlock(&inode->i_mutex);
+	res = do_readdir(file, buf, filler);
 out:
 	return res;
 }
--- a/fs/union.c
+++ b/fs/union.c
@@ -18,6 +18,8 @@
 #include <linux/hash.h>
 #include <linux/fs.h>
 #include <linux/union.h>
+#include <linux/module.h>
+#include <linux/file.h>
 
 /*
  * This is borrowed from fs/inode.c. The hashtable for lookups. Somebody
@@ -462,3 +464,337 @@ void detach_mnt_union(struct vfsmount *m
 	union_put(um);
 	return;
 }
+
+
+/*
+ * Union mounts support for readdir.
+ */
+
+/* This is a copy from fs/readdir.c */
+struct getdents_callback {
+	struct linux_dirent __user *current_dir;
+	struct linux_dirent __user *previous;
+	int count;
+	int error;
+};
+
+/* The readdir union cache object */
+struct union_cache_entry {
+	struct list_head list;
+	struct qstr name;
+};
+
+static int union_cache_add_entry(struct list_head *list,
+				 const char *name, int namelen)
+{
+	struct union_cache_entry *this;
+	char *tmp_name;
+
+	this = kmalloc(sizeof(*this), GFP_KERNEL);
+	if (!this) {
+		printk(KERN_CRIT
+		       "union_cache_add_entry(): out of kernel memory\n");
+		return -ENOMEM;
+	}
+
+	tmp_name = kmalloc(namelen + 1, GFP_KERNEL);
+	if (!tmp_name) {
+		printk(KERN_CRIT
+		       "union_cache_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 union_cache_free(struct list_head *uc_list)
+{
+	struct list_head *p;
+	struct list_head *ptmp;
+	int count = 0;
+
+	list_for_each_safe(p, ptmp, uc_list) {
+		struct union_cache_entry *this;
+
+		this = list_entry(p, struct union_cache_entry, list);
+		list_del_init(&this->list);
+		kfree(this->name.name);
+		kfree(this);
+		count++;
+	}
+	return;
+}
+
+static int union_cache_find_entry(struct list_head *uc_list,
+				  const char *name, int namelen)
+{
+	struct union_cache_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;
+}
+
+/*
+ * There are four filldir() wrapper necessary for the union mount readdir
+ * implementation:
+ *
+ * - filldir_topmost(): fills the union's readdir cache and the user space
+ *			buffer. This is only used for the topmost directory
+ *			in the union stack.
+ * - filldir_topmost_cacheonly(): only fills the union's readdir cache.
+ *			This is only used for the topmost directory in the
+ *			union stack.
+ * - filldir_overlaid(): fills the union's readdir cache and the user space
+ *			buffer. This is only used for directories on the
+ *			stack's lower layers.
+ * - filldir_overlaid_cacheonly(): only fills the union's readdir cache.
+ *			This is only used for directories on the stack's
+ *			lower layers.
+ */
+
+struct union_cache_callback {
+	struct getdents_callback *buf;	/* original getdents_callback */
+	struct list_head list;		/* list of union cache entries */
+	filldir_t filler;		/* the filldir() we should call */
+	loff_t offset;			/* base offset of our dirents */
+	loff_t count;			/* maximum number of bytes to "read" */
+};
+
+static int filldir_topmost(void *buf, const char *name, int namlen,
+			   loff_t offset, u64 ino, unsigned int d_type)
+{
+	struct union_cache_callback *cb = buf;
+
+	union_cache_add_entry(&cb->list, name, namlen);
+	return cb->filler(cb->buf, name, namlen, cb->offset + offset, ino,
+			  d_type);
+}
+
+static int filldir_topmost_cacheonly(void *buf, const char *name, int namlen,
+				     loff_t offset, u64 ino,
+				     unsigned int d_type)
+{
+	struct union_cache_callback *cb = buf;
+
+	if (offset > cb->count)
+		return -EINVAL;
+
+	union_cache_add_entry(&cb->list, name, namlen);
+	return 0;
+}
+
+static int filldir_overlaid(void *buf, const char *name, int namlen,
+			    loff_t offset, u64 ino, unsigned int d_type)
+{
+	struct union_cache_callback *cb = buf;
+
+	switch (namlen) {
+	case 2:
+		if (name[1] != '.')
+			break;
+	case 1:
+		if (name[0] != '.')
+			break;
+		return 0;
+	}
+
+	if (union_cache_find_entry(&cb->list, name, namlen))
+		return 0;
+
+	union_cache_add_entry(&cb->list, name, namlen);
+	return cb->filler(cb->buf, name, namlen, cb->offset + offset, ino,
+			  d_type);
+}
+
+static int filldir_overlaid_cacheonly(void *buf, const char *name, int namlen,
+				      loff_t offset, u64 ino,
+				      unsigned int d_type)
+{
+	struct union_cache_callback *cb = buf;
+
+	if (offset > cb->count)
+		return -EINVAL;
+
+	switch (namlen) {
+	case 2:
+		if (name[1] != '.')
+			break;
+	case 1:
+		if (name[0] != '.')
+			break;
+		return 0;
+	}
+
+	if (union_cache_find_entry(&cb->list, name, namlen))
+		return 0;
+
+	union_cache_add_entry(&cb->list, name, namlen);
+	return 0;
+}
+
+/*
+ * readdir_union_cache - A helper to fill the readdir cache
+ */
+static int readdir_union_cache(struct file *file, void *_buf, filldir_t filler)
+{
+	struct union_cache_callback *cb = _buf;
+	int old_count;
+	loff_t old_pos;
+	int res;
+
+	old_count = cb->count;
+	cb->count = ((file->f_pos > i_size_read(file->f_path.dentry->d_inode)) ?
+		      i_size_read(file->f_path.dentry->d_inode) :
+		      file->f_pos) & INT_MAX;
+	old_pos = file->f_pos;
+	file->f_pos = 0;
+	res = file->f_op->readdir(file, _buf, filler);
+	file->f_pos = old_pos;
+	cb->count = old_count;
+	return res;
+}
+
+/*
+ * readdir_union - A wrapper around ->readdir()
+ *
+ * This is a wrapper around the filesystems readdir(), which is walking
+ * the union stack and calls ->readdir() for every directory in the stack.
+ * The directory entries are read into the union mounts readdir cache to
+ * support whiteout's and duplicate removal.
+ */
+int readdir_union(struct file *file, void *buf, filldir_t filler)
+{
+	struct inode *inode = file->f_path.dentry->d_inode;
+	struct union_cache_callback cb;
+	struct path path;
+	loff_t offset = 0;
+	int res;
+
+	mutex_lock(&inode->i_mutex);
+	if (IS_DEADDIR(inode)) {
+		mutex_unlock(&inode->i_mutex);
+		return -ENOENT;
+	}
+
+	INIT_LIST_HEAD(&cb.list);
+	cb.buf = buf;
+	cb.filler = filler;
+	cb.offset = 0;
+	offset = i_size_read(file->f_path.dentry->d_inode);
+	cb.count = file->f_pos;
+
+	if (file->f_pos > 0) {
+		/*
+		 * We have already read from this dir, lets read that stuff to
+		 * our union-cache only
+		 */
+		res = readdir_union_cache(file, &cb,
+					  filldir_topmost_cacheonly);
+		if (res) {
+			mutex_unlock(&inode->i_mutex);
+			goto out;
+		}
+	}
+
+	if (file->f_pos < offset) {
+		res = file->f_op->readdir(file, &cb, filldir_topmost);
+		file_accessed(file);
+		if (res) {
+			mutex_unlock(&inode->i_mutex);
+			goto out;
+		}
+		/* We read until EOF of this directory */
+		file->f_pos = offset;
+	}
+
+	mutex_unlock(&inode->i_mutex);
+
+	path = file->f_path;
+	pathget(&path);
+	while (follow_union_down(&path.mnt, &path.dentry)) {
+		struct file *ftmp;
+
+		/* get path reference for filep */
+		pathget(&path);
+		ftmp = dentry_open(path.dentry, path.mnt,
+				   ((file->f_flags & ~(O_ACCMODE)) |
+				    O_RDONLY | O_DIRECTORY | O_NOATIME));
+		if (IS_ERR(ftmp)) {
+			res = PTR_ERR(ftmp);
+			break;
+		}
+
+		inode = path.dentry->d_inode;
+		mutex_lock(&inode->i_mutex);
+
+		/* rearrange the file position */
+		cb.offset += offset;
+		offset = i_size_read(inode);
+		ftmp->f_pos = file->f_pos - cb.offset;
+		cb.count = ftmp->f_pos;
+		if (ftmp->f_pos < 0) {
+			mutex_unlock(&inode->i_mutex);
+			fput(ftmp);
+			break;
+		}
+
+		res = -ENOENT;
+		if (IS_DEADDIR(inode))
+			goto out_fput;
+
+		if (ftmp->f_pos > 0) {
+			/*
+			 * We have already read from this dir, lets read that
+			 * stuff to our union-cache only
+			 */
+			res = readdir_union_cache(ftmp, &cb,
+						  filldir_overlaid_cacheonly);
+			if (res)
+				goto out_fput;
+		}
+
+		if (ftmp->f_pos < offset) {
+			res = ftmp->f_op->readdir(ftmp, &cb, filldir_overlaid);
+			file_accessed(ftmp);
+			if (res)
+				file->f_pos += ftmp->f_pos;
+			else
+				/*
+				 * We read until EOF of this directory, so lets
+				 * advance the f_pos by the maximum offset
+				 * (i_size) of this directory
+				 */
+				file->f_pos += offset;
+		}
+
+		file_accessed(ftmp);
+
+out_fput:
+		mutex_unlock(&inode->i_mutex);
+		fput(ftmp);
+
+		if (res)
+			break;
+	}
+	pathput(&path);
+out:
+	union_cache_free(&cb.list);
+	return res;
+}
--- a/include/linux/union.h
+++ b/include/linux/union.h
@@ -53,6 +53,7 @@ extern void __shrink_d_unions(struct den
 extern int attach_mnt_union(struct vfsmount *, struct vfsmount *,
 			    struct dentry *);
 extern void detach_mnt_union(struct vfsmount *);
+extern int readdir_union(struct file *, void *, filldir_t);
 
 #else /* CONFIG_UNION_MOUNT */
 
@@ -69,5 +70,29 @@ extern void detach_mnt_union(struct vfsm
 #define detach_mnt_union(x)		do { } while (0)
 
 #endif	/* CONFIG_UNION_MOUNT */
+
+static inline int do_readdir(struct file *file, void *buf, filldir_t filler)
+{
+	struct inode *inode = file->f_path.dentry->d_inode;
+	int res;
+
+#ifdef CONFIG_UNION_MOUNT
+	if (IS_MNT_UNION(file->f_path.mnt))
+		res = readdir_union(file, buf, filler);
+	else
+#endif
+	{
+		mutex_lock(&inode->i_mutex);
+		res = -ENOENT;
+		if (!IS_DEADDIR(inode)) {
+			res = file->f_op->readdir(file, buf, filler);
+			file_accessed(file);
+		}
+		mutex_unlock(&inode->i_mutex);
+	}
+
+	return res;
+}
+
 #endif	/* __KERNEL__ */
 #endif	/* __LINUX_UNION_H */

-- 


  parent reply	other threads:[~2007-07-30 16:17 UTC|newest]

Thread overview: 65+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2007-07-30 16:13 [RFC 00/26] VFS based Union Mount (V2) Jan Blunck
2007-07-30 16:13 ` [RFC 01/26] [PATCH 14/18] shmem: convert to using splice instead of sendfile() Jan Blunck
2007-07-30 16:13 ` [RFC 02/26] VFS: Export dput_path() and path_to_nameidata() Jan Blunck
2007-07-30 16:13 ` [RFC 03/26] VFS: Make lookup_hash() return a struct path Jan Blunck
2007-07-30 16:13 ` [RFC 04/26] VFS: Make lookup_create() " Jan Blunck
2007-07-30 16:13 ` [RFC 05/26] VFS: cache_lookup() cleanup Jan Blunck
2007-07-30 16:13 ` [RFC 06/26] VFS: Make real_lookup() return a struct path Jan Blunck
2007-07-30 16:13 ` [RFC 07/26] VFS: Introduce dput() variante that maintains a kill-list Jan Blunck
2007-07-30 16:13 ` [RFC 08/26] VFS: Export lives_below_in_same_fs() Jan Blunck
2007-07-30 16:13 ` [RFC 09/26] linux/stat.h: Add the filetype white-out Jan Blunck
2007-07-30 16:13 ` [RFC 10/26] VFS white-out handling Jan Blunck
2007-07-30 16:13 ` [RFC 11/26] tmpfs white-out support Jan Blunck
2007-08-01 15:13   ` Hugh Dickins
2007-08-02  2:48     ` Matt Mackall
2007-07-30 16:13 ` [RFC 12/26] ext2 " Jan Blunck
2007-07-31  3:45   ` Theodore Tso
2007-07-31  7:44     ` Jan Blunck
2007-07-31  8:32       ` Andreas Dilger
2007-07-31  9:08         ` Jan Blunck
2007-07-31 10:53       ` Theodore Tso
2007-08-02 19:31         ` Pavel Machek
2007-07-31 16:36   ` Josef Sipek
2007-07-31 17:00     ` Jan Blunck
2007-07-31 17:11       ` Josef Sipek
2007-08-01 15:23         ` Dave Kleikamp
2007-08-01 18:44           ` Josef Sipek
2007-08-01 19:10             ` Dave Kleikamp
2007-08-01 19:33               ` Josef Sipek
2007-08-01 19:52                 ` Dave Kleikamp
2007-08-01 22:06                   ` Erez Zadok
2007-08-02 12:05                     ` Jan Blunck
2007-08-02 11:55                 ` Jan Blunck
2007-08-02 17:50                 ` Jörn Engel
2007-08-02 18:15                   ` Jeremy Maitin-Shepard
2007-08-02  5:24             ` Ph. Marek
2007-08-02 12:12               ` Jan Blunck
2007-08-02 10:26         ` Jan Blunck
2007-08-01 10:00       ` Hans-Peter Jansen
2007-08-01 11:43         ` Josef Sipek
2007-08-01 18:01         ` Jan Engelhardt
2007-07-31 17:03     ` Mark Williamson
2007-07-31 17:16       ` Josef Sipek
2007-08-01 17:58     ` Jan Engelhardt
2007-08-01 18:03       ` Josef Sipek
2007-07-30 16:13 ` [RFC 13/26] ext3 whiteout support Jan Blunck
2007-07-30 16:13 ` [RFC 14/26] union-mount: Documentation Jan Blunck
2007-07-30 16:13 ` [RFC 15/26] union-mount: Add union-mount mount flag Jan Blunck
2007-07-30 16:13 ` [RFC 16/26] union-mount: Introduce union_mount structure Jan Blunck
2007-08-06  5:57   ` Bharata B Rao
2007-07-30 16:13 ` [RFC 17/26] union-mount: Drive the union cache via dcache Jan Blunck
2007-07-30 16:13 ` [RFC 18/26] union-mount: Changes to the namespace handling Jan Blunck
2007-08-08 10:10   ` Bharata B Rao
2007-07-30 16:13 ` [RFC 19/26] union-mount: Make lookup work for union-mounted file systems Jan Blunck
2007-08-09  5:42   ` Bharata B Rao
2007-07-30 16:13 ` Jan Blunck [this message]
2007-08-06 11:08   ` [RFC 20/26] union-mount: Simple union-mount readdir implementation Bharata B Rao
2007-07-30 16:13 ` [RFC 21/26] union-mount: in-kernel file copy between union mounted filesystems Jan Blunck
2007-07-30 16:13 ` [RFC 22/26] union-mount: white-out changes for copy-on-open Jan Blunck
2007-07-30 16:13 ` [RFC 23/26] union-mount: copyup on rename Jan Blunck
2007-07-30 16:13 ` [RFC 24/26] union-mount: dont report EROFS for union mounts Jan Blunck
2007-07-30 16:13 ` [RFC 25/26] union-mount: Debug Infrastructure Jan Blunck
2007-07-30 16:13 ` [RFC 26/26] union-mount: Debug code Jan Blunck
2007-07-30 18:23 ` [RFC 00/26] VFS based Union Mount (V2) Al Boldi
2007-08-02  6:49 ` Bharata B Rao
2007-08-02 10:17   ` Jan Blunck

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20070730161325.004086646@weierstrass.suse.de \
    --to=jblunck@suse.de \
    --cc=bharata@linux.vnet.ibm.com \
    --cc=linux-fsdevel@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®