GIT: unionfs2-2.6.27.y: Unionfs: use dget_parent to keep parent dentry stable
Erez Zadok
ezk at fsl.cs.sunysb.edu
Thu Aug 12 23:18:09 EDT 2010
commit 4b131ffbfe67109b27691550fff93d170aaf230e
Author: Erez Zadok <ezk at cs.sunysb.edu>
Date: Tue Sep 16 00:59:08 2008 -0400
Unionfs: use dget_parent to keep parent dentry stable
Signed-off-by: Erez Zadok <ezk at cs.sunysb.edu>
diff --git a/fs/unionfs/commonfops.c b/fs/unionfs/commonfops.c
index 7711f93..e68bc5a 100644
--- a/fs/unionfs/commonfops.c
+++ b/fs/unionfs/commonfops.c
@@ -24,7 +24,7 @@
* stolen from NFS's silly rename
*/
static int copyup_deleted_file(struct file *file, struct dentry *dentry,
- int bstart, int bindex)
+ struct dentry *parent, int bstart, int bindex)
{
static unsigned int counter;
const int i_inosize = sizeof(dentry->d_inode->i_ino) * 2;
@@ -71,8 +71,7 @@ retry:
} while (tmp_dentry->d_inode != NULL); /* need negative dentry */
dput(tmp_dentry);
- err = copyup_named_file(dentry->d_parent->d_inode, file, name, bstart,
- bindex,
+ err = copyup_named_file(parent->d_inode, file, name, bstart, bindex,
i_size_read(file->f_path.dentry->d_inode));
if (err) {
if (unlikely(err == -EEXIST))
@@ -199,7 +198,8 @@ static int open_highest_file(struct file *file, bool willwrite)
struct file *lower_file;
struct dentry *lower_dentry;
struct dentry *dentry = file->f_path.dentry;
- struct inode *parent_inode = dentry->d_parent->d_inode;
+ struct dentry *parent = dget_parent(dentry);
+ struct inode *parent_inode = parent->d_inode;
struct super_block *sb = dentry->d_sb;
bstart = dbstart(dentry);
@@ -235,15 +235,16 @@ static int open_highest_file(struct file *file, bool willwrite)
memcpy(&lower_file->f_ra, &file->f_ra, sizeof(struct file_ra_state));
out:
+ dput(parent);
return err;
}
/* perform a delayed copyup of a read-write file on a read-only branch */
-static int do_delayed_copyup(struct file *file)
+static int do_delayed_copyup(struct file *file, struct dentry *parent)
{
int bindex, bstart, bend, err = 0;
struct dentry *dentry = file->f_path.dentry;
- struct inode *parent_inode = dentry->d_parent->d_inode;
+ struct inode *parent_inode = parent->d_inode;
bstart = fbstart(file);
bend = fbend(file);
@@ -257,8 +258,8 @@ static int do_delayed_copyup(struct file *file)
bindex,
i_size_read(dentry->d_inode));
else
- err = copyup_deleted_file(file, dentry, bstart,
- bindex);
+ err = copyup_deleted_file(file, dentry, parent,
+ bstart, bindex);
/* if succeeded, set lower open-file flags and break */
if (!err) {
struct file *lower_file;
@@ -294,6 +295,7 @@ out:
* Expects dentry/parent to be locked already, and revalidated.
*/
static int __unionfs_file_revalidate(struct file *file, struct dentry *dentry,
+ struct dentry *parent,
struct super_block *sb, int sbgen,
int dgen, bool willwrite)
{
@@ -375,7 +377,7 @@ out_may_copyup:
is_robranch(dentry)) {
pr_debug("unionfs: do delay copyup of \"%s\"\n",
dentry->d_name.name);
- err = do_delayed_copyup(file);
+ err = do_delayed_copyup(file, parent);
/* regular files have only one open lower file */
if (!err && !S_ISDIR(dentry->d_inode->i_mode))
fbend(file) = fbstart(file);
@@ -394,10 +396,12 @@ out:
/*
* Revalidate the struct file
* @file: file to revalidate
+ * @parent: parent dentry (locked by caller)
* @willwrite: true if caller may cause changes to the file; false otherwise.
* Caller must lock/unlock dentry's branch configuration.
*/
-int unionfs_file_revalidate(struct file *file, bool willwrite)
+int unionfs_file_revalidate(struct file *file, struct dentry *parent,
+ bool willwrite)
{
struct super_block *sb;
struct dentry *dentry;
@@ -407,6 +411,7 @@ int unionfs_file_revalidate(struct file *file, bool willwrite)
dentry = file->f_path.dentry;
sb = dentry->d_sb;
verify_locked(dentry);
+ verify_locked(parent);
/*
* First revalidate the dentry inside struct file,
@@ -414,7 +419,7 @@ int unionfs_file_revalidate(struct file *file, bool willwrite)
*/
reval_dentry:
if (!d_deleted(dentry) &&
- !__unionfs_d_revalidate_chain(dentry, NULL, willwrite)) {
+ !__unionfs_d_revalidate(dentry, parent, NULL, willwrite)) {
err = -ESTALE;
goto out;
}
@@ -430,51 +435,7 @@ reval_dentry:
}
BUG_ON(sbgen > dgen);
- err = __unionfs_file_revalidate(file, dentry, sb,
- sbgen, dgen, willwrite);
-out:
- return err;
-}
-
-/* same as unionfs_file_revalidate, but parent dentry must be locked too */
-int unionfs_file_revalidate_locked(struct file *file, bool willwrite)
-{
- struct super_block *sb;
- struct dentry *dentry;
- int sbgen, dgen;
- int err = 0, valid;
-
- dentry = file->f_path.dentry;
- sb = dentry->d_sb;
- verify_locked(dentry);
- verify_locked(dentry->d_parent);
-
- /* first revalidate (locked) parent, then child */
- valid = __unionfs_d_revalidate_chain(dentry->d_parent, NULL, false);
- if (unlikely(!valid)) {
- err = -ESTALE; /* same as what real_lookup does */
- goto out;
- }
-
-reval_dentry:
- if (!d_deleted(dentry) &&
- !__unionfs_d_revalidate_one_locked(dentry, NULL, willwrite)) {
- err = -ESTALE;
- goto out;
- }
-
- sbgen = atomic_read(&UNIONFS_SB(sb)->generation);
- dgen = atomic_read(&UNIONFS_D(dentry)->generation);
-
- if (unlikely(sbgen > dgen)) {
- pr_debug("unionfs: retry (locked) dentry %s revalidation\n",
- dentry->d_name.name);
- schedule();
- goto reval_dentry;
- }
- BUG_ON(sbgen > dgen);
-
- err = __unionfs_file_revalidate(file, dentry, sb,
+ err = __unionfs_file_revalidate(file, dentry, parent, sb,
sbgen, dgen, willwrite);
out:
return err;
@@ -517,7 +478,8 @@ static int __open_dir(struct inode *inode, struct file *file)
}
/* unionfs_open helper function: open a file */
-static int __open_file(struct inode *inode, struct file *file)
+static int __open_file(struct inode *inode, struct file *file,
+ struct dentry *parent)
{
struct dentry *lower_dentry;
struct file *lower_file;
@@ -545,9 +507,8 @@ static int __open_file(struct inode *inode, struct file *file)
/* copyup the file */
for (bindex = bstart - 1; bindex >= 0; bindex--) {
- err = copyup_file(
- file->f_path.dentry->d_parent->d_inode,
- file, bstart, bindex, size);
+ err = copyup_file(parent->d_inode, file,
+ bstart, bindex, size);
if (!err)
break;
}
@@ -586,20 +547,14 @@ int unionfs_open(struct inode *inode, struct file *file)
int err = 0;
struct file *lower_file = NULL;
struct dentry *dentry = file->f_path.dentry;
+ struct dentry *parent;
int bindex = 0, bstart = 0, bend = 0;
int size;
int valid = 0;
unionfs_read_lock(inode->i_sb, UNIONFS_SMUTEX_PARENT);
+ parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT);
unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD);
- if (dentry != dentry->d_parent)
- unionfs_lock_dentry(dentry->d_parent, UNIONFS_DMUTEX_PARENT);
-
- valid = __unionfs_d_revalidate_chain(dentry->d_parent, NULL, false);
- if (unlikely(!valid)) {
- err = -ESTALE;
- goto out_nofree;
- }
/* don't open unhashed/deleted files */
if (d_deleted(dentry)) {
@@ -607,6 +562,13 @@ int unionfs_open(struct inode *inode, struct file *file)
goto out_nofree;
}
+ /* XXX: should I change 'false' below to the 'willwrite' flag? */
+ valid = __unionfs_d_revalidate(dentry, parent, NULL, false);
+ if (unlikely(!valid)) {
+ err = -ESTALE;
+ goto out_nofree;
+ }
+
file->private_data =
kzalloc(sizeof(struct unionfs_file_info), GFP_KERNEL);
if (unlikely(!UNIONFS_F(file))) {
@@ -641,7 +603,7 @@ int unionfs_open(struct inode *inode, struct file *file)
if (S_ISDIR(inode->i_mode))
err = __open_dir(inode, file); /* open a dir */
else
- err = __open_file(inode, file); /* open a file */
+ err = __open_file(inode, file, parent); /* open a file */
/* freeing the allocated resources, and fput the opened files */
if (err) {
@@ -669,9 +631,8 @@ out_nofree:
unionfs_check_file(file);
unionfs_check_inode(inode);
}
- if (dentry != dentry->d_parent)
- unionfs_unlock_dentry(dentry->d_parent);
unionfs_unlock_dentry(dentry);
+ unionfs_unlock_parent(dentry, parent);
unionfs_read_unlock(inode->i_sb);
return err;
}
@@ -688,10 +649,12 @@ int unionfs_file_release(struct inode *inode, struct file *file)
struct unionfs_inode_info *inodeinfo;
struct super_block *sb = inode->i_sb;
struct dentry *dentry = file->f_path.dentry;
+ struct dentry *parent;
int bindex, bstart, bend;
int fgen, err = 0;
unionfs_read_lock(sb, UNIONFS_SMUTEX_PARENT);
+ parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT);
unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD);
/*
@@ -699,7 +662,8 @@ int unionfs_file_release(struct inode *inode, struct file *file)
* This is important for open-but-unlinked files, as well as mmap
* support.
*/
- err = unionfs_file_revalidate(file, UNIONFS_F(file)->wrote_to_file);
+ err = unionfs_file_revalidate(file, parent,
+ UNIONFS_F(file)->wrote_to_file);
if (unlikely(err))
goto out;
unionfs_check_file(file);
@@ -745,6 +709,7 @@ int unionfs_file_release(struct inode *inode, struct file *file)
out:
unionfs_unlock_dentry(dentry);
+ unionfs_unlock_parent(dentry, parent);
unionfs_read_unlock(sb);
return err;
}
@@ -780,8 +745,8 @@ out:
* We use fd_set and therefore we are limited to the number of the branches
* to FD_SETSIZE, which is currently 1024 - plenty for most people
*/
-static int unionfs_ioctl_queryfile(struct file *file, unsigned int cmd,
- unsigned long arg)
+static int unionfs_ioctl_queryfile(struct file *file, struct dentry *parent,
+ unsigned int cmd, unsigned long arg)
{
int err = 0;
fd_set branchlist;
@@ -793,7 +758,7 @@ static int unionfs_ioctl_queryfile(struct file *file, unsigned int cmd,
dentry = file->f_path.dentry;
orig_bstart = dbstart(dentry);
orig_bend = dbend(dentry);
- err = unionfs_partial_lookup(dentry);
+ err = unionfs_partial_lookup(dentry, parent);
if (err)
goto out;
bstart = dbstart(dentry);
@@ -839,11 +804,13 @@ long unionfs_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
long err;
struct dentry *dentry = file->f_path.dentry;
+ struct dentry *parent;
unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_PARENT);
+ parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT);
unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD);
- err = unionfs_file_revalidate(file, true);
+ err = unionfs_file_revalidate(file, parent, true);
if (unlikely(err))
goto out;
@@ -858,7 +825,7 @@ long unionfs_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
case UNIONFS_IOCTL_QUERYFILE:
/* Return list of branches containing the given file */
- err = unionfs_ioctl_queryfile(file, cmd, arg);
+ err = unionfs_ioctl_queryfile(file, parent, cmd, arg);
break;
default:
@@ -870,6 +837,7 @@ long unionfs_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
out:
unionfs_check_file(file);
unionfs_unlock_dentry(dentry);
+ unionfs_unlock_parent(dentry, parent);
unionfs_read_unlock(dentry->d_sb);
return err;
}
@@ -879,12 +847,15 @@ int unionfs_flush(struct file *file, fl_owner_t id)
int err = 0;
struct file *lower_file = NULL;
struct dentry *dentry = file->f_path.dentry;
+ struct dentry *parent;
int bindex, bstart, bend;
unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_PARENT);
+ parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT);
unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD);
- err = unionfs_file_revalidate(file, UNIONFS_F(file)->wrote_to_file);
+ err = unionfs_file_revalidate(file, parent,
+ UNIONFS_F(file)->wrote_to_file);
if (unlikely(err))
goto out;
unionfs_check_file(file);
@@ -907,6 +878,7 @@ out:
if (!err)
unionfs_check_file(file);
unionfs_unlock_dentry(dentry);
+ unionfs_unlock_parent(dentry, parent);
unionfs_read_unlock(dentry->d_sb);
return err;
}
diff --git a/fs/unionfs/dentry.c b/fs/unionfs/dentry.c
index 7f0c7f7..fb11c41 100644
--- a/fs/unionfs/dentry.c
+++ b/fs/unionfs/dentry.c
@@ -62,6 +62,7 @@ static inline void __dput_lowers(struct dentry *dentry, int start, int end)
* Returns true if valid, false otherwise.
*/
static bool __unionfs_d_revalidate_one(struct dentry *dentry,
+ struct dentry *parent,
struct nameidata *nd)
{
bool valid = true; /* default is valid */
@@ -77,9 +78,6 @@ static bool __unionfs_d_revalidate_one(struct dentry *dentry,
else
memset(&lowernd, 0, sizeof(struct nameidata));
- verify_locked(dentry);
- verify_locked(dentry->d_parent);
-
sbgen = atomic_read(&UNIONFS_SB(dentry->d_sb)->generation);
/* if the dentry is unhashed, do NOT revalidate */
if (d_deleted(dentry))
@@ -102,8 +100,11 @@ static bool __unionfs_d_revalidate_one(struct dentry *dentry,
BUG_ON(IS_ROOT(dentry));
/* We can't work correctly if our parent isn't valid. */
- pdgen = atomic_read(&UNIONFS_D(dentry->d_parent)->generation);
- BUG_ON(pdgen != sbgen); /* should never happen here */
+ pdgen = atomic_read(&UNIONFS_D(parent)->generation);
+ if (pdgen != sbgen) {
+ valid = false;
+ goto out;
+ }
/* Free the pointers for our inodes and this dentry. */
bstart = dbstart(dentry);
@@ -137,7 +138,8 @@ static bool __unionfs_d_revalidate_one(struct dentry *dentry,
goto out;
}
- result = unionfs_lookup_full(dentry, &lowernd, interpose_flag);
+ result = unionfs_lookup_full(dentry, parent,
+ &lowernd, interpose_flag);
if (result) {
if (IS_ERR(result)) {
valid = false;
@@ -193,7 +195,7 @@ static bool __unionfs_d_revalidate_one(struct dentry *dentry,
* If we get here, and we copy the meta-data from the lower
* inode to our inode, then it is vital that we have already
* purged all unionfs-level file data. We do that in the
- * caller (__unionfs_d_revalidate_chain) by calling
+ * caller (__unionfs_d_revalidate) by calling
* purge_inode_data.
*/
unionfs_copy_attr_all(dentry->d_inode,
@@ -302,19 +304,20 @@ static inline void purge_inode_data(struct inode *inode)
/*
* Revalidate a single file/symlink/special dentry. Assume that info nodes
- * of the dentry and its parent are locked. Assume that parent(s) are all
- * valid already, but the child may not yet be valid. Returns true if
- * valid, false otherwise.
+ * of the @dentry and its @parent are locked. Assume parent is invalid,
+ * otherwise return false (and let's hope the VFS will try to re-lookup this
+ * dentry). Returns true if valid, false otherwise.
*/
-bool __unionfs_d_revalidate_one_locked(struct dentry *dentry,
- struct nameidata *nd,
- bool willwrite)
+bool __unionfs_d_revalidate(struct dentry *dentry, struct dentry *parent,
+ struct nameidata *nd, bool willwrite)
{
bool valid = false; /* default is invalid */
int sbgen, dgen, bindex;
verify_locked(dentry);
- verify_locked(dentry->d_parent);
+ verify_locked(parent);
+ if (!is_valid(parent))
+ goto out;
sbgen = atomic_read(&UNIONFS_SB(dentry->d_sb)->generation);
dgen = atomic_read(&UNIONFS_D(dentry)->generation);
@@ -334,7 +337,7 @@ bool __unionfs_d_revalidate_one_locked(struct dentry *dentry,
if (!willwrite)
purge_inode_data(dentry->d_inode);
}
- valid = __unionfs_d_revalidate_one(dentry, nd);
+ valid = __unionfs_d_revalidate_one(dentry, parent, nd);
/*
* If __unionfs_d_revalidate_one() succeeded above, then it will
@@ -353,152 +356,37 @@ out:
return valid;
}
-/*
- * Revalidate a parent chain of dentries, then the actual node.
- * Assumes that dentry is locked, but will lock all parents if/when needed.
- *
- * If 'willwrite' is true, and the lower inode times are not in sync, then
- * *don't* purge_inode_data, as it could deadlock if ->write calls us and we
- * try to truncate a locked page. Besides, if unionfs is about to write
- * data to a file, then there's the data unionfs is about to write is more
- * authoritative than what's below, therefore we can safely overwrite the
- * lower inode times and data.
- */
-bool __unionfs_d_revalidate_chain(struct dentry *dentry, struct nameidata *nd,
- bool willwrite)
-{
- bool valid = false; /* default is invalid */
- struct dentry **chain = NULL; /* chain of dentries to reval */
- int chain_len = 0;
- struct dentry *dtmp;
- int sbgen, dgen, i;
- int saved_bstart, saved_bend, bindex;
-
- /* find length of chain needed to revalidate */
- /* XXX: should I grab some global (dcache?) lock? */
- chain_len = 0;
- sbgen = atomic_read(&UNIONFS_SB(dentry->d_sb)->generation);
- dtmp = dentry->d_parent;
- verify_locked(dentry);
- if (dentry != dtmp)
- unionfs_lock_dentry(dtmp, UNIONFS_DMUTEX_REVAL_PARENT);
- dgen = atomic_read(&UNIONFS_D(dtmp)->generation);
- /* XXX: should we check if is_newer_lower all the way up? */
- if (unlikely(is_newer_lower(dtmp))) {
- /*
- * Special case: the root dentry's generation number must
- * always be valid, but its lower inode times don't have to
- * be, so sync up the times only.
- */
- if (IS_ROOT(dtmp)) {
- unionfs_copy_attr_times(dtmp->d_inode);
- } else {
- /*
- * reset generation number to zero, guaranteed to be
- * "old"
- */
- dgen = 0;
- atomic_set(&UNIONFS_D(dtmp)->generation, dgen);
- }
- purge_inode_data(dtmp->d_inode);
- }
- if (dentry != dtmp)
- unionfs_unlock_dentry(dtmp);
- while (sbgen != dgen) {
- /* The root entry should always be valid */
- BUG_ON(IS_ROOT(dtmp));
- chain_len++;
- dtmp = dtmp->d_parent;
- dgen = atomic_read(&UNIONFS_D(dtmp)->generation);
- }
- if (chain_len == 0)
- goto out_this; /* shortcut if parents are OK */
-
- /*
- * Allocate array of dentries to reval. We could use linked lists,
- * but the number of entries we need to alloc here is often small,
- * and short lived, so locality will be better.
- */
- chain = kzalloc(chain_len * sizeof(struct dentry *), GFP_KERNEL);
- if (unlikely(!chain)) {
- printk(KERN_CRIT "unionfs: no more memory in %s\n",
- __func__);
- goto out;
- }
-
- /* grab all dentries in chain, in child to parent order */
- dtmp = dentry;
- for (i = chain_len-1; i >= 0; i--)
- dtmp = chain[i] = dget_parent(dtmp);
-
- /*
- * call __unionfs_d_revalidate_one() on each dentry, but in parent
- * to child order.
- */
- for (i = 0; i < chain_len; i++) {
- unionfs_lock_dentry(chain[i], UNIONFS_DMUTEX_REVAL_CHILD);
- if (chain[i] != chain[i]->d_parent)
- unionfs_lock_dentry(chain[i]->d_parent,
- UNIONFS_DMUTEX_REVAL_PARENT);
- saved_bstart = dbstart(chain[i]);
- saved_bend = dbend(chain[i]);
- sbgen = atomic_read(&UNIONFS_SB(dentry->d_sb)->generation);
- dgen = atomic_read(&UNIONFS_D(chain[i])->generation);
-
- valid = __unionfs_d_revalidate_one(chain[i], nd);
- /* XXX: is this the correct mntput condition?! */
- if (valid && chain_len > 0 &&
- sbgen != dgen && chain[i]->d_inode &&
- S_ISDIR(chain[i]->d_inode->i_mode)) {
- for (bindex = saved_bstart; bindex <= saved_bend;
- bindex++)
- unionfs_mntput(chain[i], bindex);
- }
- if (chain[i] != chain[i]->d_parent)
- unionfs_unlock_dentry(chain[i]->d_parent);
- unionfs_unlock_dentry(chain[i]);
-
- if (unlikely(!valid))
- goto out_free;
- }
-
-
-out_this:
- /* finally, lock this dentry and revalidate it */
- verify_locked(dentry); /* verify child is locked */
- if (dentry != dentry->d_parent)
- unionfs_lock_dentry(dentry->d_parent,
- UNIONFS_DMUTEX_REVAL_PARENT);
- valid = __unionfs_d_revalidate_one_locked(dentry, nd, willwrite);
- if (dentry != dentry->d_parent)
- unionfs_unlock_dentry(dentry->d_parent);
-
-out_free:
- /* unlock/dput all dentries in chain and return status */
- if (chain_len > 0) {
- for (i = 0; i < chain_len; i++)
- dput(chain[i]);
- kfree(chain);
- }
-out:
- return valid;
-}
-
static int unionfs_d_revalidate(struct dentry *dentry, struct nameidata *nd)
{
- int err;
+ bool valid = true;
+ int err = 1; /* 1 means valid for the VFS */
+ struct dentry *parent;
unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_CHILD);
-
+ parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT);
unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD);
- err = __unionfs_d_revalidate_chain(dentry, nd, false);
- if (likely(err > 0)) { /* true==1: dentry is valid */
+
+ if (dentry != parent) {
+ valid = is_valid(parent);
+ if (unlikely(!valid)) {
+ err = valid;
+ goto out;
+ }
+ }
+ valid = __unionfs_d_revalidate(dentry, parent, nd, false);
+ if (likely(valid)) {
unionfs_postcopyup_setmnt(dentry);
unionfs_check_dentry(dentry);
unionfs_check_nd(nd);
}
- unionfs_unlock_dentry(dentry);
+out:
+ if (unlikely(!valid)) {
+ d_drop(dentry);
+ err = valid;
+ }
+ unionfs_unlock_dentry(dentry);
+ unionfs_unlock_parent(dentry, parent);
unionfs_read_unlock(dentry->d_sb);
return err;
diff --git a/fs/unionfs/dirfops.c b/fs/unionfs/dirfops.c
index 7c44d09..63fb419 100644
--- a/fs/unionfs/dirfops.c
+++ b/fs/unionfs/dirfops.c
@@ -94,6 +94,7 @@ static int unionfs_readdir(struct file *file, void *dirent, filldir_t filldir)
int err = 0;
struct file *lower_file = NULL;
struct dentry *dentry = file->f_path.dentry;
+ struct dentry *parent;
struct inode *inode = NULL;
struct unionfs_getdents_callback buf;
struct unionfs_dir_state *uds;
@@ -101,9 +102,10 @@ static int unionfs_readdir(struct file *file, void *dirent, filldir_t filldir)
loff_t offset;
unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_PARENT);
+ parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT);
unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD);
- err = unionfs_file_revalidate(file, false);
+ err = unionfs_file_revalidate(file, parent, false);
if (unlikely(err))
goto out;
@@ -190,6 +192,7 @@ out:
if (!err)
unionfs_check_file(file);
unionfs_unlock_dentry(dentry);
+ unionfs_unlock_parent(dentry, parent);
unionfs_read_unlock(dentry->d_sb);
return err;
}
@@ -208,12 +211,14 @@ static loff_t unionfs_dir_llseek(struct file *file, loff_t offset, int origin)
{
struct unionfs_dir_state *rdstate;
struct dentry *dentry = file->f_path.dentry;
+ struct dentry *parent;
loff_t err;
unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_PARENT);
+ parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT);
unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD);
- err = unionfs_file_revalidate(file, false);
+ err = unionfs_file_revalidate(file, parent, false);
if (unlikely(err))
goto out;
@@ -275,6 +280,7 @@ out:
if (!err)
unionfs_check_file(file);
unionfs_unlock_dentry(dentry);
+ unionfs_unlock_parent(dentry, parent);
unionfs_read_unlock(dentry->d_sb);
return err;
}
diff --git a/fs/unionfs/dirhelper.c b/fs/unionfs/dirhelper.c
index d936f03..aa31e91 100644
--- a/fs/unionfs/dirhelper.c
+++ b/fs/unionfs/dirhelper.c
@@ -68,7 +68,8 @@ out:
}
/* Is a directory logically empty? */
-int check_empty(struct dentry *dentry, struct unionfs_dir_state **namelist)
+int check_empty(struct dentry *dentry, struct dentry *parent,
+ struct unionfs_dir_state **namelist)
{
int err = 0;
struct dentry *lower_dentry = NULL;
@@ -83,7 +84,7 @@ int check_empty(struct dentry *dentry, struct unionfs_dir_state **namelist)
BUG_ON(!S_ISDIR(dentry->d_inode->i_mode));
- err = unionfs_partial_lookup(dentry);
+ err = unionfs_partial_lookup(dentry, parent);
if (err)
goto out;
diff --git a/fs/unionfs/fanout.h b/fs/unionfs/fanout.h
index 4f264de..69c0921 100644
--- a/fs/unionfs/fanout.h
+++ b/fs/unionfs/fanout.h
@@ -271,6 +271,29 @@ static inline void unionfs_unlock_dentry(struct dentry *d)
mutex_unlock(&UNIONFS_D(d)->lock);
}
+static inline struct dentry *unionfs_lock_parent(struct dentry *d,
+ unsigned int subclass)
+{
+ struct dentry *p;
+
+ BUG_ON(!d);
+ p = dget_parent(d);
+ if (p != d)
+ mutex_lock_nested(&UNIONFS_D(p)->lock, subclass);
+ return p;
+}
+
+static inline void unionfs_unlock_parent(struct dentry *d, struct dentry *p)
+{
+ BUG_ON(!d);
+ BUG_ON(!p);
+ if (p != d) {
+ BUG_ON(!mutex_is_locked(&UNIONFS_D(p)->lock));
+ mutex_unlock(&UNIONFS_D(p)->lock);
+ }
+ dput(p);
+}
+
static inline void verify_locked(struct dentry *d)
{
BUG_ON(!d);
diff --git a/fs/unionfs/file.c b/fs/unionfs/file.c
index 965d071..3cc6a76 100644
--- a/fs/unionfs/file.c
+++ b/fs/unionfs/file.c
@@ -24,10 +24,13 @@ static ssize_t unionfs_read(struct file *file, char __user *buf,
int err;
struct file *lower_file;
struct dentry *dentry = file->f_path.dentry;
+ struct dentry *parent;
unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_PARENT);
+ parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT);
unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD);
- err = unionfs_file_revalidate(file, false);
+
+ err = unionfs_file_revalidate(file, parent, false);
if (unlikely(err))
goto out;
@@ -42,6 +45,7 @@ static ssize_t unionfs_read(struct file *file, char __user *buf,
out:
unionfs_unlock_dentry(dentry);
+ unionfs_unlock_parent(dentry, parent);
unionfs_read_unlock(dentry->d_sb);
return err;
}
@@ -52,12 +56,13 @@ static ssize_t unionfs_write(struct file *file, const char __user *buf,
int err = 0;
struct file *lower_file;
struct dentry *dentry = file->f_path.dentry;
+ struct dentry *parent;
unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_PARENT);
+ parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT);
unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD);
- if (dentry != dentry->d_parent)
- unionfs_lock_dentry(dentry->d_parent, UNIONFS_DMUTEX_PARENT);
- err = unionfs_file_revalidate_locked(file, true);
+
+ err = unionfs_file_revalidate(file, parent, true);
if (unlikely(err))
goto out;
@@ -74,9 +79,8 @@ static ssize_t unionfs_write(struct file *file, const char __user *buf,
}
out:
- if (dentry != dentry->d_parent)
- unionfs_unlock_dentry(dentry->d_parent);
unionfs_unlock_dentry(dentry);
+ unionfs_unlock_parent(dentry, parent);
unionfs_read_unlock(dentry->d_sb);
return err;
}
@@ -93,14 +97,16 @@ static int unionfs_mmap(struct file *file, struct vm_area_struct *vma)
bool willwrite;
struct file *lower_file;
struct dentry *dentry = file->f_path.dentry;
+ struct dentry *parent;
struct vm_operations_struct *saved_vm_ops = NULL;
unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_PARENT);
+ parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT);
unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD);
/* This might be deferred to mmap's writepage */
willwrite = ((vma->vm_flags | VM_SHARED | VM_WRITE) == vma->vm_flags);
- err = unionfs_file_revalidate(file, willwrite);
+ err = unionfs_file_revalidate(file, parent, willwrite);
if (unlikely(err))
goto out;
unionfs_check_file(file);
@@ -157,10 +163,11 @@ static int unionfs_mmap(struct file *file, struct vm_area_struct *vma)
out:
if (!err) {
/* copyup could cause parent dir times to change */
- unionfs_copy_attr_times(dentry->d_parent->d_inode);
+ unionfs_copy_attr_times(parent->d_inode);
unionfs_check_file(file);
}
unionfs_unlock_dentry(dentry);
+ unionfs_unlock_parent(dentry, parent);
unionfs_read_unlock(dentry->d_sb);
return err;
}
@@ -170,12 +177,15 @@ int unionfs_fsync(struct file *file, struct dentry *dentry, int datasync)
int bindex, bstart, bend;
struct file *lower_file;
struct dentry *lower_dentry;
+ struct dentry *parent;
struct inode *lower_inode, *inode;
int err = -EINVAL;
unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_PARENT);
+ parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT);
unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD);
- err = unionfs_file_revalidate(file, true);
+
+ err = unionfs_file_revalidate(file, parent, true);
if (unlikely(err))
goto out;
unionfs_check_file(file);
@@ -212,6 +222,7 @@ out:
if (!err)
unionfs_check_file(file);
unionfs_unlock_dentry(dentry);
+ unionfs_unlock_parent(dentry, parent);
unionfs_read_unlock(dentry->d_sb);
return err;
}
@@ -221,12 +232,15 @@ int unionfs_fasync(int fd, struct file *file, int flag)
int bindex, bstart, bend;
struct file *lower_file;
struct dentry *dentry = file->f_path.dentry;
+ struct dentry *parent;
struct inode *lower_inode, *inode;
int err = 0;
unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_PARENT);
+ parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT);
unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD);
- err = unionfs_file_revalidate(file, true);
+
+ err = unionfs_file_revalidate(file, parent, true);
if (unlikely(err))
goto out;
unionfs_check_file(file);
@@ -260,6 +274,7 @@ out:
if (!err)
unionfs_check_file(file);
unionfs_unlock_dentry(dentry);
+ unionfs_unlock_parent(dentry, parent);
unionfs_read_unlock(dentry->d_sb);
return err;
}
@@ -271,10 +286,13 @@ static ssize_t unionfs_splice_read(struct file *file, loff_t *ppos,
ssize_t err;
struct file *lower_file;
struct dentry *dentry = file->f_path.dentry;
+ struct dentry *parent;
unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_PARENT);
+ parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT);
unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD);
- err = unionfs_file_revalidate(file, false);
+
+ err = unionfs_file_revalidate(file, parent, false);
if (unlikely(err))
goto out;
@@ -289,6 +307,7 @@ static ssize_t unionfs_splice_read(struct file *file, loff_t *ppos,
out:
unionfs_unlock_dentry(dentry);
+ unionfs_unlock_parent(dentry, parent);
unionfs_read_unlock(dentry->d_sb);
return err;
}
@@ -300,10 +319,13 @@ static ssize_t unionfs_splice_write(struct pipe_inode_info *pipe,
ssize_t err = 0;
struct file *lower_file;
struct dentry *dentry = file->f_path.dentry;
+ struct dentry *parent;
unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_PARENT);
+ parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT);
unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD);
- err = unionfs_file_revalidate(file, true);
+
+ err = unionfs_file_revalidate(file, parent, true);
if (unlikely(err))
goto out;
@@ -320,6 +342,7 @@ static ssize_t unionfs_splice_write(struct pipe_inode_info *pipe,
out:
unionfs_unlock_dentry(dentry);
+ unionfs_unlock_parent(dentry, parent);
unionfs_read_unlock(dentry->d_sb);
return err;
}
diff --git a/fs/unionfs/inode.c b/fs/unionfs/inode.c
index 0d2eac1..dcf066c 100644
--- a/fs/unionfs/inode.c
+++ b/fs/unionfs/inode.c
@@ -96,33 +96,27 @@ out:
return lower_dentry;
}
-static int unionfs_create(struct inode *parent, struct dentry *dentry,
+static int unionfs_create(struct inode *dir, struct dentry *dentry,
int mode, struct nameidata *nd)
{
int err = 0;
struct dentry *lower_dentry = NULL;
struct dentry *lower_parent_dentry = NULL;
+ struct dentry *parent;
int valid = 0;
struct nameidata lower_nd;
unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_CHILD);
+ parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT);
unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD);
- unionfs_lock_dentry(dentry->d_parent, UNIONFS_DMUTEX_PARENT);
- valid = __unionfs_d_revalidate_chain(dentry->d_parent, nd, false);
+ valid = __unionfs_d_revalidate(dentry, parent, nd, false);
if (unlikely(!valid)) {
err = -ESTALE; /* same as what real_lookup does */
goto out;
}
- valid = __unionfs_d_revalidate_one_locked(dentry, nd, false);
- /*
- * It's only a bug if this dentry was not negative and couldn't be
- * revalidated (shouldn't happen).
- */
- BUG_ON(!valid && dentry->d_inode);
-
- lower_dentry = find_writeable_branch(parent, dentry);
+ lower_dentry = find_writeable_branch(dir, dentry);
if (IS_ERR(lower_dentry)) {
err = PTR_ERR(lower_dentry);
goto out;
@@ -142,13 +136,13 @@ static int unionfs_create(struct inode *parent, struct dentry *dentry,
release_lower_nd(&lower_nd, err);
if (!err) {
- err = PTR_ERR(unionfs_interpose(dentry, parent->i_sb, 0));
+ err = PTR_ERR(unionfs_interpose(dentry, dir->i_sb, 0));
if (!err) {
- unionfs_copy_attr_times(parent);
- fsstack_copy_inode_size(parent,
+ unionfs_copy_attr_times(dir);
+ fsstack_copy_inode_size(dir,
lower_parent_dentry->d_inode);
/* update no. of links on parent directory */
- parent->i_nlink = unionfs_get_nlinks(parent);
+ dir->i_nlink = unionfs_get_nlinks(dir);
}
}
@@ -157,12 +151,12 @@ static int unionfs_create(struct inode *parent, struct dentry *dentry,
out:
if (!err) {
unionfs_postcopyup_setmnt(dentry);
- unionfs_check_inode(parent);
+ unionfs_check_inode(dir);
unionfs_check_dentry(dentry);
unionfs_check_nd(nd);
}
- unionfs_unlock_dentry(dentry->d_parent);
unionfs_unlock_dentry(dentry);
+ unionfs_unlock_parent(dentry, parent);
unionfs_read_unlock(dentry->d_sb);
return err;
}
@@ -172,17 +166,22 @@ out:
* do NOT want to call __unionfs_d_revalidate_chain because by definition,
* we don't have a valid dentry here yet.
*/
-static struct dentry *unionfs_lookup(struct inode *parent,
+static struct dentry *unionfs_lookup(struct inode *dir,
struct dentry *dentry,
struct nameidata *nd)
{
struct path path_save = {NULL, NULL};
- struct dentry *ret;
+ struct dentry *ret, *parent;
int err = 0;
+ bool valid;
unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_CHILD);
- if (dentry != dentry->d_parent)
- unionfs_lock_dentry(dentry->d_parent, UNIONFS_DMUTEX_ROOT);
+ parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT);
+ valid = is_valid(parent);
+ if (unlikely(!valid)) {
+ ret = ERR_PTR(-ESTALE);
+ goto out;
+ }
/* save the dentry & vfsmnt from namei */
if (nd) {
@@ -191,7 +190,7 @@ static struct dentry *unionfs_lookup(struct inode *parent,
}
/*
- * unionfs_lookup_backend returns a locked dentry upon success,
+ * unionfs_lookup_full returns a locked dentry upon success,
* so we'll have to unlock it below.
*/
@@ -201,7 +200,8 @@ static struct dentry *unionfs_lookup(struct inode *parent,
ret = ERR_PTR(err);
goto out;
}
- ret = unionfs_lookup_full(dentry, nd, INTERPOSE_LOOKUP);
+
+ ret = unionfs_lookup_full(dentry, parent, nd, INTERPOSE_LOOKUP);
/* restore the dentry & vfsmnt in namei */
if (nd) {
@@ -219,18 +219,16 @@ static struct dentry *unionfs_lookup(struct inode *parent,
unionfs_copy_attr_times(dentry->d_inode);
}
- unionfs_check_inode(parent);
+ unionfs_check_inode(dir);
if (!IS_ERR(ret)) {
unionfs_check_dentry(dentry);
unionfs_check_nd(nd);
}
- unionfs_unlock_dentry(dentry);
+ unionfs_check_dentry(parent);
+ unionfs_unlock_dentry(dentry); /* locked in new_dentry_private data */
out:
- if (dentry != dentry->d_parent) {
- unionfs_check_dentry(dentry->d_parent);
- unionfs_unlock_dentry(dentry->d_parent);
- }
+ unionfs_unlock_parent(dentry, parent);
unionfs_read_unlock(dentry->d_sb);
return ret;
@@ -243,19 +241,28 @@ static int unionfs_link(struct dentry *old_dentry, struct inode *dir,
struct dentry *lower_old_dentry = NULL;
struct dentry *lower_new_dentry = NULL;
struct dentry *lower_dir_dentry = NULL;
+ struct dentry *old_parent, *new_parent;
char *name = NULL;
+ bool valid;
unionfs_read_lock(old_dentry->d_sb, UNIONFS_SMUTEX_CHILD);
- unionfs_double_lock_dentry(new_dentry, old_dentry);
+ old_parent = dget_parent(old_dentry);
+ new_parent = dget_parent(new_dentry);
+ unionfs_double_lock_parents(old_parent, new_parent);
+ unionfs_double_lock_dentry(old_dentry, new_dentry);
- if (unlikely(!__unionfs_d_revalidate_chain(old_dentry, NULL, false))) {
+ valid = __unionfs_d_revalidate(old_dentry, old_parent, NULL, false);
+ if (unlikely(!valid)) {
err = -ESTALE;
goto out;
}
- if (unlikely(new_dentry->d_inode &&
- !__unionfs_d_revalidate_chain(new_dentry, NULL, false))) {
- err = -ESTALE;
- goto out;
+ if (new_dentry->d_inode) {
+ valid = __unionfs_d_revalidate(new_dentry, new_parent,
+ NULL, false);
+ if (unlikely(!valid)) {
+ err = -ESTALE;
+ goto out;
+ }
}
lower_new_dentry = unionfs_lower_dentry(new_dentry);
@@ -305,7 +312,7 @@ docopyup:
int bindex;
for (bindex = old_bstart - 1; bindex >= 0; bindex--) {
- err = copyup_dentry(old_dentry->d_parent->d_inode,
+ err = copyup_dentry(old_parent->d_inode,
old_dentry, old_bstart,
bindex, old_dentry->d_name.name,
old_dentry->d_name.len, NULL,
@@ -358,38 +365,36 @@ out:
unionfs_check_dentry(new_dentry);
unionfs_check_dentry(old_dentry);
- unionfs_unlock_dentry(new_dentry);
- unionfs_unlock_dentry(old_dentry);
+ unionfs_double_unlock_dentry(old_dentry, new_dentry);
+ unionfs_double_unlock_parents(old_parent, new_parent);
+ dput(new_parent);
+ dput(old_parent);
unionfs_read_unlock(old_dentry->d_sb);
return err;
}
-static int unionfs_symlink(struct inode *parent, struct dentry *dentry,
+static int unionfs_symlink(struct inode *dir, struct dentry *dentry,
const char *symname)
{
int err = 0;
struct dentry *lower_dentry = NULL;
struct dentry *wh_dentry = NULL;
struct dentry *lower_parent_dentry = NULL;
+ struct dentry *parent;
char *name = NULL;
int valid = 0;
umode_t mode;
unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_CHILD);
+ parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT);
unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD);
- unionfs_lock_dentry(dentry->d_parent, UNIONFS_DMUTEX_PARENT);
- valid = __unionfs_d_revalidate_chain(dentry->d_parent, NULL, false);
+ valid = __unionfs_d_revalidate(dentry, parent, NULL, false);
if (unlikely(!valid)) {
err = -ESTALE;
goto out;
}
- if (unlikely(dentry->d_inode &&
- !__unionfs_d_revalidate_one_locked(dentry, NULL, false))) {
- err = -ESTALE;
- goto out;
- }
/*
* It's only a bug if this dentry was not negative and couldn't be
@@ -397,7 +402,7 @@ static int unionfs_symlink(struct inode *parent, struct dentry *dentry,
*/
BUG_ON(!valid && dentry->d_inode);
- lower_dentry = find_writeable_branch(parent, dentry);
+ lower_dentry = find_writeable_branch(dir, dentry);
if (IS_ERR(lower_dentry)) {
err = PTR_ERR(lower_dentry);
goto out;
@@ -412,13 +417,13 @@ static int unionfs_symlink(struct inode *parent, struct dentry *dentry,
mode = S_IALLUGO;
err = vfs_symlink(lower_parent_dentry->d_inode, lower_dentry, symname);
if (!err) {
- err = PTR_ERR(unionfs_interpose(dentry, parent->i_sb, 0));
+ err = PTR_ERR(unionfs_interpose(dentry, dir->i_sb, 0));
if (!err) {
- unionfs_copy_attr_times(parent);
- fsstack_copy_inode_size(parent,
+ unionfs_copy_attr_times(dir);
+ fsstack_copy_inode_size(dir,
lower_parent_dentry->d_inode);
/* update no. of links on parent directory */
- parent->i_nlink = unionfs_get_nlinks(parent);
+ dir->i_nlink = unionfs_get_nlinks(dir);
}
}
@@ -430,38 +435,34 @@ out:
if (!err) {
unionfs_postcopyup_setmnt(dentry);
- unionfs_check_inode(parent);
+ unionfs_check_inode(dir);
unionfs_check_dentry(dentry);
}
- unionfs_unlock_dentry(dentry->d_parent);
unionfs_unlock_dentry(dentry);
+ unionfs_unlock_parent(dentry, parent);
unionfs_read_unlock(dentry->d_sb);
return err;
}
-static int unionfs_mkdir(struct inode *parent, struct dentry *dentry, int mode)
+static int unionfs_mkdir(struct inode *dir, struct dentry *dentry, int mode)
{
int err = 0;
struct dentry *lower_dentry = NULL;
struct dentry *lower_parent_dentry = NULL;
+ struct dentry *parent;
int bindex = 0, bstart;
char *name = NULL;
int valid;
unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_CHILD);
+ parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT);
unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD);
- unionfs_lock_dentry(dentry->d_parent, UNIONFS_DMUTEX_PARENT);
- valid = __unionfs_d_revalidate_chain(dentry->d_parent, NULL, false);
+ valid = __unionfs_d_revalidate(dentry, parent, NULL, false);
if (unlikely(!valid)) {
err = -ESTALE; /* same as what real_lookup does */
goto out;
}
- if (unlikely(dentry->d_inode &&
- !__unionfs_d_revalidate_one_locked(dentry, NULL, false))) {
- err = -ESTALE;
- goto out;
- }
bstart = dbstart(dentry);
@@ -488,7 +489,7 @@ static int unionfs_mkdir(struct inode *parent, struct dentry *dentry, int mode)
lower_dentry = unionfs_lower_dentry_idx(dentry, bindex);
if (!lower_dentry) {
- lower_dentry = create_parents(parent, dentry,
+ lower_dentry = create_parents(dir, dentry,
dentry->d_name.name,
bindex);
if (!lower_dentry || IS_ERR(lower_dentry)) {
@@ -515,6 +516,7 @@ static int unionfs_mkdir(struct inode *parent, struct dentry *dentry, int mode)
break;
for (i = bindex + 1; i <= bend; i++) {
+ /* XXX: use path_put_lowers? */
if (unionfs_lower_dentry_idx(dentry, i)) {
dput(unionfs_lower_dentry_idx(dentry, i));
unionfs_set_lower_dentry_idx(dentry, i, NULL);
@@ -526,14 +528,14 @@ static int unionfs_mkdir(struct inode *parent, struct dentry *dentry, int mode)
* Only INTERPOSE_LOOKUP can return a value other than 0 on
* err.
*/
- err = PTR_ERR(unionfs_interpose(dentry, parent->i_sb, 0));
+ err = PTR_ERR(unionfs_interpose(dentry, dir->i_sb, 0));
if (!err) {
- unionfs_copy_attr_times(parent);
- fsstack_copy_inode_size(parent,
+ unionfs_copy_attr_times(dir);
+ fsstack_copy_inode_size(dir,
lower_parent_dentry->d_inode);
/* update number of links on parent directory */
- parent->i_nlink = unionfs_get_nlinks(parent);
+ dir->i_nlink = unionfs_get_nlinks(dir);
}
err = make_dir_opaque(dentry, dbstart(dentry));
@@ -557,39 +559,35 @@ out:
unionfs_copy_attr_times(dentry->d_inode);
unionfs_postcopyup_setmnt(dentry);
}
- unionfs_check_inode(parent);
+ unionfs_check_inode(dir);
unionfs_check_dentry(dentry);
- unionfs_unlock_dentry(dentry->d_parent);
unionfs_unlock_dentry(dentry);
+ unionfs_unlock_parent(dentry, parent);
unionfs_read_unlock(dentry->d_sb);
return err;
}
-static int unionfs_mknod(struct inode *parent, struct dentry *dentry, int mode,
+static int unionfs_mknod(struct inode *dir, struct dentry *dentry, int mode,
dev_t dev)
{
int err = 0;
struct dentry *lower_dentry = NULL;
struct dentry *wh_dentry = NULL;
struct dentry *lower_parent_dentry = NULL;
+ struct dentry *parent;
char *name = NULL;
int valid = 0;
unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_CHILD);
+ parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT);
unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD);
- unionfs_lock_dentry(dentry->d_parent, UNIONFS_DMUTEX_PARENT);
- valid = __unionfs_d_revalidate_chain(dentry->d_parent, NULL, false);
+ valid = __unionfs_d_revalidate(dentry, parent, NULL, false);
if (unlikely(!valid)) {
err = -ESTALE;
goto out;
}
- if (unlikely(dentry->d_inode &&
- !__unionfs_d_revalidate_one_locked(dentry, NULL, false))) {
- err = -ESTALE;
- goto out;
- }
/*
* It's only a bug if this dentry was not negative and couldn't be
@@ -597,7 +595,7 @@ static int unionfs_mknod(struct inode *parent, struct dentry *dentry, int mode,
*/
BUG_ON(!valid && dentry->d_inode);
- lower_dentry = find_writeable_branch(parent, dentry);
+ lower_dentry = find_writeable_branch(dir, dentry);
if (IS_ERR(lower_dentry)) {
err = PTR_ERR(lower_dentry);
goto out;
@@ -611,13 +609,13 @@ static int unionfs_mknod(struct inode *parent, struct dentry *dentry, int mode,
err = vfs_mknod(lower_parent_dentry->d_inode, lower_dentry, mode, dev);
if (!err) {
- err = PTR_ERR(unionfs_interpose(dentry, parent->i_sb, 0));
+ err = PTR_ERR(unionfs_interpose(dentry, dir->i_sb, 0));
if (!err) {
- unionfs_copy_attr_times(parent);
- fsstack_copy_inode_size(parent,
+ unionfs_copy_attr_times(dir);
+ fsstack_copy_inode_size(dir,
lower_parent_dentry->d_inode);
/* update no. of links on parent directory */
- parent->i_nlink = unionfs_get_nlinks(parent);
+ dir->i_nlink = unionfs_get_nlinks(dir);
}
}
@@ -629,11 +627,11 @@ out:
if (!err) {
unionfs_postcopyup_setmnt(dentry);
- unionfs_check_inode(parent);
+ unionfs_check_inode(dir);
unionfs_check_dentry(dentry);
}
- unionfs_unlock_dentry(dentry->d_parent);
unionfs_unlock_dentry(dentry);
+ unionfs_unlock_parent(dentry, parent);
unionfs_read_unlock(dentry->d_sb);
return err;
}
@@ -643,11 +641,13 @@ static int unionfs_readlink(struct dentry *dentry, char __user *buf,
{
int err;
struct dentry *lower_dentry;
+ struct dentry *parent;
unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_CHILD);
+ parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT);
unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD);
- if (unlikely(!__unionfs_d_revalidate_chain(dentry, NULL, false))) {
+ if (unlikely(!__unionfs_d_revalidate(dentry, parent, NULL, false))) {
err = -ESTALE;
goto out;
}
@@ -669,6 +669,7 @@ static int unionfs_readlink(struct dentry *dentry, char __user *buf,
out:
unionfs_check_dentry(dentry);
unionfs_unlock_dentry(dentry);
+ unionfs_unlock_parent(dentry, parent);
unionfs_read_unlock(dentry->d_sb);
return err;
@@ -722,14 +723,16 @@ out:
return ERR_PTR(err);
}
-/* FIXME: We may not have to lock here */
static void unionfs_put_link(struct dentry *dentry, struct nameidata *nd,
void *cookie)
{
- unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_CHILD);
+ struct dentry *parent;
+ unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_CHILD);
+ parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT);
unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD);
- if (unlikely(!__unionfs_d_revalidate_chain(dentry, nd, false)))
+
+ if (unlikely(!__unionfs_d_revalidate(dentry, parent, nd, false)))
printk(KERN_ERR
"unionfs: put_link failed to revalidate dentry\n");
@@ -737,10 +740,10 @@ static void unionfs_put_link(struct dentry *dentry, struct nameidata *nd,
unionfs_check_nd(nd);
kfree(nd_get_link(nd));
unionfs_unlock_dentry(dentry);
+ unionfs_unlock_parent(dentry, parent);
unionfs_read_unlock(dentry->d_sb);
}
-
/*
* This is a variant of fs/namei.c:permission() or inode_permission() which
* skips over EROFS tests (because we perform copyup on EROFS).
@@ -874,15 +877,17 @@ static int unionfs_setattr(struct dentry *dentry, struct iattr *ia)
{
int err = 0;
struct dentry *lower_dentry;
+ struct dentry *parent;
struct inode *inode;
struct inode *lower_inode;
int bstart, bend, bindex;
loff_t size;
unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_CHILD);
+ parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT);
unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD);
- if (unlikely(!__unionfs_d_revalidate_chain(dentry, NULL, false))) {
+ if (unlikely(!__unionfs_d_revalidate(dentry, parent, NULL, false))) {
err = -ESTALE;
goto out;
}
@@ -922,7 +927,7 @@ static int unionfs_setattr(struct dentry *dentry, struct iattr *ia)
size = i_size_read(inode);
/* copyup to next available branch */
for (bindex = bstart - 1; bindex >= 0; bindex--) {
- err = copyup_dentry(dentry->d_parent->d_inode,
+ err = copyup_dentry(parent->d_inode,
dentry, bstart, bindex,
dentry->d_name.name,
dentry->d_name.len,
@@ -982,6 +987,7 @@ out:
if (!err)
unionfs_check_dentry(dentry);
unionfs_unlock_dentry(dentry);
+ unionfs_unlock_parent(dentry, parent);
unionfs_read_unlock(dentry->d_sb);
return err;
diff --git a/fs/unionfs/lookup.c b/fs/unionfs/lookup.c
index a3b4235..5bf991a 100644
--- a/fs/unionfs/lookup.c
+++ b/fs/unionfs/lookup.c
@@ -69,13 +69,13 @@ struct dentry *__lookup_one(struct dentry *base, struct vfsmount *mnt,
* Returns: 0 (ok), or -ERRNO if an error occurred.
* XXX: get rid of _partial_lookup and make callers call _lookup_full directly
*/
-int unionfs_partial_lookup(struct dentry *dentry)
+int unionfs_partial_lookup(struct dentry *dentry, struct dentry *parent)
{
struct dentry *tmp;
struct nameidata nd = { .flags = 0 };
int err = -ENOSYS;
- tmp = unionfs_lookup_full(dentry, &nd, INTERPOSE_PARTIAL);
+ tmp = unionfs_lookup_full(dentry, parent, &nd, INTERPOSE_PARTIAL);
if (!tmp) {
err = 0;
@@ -288,6 +288,7 @@ void release_lower_nd(struct nameidata *nd, int err)
* dentry's info, which the caller must unlock.
*/
struct dentry *unionfs_lookup_full(struct dentry *dentry,
+ struct dentry *parent,
struct nameidata *nd_unused, int lookupmode)
{
int err = 0;
@@ -296,7 +297,6 @@ struct dentry *unionfs_lookup_full(struct dentry *dentry,
struct vfsmount *lower_dir_mnt;
struct dentry *wh_lower_dentry = NULL;
struct dentry *lower_dir_dentry = NULL;
- struct dentry *parent_dentry = NULL;
struct dentry *d_interposed = NULL;
int bindex, bstart, bend, bopaque;
int opaque, num_positive = 0;
@@ -310,6 +310,7 @@ struct dentry *unionfs_lookup_full(struct dentry *dentry,
* new_dentry_private_data already locked.
*/
verify_locked(dentry);
+ verify_locked(parent);
/* must initialize dentry operations */
dentry->d_op = &unionfs_dops;
@@ -317,7 +318,6 @@ struct dentry *unionfs_lookup_full(struct dentry *dentry,
/* We never partial lookup the root directory. */
if (IS_ROOT(dentry))
goto out;
- parent_dentry = dget_parent(dentry);
name = dentry->d_name.name;
namelen = dentry->d_name.len;
@@ -329,9 +329,9 @@ struct dentry *unionfs_lookup_full(struct dentry *dentry,
}
/* Now start the actual lookup procedure. */
- bstart = dbstart(parent_dentry);
- bend = dbend(parent_dentry);
- bopaque = dbopaque(parent_dentry);
+ bstart = dbstart(parent);
+ bend = dbend(parent);
+ bopaque = dbopaque(parent);
BUG_ON(bstart < 0);
/* adjust bend to bopaque if needed */
@@ -356,7 +356,7 @@ struct dentry *unionfs_lookup_full(struct dentry *dentry,
}
lower_dir_dentry =
- unionfs_lower_dentry_idx(parent_dentry, bindex);
+ unionfs_lower_dentry_idx(parent, bindex);
/* if the lower dentry's parent does not exist, skip this */
if (!lower_dir_dentry || !lower_dir_dentry->d_inode)
continue;
@@ -381,7 +381,7 @@ struct dentry *unionfs_lookup_full(struct dentry *dentry,
dput(wh_lower_dentry);
/* Now do regular lookup; lookup @name */
- lower_dir_mnt = unionfs_lower_mnt_idx(parent_dentry, bindex);
+ lower_dir_mnt = unionfs_lower_mnt_idx(parent, bindex);
lower_mnt = NULL; /* XXX: needed? */
lower_dentry = __lookup_one(lower_dir_dentry, lower_dir_mnt,
@@ -428,7 +428,7 @@ struct dentry *unionfs_lookup_full(struct dentry *dentry,
dbend(dentry) = bindex;
/* update parent directory's atime with the bindex */
- fsstack_copy_attr_atime(parent_dentry->d_inode,
+ fsstack_copy_attr_atime(parent->d_inode,
lower_dir_dentry->d_inode);
}
@@ -465,7 +465,7 @@ struct dentry *unionfs_lookup_full(struct dentry *dentry,
if (unionfs_lower_dentry_idx(dentry, bindex))
goto out;
lower_dir_dentry =
- unionfs_lower_dentry_idx(parent_dentry, bindex);
+ unionfs_lower_dentry_idx(parent, bindex);
if (!lower_dir_dentry || !lower_dir_dentry->d_inode)
goto out;
if (!S_ISDIR(lower_dir_dentry->d_inode->i_mode))
@@ -565,7 +565,6 @@ out:
BUG_ON(dbstart(d_interposed) >= 0 && dbend(d_interposed) < 0);
}
- dput(parent_dentry);
if (!err && d_interposed)
return d_interposed;
return ERR_PTR(err);
diff --git a/fs/unionfs/rename.c b/fs/unionfs/rename.c
index da7d589..fa3c98e 100644
--- a/fs/unionfs/rename.c
+++ b/fs/unionfs/rename.c
@@ -22,7 +22,8 @@
* This is a helper function for rename, used when rename ends up with hosed
* over dentries and we need to revert.
*/
-static int unionfs_refresh_lower_dentry(struct dentry *dentry, int bindex)
+static int unionfs_refresh_lower_dentry(struct dentry *dentry,
+ struct dentry *parent, int bindex)
{
struct dentry *lower_dentry;
struct dentry *lower_parent;
@@ -30,9 +31,7 @@ static int unionfs_refresh_lower_dentry(struct dentry *dentry, int bindex)
verify_locked(dentry);
- unionfs_lock_dentry(dentry->d_parent, UNIONFS_DMUTEX_CHILD);
- lower_parent = unionfs_lower_dentry_idx(dentry->d_parent, bindex);
- unionfs_unlock_dentry(dentry->d_parent);
+ lower_parent = unionfs_lower_dentry_idx(parent, bindex);
BUG_ON(!S_ISDIR(lower_parent->d_inode->i_mode));
@@ -61,7 +60,9 @@ out:
}
static int __unionfs_rename(struct inode *old_dir, struct dentry *old_dentry,
+ struct dentry *old_parent,
struct inode *new_dir, struct dentry *new_dentry,
+ struct dentry *new_parent,
int bindex)
{
int err = 0;
@@ -76,7 +77,7 @@ static int __unionfs_rename(struct inode *old_dir, struct dentry *old_dentry,
if (!lower_new_dentry) {
lower_new_dentry =
- create_parents(new_dentry->d_parent->d_inode,
+ create_parents(new_parent->d_inode,
new_dentry, new_dentry->d_name.name,
bindex);
if (IS_ERR(lower_new_dentry)) {
@@ -155,15 +156,16 @@ out:
*/
static int do_unionfs_rename(struct inode *old_dir,
struct dentry *old_dentry,
+ struct dentry *old_parent,
struct inode *new_dir,
- struct dentry *new_dentry)
+ struct dentry *new_dentry,
+ struct dentry *new_parent)
{
int err = 0;
int bindex, bwh_old;
int old_bstart, old_bend;
int new_bstart, new_bend;
int do_copyup = -1;
- struct dentry *parent_dentry;
int local_err = 0;
int eio = 0;
int revert = 0;
@@ -171,13 +173,13 @@ static int do_unionfs_rename(struct inode *old_dir,
old_bstart = dbstart(old_dentry);
bwh_old = old_bstart;
old_bend = dbend(old_dentry);
- parent_dentry = old_dentry->d_parent;
new_bstart = dbstart(new_dentry);
new_bend = dbend(new_dentry);
/* Rename source to destination. */
- err = __unionfs_rename(old_dir, old_dentry, new_dir, new_dentry,
+ err = __unionfs_rename(old_dir, old_dentry, old_parent,
+ new_dir, new_dentry, new_parent,
old_bstart);
if (err) {
if (!IS_COPYUP_ERR(err))
@@ -206,11 +208,11 @@ static int do_unionfs_rename(struct inode *old_dir,
err = vfs_unlink(unlink_dir_dentry->d_inode,
unlink_dentry);
- fsstack_copy_attr_times(new_dentry->d_parent->d_inode,
+ fsstack_copy_attr_times(new_parent->d_inode,
unlink_dir_dentry->d_inode);
/* propagate number of hard-links */
- new_dentry->d_parent->d_inode->i_nlink =
- unionfs_get_nlinks(new_dentry->d_parent->d_inode);
+ new_parent->d_inode->i_nlink =
+ unionfs_get_nlinks(new_parent->d_inode);
unlock_dir(unlink_dir_dentry);
if (!err) {
@@ -232,7 +234,7 @@ static int do_unionfs_rename(struct inode *old_dir,
* copyup the file into some left directory, so that
* you can rename it
*/
- err = copyup_dentry(old_dentry->d_parent->d_inode,
+ err = copyup_dentry(old_parent->d_inode,
old_dentry, old_bstart, bindex,
old_dentry->d_name.name,
old_dentry->d_name.len, NULL,
@@ -241,8 +243,8 @@ static int do_unionfs_rename(struct inode *old_dir,
if (err)
continue;
bwh_old = bindex;
- err = __unionfs_rename(old_dir, old_dentry,
- new_dir, new_dentry,
+ err = __unionfs_rename(old_dir, old_dentry, old_parent,
+ new_dir, new_dentry, new_parent,
bindex);
break;
}
@@ -281,14 +283,16 @@ out:
revert:
/* Do revert here. */
- local_err = unionfs_refresh_lower_dentry(new_dentry, old_bstart);
+ local_err = unionfs_refresh_lower_dentry(new_dentry, new_parent,
+ old_bstart);
if (local_err) {
printk(KERN_ERR "unionfs: revert failed in rename: "
"the new refresh failed\n");
eio = -EIO;
}
- local_err = unionfs_refresh_lower_dentry(old_dentry, old_bstart);
+ local_err = unionfs_refresh_lower_dentry(old_dentry, old_parent,
+ old_bstart);
if (local_err) {
printk(KERN_ERR "unionfs: revert failed in rename: "
"the old refresh failed\n");
@@ -312,8 +316,9 @@ revert:
goto revert_out;
}
- local_err = __unionfs_rename(new_dir, new_dentry,
- old_dir, old_dentry, old_bstart);
+ local_err = __unionfs_rename(new_dir, new_dentry, new_parent,
+ old_dir, old_dentry, old_parent,
+ old_bstart);
/* If we can't fix it, then we cop-out with -EIO. */
if (local_err) {
@@ -321,10 +326,12 @@ revert:
eio = -EIO;
}
- local_err = unionfs_refresh_lower_dentry(new_dentry, bindex);
+ local_err = unionfs_refresh_lower_dentry(new_dentry, new_parent,
+ bindex);
if (local_err)
eio = -EIO;
- local_err = unionfs_refresh_lower_dentry(old_dentry, bindex);
+ local_err = unionfs_refresh_lower_dentry(old_dentry, old_parent,
+ bindex);
if (local_err)
eio = -EIO;
@@ -340,11 +347,11 @@ revert_out:
* return EXDEV to the user-space utility that caused this, and let the
* user-space recurse and ask us to copy up each file separately.
*/
-static int may_rename_dir(struct dentry *dentry)
+static int may_rename_dir(struct dentry *dentry, struct dentry *parent)
{
int err, bstart;
- err = check_empty(dentry, NULL);
+ err = check_empty(dentry, parent, NULL);
if (err == -ENOTEMPTY) {
if (is_robranch(dentry))
return -EXDEV;
@@ -357,41 +364,61 @@ static int may_rename_dir(struct dentry *dentry)
return 0;
dbstart(dentry) = bstart + 1;
- err = check_empty(dentry, NULL);
+ err = check_empty(dentry, parent, NULL);
dbstart(dentry) = bstart;
if (err == -ENOTEMPTY)
err = -EXDEV;
return err;
}
+/*
+ * The locking rules in unionfs_rename are complex. We could use a simpler
+ * superblock-level name-space lock for renames and copy-ups.
+ */
int unionfs_rename(struct inode *old_dir, struct dentry *old_dentry,
struct inode *new_dir, struct dentry *new_dentry)
{
int err = 0;
struct dentry *wh_dentry;
+ struct dentry *old_parent, *new_parent;
+ int valid = true;
unionfs_read_lock(old_dentry->d_sb, UNIONFS_SMUTEX_CHILD);
+ old_parent = dget_parent(old_dentry);
+ new_parent = dget_parent(new_dentry);
+ /* un/lock parent dentries only if they differ from old/new_dentry */
+ if (old_parent != old_dentry &&
+ old_parent != new_dentry)
+ unionfs_lock_dentry(old_parent, UNIONFS_DMUTEX_REVAL_PARENT);
+ if (new_parent != old_dentry &&
+ new_parent != new_dentry &&
+ new_parent != old_parent)
+ unionfs_lock_dentry(new_parent, UNIONFS_DMUTEX_REVAL_CHILD);
unionfs_double_lock_dentry(old_dentry, new_dentry);
- if (unlikely(!__unionfs_d_revalidate_chain(old_dentry, NULL, false))) {
+ valid = __unionfs_d_revalidate(old_dentry, old_parent, NULL, false);
+ if (!valid) {
err = -ESTALE;
goto out;
}
- if (unlikely(!d_deleted(new_dentry) && new_dentry->d_inode &&
- !__unionfs_d_revalidate_chain(new_dentry, NULL, false))) {
- err = -ESTALE;
- goto out;
+ if (!d_deleted(new_dentry) && new_dentry->d_inode) {
+ valid = __unionfs_d_revalidate(new_dentry, new_parent,
+ NULL, false);
+ if (!valid) {
+ err = -ESTALE;
+ goto out;
+ }
}
if (!S_ISDIR(old_dentry->d_inode->i_mode))
- err = unionfs_partial_lookup(old_dentry);
+ err = unionfs_partial_lookup(old_dentry, old_parent);
else
- err = may_rename_dir(old_dentry);
+ err = may_rename_dir(old_dentry, old_parent);
if (err)
goto out;
- err = unionfs_partial_lookup(new_dentry);
+ err = unionfs_partial_lookup(new_dentry, new_parent);
if (err)
goto out;
@@ -413,7 +440,7 @@ int unionfs_rename(struct inode *old_dir, struct dentry *old_dentry,
if (S_ISDIR(new_dentry->d_inode->i_mode)) {
struct unionfs_dir_state *namelist = NULL;
/* check if this unionfs directory is empty or not */
- err = check_empty(new_dentry, &namelist);
+ err = check_empty(new_dentry, new_parent, &namelist);
if (err)
goto out;
@@ -429,7 +456,8 @@ int unionfs_rename(struct inode *old_dir, struct dentry *old_dentry,
}
}
- err = do_unionfs_rename(old_dir, old_dentry, new_dir, new_dentry);
+ err = do_unionfs_rename(old_dir, old_dentry, old_parent,
+ new_dir, new_dentry, new_parent);
if (err)
goto out;
@@ -471,8 +499,18 @@ int unionfs_rename(struct inode *old_dir, struct dentry *old_dentry,
out:
if (err) /* clear the new_dentry stuff created */
d_drop(new_dentry);
- unionfs_unlock_dentry(new_dentry);
- unionfs_unlock_dentry(old_dentry);
+
+ unionfs_double_unlock_dentry(old_dentry, new_dentry);
+ if (new_parent != old_dentry &&
+ new_parent != new_dentry &&
+ new_parent != old_parent)
+ unionfs_unlock_dentry(new_parent);
+ if (old_parent != old_dentry &&
+ old_parent != new_dentry)
+ unionfs_unlock_dentry(old_parent);
+ dput(new_parent);
+ dput(old_parent);
unionfs_read_unlock(old_dentry->d_sb);
+
return err;
}
diff --git a/fs/unionfs/super.c b/fs/unionfs/super.c
index e774ef3..cfd9494 100644
--- a/fs/unionfs/super.c
+++ b/fs/unionfs/super.c
@@ -150,13 +150,17 @@ static int unionfs_statfs(struct dentry *dentry, struct kstatfs *buf)
int err = 0;
struct super_block *sb;
struct dentry *lower_dentry;
+ struct dentry *parent;
+ bool valid;
sb = dentry->d_sb;
unionfs_read_lock(sb, UNIONFS_SMUTEX_CHILD);
+ parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT);
unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD);
- if (unlikely(!__unionfs_d_revalidate_chain(dentry, NULL, false))) {
+ valid = __unionfs_d_revalidate(dentry, parent, NULL, false);
+ if (unlikely(!valid)) {
err = -ESTALE;
goto out;
}
@@ -185,6 +189,7 @@ static int unionfs_statfs(struct dentry *dentry, struct kstatfs *buf)
out:
unionfs_check_dentry(dentry);
unionfs_unlock_dentry(dentry);
+ unionfs_unlock_parent(dentry, parent);
unionfs_read_unlock(sb);
return err;
}
diff --git a/fs/unionfs/union.h b/fs/unionfs/union.h
index 1b9c3f7..b15be16 100644
--- a/fs/unionfs/union.h
+++ b/fs/unionfs/union.h
@@ -292,11 +292,56 @@ static inline void unionfs_double_lock_dentry(struct dentry *d1,
{
BUG_ON(d1 == d2);
if (d1 < d2) {
- unionfs_lock_dentry(d2, UNIONFS_DMUTEX_CHILD);
unionfs_lock_dentry(d1, UNIONFS_DMUTEX_PARENT);
+ unionfs_lock_dentry(d2, UNIONFS_DMUTEX_CHILD);
} else {
- unionfs_lock_dentry(d1, UNIONFS_DMUTEX_CHILD);
unionfs_lock_dentry(d2, UNIONFS_DMUTEX_PARENT);
+ unionfs_lock_dentry(d1, UNIONFS_DMUTEX_CHILD);
+ }
+}
+
+static inline void unionfs_double_unlock_dentry(struct dentry *d1,
+ struct dentry *d2)
+{
+ BUG_ON(d1 == d2);
+ if (d1 < d2) { /* unlock in reverse order than double_lock_dentry */
+ unionfs_unlock_dentry(d1);
+ unionfs_unlock_dentry(d2);
+ } else {
+ unionfs_unlock_dentry(d2);
+ unionfs_unlock_dentry(d1);
+ }
+}
+
+static inline void unionfs_double_lock_parents(struct dentry *p1,
+ struct dentry *p2)
+{
+ if (p1 == p2) {
+ unionfs_lock_dentry(p1, UNIONFS_DMUTEX_REVAL_PARENT);
+ return;
+ }
+ if (p1 < p2) {
+ unionfs_lock_dentry(p1, UNIONFS_DMUTEX_REVAL_PARENT);
+ unionfs_lock_dentry(p2, UNIONFS_DMUTEX_REVAL_CHILD);
+ } else {
+ unionfs_lock_dentry(p2, UNIONFS_DMUTEX_REVAL_PARENT);
+ unionfs_lock_dentry(p1, UNIONFS_DMUTEX_REVAL_CHILD);
+ }
+}
+
+static inline void unionfs_double_unlock_parents(struct dentry *p1,
+ struct dentry *p2)
+{
+ if (p1 == p2) {
+ unionfs_unlock_dentry(p1);
+ return;
+ }
+ if (p1 < p2) { /* unlock in reverse order of double_lock_parents */
+ unionfs_unlock_dentry(p1);
+ unionfs_unlock_dentry(p2);
+ } else {
+ unionfs_unlock_dentry(p2);
+ unionfs_unlock_dentry(p1);
}
}
@@ -316,8 +361,10 @@ extern struct dentry *create_parents(struct inode *dir, struct dentry *dentry,
const char *name, int bindex);
/* partial lookup */
-extern int unionfs_partial_lookup(struct dentry *dentry);
+extern int unionfs_partial_lookup(struct dentry *dentry,
+ struct dentry *parent);
extern struct dentry *unionfs_lookup_full(struct dentry *dentry,
+ struct dentry *parent,
struct nameidata *nd_unused,
int lookupmode);
@@ -336,7 +383,7 @@ extern void unionfs_postcopyup_setmnt(struct dentry *dentry);
extern void unionfs_postcopyup_release(struct dentry *dentry);
/* Is this directory empty: 0 if it is empty, -ENOTEMPTY if not. */
-extern int check_empty(struct dentry *dentry,
+extern int check_empty(struct dentry *dentry, struct dentry *parent,
struct unionfs_dir_state **namelist);
/* whiteout and opaque directory helpers */
extern char *alloc_whname(const char *name, int len);
@@ -363,8 +410,8 @@ extern int unionfs_setlk(struct file *file, int cmd, struct file_lock *fl);
extern int unionfs_getlk(struct file *file, struct file_lock *fl);
/* Common file operations. */
-extern int unionfs_file_revalidate(struct file *file, bool willwrite);
-extern int unionfs_file_revalidate_locked(struct file *file, bool willwrite);
+extern int unionfs_file_revalidate(struct file *file, struct dentry *parent,
+ bool willwrite);
extern int unionfs_open(struct inode *inode, struct file *file);
extern int unionfs_file_release(struct inode *inode, struct file *file);
extern int unionfs_flush(struct file *file, fl_owner_t id);
@@ -381,11 +428,10 @@ extern int unionfs_rename(struct inode *old_dir, struct dentry *old_dentry,
extern int unionfs_unlink(struct inode *dir, struct dentry *dentry);
extern int unionfs_rmdir(struct inode *dir, struct dentry *dentry);
-extern bool __unionfs_d_revalidate_one_locked(struct dentry *dentry,
- struct nameidata *nd,
- bool willwrite);
-extern bool __unionfs_d_revalidate_chain(struct dentry *dentry,
- struct nameidata *nd, bool willwrite);
+extern bool __unionfs_d_revalidate(struct dentry *dentry,
+ struct dentry *parent,
+ struct nameidata *nd,
+ bool willwrite);
extern bool is_negative_lower(const struct dentry *dentry);
extern bool is_newer_lower(const struct dentry *dentry);
extern void purge_sb_data(struct super_block *sb);
@@ -510,6 +556,16 @@ static inline void unlock_dir(struct dentry *dir)
dput(dir);
}
+/* true if dentry is valid, false otherwise (i.e., needs revalidation) */
+static inline bool is_valid(const struct dentry *dentry)
+{
+ if (is_negative_lower(dentry) ||
+ (atomic_read(&UNIONFS_SB(dentry->d_sb)->generation) !=
+ atomic_read(&UNIONFS_D(dentry)->generation)))
+ return false;
+ return true;
+}
+
static inline struct vfsmount *unionfs_mntget(struct dentry *dentry,
int bindex)
{
diff --git a/fs/unionfs/unlink.c b/fs/unionfs/unlink.c
index d711e9d..679f4ca 100644
--- a/fs/unionfs/unlink.c
+++ b/fs/unionfs/unlink.c
@@ -44,14 +44,15 @@
* as as per Documentation/filesystems/unionfs/concepts.txt).
*
*/
-static int unionfs_unlink_whiteout(struct inode *dir, struct dentry *dentry)
+static int unionfs_unlink_whiteout(struct inode *dir, struct dentry *dentry,
+ struct dentry *parent)
{
struct dentry *lower_dentry;
struct dentry *lower_dir_dentry;
int bindex;
int err = 0;
- err = unionfs_partial_lookup(dentry);
+ err = unionfs_partial_lookup(dentry, parent);
if (err)
goto out;
@@ -123,30 +124,26 @@ int unionfs_unlink(struct inode *dir, struct dentry *dentry)
{
int err = 0;
struct inode *inode = dentry->d_inode;
+ struct dentry *parent;
int valid;
BUG_ON(S_ISDIR(inode->i_mode));
unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_CHILD);
+ parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT);
unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD);
- unionfs_lock_dentry(dentry->d_parent, UNIONFS_DMUTEX_PARENT);
- valid = __unionfs_d_revalidate_chain(dentry->d_parent, NULL, false);
- if (unlikely(!valid)) {
- err = -ESTALE;
- goto out;
- }
- valid = __unionfs_d_revalidate_one_locked(dentry, NULL, false);
+ valid = __unionfs_d_revalidate(dentry, parent, NULL, false);
if (unlikely(!valid)) {
err = -ESTALE;
goto out;
}
unionfs_check_dentry(dentry);
- err = unionfs_unlink_whiteout(dir, dentry);
+ err = unionfs_unlink_whiteout(dir, dentry, parent);
/* call d_drop so the system "forgets" about us */
if (!err) {
unionfs_postcopyup_release(dentry);
- unionfs_postcopyup_setmnt(dentry->d_parent);
+ unionfs_postcopyup_setmnt(parent);
if (inode->i_nlink == 0) /* drop lower inodes */
iput_lowers_all(inode, false);
d_drop(dentry);
@@ -162,8 +159,8 @@ out:
unionfs_check_dentry(dentry);
unionfs_check_inode(dir);
}
- unionfs_unlock_dentry(dentry->d_parent);
unionfs_unlock_dentry(dentry);
+ unionfs_unlock_parent(dentry, parent);
unionfs_read_unlock(dentry->d_sb);
return err;
}
@@ -209,19 +206,23 @@ int unionfs_rmdir(struct inode *dir, struct dentry *dentry)
{
int err = 0;
struct unionfs_dir_state *namelist = NULL;
+ struct dentry *parent;
int dstart, dend;
+ bool valid;
unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_CHILD);
+ parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT);
unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD);
- if (unlikely(!__unionfs_d_revalidate_chain(dentry, NULL, false))) {
+ valid = __unionfs_d_revalidate(dentry, parent, NULL, false);
+ if (unlikely(!valid)) {
err = -ESTALE;
goto out;
}
unionfs_check_dentry(dentry);
/* check if this unionfs directory is empty or not */
- err = check_empty(dentry, &namelist);
+ err = check_empty(dentry, parent, &namelist);
if (err)
goto out;
@@ -275,6 +276,7 @@ out:
free_rdstate(namelist);
unionfs_unlock_dentry(dentry);
+ unionfs_unlock_parent(dentry, parent);
unionfs_read_unlock(dentry->d_sb);
return err;
}
diff --git a/fs/unionfs/whiteout.c b/fs/unionfs/whiteout.c
index db7a21e..0934ac8 100644
--- a/fs/unionfs/whiteout.c
+++ b/fs/unionfs/whiteout.c
@@ -134,7 +134,7 @@ struct dentry *find_first_whiteout(struct dentry *dentry)
struct dentry *parent, *lower_parent, *wh_dentry;
parent = dget_parent(dentry);
- unionfs_lock_dentry(parent, UNIONFS_DMUTEX_WHITEOUT);
+
bstart = dbstart(parent);
bend = dbend(parent);
wh_dentry = ERR_PTR(-ENOENT);
@@ -151,7 +151,7 @@ struct dentry *find_first_whiteout(struct dentry *dentry)
dput(wh_dentry);
wh_dentry = ERR_PTR(-ENOENT);
}
- unionfs_unlock_dentry(parent);
+
dput(parent);
return wh_dentry;
@@ -238,7 +238,7 @@ int check_unlink_whiteout(struct dentry *dentry, struct dentry *lower_dentry,
err = -EIO;
printk(KERN_ERR "unionfs: found both whiteout and regular "
"file in directory %s (branch %d)\n",
- lower_dentry->d_parent->d_name.name, bindex);
+ lower_dir_dentry->d_name.name, bindex);
goto out_dput;
}
diff --git a/fs/unionfs/xattr.c b/fs/unionfs/xattr.c
index 93a8fce..6eb9503 100644
--- a/fs/unionfs/xattr.c
+++ b/fs/unionfs/xattr.c
@@ -43,12 +43,16 @@ ssize_t unionfs_getxattr(struct dentry *dentry, const char *name, void *value,
size_t size)
{
struct dentry *lower_dentry = NULL;
+ struct dentry *parent;
int err = -EOPNOTSUPP;
+ bool valid;
unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_CHILD);
+ parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT);
unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD);
- if (unlikely(!__unionfs_d_revalidate_chain(dentry, NULL, false))) {
+ valid = __unionfs_d_revalidate(dentry, parent, NULL, false);
+ if (unlikely(!valid)) {
err = -ESTALE;
goto out;
}
@@ -60,6 +64,7 @@ ssize_t unionfs_getxattr(struct dentry *dentry, const char *name, void *value,
out:
unionfs_check_dentry(dentry);
unionfs_unlock_dentry(dentry);
+ unionfs_unlock_parent(dentry, parent);
unionfs_read_unlock(dentry->d_sb);
return err;
}
@@ -72,12 +77,16 @@ int unionfs_setxattr(struct dentry *dentry, const char *name,
const void *value, size_t size, int flags)
{
struct dentry *lower_dentry = NULL;
+ struct dentry *parent;
int err = -EOPNOTSUPP;
+ bool valid;
unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_CHILD);
+ parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT);
unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD);
- if (unlikely(!__unionfs_d_revalidate_chain(dentry, NULL, false))) {
+ valid = __unionfs_d_revalidate(dentry, parent, NULL, false);
+ if (unlikely(!valid)) {
err = -ESTALE;
goto out;
}
@@ -90,6 +99,7 @@ int unionfs_setxattr(struct dentry *dentry, const char *name,
out:
unionfs_check_dentry(dentry);
unionfs_unlock_dentry(dentry);
+ unionfs_unlock_parent(dentry, parent);
unionfs_read_unlock(dentry->d_sb);
return err;
}
@@ -101,12 +111,16 @@ out:
int unionfs_removexattr(struct dentry *dentry, const char *name)
{
struct dentry *lower_dentry = NULL;
+ struct dentry *parent;
int err = -EOPNOTSUPP;
+ bool valid;
unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_CHILD);
+ parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT);
unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD);
- if (unlikely(!__unionfs_d_revalidate_chain(dentry, NULL, false))) {
+ valid = __unionfs_d_revalidate(dentry, parent, NULL, false);
+ if (unlikely(!valid)) {
err = -ESTALE;
goto out;
}
@@ -118,6 +132,7 @@ int unionfs_removexattr(struct dentry *dentry, const char *name)
out:
unionfs_check_dentry(dentry);
unionfs_unlock_dentry(dentry);
+ unionfs_unlock_parent(dentry, parent);
unionfs_read_unlock(dentry->d_sb);
return err;
}
@@ -129,13 +144,17 @@ out:
ssize_t unionfs_listxattr(struct dentry *dentry, char *list, size_t size)
{
struct dentry *lower_dentry = NULL;
+ struct dentry *parent;
int err = -EOPNOTSUPP;
char *encoded_list = NULL;
+ bool valid;
unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_CHILD);
+ parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT);
unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD);
- if (unlikely(!__unionfs_d_revalidate_chain(dentry, NULL, false))) {
+ valid = __unionfs_d_revalidate(dentry, parent, NULL, false);
+ if (unlikely(!valid)) {
err = -ESTALE;
goto out;
}
@@ -148,6 +167,7 @@ ssize_t unionfs_listxattr(struct dentry *dentry, char *list, size_t size)
out:
unionfs_check_dentry(dentry);
unionfs_unlock_dentry(dentry);
+ unionfs_unlock_parent(dentry, parent);
unionfs_read_unlock(dentry->d_sb);
return err;
}
More information about the unionfs-cvs
mailing list