[PATCH 12/21] Unionfs: Grab the unionfs sb private data lock around branch info users

From: Josef 'Jeff' Sipek
Date: Mon Apr 09 2007 - 10:56:02 EST


From: Erez Zadok <ezk@xxxxxxxxxxxxx>

Locking/concurrency/race fixes. Use the unionfs superblock rwsem, and grab
the read lock around every op that uses branch-related information, such as
branch counters. Grab the write rwsem lock in operations which attempt to
change branch information, such as when adding/deleting branches. This
will, for example, cause branch-management remount commands (which are
infrequent) to block a bit until all in-progress file operations on open
files are done.

Signed-off-by: Erez Zadok <ezk@xxxxxxxxxxxxx>
[jsipek: whitespace fixes & more locks/unlocks]
Signed-off-by: Josef 'Jeff' Sipek <jsipek@xxxxxxxxxxxxx>
---
fs/unionfs/commonfops.c | 25 ++++++++++++++++++++++---
fs/unionfs/copyup.c | 14 ++++++++++----
fs/unionfs/dirfops.c | 4 ++++
fs/unionfs/dirhelper.c | 6 ++++++
fs/unionfs/file.c | 14 ++++++++++++++
fs/unionfs/inode.c | 10 +++++++---
fs/unionfs/main.c | 4 ++++
fs/unionfs/super.c | 6 ++++++
fs/unionfs/union.h | 10 +++++++---
9 files changed, 80 insertions(+), 13 deletions(-)

diff --git a/fs/unionfs/commonfops.c b/fs/unionfs/commonfops.c
index 9b6128c..6606cba 100644
--- a/fs/unionfs/commonfops.c
+++ b/fs/unionfs/commonfops.c
@@ -170,7 +170,9 @@ static int open_all_files(struct file *file)

dget(hidden_dentry);
unionfs_mntget(dentry, bindex);
+ unionfs_read_lock(sb);
branchget(sb, bindex);
+ unionfs_read_unlock(sb);

hidden_file = dentry_open(hidden_dentry,
unionfs_lower_mnt_idx(dentry, bindex),
@@ -215,7 +217,9 @@ static int open_highest_file(struct file *file, int willwrite)

dget(hidden_dentry);
unionfs_mntget(dentry, bstart);
+ unionfs_read_lock(sb);
branchget(sb, bstart);
+ unionfs_read_unlock(sb);
hidden_file = dentry_open(hidden_dentry,
unionfs_lower_mnt_idx(dentry, bstart), file->f_flags);
if (IS_ERR(hidden_file)) {
@@ -256,7 +260,9 @@ static int do_delayed_copyup(struct file *file, struct dentry *dentry)
bend = fbend(file);
for (bindex = bstart; bindex <= bend; bindex++) {
if (unionfs_lower_file_idx(file, bindex)) {
+ unionfs_read_lock(dentry->d_sb);
branchput(dentry->d_sb, bindex);
+ unionfs_read_unlock(dentry->d_sb);
fput(unionfs_lower_file_idx(file, bindex));
unionfs_set_lower_file_idx(file, bindex, NULL);
}
@@ -387,7 +393,9 @@ static int __open_dir(struct inode *inode, struct file *file)
/* The branchget goes after the open, because otherwise
* we would miss the reference on release.
*/
+ unionfs_read_lock(inode->i_sb);
branchget(inode->i_sb, bindex);
+ unionfs_read_unlock(inode->i_sb);
}

return 0;
@@ -443,7 +451,9 @@ static int __open_file(struct inode *inode, struct file *file)
return PTR_ERR(hidden_file);

unionfs_set_lower_file(file, hidden_file);
+ unionfs_read_lock(inode->i_sb);
branchget(inode->i_sb, bstart);
+ unionfs_read_unlock(inode->i_sb);

return 0;
}
@@ -456,6 +466,7 @@ int unionfs_open(struct inode *inode, struct file *file)
int bindex = 0, bstart = 0, bend = 0;
int size;

+ unionfs_read_lock(inode->i_sb);
file->private_data = kzalloc(sizeof(struct unionfs_file_info), GFP_KERNEL);
if (!UNIONFS_F(file)) {
err = -ENOMEM;
@@ -481,7 +492,6 @@ int unionfs_open(struct inode *inode, struct file *file)

dentry = file->f_dentry;
unionfs_lock_dentry(dentry);
- unionfs_read_lock(inode->i_sb);

bstart = fbstart(file) = dbstart(dentry);
bend = fbend(file) = dbend(dentry);
@@ -504,14 +514,15 @@ int unionfs_open(struct inode *inode, struct file *file)
if (!hidden_file)
continue;

+ unionfs_read_lock(file->f_dentry->d_sb);
branchput(file->f_dentry->d_sb, bindex);
+ unionfs_read_unlock(file->f_dentry->d_sb);
/* fput calls dput for hidden_dentry */
fput(hidden_file);
}
}

unionfs_unlock_dentry(dentry);
- unionfs_read_unlock(inode->i_sb);

out:
if (err) {
@@ -520,6 +531,7 @@ out:
kfree(UNIONFS_F(file));
}
out_nofree:
+ unionfs_read_unlock(inode->i_sb);
return err;
}

@@ -532,6 +544,7 @@ int unionfs_file_release(struct inode *inode, struct file *file)
int bindex, bstart, bend;
int fgen;

+ unionfs_read_lock(inode->i_sb);
/* fput all the hidden files */
fgen = atomic_read(&fileinfo->generation);
bstart = fbstart(file);
@@ -565,6 +578,7 @@ int unionfs_file_release(struct inode *inode, struct file *file)
fileinfo->rdstate = NULL;
}
kfree(fileinfo);
+ unionfs_read_unlock(inode->i_sb);
return 0;
}

@@ -593,6 +607,7 @@ static long do_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
}

out:
+ unionfs_read_unlock(file->f_dentry->d_sb);
return err;
}

@@ -600,6 +615,7 @@ long unionfs_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
long err;

+ unionfs_read_lock(file->f_dentry->d_sb);
if ((err = unionfs_file_revalidate(file, 1)))
goto out;

@@ -635,8 +651,11 @@ int unionfs_flush(struct file *file, fl_owner_t id)
struct dentry *dentry = file->f_dentry;
int bindex, bstart, bend;

+ unionfs_read_lock(file->f_dentry->d_sb);
+
if ((err = unionfs_file_revalidate(file, 1)))
goto out;
+
if (!atomic_dec_and_test(&UNIONFS_I(dentry->d_inode)->totalopens))
goto out;

@@ -664,6 +683,6 @@ int unionfs_flush(struct file *file, fl_owner_t id)
out_lock:
unionfs_unlock_dentry(dentry);
out:
+ unionfs_read_unlock(file->f_dentry->d_sb);
return err;
}
-
diff --git a/fs/unionfs/copyup.c b/fs/unionfs/copyup.c
index 4ccef81..411553a 100644
--- a/fs/unionfs/copyup.c
+++ b/fs/unionfs/copyup.c
@@ -207,7 +207,9 @@ static int __copyup_reg_data(struct dentry *dentry,

/* open old file */
unionfs_mntget(dentry, old_bindex);
+ unionfs_read_lock(sb);
branchget(sb, old_bindex);
+ unionfs_read_unlock(sb);
input_file = dentry_open(old_hidden_dentry,
unionfs_lower_mnt_idx(dentry, old_bindex),
O_RDONLY | O_LARGEFILE);
@@ -224,7 +226,9 @@ static int __copyup_reg_data(struct dentry *dentry,
/* open new file */
dget(new_hidden_dentry);
unionfs_mntget(dentry, new_bindex);
+ unionfs_read_lock(sb);
branchget(sb, new_bindex);
+ unionfs_read_unlock(sb);
output_file = dentry_open(new_hidden_dentry,
unionfs_lower_mnt_idx(dentry, new_bindex),
O_WRONLY | O_LARGEFILE);
@@ -295,13 +299,17 @@ out_close_out:
fput(output_file);

out_close_in2:
+ unionfs_read_lock(sb);
branchput(sb, new_bindex);
+ unionfs_read_unlock(sb);

out_close_in:
fput(input_file);

out:
+ unionfs_read_lock(sb);
branchput(sb, old_bindex);
+ unionfs_read_unlock(sb);

return err;
}
@@ -350,8 +358,6 @@ static int copyup_named_dentry(struct inode *dir, struct dentry *dentry,

sb = dir->i_sb;

- unionfs_read_lock(sb);
-
if ((err = is_robranch_super(sb, new_bindex)))
goto out;

@@ -443,7 +449,9 @@ out_unlink:
/* need to close the file */

fput(*copyup_file);
+ unionfs_read_lock(sb);
branchput(sb, new_bindex);
+ unionfs_read_unlock(sb);
}

/*
@@ -469,8 +477,6 @@ out_free:
kfree(symbuf);

out:
- unionfs_read_unlock(sb);
-
return err;
}

diff --git a/fs/unionfs/dirfops.c b/fs/unionfs/dirfops.c
index 8f568c7..6ff32a0 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 bend;
loff_t offset;

+ unionfs_read_lock(file->f_dentry->d_sb);
if ((err = unionfs_file_revalidate(file, 0)))
goto out;

@@ -175,6 +176,7 @@ static int unionfs_readdir(struct file *file, void *dirent, filldir_t filldir)
file->f_pos = rdstate2offset(uds);

out:
+ unionfs_read_unlock(file->f_dentry->d_sb);
return err;
}

@@ -192,6 +194,7 @@ static loff_t unionfs_dir_llseek(struct file *file, loff_t offset, int origin)
struct unionfs_dir_state *rdstate;
loff_t err;

+ unionfs_read_lock(file->f_dentry->d_sb);
if ((err = unionfs_file_revalidate(file, 0)))
goto out;

@@ -245,6 +248,7 @@ static loff_t unionfs_dir_llseek(struct file *file, loff_t offset, int origin)
}

out:
+ unionfs_read_unlock(file->f_dentry->d_sb);
return err;
}

diff --git a/fs/unionfs/dirhelper.c b/fs/unionfs/dirhelper.c
index bb5f7bc..bd15eb4 100644
--- a/fs/unionfs/dirhelper.c
+++ b/fs/unionfs/dirhelper.c
@@ -226,14 +226,18 @@ int check_empty(struct dentry *dentry, struct unionfs_dir_state **namelist)

dget(hidden_dentry);
unionfs_mntget(dentry, bindex);
+ unionfs_read_lock(sb);
branchget(sb, bindex);
+ unionfs_read_unlock(sb);
hidden_file =
dentry_open(hidden_dentry, unionfs_lower_mnt_idx(dentry, bindex),
O_RDONLY);
if (IS_ERR(hidden_file)) {
err = PTR_ERR(hidden_file);
dput(hidden_dentry);
+ unionfs_read_lock(sb);
branchput(sb, bindex);
+ unionfs_read_unlock(sb);
goto out;
}

@@ -248,7 +252,9 @@ int check_empty(struct dentry *dentry, struct unionfs_dir_state **namelist)

/* fput calls dput for hidden_dentry */
fput(hidden_file);
+ unionfs_read_lock(sb);
branchput(sb, bindex);
+ unionfs_read_unlock(sb);

if (err < 0)
goto out;
diff --git a/fs/unionfs/file.c b/fs/unionfs/file.c
index 9ce092d..84d6bab 100644
--- a/fs/unionfs/file.c
+++ b/fs/unionfs/file.c
@@ -27,6 +27,7 @@ static loff_t unionfs_llseek(struct file *file, loff_t offset, int origin)
loff_t err;
struct file *hidden_file = NULL;

+ unionfs_read_lock(file->f_dentry->d_sb);
if ((err = unionfs_file_revalidate(file, 0)))
goto out;

@@ -48,6 +49,7 @@ static loff_t unionfs_llseek(struct file *file, loff_t offset, int origin)
file->f_version++;
}
out:
+ unionfs_read_unlock(file->f_dentry->d_sb);
return err;
}

@@ -58,6 +60,7 @@ static ssize_t unionfs_read(struct file * file, char __user * buf,
loff_t pos = *ppos;
int err;

+ unionfs_read_lock(file->f_dentry->d_sb);
if ((err = unionfs_file_revalidate(file, 0)))
goto out;

@@ -70,6 +73,7 @@ static ssize_t unionfs_read(struct file * file, char __user * buf,
*ppos = pos;

out:
+ unionfs_read_unlock(file->f_dentry->d_sb);
return err;
}

@@ -123,12 +127,14 @@ static ssize_t unionfs_write(struct file * file, const char __user * buf,
{
int err = 0;

+ unionfs_read_lock(file->f_dentry->d_sb);
if ((err = unionfs_file_revalidate(file, 1)))
goto out;

err = __unionfs_write(file, buf, count, ppos);

out:
+ unionfs_read_unlock(file->f_dentry->d_sb);
return err;
}

@@ -143,6 +149,7 @@ static unsigned int unionfs_poll(struct file *file, poll_table * wait)
unsigned int mask = DEFAULT_POLLMASK;
struct file *hidden_file = NULL;

+ unionfs_read_lock(file->f_dentry->d_sb);
if (unionfs_file_revalidate(file, 0)) {
/* We should pretend an error happend. */
mask = POLLERR | POLLIN | POLLOUT;
@@ -157,6 +164,7 @@ static unsigned int unionfs_poll(struct file *file, poll_table * wait)
mask = hidden_file->f_op->poll(hidden_file, wait);

out:
+ unionfs_read_unlock(file->f_dentry->d_sb);
return mask;
}

@@ -184,6 +192,7 @@ static int unionfs_mmap(struct file *file, struct vm_area_struct *vma)
int err = 0;
int willwrite;

+ unionfs_read_lock(file->f_dentry->d_sb);
/* This might could be deferred to mmap's writepage. */
willwrite = ((vma->vm_flags | VM_SHARED | VM_WRITE) == vma->vm_flags);
if ((err = unionfs_file_revalidate(file, willwrite)))
@@ -192,6 +201,7 @@ static int unionfs_mmap(struct file *file, struct vm_area_struct *vma)
err = __do_mmap(file, vma);

out:
+ unionfs_read_unlock(file->f_dentry->d_sb);
return err;
}

@@ -200,6 +210,7 @@ static int unionfs_fsync(struct file *file, struct dentry *dentry, int datasync)
int err;
struct file *hidden_file = NULL;

+ unionfs_read_lock(file->f_dentry->d_sb);
if ((err = unionfs_file_revalidate(file, 1)))
goto out;

@@ -215,6 +226,7 @@ static int unionfs_fsync(struct file *file, struct dentry *dentry, int datasync)
mutex_unlock(&hidden_file->f_dentry->d_inode->i_mutex);

out:
+ unionfs_read_unlock(file->f_dentry->d_sb);
return err;
}

@@ -223,6 +235,7 @@ static int unionfs_fasync(int fd, struct file *file, int flag)
int err = 0;
struct file *hidden_file = NULL;

+ unionfs_read_lock(file->f_dentry->d_sb);
if ((err = unionfs_file_revalidate(file, 1)))
goto out;

@@ -232,6 +245,7 @@ static int unionfs_fasync(int fd, struct file *file, int flag)
err = hidden_file->f_op->fasync(fd, hidden_file, flag);

out:
+ unionfs_read_unlock(file->f_dentry->d_sb);
return err;
}

diff --git a/fs/unionfs/inode.c b/fs/unionfs/inode.c
index 1b2e8a8..6dfc16f 100644
--- a/fs/unionfs/inode.c
+++ b/fs/unionfs/inode.c
@@ -789,9 +789,13 @@ static int inode_permission(struct inode *inode, int mask, struct nameidata *nd,
retval = inode->i_op->permission(inode, submask, nd);
if ((retval == -EACCES) && (submask & MAY_WRITE) &&
(!strcmp("nfs", (inode)->i_sb->s_type->name)) &&
- (nd) && (nd->mnt) && (nd->mnt->mnt_sb) &&
- (branchperms(nd->mnt->mnt_sb, bindex) & MAY_NFSRO)) {
- retval = generic_permission(inode, submask, NULL);
+ (nd) && (nd->mnt) && (nd->mnt->mnt_sb)) {
+ int perms;
+ unionfs_read_lock(nd->mnt->mnt_sb);
+ perms = branchperms(nd->mnt->mnt_sb, bindex);
+ unionfs_read_unlock(nd->mnt->mnt_sb);
+ if (perms & MAY_NFSRO)
+ retval = generic_permission(inode, submask, NULL);
}
} else
retval = generic_permission(inode, submask, NULL);
diff --git a/fs/unionfs/main.c b/fs/unionfs/main.c
index b80b554..4fffafa 100644
--- a/fs/unionfs/main.c
+++ b/fs/unionfs/main.c
@@ -556,11 +556,15 @@ static int unionfs_read_super(struct super_block *sb, void *raw_data,

d = hidden_root_info->lower_paths[bindex].dentry;

+ unionfs_write_lock(sb);
unionfs_set_lower_super_idx(sb, bindex, d->d_sb);
+ unionfs_write_unlock(sb);
}

/* Unionfs: Max Bytes is the maximum bytes from highest priority branch */
+ unionfs_read_lock(sb);
sb->s_maxbytes = unionfs_lower_super_idx(sb, 0)->s_maxbytes;
+ unionfs_read_unlock(sb);

sb->s_op = &unionfs_sops;

diff --git a/fs/unionfs/super.c b/fs/unionfs/super.c
index 7f0d174..5d11908 100644
--- a/fs/unionfs/super.c
+++ b/fs/unionfs/super.c
@@ -131,7 +131,9 @@ static int unionfs_statfs(struct dentry *dentry, struct kstatfs *buf)

sb = dentry->d_sb;

+ unionfs_read_lock(sb);
hidden_sb = unionfs_lower_super_idx(sb, sbstart(sb));
+ unionfs_read_unlock(sb);
err = vfs_statfs(hidden_sb->s_root, buf);

buf->f_type = UNIONFS_SUPER_MAGIC;
@@ -884,7 +886,9 @@ static void unionfs_umount_begin(struct vfsmount *mnt, int flags)
bend = sbend(sb);
for (bindex = bstart; bindex <= bend; bindex++) {
hidden_mnt = unionfs_lower_mnt_idx(sb->s_root, bindex);
+ unionfs_read_lock(sb);
hidden_sb = unionfs_lower_super_idx(sb, bindex);
+ unionfs_read_unlock(sb);

if (hidden_mnt && hidden_sb && hidden_sb->s_op &&
hidden_sb->s_op->umount_begin)
@@ -922,7 +926,9 @@ static int unionfs_show_options(struct seq_file *m, struct vfsmount *mnt)
goto out;
}

+ unionfs_read_lock(sb);
perms = branchperms(sb, bindex);
+ unionfs_read_unlock(sb);

seq_printf(m, "%s=%s", path,
perms & MAY_WRITE ? "rw" : "ro");
diff --git a/fs/unionfs/union.h b/fs/unionfs/union.h
index 66ffe88..faaa87e 100644
--- a/fs/unionfs/union.h
+++ b/fs/unionfs/union.h
@@ -301,7 +301,6 @@ extern int unionfs_rename(struct inode *old_dir, struct dentry *old_dentry,
int unionfs_unlink(struct inode *dir, struct dentry *dentry);
int unionfs_rmdir(struct inode *dir, struct dentry *dentry);

-int __unionfs_d_revalidate(struct dentry *dentry, struct nameidata *nd);
int __unionfs_d_revalidate_chain(struct dentry *dentry, struct nameidata *nd);

/* The values for unionfs_interpose's flag. */
@@ -368,7 +367,11 @@ static inline int set_branchperms(struct super_block *sb, int index, int perms)
/* Is this file on a read-only branch? */
static inline int is_robranch_super(const struct super_block *sb, int index)
{
- return (!(branchperms(sb, index) & MAY_WRITE)) ? -EROFS : 0;
+ int ret;
+ unionfs_read_lock(sb);
+ ret = (!(branchperms(sb, index) & MAY_WRITE)) ? -EROFS : 0;
+ unionfs_read_unlock(sb);
+ return ret;
}

/* Is this file on a read-only branch? */
@@ -378,10 +381,11 @@ static inline int is_robranch_idx(const struct dentry *dentry, int index)

BUG_ON(index < 0);

+ unionfs_read_lock(dentry->d_sb);
if ((!(branchperms(dentry->d_sb, index) & MAY_WRITE)) ||
IS_RDONLY(unionfs_lower_dentry_idx(dentry, index)->d_inode))
err = -EROFS;
-
+ unionfs_read_unlock(dentry->d_sb);
return err;
}

--
1.5.0.3.268.g3dda

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