All of lore.kernel.org
 help / color / mirror / Atom feed
From: "Josef 'Jeff' Sipek" <jsipek@cs.sunysb.edu>
To: linux-kernel@vger.kernel.org, linux-fsdevel@vger.kernel.org
Cc: akpm@linux-foundation.org, Erez Zadok <ezk@cs.sunysb.edu>,
	"Josef 'Jeff' Sipek" <jsipek@cs.sunysb.edu>
Subject: [PATCH 09/21] Unionfs: Bulk of branch-management remount code
Date: Mon,  9 Apr 2007 10:54:00 -0400	[thread overview]
Message-ID: <11761304542331-git-send-email-jsipek@cs.sunysb.edu> (raw)
In-Reply-To: <11761304521844-git-send-email-jsipek@cs.sunysb.edu>

From: Erez Zadok <ezk@cs.sunysb.edu>

Signed-off-by: Erez Zadok <ezk@cs.sunysb.edu>
Signed-off-by: Josef 'Jeff' Sipek <jsipek@cs.sunysb.edu>
---
 fs/unionfs/main.c  |   52 +++--
 fs/unionfs/super.c |  612 +++++++++++++++++++++++++++++++++++++++++++++++++++-
 fs/unionfs/union.h |    6 +
 3 files changed, 646 insertions(+), 24 deletions(-)

diff --git a/fs/unionfs/main.c b/fs/unionfs/main.c
index ed264c7..1c93b13 100644
--- a/fs/unionfs/main.c
+++ b/fs/unionfs/main.c
@@ -181,7 +181,7 @@ void unionfs_reinterpose(struct dentry *dentry)
  * 2) it exists
  * 3) is a directory
  */
-static int check_branch(struct nameidata *nd)
+int check_branch(struct nameidata *nd)
 {
 	if (!strcmp(nd->dentry->d_sb->s_type->name, "unionfs"))
 		return -EINVAL;
@@ -211,20 +211,29 @@ static int is_branch_overlap(struct dentry *dent1, struct dentry *dent2)
 	return (dent == dent1);
 }
 
-/* parse branch mode */
-static int parse_branch_mode(char *name)
+/*
+ * Parse branch mode helper function
+ */
+int __parse_branch_mode(const char *name)
 {
-	int perms;
-	int l = strlen(name);
-	if (!strcmp(name + l - 3, "=ro")) {
-		perms = MAY_READ;
-		name[l - 3] = '\0';
-	} else if (!strcmp(name + l - 3, "=rw")) {
-		perms = MAY_READ | MAY_WRITE;
-		name[l - 3] = '\0';
-	} else
-		perms = MAY_READ | MAY_WRITE;
+	if (!name)
+		return 0;
+	if (!strcmp(name, "ro"))
+		return MAY_READ;
+	if (!strcmp(name, "rw"))
+		return (MAY_READ | MAY_WRITE);
+	return 0;
+}
 
+/*
+ * Parse "ro" or "rw" options, but default to "rw" of no mode options
+ * was specified.
+ */
+int parse_branch_mode(const char *name)
+{
+	int perms =  __parse_branch_mode(name);
+	if (perms == 0)
+		perms = MAY_READ | MAY_WRITE;
 	return perms;
 }
 
@@ -271,18 +280,22 @@ static int parse_dirs_option(struct super_block *sb, struct unionfs_dentry_info
 		goto out;
 	}
 
-	/* now parsing the string b1:b2=rw:b3=ro:b4 */
+	/* now parsing a string such as "b1:b2=rw:b3=ro:b4" */
 	branches = 0;
 	while ((name = strsep(&options, ":")) != NULL) {
 		int perms;
+		char *mode = strchr(name, '=');
 
-		if (!*name)
+		if (!name || !*name)
 			continue;
 
 		branches++;
 
-		/* strip off =rw or =ro if it is specified. */
-		perms = parse_branch_mode(name);
+		/* strip off '=' if any */
+		if (mode)
+			*mode++ = '\0';
+
+		perms = parse_branch_mode(mode);
 		if (!bindex && !(perms & MAY_WRITE)) {
 			err = -EINVAL;
 			goto out;
@@ -305,8 +318,11 @@ static int parse_dirs_option(struct super_block *sb, struct unionfs_dentry_info
 		hidden_root_info->lower_paths[bindex].dentry = nd.dentry;
 		hidden_root_info->lower_paths[bindex].mnt = nd.mnt;
 
+		unionfs_write_lock(sb);
 		set_branchperms(sb, bindex, perms);
 		set_branch_count(sb, bindex, 0);
+		new_branch_id(sb, bindex);
+		unionfs_write_unlock(sb);
 
 		if (hidden_root_info->bstart < 0)
 			hidden_root_info->bstart = bindex;
@@ -387,7 +403,7 @@ static struct unionfs_dentry_info *unionfs_parse_options(struct super_block *sb,
 		char *endptr;
 		int intval;
 
-		if (!*optname)
+		if (!optname || !*optname)
 			continue;
 
 		optarg = strchr(optname, '=');
diff --git a/fs/unionfs/super.c b/fs/unionfs/super.c
index 037c47d..7f0d174 100644
--- a/fs/unionfs/super.c
+++ b/fs/unionfs/super.c
@@ -143,14 +143,614 @@ static int unionfs_statfs(struct dentry *dentry, struct kstatfs *buf)
 	return err;
 }
 
-/* We don't support a standard text remount. Eventually it would be nice to
- * support a full-on remount, so that you can have all of the directories
- * change at once, but that would require some pretty complicated matching
- * code.
+/* handle mode changing during remount */
+static int do_remount_mode_option(char *optarg, int cur_branches,
+				  struct unionfs_data *new_data,
+				  struct path *new_lower_paths)
+{
+	int err = -EINVAL;
+	int perms, idx;
+	char *modename = strchr(optarg, '=');
+	struct nameidata nd;
+
+	/* by now, optarg contains the branch name */
+	if (!*optarg) {
+		printk("unionfs: no branch specified for mode change.\n");
+		goto out;
+	}
+	if (!modename) {
+		printk("unionfs: branch \"%s\" requires a mode.\n", optarg);
+		goto out;
+	}
+	*modename++ = '\0';
+	perms = __parse_branch_mode(modename);
+	if (perms == 0) {
+		printk("unionfs: invalid mode \"%s\" for \"%s\".\n",
+		       modename, optarg);
+		goto out;
+	}
+
+	/*
+	 * Find matching branch index.  For now, this assumes that nothing
+	 * has been mounted on top of this Unionfs stack.  Once we have /odf
+	 * and cache-coherency resolved, we'll address the branch-path
+	 * uniqueness.
+	 */
+	err = path_lookup(optarg, LOOKUP_FOLLOW, &nd);
+	if (err) {
+		printk(KERN_WARNING "unionfs: error accessing "
+		       "hidden directory \"%s\" (error %d)\n",
+		       optarg, err);
+		goto out;
+	}
+	for (idx=0; idx<cur_branches; idx++)
+		if (nd.mnt == new_lower_paths[idx].mnt &&
+		    nd.dentry == new_lower_paths[idx].dentry)
+			break;
+	path_release(&nd);	/* no longer needed */
+	if (idx == cur_branches) {
+		err = -ENOENT;	/* err may have been reset above */
+		printk(KERN_WARNING "unionfs: branch \"%s\" "
+		       "not found\n", optarg);
+		goto out;
+	}
+	/* check/change mode for existing branch */
+	/* we don't warn if perms==branchperms */
+	new_data[idx].branchperms = perms;
+	err = 0;
+out:
+	return err;
+}
+
+/* handle branch deletion during remount */
+static int do_remount_del_option(char *optarg, int cur_branches,
+				 struct unionfs_data *new_data,
+				 struct path *new_lower_paths)
+{
+	int err = -EINVAL;
+	int idx;
+	struct nameidata nd;
+	/* optarg contains the branch name to delete */
+
+	/*
+	 * Find matching branch index.  For now, this assumes that nothing
+	 * has been mounted on top of this Unionfs stack.  Once we have /odf
+	 * and cache-coherency resolved, we'll address the branch-path
+	 * uniqueness.
+	 */
+	err = path_lookup(optarg, LOOKUP_FOLLOW, &nd);
+	if (err) {
+		printk(KERN_WARNING "unionfs: error accessing "
+		       "hidden directory \"%s\" (error %d)\n",
+		       optarg, err);
+		goto out;
+	}
+	for (idx=0; idx < cur_branches; idx++)
+		if (nd.mnt == new_lower_paths[idx].mnt &&
+		    nd.dentry == new_lower_paths[idx].dentry)
+			break;
+	path_release(&nd);	/* no longer needed */
+	if (idx == cur_branches) {
+		printk(KERN_WARNING "unionfs: branch \"%s\" "
+		       "not found\n", optarg);
+		err = -ENOENT;
+		goto out;
+	}
+	/* check if there are any open files on the branch to be deleted */
+	if (atomic_read(&new_data[idx].open_files) > 0) {
+		err = -EBUSY;
+		goto out;
+	}
+
+	/*
+	 * Now we have to delete the branch.  First, release any handles it
+	 * has.  Then, move the remaining array indexes past "idx" in
+	 * new_data and new_lower_paths one to the left.  Finally, adjust
+	 * cur_branches.
+	 */
+	pathput(&new_lower_paths[idx]);
+
+	if (idx < cur_branches - 1) {
+		/* if idx==cur_branches-1, we delete last branch: easy */
+		memmove(&new_data[idx], &new_data[idx+1],
+			(cur_branches - 1 - idx) * sizeof(struct unionfs_data));
+		memmove(&new_lower_paths[idx], &new_lower_paths[idx+1],
+			(cur_branches - 1 - idx) * sizeof(struct path));
+	}
+
+	err = 0;
+out:
+	return err;
+}
+
+/* handle branch insertion during remount */
+static int do_remount_add_option(char *optarg, int cur_branches,
+				 struct unionfs_data *new_data,
+				 struct path *new_lower_paths,
+				 int *high_branch_id)
+{
+	int err = -EINVAL;
+	int perms;
+	int idx = 0;		/* default: insert at beginning */
+	char *new_branch , *modename = NULL;
+	struct nameidata nd;
+
+	/*
+	 * optarg can be of several forms:
+	 *
+	 * /bar:/foo		insert /foo before /bar
+	 * /bar:/foo=ro		insert /foo in ro mode before /bar
+	 * /foo			insert /foo in the beginning (prepend)
+	 * :/foo		insert /foo at the end (append)
+	 */
+	if (*optarg == ':') {	/* append? */
+		new_branch = optarg + 1; /* skip ':' */
+		idx = cur_branches;
+		goto found_insertion_point;
+	}
+	new_branch = strchr(optarg, ':');
+	if (!new_branch) {	/* prepend? */
+		new_branch = optarg;
+		goto found_insertion_point;
+	}
+	*new_branch++ = '\0';	/* holds path+mode of new branch */
+
+	/*
+	 * Find matching branch index.  For now, this assumes that nothing
+	 * has been mounted on top of this Unionfs stack.  Once we have /odf
+	 * and cache-coherency resolved, we'll address the branch-path
+	 * uniqueness.
+	 */
+	err = path_lookup(optarg, LOOKUP_FOLLOW, &nd);
+	if (err) {
+		printk(KERN_WARNING "unionfs: error accessing "
+		       "hidden directory \"%s\" (error %d)\n",
+		       optarg, err);
+		goto out;
+	}
+	for (idx=0; idx < cur_branches; idx++)
+		if (nd.mnt == new_lower_paths[idx].mnt &&
+		    nd.dentry == new_lower_paths[idx].dentry)
+			break;
+	path_release(&nd);	/* no longer needed */
+	if (idx == cur_branches) {
+		printk(KERN_WARNING "unionfs: branch \"%s\" "
+		       "not found\n", optarg);
+		err = -ENOENT;
+		goto out;
+	}
+
+	/*
+	 * At this point idx will hold the index where the new branch should
+	 * be inserted before.
+	 */
+found_insertion_point:
+	/* find the mode for the new branch */
+	if (new_branch)
+		modename = strchr(new_branch, '=');
+	if (modename)
+		*modename++ = '\0';
+	perms = parse_branch_mode(modename);
+
+	if (!new_branch || !*new_branch) {
+		printk(KERN_WARNING "unionfs: null new branch\n");
+		err = -EINVAL;
+		goto out;
+	}
+	err = path_lookup(new_branch, LOOKUP_FOLLOW, &nd);
+	if (err) {
+		printk(KERN_WARNING "unionfs: error accessing "
+		       "hidden directory \"%s\" (error %d)\n",
+		       new_branch, err);
+		goto out;
+	}
+	/* it's probably safe to check_mode the new branch to insert */
+	if ((err = check_branch(&nd))) {
+		printk(KERN_WARNING "unionfs: hidden directory "
+		       "\"%s\" is not a valid branch\n", optarg);
+		path_release(&nd);
+		goto out;
+	}
+
+	/*
+	 * Now we have to insert the new branch.  But first, move the bits
+	 * to make space for the new branch, if needed.  Finally, adjust
+	 * cur_branches.
+	 * We don't release nd here; it's kept until umount/remount.
+	 */
+	if (idx < cur_branches) {
+		/* if idx==cur_branches, we append: easy */
+		memmove(&new_data[idx+1], &new_data[idx],
+			(cur_branches - idx) * sizeof(struct unionfs_data));
+		memmove(&new_lower_paths[idx+1], &new_lower_paths[idx],
+			(cur_branches - idx) * sizeof(struct path));
+	}
+	new_lower_paths[idx].dentry = nd.dentry;
+	new_lower_paths[idx].mnt = nd.mnt;
+
+	new_data[idx].sb = nd.dentry->d_sb;
+	atomic_set(&new_data[idx].open_files, 0);
+	new_data[idx].branchperms = perms;
+	new_data[idx].branch_id = ++*high_branch_id; /* assign new branch ID */
+
+	err = 0;
+out:
+	return err;
+}
+
+
+/*
+ * Support branch management options on remount.
+ *
+ * See Documentation/filesystems/unionfs/ for details.
+ *
+ * @flags: numeric mount options
+ * @options: mount options string
+ *
+ * This function can rearrange a mounted union dynamically, adding and
+ * removing branches, including changing branch modes.  Clearly this has to
+ * be done safely and atomically.  Luckily, the VFS already calls this
+ * function with lock_super(sb) and lock_kernel() held, preventing
+ * concurrent mixing of new mounts, remounts, and unmounts.  Moreover,
+ * do_remount_sb(), our caller function, already called shrink_dcache_sb(sb)
+ * to purge dentries/inodes from our superblock, and also called
+ * fsync_super(sb) to purge any dirty pages.  So we're good.
+ *
+ * XXX: however, our remount code may also need to invalidate mapped pages
+ * so as to force them to be re-gotten from the (newly reconfigured) lower
+ * branches.  This has to wait for proper mmap and cache coherency support
+ * in the VFS.
+ *
  */
-static int unionfs_remount_fs(struct super_block *sb, int *flags, char *data)
+static int unionfs_remount_fs(struct super_block *sb, int *flags,
+			      char *options)
 {
-	return -ENOSYS;
+	int err = 0;
+	int i;
+	char *optionstmp, *tmp_to_free;	/* kstrdup'ed of "options" */
+	char *optname;
+	int cur_branches;	/* no. of current branches */
+	int new_branches;	/* no. of branches actually left in the end */
+	int add_branches;	/* est. no. of branches to add */
+	int del_branches;	/* est. no. of branches to del */
+	int max_branches;	/* max possible no. of branches */
+	struct unionfs_data *new_data = NULL, *tmp_data = NULL;
+	struct path *new_lower_paths = NULL, *tmp_lower_paths = NULL;
+	int new_high_branch_id;	/* new high branch ID */
+
+	unionfs_write_lock(sb);
+
+	/*
+	 * The VFS will take care of "ro" and "rw" flags, so anything else
+	 * is an error.  So we need to check if any other flags may have
+	 * been passed (none are allowed/supported as of now).
+	 */
+	if ((*flags & ~MS_RDONLY) != 0) {
+		printk(KERN_WARNING
+		       "unionfs: remount flags 0x%x unsupported\n", *flags);
+		err = -EINVAL;
+		goto out_error;
+	}
+
+	/*
+	 * If 'options' is NULL, it's probably because the user just changed
+	 * the union to a "ro" or "rw" and the VFS took care of it.  So
+	 * nothing to do and we're done.
+	 */
+	if (options[0] == '\0')
+		goto out_error;
+
+	/*
+	 * Find out how many branches we will have in the end, counting
+	 * "add" and "del" commands.  Copy the "options" string because
+	 * strsep modifies the string and we need it later.
+	 */
+	optionstmp = tmp_to_free = kstrdup(options, GFP_KERNEL);
+	if (!optionstmp) {
+		err = -ENOMEM;
+		goto out_free;
+	}
+	new_branches = cur_branches = sbmax(sb); /* current no. branches */
+	add_branches = del_branches = 0;
+	new_high_branch_id = sbhbid(sb); /* save current high_branch_id */
+	while ((optname = strsep(&optionstmp, ",")) != NULL) {
+		char *optarg;
+
+		if (!optname || !*optname)
+			continue;
+
+		optarg = strchr(optname, '=');
+		if (optarg)
+			*optarg++ = '\0';
+
+		if (!strcmp("add", optname))
+			add_branches++;
+		else if (!strcmp("del", optname))
+			del_branches++;
+	}
+	kfree(tmp_to_free);
+	/* after all changes, will we have at least one branch left? */
+	if ((new_branches + add_branches - del_branches) < 1) {
+		printk(KERN_WARNING
+		       "unionfs: no branches left after remount\n");
+		err = -EINVAL;
+		goto out_free;
+	}
+
+	/*
+	 * Since we haven't actually parsed all the add/del options, nor
+	 * have we checked them for errors, we don't know for sure how many
+	 * branches we will have after all changes have taken place.  In
+	 * fact, the total number of branches left could be less than what
+	 * we have now.  So we need to allocate space for a temporary
+	 * placeholder that is at least as large as the maximum number of
+	 * branches we *could* have, which is the current number plus all
+	 * the additions.  Once we're done with these temp placeholders, we
+	 * may have to re-allocate the final size, copy over from the temp,
+	 * and then free the temps (done near the end of this function).
+	 */
+	max_branches = cur_branches + add_branches;
+	/* allocate space for new pointers to hidden dentry */
+	tmp_data = kcalloc(max_branches,
+			   sizeof(struct unionfs_data), GFP_KERNEL);
+	if (!tmp_data) {
+		err = -ENOMEM;
+		goto out_free;
+	}
+	/* allocate space for new pointers to lower paths */
+	tmp_lower_paths = kcalloc(max_branches,
+			   sizeof(struct path), GFP_KERNEL);
+	if (!tmp_lower_paths) {
+		err = -ENOMEM;
+		goto out_free;
+	}
+	/* copy current info into new placeholders, incrementing refcnts */
+	memcpy(tmp_data, UNIONFS_SB(sb)->data,
+	       cur_branches * sizeof(struct unionfs_data));
+	memcpy(tmp_lower_paths, UNIONFS_D(sb->s_root)->lower_paths,
+	       cur_branches * sizeof(struct path));
+	for (i=0; i<cur_branches; i++)
+		pathget(&tmp_lower_paths[i]); /* drop refs at end of fxn */
+
+	/*******************************************************************
+	 * For each branch command, do path_lookup on the requested branch,
+	 * and apply the change to a temp branch list.  To handle errors, we
+	 * already dup'ed the old arrays (above), and increased the refcnts
+	 * on various f/s objects.  So now we can do all the path_lookups
+	 * and branch-management commands on the new arrays.  If it fail mid
+	 * way, we free the tmp arrays and *put all objects.  If we succeed,
+	 * then we free old arrays and *put its objects, and then replace
+	 * the arrays with the new tmp list (we may have to re-allocate the
+	 * memory because the temp lists could have been larger than what we
+	 * actually needed).
+	 *******************************************************************/
+
+	while ((optname = strsep(&options, ",")) != NULL) {
+		char *optarg;
+
+		if (!optname || !*optname)
+			continue;
+		/*
+		 * At this stage optname holds a comma-delimited option, but
+		 * without the commas.  Next, we need to break the string on
+		 * the '=' symbol to separate CMD=ARG, where ARG itself can
+		 * be KEY=VAL.  For example, in mode=/foo=rw, CMD is "mode",
+		 * KEY is "/foo", and VAL is "rw".
+		 */
+		optarg = strchr(optname, '=');
+		if (optarg)
+			*optarg++ = '\0';
+		/* incgen remount option (instead of old ioctl) */
+		if (!strcmp("incgen", optname)) {
+			err = 0;
+			goto out_no_change;
+		}
+
+		/*
+		 * All of our options take an argument now.  (Insert ones
+		 * that don't above this check.)  So at this stage optname
+		 * contains the CMD part and optarg contains the ARG part.
+		 */
+		if (!optarg || !*optarg) {
+			printk("unionfs: all remount options require "
+			       "an argument (%s).\n", optname);
+			err = -EINVAL;
+			goto out_release;
+		}
+
+		if (!strcmp("add", optname)) {
+			err = do_remount_add_option(optarg, new_branches,
+						    tmp_data,
+						    tmp_lower_paths,
+						    &new_high_branch_id);
+			if (err)
+				goto out_release;
+			new_branches++;
+			if (new_branches > UNIONFS_MAX_BRANCHES) {
+				printk("unionfs: command exceeds %d branches\n",
+				       UNIONFS_MAX_BRANCHES);
+				err = -E2BIG;
+				goto out_release;
+			}
+			continue;
+		}
+		if (!strcmp("del", optname)) {
+			err = do_remount_del_option(optarg, new_branches,
+						    tmp_data,
+						    tmp_lower_paths);
+			if (err)
+				goto out_release;
+			new_branches--;
+			continue;
+		}
+		if (!strcmp("mode", optname)) {
+			err = do_remount_mode_option(optarg, new_branches,
+						     tmp_data,
+						     tmp_lower_paths);
+			if (err)
+				goto out_release;
+			continue;
+		}
+
+		/*
+		 * When you use "mount -o remount,ro", mount(8) will
+		 * reportedly pass the original dirs= string from
+		 * /proc/mounts.  So for now, we have to ignore dirs= and
+		 * not consider it an error, unless we want to allow users
+		 * to pass dirs= in remount.  Note that to allow the VFS to
+		 * actually process the ro/rw remount options, we have to
+		 * return 0 from this function.
+		 */
+		if (!strcmp("dirs", optname)) {
+			printk(KERN_WARNING
+			       "unionfs: remount ignoring option \"%s\".\n",
+			       optname);
+			continue;
+		}
+
+		err = -EINVAL;
+		printk(KERN_WARNING
+		       "unionfs: unrecognized option \"%s\"\n", optname);
+		goto out_release;
+	}
+
+out_no_change:
+
+	/******************************************************************
+	 * WE'RE ALMOST DONE: see if we need to allocate a small-sized new
+	 * vector, copy the vectors to their correct place, release the
+	 * refcnt of the older ones, and return.
+	 * Also handle invalidating any pgaes that will have to be re-read.
+	 *******************************************************************/
+
+	/*
+	 * Allocate space for actual pointers, if needed.  By the time we
+	 * finish this block of code, new_branches and new_lower_paths will
+	 * have the correct size.  None of this code below would be needed
+	 * if the kernel had a realloc() function, at least one capable of
+	 * shrinking/truncating an allocation's size (hint, hint).
+	 */
+	if (new_branches < max_branches) {
+
+		/* allocate space for new pointers to hidden dentry */
+		new_data = kcalloc(new_branches,
+				   sizeof(struct unionfs_data), GFP_KERNEL);
+		if (!new_data) {
+			err = -ENOMEM;
+			goto out_release;
+		}
+		/* allocate space for new pointers to lower paths */
+		new_lower_paths = kcalloc(new_branches,
+					  sizeof(struct path), GFP_KERNEL);
+		if (!new_lower_paths) {
+			err = -ENOMEM;
+			goto out_release;
+		}
+		/*
+		 * copy current info into new placeholders, incrementing
+		 * refcounts.
+		 */
+		memcpy(new_data, tmp_data,
+		       new_branches * sizeof(struct unionfs_data));
+		memcpy(new_lower_paths, tmp_lower_paths,
+		       new_branches * sizeof(struct path));
+		/*
+		 * Since we already hold various refcnts on the objects, we
+		 * don't need to redo it here.  Just free the older memory
+		 * and re-point the pointers.
+		 */
+		kfree(tmp_data);
+		kfree(tmp_lower_paths);
+		/* no need to nullify pointers here */
+	} else {
+		/* number of branches didn't change, no need to re-alloc */
+		new_data = tmp_data;
+		new_lower_paths = tmp_lower_paths;
+	}
+
+	/*
+	 * OK, just before we actually put the new set of branches in place,
+	 * we need to ensure that our own f/s has no dirty objects left.
+	 * Luckily, do_remount_sb() already calls shrink_dcache_sb(sb) and
+	 * fsync_super(sb), taking care of dentries, inodes, and dirty
+	 * pages.  So all that's left is for us to invalidate any leftover
+	 * (non-dirty) pages to ensure that they will be re-read from the
+	 * new lower branches (and to support mmap).
+	 */
+
+	/*
+	 * No we call drop_pagecache_sb() to invalidate all pages in this
+	 * super.  This function calls invalidate_inode_pages(mapping),
+	 * which calls invalidate_mapping_pages(): the latter, however, will
+	 * not invalidate pages which are dirty, locked, under writeback, or
+	 * mapped into pagetables.  We shouldn't have to worry about dirty
+	 * or under-writeback pages, because do_remount_sb() called
+	 * fsync_super() which would not have returned until all dirty pages
+	 * were flushed.
+	 *
+	 * But do w have to worry about locked pages?  Is there any chance
+	 * that in here we'll get locked pages?
+	 *
+	 * XXX: what about pages mapped into pagetables?  Are these pages
+	 * which user processes may have mmap(2)'ed?  If so, then we need to
+	 * invalidate those too, no?  Maybe we'll have to write our own
+	 * version of invalidate_mapping_pages() which also handled mapped
+	 * pages.
+	 *
+	 * XXX: Alternatively, maybe we should call truncate_inode_pages(),
+	 * which use two passes over the pages list, and will truncate all
+	 * pages.
+	 */
+	drop_pagecache_sb(sb);
+
+	/* copy new vectors into their correct place */
+	tmp_data = UNIONFS_SB(sb)->data;
+	UNIONFS_SB(sb)->data = new_data;
+	new_data = NULL;	/* so don't free good pointers below */
+	tmp_lower_paths = UNIONFS_D(sb->s_root)->lower_paths;
+	UNIONFS_D(sb->s_root)->lower_paths = new_lower_paths;
+	new_lower_paths = NULL;	/* so don't free good pointers below */
+
+	/* update our unionfs_sb_info and root dentry index of last branch */
+	i = sbmax(sb);		/* save no. of branches to release at end */
+	sbend(sb) = new_branches - 1;
+	set_dbend(sb->s_root, new_branches - 1);
+	UNIONFS_D(sb->s_root)->bcount = new_branches;
+	new_branches = i;	/* no. of branches to release below */
+
+	/* maxbytes may have changed */
+	sb->s_maxbytes = unionfs_lower_super_idx(sb, 0)->s_maxbytes;
+	/* update high branch ID */
+	sbhbid(sb) = new_high_branch_id;
+
+	/* update our sb->generation for revalidating objects */
+	i = atomic_inc_return(&UNIONFS_SB(sb)->generation);
+	atomic_set(&UNIONFS_D(sb->s_root)->generation, i);
+	atomic_set(&UNIONFS_I(sb->s_root->d_inode)->generation, i);
+	printk("unionfs: new generation number %d\n", i);
+	err = 0;		/* reset to success */
+
+	/*
+	 * The code above falls through to the next label, and releases the
+	 * refcnts of the older ones (stored in tmp_*): if we fell through
+	 * here, it means success.  However, if we jump directly to this
+	 * label from any error above, then an error occurred after we
+	 * grabbed various refcnts, and so we have to release the
+	 * temporarily constructed structures.
+	 */
+out_release:
+	/* no need to cleanup/release anything in tmp_data */
+	if (tmp_lower_paths)
+		for (i=0; i<new_branches; i++)
+			pathput(&tmp_lower_paths[i]);
+out_free:
+	kfree(tmp_lower_paths);
+	kfree(tmp_data);
+	kfree(new_lower_paths);
+	kfree(new_data);
+out_error:
+	unionfs_write_unlock(sb);
+	return err;
 }
 
 /*
diff --git a/fs/unionfs/union.h b/fs/unionfs/union.h
index 7bd6306..715a3ad 100644
--- a/fs/unionfs/union.h
+++ b/fs/unionfs/union.h
@@ -60,6 +60,9 @@
 /* number of times we try to get a unique temporary file name */
 #define GET_TMPNAM_MAX_RETRY	5
 
+/* maximum number of branches we support, to avoid memory blowup */
+#define UNIONFS_MAX_BRANCHES	128
+
 /* Operations vectors defined in specific files. */
 extern struct file_operations unionfs_main_fops;
 extern struct file_operations unionfs_dir_fops;
@@ -408,6 +411,9 @@ static inline int is_robranch(const struct dentry *dentry)
  * EXTERNALS:
  */
 extern char *alloc_whname(const char *name, int len);
+extern int check_branch(struct nameidata *nd);
+extern int __parse_branch_mode(const char *name);
+extern int parse_branch_mode(const char *name);
 
 /* These two functions are here because it is kind of daft to copy and paste the
  * contents of the two functions to 32+ places in unionfs
-- 
1.5.0.3.268.g3dda


  parent reply	other threads:[~2007-04-09 14:58 UTC|newest]

Thread overview: 27+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2007-04-09 14:53 [GIT PULL -mm] Unionfs branch management code Josef 'Jeff' Sipek
2007-04-09 14:53 ` [PATCH 01/21] fs: Introduce path{get,put} Josef 'Jeff' Sipek
2007-04-09 14:53 ` [PATCH 02/21] fs: Export drop_pagecache_sb symbol Josef 'Jeff' Sipek
2007-04-09 14:53 ` [PATCH 03/21] Unionfs: Documentation updates for branch-management Josef 'Jeff' Sipek
2007-04-09 14:53 ` [PATCH 04/21] Unionfs: Proper comment on rwsem field Josef 'Jeff' Sipek
2007-04-09 14:53 ` [PATCH 05/21] Unionfs: Rename unionfs_data sbcount field to more appropriate open_files Josef 'Jeff' Sipek
2007-04-09 14:53 ` [PATCH 06/21] Unionfs: Provide more helpful info on branch leaks during unmount Josef 'Jeff' Sipek
2007-04-09 14:53 ` [PATCH 07/21] Unionfs: Actually verify if dentry's info node is locked Josef 'Jeff' Sipek
2007-04-09 14:53 ` [PATCH 08/21] Unionfs: Introduce branch-id code Josef 'Jeff' Sipek
2007-04-09 14:54 ` Josef 'Jeff' Sipek [this message]
2007-04-09 14:54 ` [PATCH 10/21] Unionfs: Introduce unionfs_mnt{get,put} Josef 'Jeff' Sipek
2007-04-09 14:54 ` [PATCH 11/21] Unionfs: Rewrite unionfs_d_revalidate Josef 'Jeff' Sipek
2007-04-09 14:54 ` [PATCH 12/21] Unionfs: Grab the unionfs sb private data lock around branch info users Josef 'Jeff' Sipek
2007-04-09 14:54 ` [PATCH 13/21] Unionfs: Remove the older incgen ioctl Josef 'Jeff' Sipek
2007-04-09 14:54 ` [PATCH 14/21] Unionfs: Document unionfs_d_release locking Josef 'Jeff' Sipek
2007-04-09 14:54 ` [PATCH 15/21] Unionfs: Decrement totalopens counter on error in unionfs_open Josef 'Jeff' Sipek
2007-04-09 14:54 ` [PATCH 16/21] Unionfs: unionfs_create needs to revalidate the dentry Josef 'Jeff' Sipek
2007-04-09 14:54 ` [PATCH 17/21] Unionfs: vfsmount reference counting fixes Josef 'Jeff' Sipek
2007-04-09 14:54 ` [PATCH 18/21] Unionfs: Pass lowernd to lower ->revalidate function Josef 'Jeff' Sipek
2007-04-09 14:54 ` [PATCH 19/21] Unionfs: Properly handle stale inodes passed to unionfs_permission Josef 'Jeff' Sipek
2007-04-09 14:54 ` [PATCH 20/21] Unionfs: Added several BUG_ONs to assert dentry validity Josef 'Jeff' Sipek
2007-04-09 14:54 ` [PATCH 21/21] Unionfs: Don't inline do_remount_{add,del,mode}_option Josef 'Jeff' Sipek
2007-04-09 17:49 ` [GIT PULL -mm] Unionfs branch management code Andrew Morton
2007-04-09 22:47   ` Josef Sipek
2007-04-10 17:22   ` Shaya Potter
2007-04-10 17:35     ` Josef Sipek
2007-04-09 19:52 ` Jan Engelhardt

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=11761304542331-git-send-email-jsipek@cs.sunysb.edu \
    --to=jsipek@cs.sunysb.edu \
    --cc=akpm@linux-foundation.org \
    --cc=ezk@cs.sunysb.edu \
    --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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.