lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-Id: <11972545501069-git-send-email-ezk@cs.sunysb.edu>
Date:	Sun,  9 Dec 2007 21:41:58 -0500
From:	Erez Zadok <ezk@...sunysb.edu>
To:	hch@...radead.org, viro@....linux.org.uk, akpm@...ux-foundation.org
Cc:	linux-kernel@...r.kernel.org, linux-fsdevel@...r.kernel.org,
	Erez Zadok <ezk@...sunysb.edu>
Subject: [PATCH 25/42] Unionfs: super_block operations

Includes read_inode, delete_inode, put_super, statfs, remount_fs (which
supports branch-management ops), clear_inode, alloc_inode, destroy_inode,
write_inode, umount_begin, and show_options.

Signed-off-by: Erez Zadok <ezk@...sunysb.edu>
---
 fs/unionfs/super.c | 1020 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 1020 insertions(+), 0 deletions(-)
 create mode 100644 fs/unionfs/super.c

diff --git a/fs/unionfs/super.c b/fs/unionfs/super.c
new file mode 100644
index 0000000..d9cf2a7
--- /dev/null
+++ b/fs/unionfs/super.c
@@ -0,0 +1,1020 @@
+/*
+ * Copyright (c) 2003-2007 Erez Zadok
+ * Copyright (c) 2003-2006 Charles P. Wright
+ * Copyright (c) 2005-2007 Josef 'Jeff' Sipek
+ * Copyright (c) 2005-2006 Junjiro Okajima
+ * Copyright (c) 2005      Arun M. Krishnakumar
+ * Copyright (c) 2004-2006 David P. Quigley
+ * Copyright (c) 2003-2004 Mohammad Nayyer Zubair
+ * Copyright (c) 2003      Puja Gupta
+ * Copyright (c) 2003      Harikesavan Krishnan
+ * Copyright (c) 2003-2007 Stony Brook University
+ * Copyright (c) 2003-2007 The Research Foundation of SUNY
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include "union.h"
+
+/*
+ * The inode cache is used with alloc_inode for both our inode info and the
+ * vfs inode.
+ */
+static struct kmem_cache *unionfs_inode_cachep;
+
+static void unionfs_read_inode(struct inode *inode)
+{
+	int size;
+	struct unionfs_inode_info *info = UNIONFS_I(inode);
+
+	unionfs_read_lock(inode->i_sb);
+
+	memset(info, 0, offsetof(struct unionfs_inode_info, vfs_inode));
+	info->bstart = -1;
+	info->bend = -1;
+	atomic_set(&info->generation,
+		   atomic_read(&UNIONFS_SB(inode->i_sb)->generation));
+	spin_lock_init(&info->rdlock);
+	info->rdcount = 1;
+	info->hashsize = -1;
+	INIT_LIST_HEAD(&info->readdircache);
+
+	size = sbmax(inode->i_sb) * sizeof(struct inode *);
+	info->lower_inodes = kzalloc(size, GFP_KERNEL);
+	if (unlikely(!info->lower_inodes)) {
+		printk(KERN_CRIT "unionfs: no kernel memory when allocating "
+		       "lower-pointer array!\n");
+		BUG();
+	}
+
+	inode->i_version++;
+	inode->i_op = &unionfs_main_iops;
+	inode->i_fop = &unionfs_main_fops;
+
+	inode->i_mapping->a_ops = &unionfs_aops;
+
+	unionfs_read_unlock(inode->i_sb);
+}
+
+/*
+ * we now define delete_inode, because there are two VFS paths that may
+ * destroy an inode: one of them calls clear inode before doing everything
+ * else that's needed, and the other is fine.  This way we truncate the inode
+ * size (and its pages) and then clear our own inode, which will do an iput
+ * on our and the lower inode.
+ *
+ * No need to lock sb info's rwsem.
+ */
+static void unionfs_delete_inode(struct inode *inode)
+{
+	i_size_write(inode, 0);	/* every f/s seems to do that */
+
+	if (inode->i_data.nrpages)
+		truncate_inode_pages(&inode->i_data, 0);
+
+	clear_inode(inode);
+}
+
+/*
+ * final actions when unmounting a file system
+ *
+ * No need to lock rwsem.
+ */
+static void unionfs_put_super(struct super_block *sb)
+{
+	int bindex, bstart, bend;
+	struct unionfs_sb_info *spd;
+	int leaks = 0;
+
+	spd = UNIONFS_SB(sb);
+	if (!spd)
+		return;
+
+	bstart = sbstart(sb);
+	bend = sbend(sb);
+
+	/* Make sure we have no leaks of branchget/branchput. */
+	for (bindex = bstart; bindex <= bend; bindex++)
+		if (unlikely(branch_count(sb, bindex) != 0)) {
+			printk(KERN_CRIT
+			       "unionfs: branch %d has %d references left!\n",
+			       bindex, branch_count(sb, bindex));
+			leaks = 1;
+		}
+	BUG_ON(leaks != 0);
+
+	kfree(spd->data);
+	kfree(spd);
+	sb->s_fs_info = NULL;
+}
+
+/*
+ * Since people use this to answer the "How big of a file can I write?"
+ * question, we report the size of the highest priority branch as the size of
+ * the union.
+ */
+static int unionfs_statfs(struct dentry *dentry, struct kstatfs *buf)
+{
+	int err	= 0;
+	struct super_block *sb;
+	struct dentry *lower_dentry;
+
+	sb = dentry->d_sb;
+
+	unionfs_read_lock(sb);
+	unionfs_lock_dentry(dentry);
+
+	if (unlikely(!__unionfs_d_revalidate_chain(dentry, NULL, false))) {
+		err = -ESTALE;
+		goto out;
+	}
+	unionfs_check_dentry(dentry);
+
+	lower_dentry = unionfs_lower_dentry(sb->s_root);
+	err = vfs_statfs(lower_dentry, buf);
+
+	/* set return buf to our f/s to avoid confusing user-level utils */
+	buf->f_type = UNIONFS_SUPER_MAGIC;
+	/*
+	 * Our maximum file name can is shorter by a few bytes because every
+	 * file name could potentially be whited-out.
+	 *
+	 * XXX: this restriction goes away with ODF.
+	 */
+	buf->f_namelen -= UNIONFS_WHLEN;
+
+	/*
+	 * reset two fields to avoid confusing user-land.
+	 * XXX: is this still necessary?
+	 */
+	memset(&buf->f_fsid, 0, sizeof(__kernel_fsid_t));
+	memset(&buf->f_spare, 0, sizeof(buf->f_spare));
+
+out:
+	unionfs_check_dentry(dentry);
+	unionfs_unlock_dentry(dentry);
+	unionfs_read_unlock(sb);
+	return err;
+}
+
+/* handle mode changing during remount */
+static noinline 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(KERN_ERR
+		       "unionfs: no branch specified for mode change\n");
+		goto out;
+	}
+	if (!modename) {
+		printk(KERN_ERR "unionfs: branch \"%s\" requires a mode\n",
+		       optarg);
+		goto out;
+	}
+	*modename++ = '\0';
+	err = parse_branch_mode(modename, &perms);
+	if (err) {
+		printk(KERN_ERR "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_ERR "unionfs: error accessing "
+		       "lower 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_ERR "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 noinline 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_ERR "unionfs: error accessing "
+		       "lower 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_ERR "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 noinline 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_ERR "unionfs: error accessing "
+		       "lower 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_ERR "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';
+	if (!new_branch || !*new_branch) {
+		printk(KERN_ERR "unionfs: null new branch\n");
+		err = -EINVAL;
+		goto out;
+	}
+	err = parse_branch_mode(modename, &perms);
+	if (err) {
+		printk(KERN_ERR "unionfs: invalid mode \"%s\" for "
+		       "branch \"%s\"\n", modename, new_branch);
+		goto out;
+	}
+	err = path_lookup(new_branch, LOOKUP_FOLLOW, &nd);
+	if (err) {
+		printk(KERN_ERR "unionfs: error accessing "
+		       "lower directory \"%s\" (error %d)\n",
+		       new_branch, err);
+		goto out;
+	}
+	/*
+	 * It's probably safe to check_mode the new branch to insert.  Note:
+	 * we don't allow inserting branches which are unionfs's by
+	 * themselves (check_branch returns EINVAL in that case).  This is
+	 * because this code base doesn't support stacking unionfs: the ODF
+	 * code base supports that correctly.
+	 */
+	err = check_branch(&nd);
+	if (err) {
+		printk(KERN_ERR "unionfs: lower 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 *options)
+{
+	int err = 0;
+	int i;
+	char *optionstmp, *tmp_to_free;	/* kstrdup'ed of "options" */
+	char *optname;
+	int cur_branches = 0;	/* no. of current branches */
+	int new_branches = 0;	/* 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;
+	struct inode **new_lower_inodes = NULL;
+	int new_high_branch_id;	/* new high branch ID */
+	int size;		/* memory allocation size, temp var */
+	int old_ibstart, old_ibend;
+
+	unionfs_write_lock(sb);
+
+	/*
+	 * The VFS will take care of "ro" and "rw" flags, and we can safely
+	 * ignore MS_SILENT, but anything else left over 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 | MS_SILENT)) != 0) {
+		printk(KERN_ERR
+		       "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 || 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.
+	 */
+	tmp_to_free = kstrdup(options, GFP_KERNEL);
+	optionstmp = tmp_to_free;
+	if (unlikely(!optionstmp)) {
+		err = -ENOMEM;
+		goto out_free;
+	}
+	cur_branches = sbmax(sb); /* current no. branches */
+	new_branches = sbmax(sb);
+	del_branches = 0;
+	add_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_ERR
+		       "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 lower dentry */
+	tmp_data = kcalloc(max_branches,
+			   sizeof(struct unionfs_data), GFP_KERNEL);
+	if (unlikely(!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 (unlikely(!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(KERN_ERR "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(KERN_ERR "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_ERR
+		       "unionfs: unrecognized option \"%s\"\n", optname);
+		goto out_release;
+	}
+
+out_no_change:
+
+	/******************************************************************
+	 * WE'RE ALMOST DONE: check if leftmost branch might be read-only,
+	 * 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 pages that will
+	 * have to be re-read.
+	 *******************************************************************/
+
+	if (!(tmp_data[0].branchperms & MAY_WRITE)) {
+		printk(KERN_ERR "unionfs: leftmost branch cannot be read-only "
+		       "(use \"remount,ro\" to create a read-only union)\n");
+		err = -EINVAL;
+		goto out_release;
+	}
+
+	/* (re)allocate space for new pointers to lower dentry */
+	size = new_branches * sizeof(struct unionfs_data);
+	new_data = krealloc(tmp_data, size, GFP_KERNEL);
+	if (unlikely(!new_data)) {
+		err = -ENOMEM;
+		goto out_release;
+	}
+
+	/* allocate space for new pointers to lower paths */
+	size = new_branches * sizeof(struct path);
+	new_lower_paths = krealloc(tmp_lower_paths, size, GFP_KERNEL);
+	if (unlikely(!new_lower_paths)) {
+		err = -ENOMEM;
+		goto out_release;
+	}
+
+	/* allocate space for new pointers to lower inodes */
+	new_lower_inodes = kcalloc(new_branches,
+				   sizeof(struct inode *), GFP_KERNEL);
+	if (unlikely(!new_lower_inodes)) {
+		err = -ENOMEM;
+		goto out_release;
+	}
+
+	/*
+	 * 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).
+	 */
+
+	/*
+	 * Now 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 page tables.  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 we 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);
+	old_ibstart = ibstart(sb->s_root->d_inode);
+	old_ibend = ibend(sb->s_root->d_inode);
+	ibend(sb->s_root->d_inode) = new_branches - 1;
+	UNIONFS_D(sb->s_root)->bcount = new_branches;
+	new_branches = i; /* no. of branches to release below */
+
+	/*
+	 * Update lower inodes: 3 steps
+	 * 1. grab ref on all new lower inodes
+	 */
+	for (i = dbstart(sb->s_root); i <= dbend(sb->s_root); i++) {
+		struct dentry *lower_dentry =
+			unionfs_lower_dentry_idx(sb->s_root, i);
+		igrab(lower_dentry->d_inode);
+		new_lower_inodes[i] = lower_dentry->d_inode;
+	}
+	/* 2. release reference on all older lower inodes */
+	for (i = old_ibstart; i <= old_ibend; i++) {
+		iput(unionfs_lower_inode_idx(sb->s_root->d_inode, i));
+		unionfs_set_lower_inode_idx(sb->s_root->d_inode, i, NULL);
+	}
+	kfree(UNIONFS_I(sb->s_root->d_inode)->lower_inodes);
+	/* 3. update root dentry's inode to new lower_inodes array */
+	UNIONFS_I(sb->s_root->d_inode)->lower_inodes = new_lower_inodes;
+	new_lower_inodes = NULL;
+
+	/* 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);
+	if (!(*flags & MS_SILENT))
+		pr_info("unionfs: new generation number %d\n", i);
+	/* finally, update the root dentry's times */
+	unionfs_copy_attr_times(sb->s_root->d_inode);
+	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);
+	kfree(new_lower_inodes);
+out_error:
+	unionfs_check_dentry(sb->s_root);
+	unionfs_write_unlock(sb);
+	return err;
+}
+
+/*
+ * Called by iput() when the inode reference count reached zero
+ * and the inode is not hashed anywhere.  Used to clear anything
+ * that needs to be, before the inode is completely destroyed and put
+ * on the inode free list.
+ *
+ * No need to lock sb info's rwsem.
+ */
+static void unionfs_clear_inode(struct inode *inode)
+{
+	int bindex, bstart, bend;
+	struct inode *lower_inode;
+	struct list_head *pos, *n;
+	struct unionfs_dir_state *rdstate;
+
+	list_for_each_safe(pos, n, &UNIONFS_I(inode)->readdircache) {
+		rdstate = list_entry(pos, struct unionfs_dir_state, cache);
+		list_del(&rdstate->cache);
+		free_rdstate(rdstate);
+	}
+
+	/*
+	 * Decrement a reference to a lower_inode, which was incremented
+	 * by our read_inode when it was created initially.
+	 */
+	bstart = ibstart(inode);
+	bend = ibend(inode);
+	if (bstart >= 0) {
+		for (bindex = bstart; bindex <= bend; bindex++) {
+			lower_inode = unionfs_lower_inode_idx(inode, bindex);
+			if (!lower_inode)
+				continue;
+			iput(lower_inode);
+		}
+	}
+
+	kfree(UNIONFS_I(inode)->lower_inodes);
+	UNIONFS_I(inode)->lower_inodes = NULL;
+}
+
+static struct inode *unionfs_alloc_inode(struct super_block *sb)
+{
+	struct unionfs_inode_info *i;
+
+	i = kmem_cache_alloc(unionfs_inode_cachep, GFP_KERNEL);
+	if (unlikely(!i))
+		return NULL;
+
+	/* memset everything up to the inode to 0 */
+	memset(i, 0, offsetof(struct unionfs_inode_info, vfs_inode));
+
+	i->vfs_inode.i_version = 1;
+	return &i->vfs_inode;
+}
+
+static void unionfs_destroy_inode(struct inode *inode)
+{
+	kmem_cache_free(unionfs_inode_cachep, UNIONFS_I(inode));
+}
+
+/* unionfs inode cache constructor */
+static void init_once(struct kmem_cache *cachep, void *obj)
+{
+	struct unionfs_inode_info *i = obj;
+
+	inode_init_once(&i->vfs_inode);
+}
+
+int unionfs_init_inode_cache(void)
+{
+	int err = 0;
+
+	unionfs_inode_cachep =
+		kmem_cache_create("unionfs_inode_cache",
+				  sizeof(struct unionfs_inode_info), 0,
+				  SLAB_RECLAIM_ACCOUNT, init_once);
+	if (unlikely(!unionfs_inode_cachep))
+		err = -ENOMEM;
+	return err;
+}
+
+/* unionfs inode cache destructor */
+void unionfs_destroy_inode_cache(void)
+{
+	if (unionfs_inode_cachep)
+		kmem_cache_destroy(unionfs_inode_cachep);
+}
+
+/*
+ * Called when we have a dirty inode, right here we only throw out
+ * parts of our readdir list that are too old.
+ *
+ * No need to grab sb info's rwsem.
+ */
+static int unionfs_write_inode(struct inode *inode, int sync)
+{
+	struct list_head *pos, *n;
+	struct unionfs_dir_state *rdstate;
+
+	spin_lock(&UNIONFS_I(inode)->rdlock);
+	list_for_each_safe(pos, n, &UNIONFS_I(inode)->readdircache) {
+		rdstate = list_entry(pos, struct unionfs_dir_state, cache);
+		/* We keep this list in LRU order. */
+		if ((rdstate->access + RDCACHE_JIFFIES) > jiffies)
+			break;
+		UNIONFS_I(inode)->rdcount--;
+		list_del(&rdstate->cache);
+		free_rdstate(rdstate);
+	}
+	spin_unlock(&UNIONFS_I(inode)->rdlock);
+
+	return 0;
+}
+
+/*
+ * Used only in nfs, to kill any pending RPC tasks, so that subsequent
+ * code can actually succeed and won't leave tasks that need handling.
+ */
+static void unionfs_umount_begin(struct vfsmount *mnt, int flags)
+{
+	struct super_block *sb, *lower_sb;
+	struct vfsmount *lower_mnt;
+	int bindex, bstart, bend;
+
+	if (!(flags & MNT_FORCE))
+		/*
+		 * we are not being MNT_FORCE'd, therefore we should emulate
+		 * old behavior
+		 */
+		return;
+
+	sb = mnt->mnt_sb;
+
+	unionfs_read_lock(sb);
+
+	bstart = sbstart(sb);
+	bend = sbend(sb);
+	for (bindex = bstart; bindex <= bend; bindex++) {
+		lower_mnt = unionfs_lower_mnt_idx(sb->s_root, bindex);
+		lower_sb = unionfs_lower_super_idx(sb, bindex);
+
+		if (lower_mnt && lower_sb && lower_sb->s_op &&
+		    lower_sb->s_op->umount_begin)
+			lower_sb->s_op->umount_begin(lower_mnt, flags);
+	}
+
+	unionfs_read_unlock(sb);
+}
+
+static int unionfs_show_options(struct seq_file *m, struct vfsmount *mnt)
+{
+	struct super_block *sb = mnt->mnt_sb;
+	int ret = 0;
+	char *tmp_page;
+	char *path;
+	int bindex, bstart, bend;
+	int perms;
+
+	unionfs_read_lock(sb);
+
+	unionfs_lock_dentry(sb->s_root);
+
+	tmp_page = (char *) __get_free_page(GFP_KERNEL);
+	if (unlikely(!tmp_page)) {
+		ret = -ENOMEM;
+		goto out;
+	}
+
+	bstart = sbstart(sb);
+	bend = sbend(sb);
+
+	seq_printf(m, ",dirs=");
+	for (bindex = bstart; bindex <= bend; bindex++) {
+		path = d_path(unionfs_lower_dentry_idx(sb->s_root, bindex),
+			      unionfs_lower_mnt_idx(sb->s_root, bindex),
+			      tmp_page, PAGE_SIZE);
+		if (IS_ERR(path)) {
+			ret = PTR_ERR(path);
+			goto out;
+		}
+
+		perms = branchperms(sb, bindex);
+
+		seq_printf(m, "%s=%s", path,
+			   perms & MAY_WRITE ? "rw" : "ro");
+		if (bindex != bend)
+			seq_printf(m, ":");
+	}
+
+out:
+	free_page((unsigned long) tmp_page);
+
+	unionfs_unlock_dentry(sb->s_root);
+
+	unionfs_read_unlock(sb);
+
+	return ret;
+}
+
+struct super_operations unionfs_sops = {
+	.read_inode	= unionfs_read_inode,
+	.delete_inode	= unionfs_delete_inode,
+	.put_super	= unionfs_put_super,
+	.statfs		= unionfs_statfs,
+	.remount_fs	= unionfs_remount_fs,
+	.clear_inode	= unionfs_clear_inode,
+	.umount_begin	= unionfs_umount_begin,
+	.show_options	= unionfs_show_options,
+	.write_inode	= unionfs_write_inode,
+	.alloc_inode	= unionfs_alloc_inode,
+	.destroy_inode	= unionfs_destroy_inode,
+};
-- 
1.5.2.2

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@...r.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ