From: Erez Zadok <[EMAIL PROTECTED]>

Signed-off-by: Erez Zadok <[EMAIL PROTECTED]>
Signed-off-by: Josef 'Jeff' Sipek <[EMAIL PROTECTED]>
---
 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

-
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