* 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;
} 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))
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);
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);
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;
* 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)
{
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);
/*
* 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;
dentry = file->f_path.dentry;
sb = dentry->d_sb;
verify_locked(dentry);
+ verify_locked(parent);
/*
* First revalidate the dentry inside struct file,
*/
reval_dentry:
if (!d_deleted(dentry) &&
- !__unionfs_d_revalidate_chain(dentry, NULL, willwrite)) {
+ !__unionfs_d_revalidate(dentry, parent, NULL, willwrite)) {
err = -ESTALE;
goto out;
}
}
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;
}
/* 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;
/* 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;
}
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)) {
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))) {
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) {
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;
}
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);
/*
* 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);
out:
unionfs_unlock_dentry(dentry);
+ unionfs_unlock_parent(dentry, parent);
unionfs_read_unlock(sb);
return err;
}
* 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;
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);
{
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;
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:
out:
unionfs_check_file(file);
unionfs_unlock_dentry(dentry);
+ unionfs_unlock_parent(dentry, parent);
unionfs_read_unlock(dentry->d_sb);
return err;
}
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);
if (!err)
unionfs_check_file(file);
unionfs_unlock_dentry(dentry);
+ unionfs_unlock_parent(dentry, parent);
unionfs_read_unlock(dentry->d_sb);
return err;
}
* 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 */
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))
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);
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;
* 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,
/*
* 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);
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
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;
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;
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;
if (!err)
unionfs_check_file(file);
unionfs_unlock_dentry(dentry);
+ unionfs_unlock_parent(dentry, parent);
unionfs_read_unlock(dentry->d_sb);
return err;
}
{
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;
if (!err)
unionfs_check_file(file);
unionfs_unlock_dentry(dentry);
+ unionfs_unlock_parent(dentry, parent);
unionfs_read_unlock(dentry->d_sb);
return err;
}
}
/* 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;
BUG_ON(!S_ISDIR(dentry->d_inode->i_mode));
- err = unionfs_partial_lookup(dentry);
+ err = unionfs_partial_lookup(dentry, parent);
if (err)
goto out;
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);
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;
out:
unionfs_unlock_dentry(dentry);
+ unionfs_unlock_parent(dentry, parent);
unionfs_read_unlock(dentry->d_sb);
return err;
}
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;
}
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;
}
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);
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;
}
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);
if (!err)
unionfs_check_file(file);
unionfs_unlock_dentry(dentry);
+ unionfs_unlock_parent(dentry, parent);
unionfs_read_unlock(dentry->d_sb);
return err;
}
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);
if (!err)
unionfs_check_file(file);
unionfs_unlock_dentry(dentry);
+ unionfs_unlock_parent(dentry, parent);
unionfs_read_unlock(dentry->d_sb);
return err;
}
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;
out:
unionfs_unlock_dentry(dentry);
+ unionfs_unlock_parent(dentry, parent);
unionfs_read_unlock(dentry->d_sb);
return err;
}
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;
out:
unionfs_unlock_dentry(dentry);
+ unionfs_unlock_parent(dentry, parent);
unionfs_read_unlock(dentry->d_sb);
return err;
}
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;
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);
}
}
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;
}
* 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) {
}
/*
- * 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.
*/
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) {
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;
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);
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,
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
*/
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;
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);
}
}
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);
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)) {
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);
* 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));
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
*/
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;
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);
}
}
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;
}
{
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;
}
out:
unionfs_check_dentry(dentry);
unionfs_unlock_dentry(dentry);
+ unionfs_unlock_parent(dentry, parent);
unionfs_read_unlock(dentry->d_sb);
return err;
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");
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).
{
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;
}
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,
if (!err)
unionfs_check_dentry(dentry);
unionfs_unlock_dentry(dentry);
+ unionfs_unlock_parent(dentry, parent);
unionfs_read_unlock(dentry->d_sb);
return err;
* 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;
* 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;
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;
* new_dentry_private_data already locked.
*/
verify_locked(dentry);
+ verify_locked(parent);
/* must initialize dentry operations */
dentry->d_op = &unionfs_dops;
/* 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;
}
/* 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 */
}
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;
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,
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);
}
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))
BUG_ON(dbstart(d_interposed) >= 0 && dbend(d_interposed) < 0);
}
- dput(parent_dentry);
if (!err && d_interposed)
return d_interposed;
return ERR_PTR(err);
* 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;
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));
}
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;
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)) {
*/
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;
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))
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) {
* 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,
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;
}
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");
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) {
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;
* 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;
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;
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;
}
}
- 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;
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;
}
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;
}
out:
unionfs_check_dentry(dentry);
unionfs_unlock_dentry(dentry);
+ unionfs_unlock_parent(dentry, parent);
unionfs_read_unlock(sb);
return err;
}
{
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);
}
}
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);
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);
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);
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);
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)
{
* 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;
{
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);
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;
}
{
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;
free_rdstate(namelist);
unionfs_unlock_dentry(dentry);
+ unionfs_unlock_parent(dentry, parent);
unionfs_read_unlock(dentry->d_sb);
return err;
}
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);
dput(wh_dentry);
wh_dentry = ERR_PTR(-ENOENT);
}
- unionfs_unlock_dentry(parent);
+
dput(parent);
return wh_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;
}
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;
}
out:
unionfs_check_dentry(dentry);
unionfs_unlock_dentry(dentry);
+ unionfs_unlock_parent(dentry, parent);
unionfs_read_unlock(dentry->d_sb);
return err;
}
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;
}
out:
unionfs_check_dentry(dentry);
unionfs_unlock_dentry(dentry);
+ unionfs_unlock_parent(dentry, parent);
unionfs_read_unlock(dentry->d_sb);
return err;
}
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;
}
out:
unionfs_check_dentry(dentry);
unionfs_unlock_dentry(dentry);
+ unionfs_unlock_parent(dentry, parent);
unionfs_read_unlock(dentry->d_sb);
return err;
}
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;
}
out:
unionfs_check_dentry(dentry);
unionfs_unlock_dentry(dentry);
+ unionfs_unlock_parent(dentry, parent);
unionfs_read_unlock(dentry->d_sb);
return err;
}