[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-Id: <12173858362938-git-send-email-ezk@cs.sunysb.edu>
Date: Tue, 29 Jul 2008 22:43:37 -0400
From: Erez Zadok <ezk@...sunysb.edu>
To: akpm@...ux-foundation.org
Cc: linux-kernel@...r.kernel.org, linux-fsdevel@...r.kernel.org,
viro@...iv.linux.org.uk, hch@...radead.org, miklos@...redi.hu,
Erez Zadok <ezk@...sunysb.edu>
Subject: [PATCH 07/19] Unionfs: overhaul whiteout code
Move all whiteout functions and helpers into a separate file, replace all
embedded whiteout code with calls to helpers. Cleanup and consolidate the
code. This will make it easier to replace the whiteout code with a
Linux-native whiteout implementation (once available).
Signed-off-by: Erez Zadok <ezk@...sunysb.edu>
---
fs/unionfs/Makefile | 2 +-
fs/unionfs/dirfops.c | 22 +-
fs/unionfs/dirhelper.c | 124 +----------
fs/unionfs/inode.c | 183 ++-------------
fs/unionfs/lookup.c | 85 +-------
fs/unionfs/rename.c | 149 ++-----------
fs/unionfs/sioq.c | 18 --
fs/unionfs/subr.c | 161 --------------
fs/unionfs/super.c | 2 +-
fs/unionfs/union.h | 40 ++---
fs/unionfs/whiteout.c | 577 ++++++++++++++++++++++++++++++++++++++++++++++++
11 files changed, 655 insertions(+), 708 deletions(-)
create mode 100644 fs/unionfs/whiteout.c
diff --git a/fs/unionfs/Makefile b/fs/unionfs/Makefile
index 17ca4a7..0dc28c1 100644
--- a/fs/unionfs/Makefile
+++ b/fs/unionfs/Makefile
@@ -2,7 +2,7 @@ obj-$(CONFIG_UNION_FS) += unionfs.o
unionfs-y := subr.o dentry.o file.o inode.o main.o super.o \
rdstate.o copyup.o dirhelper.o rename.o unlink.o \
- lookup.o commonfops.o dirfops.o sioq.o mmap.o
+ lookup.o commonfops.o dirfops.o sioq.o mmap.o whiteout.o
unionfs-$(CONFIG_UNION_FS_XATTR) += xattr.o
diff --git a/fs/unionfs/dirfops.c b/fs/unionfs/dirfops.c
index 8272fb6..e35afa4 100644
--- a/fs/unionfs/dirfops.c
+++ b/fs/unionfs/dirfops.c
@@ -36,37 +36,33 @@ struct unionfs_getdents_callback {
};
/* based on generic filldir in fs/readir.c */
-static int unionfs_filldir(void *dirent, const char *name, int namelen,
+static int unionfs_filldir(void *dirent, const char *oname, int namelen,
loff_t offset, u64 ino, unsigned int d_type)
{
struct unionfs_getdents_callback *buf = dirent;
struct filldir_node *found = NULL;
int err = 0;
- int is_wh_entry = 0;
+ int is_whiteout;
+ char *name = (char *) oname;
buf->filldir_called++;
- if ((namelen > UNIONFS_WHLEN) &&
- !strncmp(name, UNIONFS_WHPFX, UNIONFS_WHLEN)) {
- name += UNIONFS_WHLEN;
- namelen -= UNIONFS_WHLEN;
- is_wh_entry = 1;
- }
+ is_whiteout = is_whiteout_name(&name, &namelen);
- found = find_filldir_node(buf->rdstate, name, namelen, is_wh_entry);
+ found = find_filldir_node(buf->rdstate, name, namelen, is_whiteout);
if (found) {
/*
* If we had non-whiteout entry in dir cache, then mark it
* as a whiteout and but leave it in the dir cache.
*/
- if (is_wh_entry && !found->whiteout)
- found->whiteout = is_wh_entry;
+ if (is_whiteout && !found->whiteout)
+ found->whiteout = is_whiteout;
goto out;
}
/* if 'name' isn't a whiteout, filldir it. */
- if (!is_wh_entry) {
+ if (!is_whiteout) {
off_t pos = rdstate2offset(buf->rdstate);
u64 unionfs_ino = ino;
@@ -85,7 +81,7 @@ static int unionfs_filldir(void *dirent, const char *name, int namelen,
}
buf->entries_written++;
err = add_filldir_node(buf->rdstate, name, namelen,
- buf->rdstate->bindex, is_wh_entry);
+ buf->rdstate->bindex, is_whiteout);
if (err)
buf->filldir_error = err;
diff --git a/fs/unionfs/dirhelper.c b/fs/unionfs/dirhelper.c
index 4b73bb6..302a4a1 100644
--- a/fs/unionfs/dirhelper.c
+++ b/fs/unionfs/dirhelper.c
@@ -18,112 +18,6 @@
#include "union.h"
-/*
- * Delete all of the whiteouts in a given directory for rmdir.
- *
- * lower directory inode should be locked
- */
-int do_delete_whiteouts(struct dentry *dentry, int bindex,
- struct unionfs_dir_state *namelist)
-{
- int err = 0;
- struct dentry *lower_dir_dentry = NULL;
- struct dentry *lower_dentry;
- char *name = NULL, *p;
- struct inode *lower_dir;
- int i;
- struct list_head *pos;
- struct filldir_node *cursor;
-
- /* Find out lower parent dentry */
- lower_dir_dentry = unionfs_lower_dentry_idx(dentry, bindex);
- BUG_ON(!S_ISDIR(lower_dir_dentry->d_inode->i_mode));
- lower_dir = lower_dir_dentry->d_inode;
- BUG_ON(!S_ISDIR(lower_dir->i_mode));
-
- err = -ENOMEM;
- name = __getname();
- if (unlikely(!name))
- goto out;
- strcpy(name, UNIONFS_WHPFX);
- p = name + UNIONFS_WHLEN;
-
- err = 0;
- for (i = 0; !err && i < namelist->size; i++) {
- list_for_each(pos, &namelist->list[i]) {
- cursor =
- list_entry(pos, struct filldir_node,
- file_list);
- /* Only operate on whiteouts in this branch. */
- if (cursor->bindex != bindex)
- continue;
- if (!cursor->whiteout)
- continue;
-
- strcpy(p, cursor->name);
- lower_dentry =
- lookup_one_len(name, lower_dir_dentry,
- cursor->namelen +
- UNIONFS_WHLEN);
- if (IS_ERR(lower_dentry)) {
- err = PTR_ERR(lower_dentry);
- break;
- }
- if (lower_dentry->d_inode)
- err = vfs_unlink(lower_dir, lower_dentry);
- dput(lower_dentry);
- if (err)
- break;
- }
- }
-
- __putname(name);
-
- /* After all of the removals, we should copy the attributes once. */
- fsstack_copy_attr_times(dentry->d_inode, lower_dir_dentry->d_inode);
-
-out:
- return err;
-}
-
-/* delete whiteouts in a dir (for rmdir operation) using sioq if necessary */
-int delete_whiteouts(struct dentry *dentry, int bindex,
- struct unionfs_dir_state *namelist)
-{
- int err;
- struct super_block *sb;
- struct dentry *lower_dir_dentry;
- struct inode *lower_dir;
- struct sioq_args args;
-
- sb = dentry->d_sb;
-
- BUG_ON(!S_ISDIR(dentry->d_inode->i_mode));
- BUG_ON(bindex < dbstart(dentry));
- BUG_ON(bindex > dbend(dentry));
- err = is_robranch_super(sb, bindex);
- if (err)
- goto out;
-
- lower_dir_dentry = unionfs_lower_dentry_idx(dentry, bindex);
- BUG_ON(!S_ISDIR(lower_dir_dentry->d_inode->i_mode));
- lower_dir = lower_dir_dentry->d_inode;
- BUG_ON(!S_ISDIR(lower_dir->i_mode));
-
- if (!permission(lower_dir, MAY_WRITE | MAY_EXEC, NULL)) {
- err = do_delete_whiteouts(dentry, bindex, namelist);
- } else {
- args.deletewh.namelist = namelist;
- args.deletewh.dentry = dentry;
- args.deletewh.bindex = bindex;
- run_sioq(__delete_whiteouts, &args);
- err = args.err;
- }
-
-out:
- return err;
-}
-
#define RD_NONE 0
#define RD_CHECK_EMPTY 1
/* The callback structure for check_empty. */
@@ -135,13 +29,14 @@ struct unionfs_rdutil_callback {
};
/* This filldir function makes sure only whiteouts exist within a directory. */
-static int readdir_util_callback(void *dirent, const char *name, int namelen,
+static int readdir_util_callback(void *dirent, const char *oname, int namelen,
loff_t offset, u64 ino, unsigned int d_type)
{
int err = 0;
struct unionfs_rdutil_callback *buf = dirent;
- int whiteout = 0;
+ int is_whiteout;
struct filldir_node *found;
+ char *name = (char *) oname;
buf->filldir_called = 1;
@@ -149,14 +44,9 @@ static int readdir_util_callback(void *dirent, const char *name, int namelen,
(name[1] == '.' && namelen == 2)))
goto out;
- if (namelen > UNIONFS_WHLEN &&
- !strncmp(name, UNIONFS_WHPFX, UNIONFS_WHLEN)) {
- namelen -= UNIONFS_WHLEN;
- name += UNIONFS_WHLEN;
- whiteout = 1;
- }
+ is_whiteout = is_whiteout_name(&name, &namelen);
- found = find_filldir_node(buf->rdstate, name, namelen, whiteout);
+ found = find_filldir_node(buf->rdstate, name, namelen, is_whiteout);
/* If it was found in the table there was a previous whiteout. */
if (found)
goto out;
@@ -166,11 +56,11 @@ static int readdir_util_callback(void *dirent, const char *name, int namelen,
* empty.
*/
err = -ENOTEMPTY;
- if ((buf->mode == RD_CHECK_EMPTY) && !whiteout)
+ if ((buf->mode == RD_CHECK_EMPTY) && !is_whiteout)
goto out;
err = add_filldir_node(buf->rdstate, name, namelen,
- buf->rdstate->bindex, whiteout);
+ buf->rdstate->bindex, is_whiteout);
out:
buf->err = err;
diff --git a/fs/unionfs/inode.c b/fs/unionfs/inode.c
index 8b4da54..bfebc0c 100644
--- a/fs/unionfs/inode.c
+++ b/fs/unionfs/inode.c
@@ -19,79 +19,6 @@
#include "union.h"
/*
- * Helper function when creating new objects (create, symlink, and mknod).
- * Checks to see if there's a whiteout in @lower_dentry's parent directory,
- * whose name is taken from @dentry. Then tries to remove that whiteout, if
- * found.
- *
- * Return 0 if no whiteout was found, or if one was found and successfully
- * removed (a zero tells the caller that @lower_dentry belongs to a good
- * branch to create the new object in). Return -ERRNO if an error occurred
- * during whiteout lookup or in trying to unlink the whiteout.
- */
-static int check_for_whiteout(struct dentry *dentry,
- struct dentry *lower_dentry)
-{
- int err = 0;
- struct dentry *wh_dentry = NULL;
- struct dentry *lower_dir_dentry;
- char *name = NULL;
-
- /*
- * check if whiteout exists in this branch, i.e. lookup .wh.foo
- * first.
- */
- name = alloc_whname(dentry->d_name.name, dentry->d_name.len);
- if (unlikely(IS_ERR(name))) {
- err = PTR_ERR(name);
- goto out;
- }
-
- wh_dentry = lookup_one_len(name, lower_dentry->d_parent,
- dentry->d_name.len + UNIONFS_WHLEN);
- if (IS_ERR(wh_dentry)) {
- err = PTR_ERR(wh_dentry);
- wh_dentry = NULL;
- goto out;
- }
-
- if (!wh_dentry->d_inode) /* no whiteout exists */
- goto out;
-
- /* .wh.foo has been found, so let's unlink it */
- lower_dir_dentry = lock_parent_wh(wh_dentry);
- /* see Documentation/filesystems/unionfs/issues.txt */
- lockdep_off();
- err = vfs_unlink(lower_dir_dentry->d_inode, wh_dentry);
- lockdep_on();
- unlock_dir(lower_dir_dentry);
-
- /*
- * Whiteouts are special files and should be deleted no matter what
- * (as if they never existed), in order to allow this create
- * operation to succeed. This is especially important in sticky
- * directories: a whiteout may have been created by one user, but
- * the newly created file may be created by another user.
- * Therefore, in order to maintain Unix semantics, if the vfs_unlink
- * above failed, then we have to try to directly unlink the
- * whiteout. Note: in the ODF version of unionfs, whiteout are
- * handled much more cleanly.
- */
- if (err == -EPERM) {
- struct inode *inode = lower_dir_dentry->d_inode;
- err = inode->i_op->unlink(inode, wh_dentry);
- }
- if (err)
- printk(KERN_ERR "unionfs: could not "
- "unlink whiteout, err = %d\n", err);
-
-out:
- dput(wh_dentry);
- kfree(name);
- return err;
-}
-
-/*
* Find a writeable branch to create new object in. Checks all writeble
* branches of the parent inode, from istart to iend order; if none are
* suitable, also tries branch 0 (which may require a copyup).
@@ -125,7 +52,9 @@ begin:
* check for whiteouts in writeable branch, and remove them
* if necessary.
*/
- err = check_for_whiteout(dentry, lower_dentry);
+ err = check_unlink_whiteout(dentry, lower_dentry, bindex);
+ if (err > 0) /* ignore if whiteout found and removed */
+ err = 0;
if (err)
continue;
/* if get here, we can write to the branch */
@@ -302,7 +231,6 @@ 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 *whiteout_dentry;
char *name = NULL;
unionfs_read_lock(old_dentry->d_sb, UNIONFS_SMUTEX_CHILD);
@@ -320,48 +248,20 @@ static int unionfs_link(struct dentry *old_dentry, struct inode *dir,
lower_new_dentry = unionfs_lower_dentry(new_dentry);
- /*
- * check if whiteout exists in the branch of new dentry, i.e. lookup
- * .wh.foo first. If present, delete it
- */
- name = alloc_whname(new_dentry->d_name.name, new_dentry->d_name.len);
- if (unlikely(IS_ERR(name))) {
- err = PTR_ERR(name);
- goto out;
- }
-
- whiteout_dentry = lookup_one_len(name, lower_new_dentry->d_parent,
- new_dentry->d_name.len +
- UNIONFS_WHLEN);
- if (IS_ERR(whiteout_dentry)) {
- err = PTR_ERR(whiteout_dentry);
- goto out;
- }
-
- if (!whiteout_dentry->d_inode) {
- dput(whiteout_dentry);
- whiteout_dentry = NULL;
- } else {
- /* found a .wh.foo entry, unlink it and then call vfs_link() */
- lower_dir_dentry = lock_parent_wh(whiteout_dentry);
- err = is_robranch_super(new_dentry->d_sb, dbstart(new_dentry));
- if (!err) {
- /* see Documentation/filesystems/unionfs/issues.txt */
- lockdep_off();
- err = vfs_unlink(lower_dir_dentry->d_inode,
- whiteout_dentry);
- lockdep_on();
- }
-
+ /* check for a whiteout in new dentry branch, and delete it */
+ err = check_unlink_whiteout(new_dentry, lower_new_dentry,
+ dbstart(new_dentry));
+ if (err > 0) { /* whiteout found and removed successfully */
+ lower_dir_dentry = dget_parent(lower_new_dentry);
fsstack_copy_attr_times(dir, lower_dir_dentry->d_inode);
+ dput(lower_dir_dentry);
dir->i_nlink = unionfs_get_nlinks(dir);
- unlock_dir(lower_dir_dentry);
- lower_dir_dentry = NULL;
- dput(whiteout_dentry);
- if (err)
- goto out;
+ err = 0;
}
+ if (err)
+ goto out;
+ /* check if parent hierachy is needed, then link in same branch */
if (dbstart(old_dentry) != dbstart(new_dentry)) {
lower_new_dentry = create_parents(dir, new_dentry,
new_dentry->d_name.name,
@@ -531,12 +431,10 @@ out:
static int unionfs_mkdir(struct inode *parent, struct dentry *dentry, int mode)
{
int err = 0;
- struct dentry *lower_dentry = NULL, *whiteout_dentry = NULL;
+ struct dentry *lower_dentry = NULL;
struct dentry *lower_parent_dentry = NULL;
int bindex = 0, bstart;
char *name = NULL;
- int whiteout_unlinked = 0;
- struct sioq_args args;
int valid;
unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_CHILD);
@@ -558,51 +456,18 @@ static int unionfs_mkdir(struct inode *parent, struct dentry *dentry, int mode)
lower_dentry = unionfs_lower_dentry(dentry);
- /*
- * check if whiteout exists in this branch, i.e. lookup .wh.foo
- * first.
- */
- name = alloc_whname(dentry->d_name.name, dentry->d_name.len);
- if (unlikely(IS_ERR(name))) {
- err = PTR_ERR(name);
- goto out;
- }
-
- whiteout_dentry = lookup_one_len(name, lower_dentry->d_parent,
- dentry->d_name.len + UNIONFS_WHLEN);
- if (IS_ERR(whiteout_dentry)) {
- err = PTR_ERR(whiteout_dentry);
- goto out;
- }
-
- if (!whiteout_dentry->d_inode) {
- dput(whiteout_dentry);
- whiteout_dentry = NULL;
- } else {
- lower_parent_dentry = lock_parent_wh(whiteout_dentry);
-
- /* found a.wh.foo entry, remove it then do vfs_mkdir */
- err = is_robranch_super(dentry->d_sb, bstart);
- if (!err) {
- args.unlink.parent = lower_parent_dentry->d_inode;
- args.unlink.dentry = whiteout_dentry;
- run_sioq(__unionfs_unlink, &args);
- err = args.err;
- }
- dput(whiteout_dentry);
-
- unlock_dir(lower_parent_dentry);
-
- if (err) {
- /* exit if the error returned was NOT -EROFS */
- if (!IS_COPYUP_ERR(err))
- goto out;
- bstart--;
- } else {
- whiteout_unlinked = 1;
- }
+ /* check for a whiteout in new dentry branch, and delete it */
+ err = check_unlink_whiteout(dentry, lower_dentry, bstart);
+ if (err > 0) /* whiteout found and removed successfully */
+ err = 0;
+ if (err) {
+ /* exit if the error returned was NOT -EROFS */
+ if (!IS_COPYUP_ERR(err))
+ goto out;
+ bstart--;
}
+ /* check if copyup's needed, and mkdir */
for (bindex = bstart; bindex >= 0; bindex--) {
int i;
int bend = dbend(dentry);
diff --git a/fs/unionfs/lookup.c b/fs/unionfs/lookup.c
index 1ba7103..0f62087 100644
--- a/fs/unionfs/lookup.c
+++ b/fs/unionfs/lookup.c
@@ -20,58 +20,6 @@
static int realloc_dentry_private_data(struct dentry *dentry);
-/* is the filename valid == !(whiteout for a file or opaque dir marker) */
-static int is_validname(const char *name)
-{
- if (!strncmp(name, UNIONFS_WHPFX, UNIONFS_WHLEN))
- return 0;
- if (!strncmp(name, UNIONFS_DIR_OPAQUE_NAME,
- sizeof(UNIONFS_DIR_OPAQUE_NAME) - 1))
- return 0;
- return 1;
-}
-
-/* The rest of these are utility functions for lookup. */
-static noinline_for_stack int is_opaque_dir(struct dentry *dentry, int bindex)
-{
- int err = 0;
- struct dentry *lower_dentry;
- struct dentry *wh_lower_dentry;
- struct inode *lower_inode;
- struct sioq_args args;
-
- lower_dentry = unionfs_lower_dentry_idx(dentry, bindex);
- lower_inode = lower_dentry->d_inode;
-
- BUG_ON(!S_ISDIR(lower_inode->i_mode));
-
- mutex_lock(&lower_inode->i_mutex);
-
- if (!permission(lower_inode, MAY_EXEC, NULL)) {
- wh_lower_dentry =
- lookup_one_len(UNIONFS_DIR_OPAQUE, lower_dentry,
- sizeof(UNIONFS_DIR_OPAQUE) - 1);
- } else {
- args.is_opaque.dentry = lower_dentry;
- run_sioq(__is_opaque_dir, &args);
- wh_lower_dentry = args.ret;
- }
-
- mutex_unlock(&lower_inode->i_mutex);
-
- if (IS_ERR(wh_lower_dentry)) {
- err = PTR_ERR(wh_lower_dentry);
- goto out;
- }
-
- /* This is an opaque dir iff wh_lower_dentry is positive */
- err = !!wh_lower_dentry->d_inode;
-
- dput(wh_lower_dentry);
-out:
- return err;
-}
-
/*
* Main (and complex) driver function for Unionfs's lookup
*
@@ -99,7 +47,6 @@ struct dentry *unionfs_lookup_backend(struct dentry *dentry,
struct dentry *first_lower_dentry = NULL;
struct vfsmount *first_lower_mnt = NULL;
int opaque;
- char *whname = NULL;
const char *name;
int namelen;
@@ -184,42 +131,19 @@ struct dentry *unionfs_lookup_backend(struct dentry *dentry,
if (!S_ISDIR(lower_dir_dentry->d_inode->i_mode))
continue;
- /* Reuse the whiteout name because its value doesn't change. */
- if (!whname) {
- whname = alloc_whname(name, namelen);
- if (unlikely(IS_ERR(whname))) {
- err = PTR_ERR(whname);
- goto out_free;
- }
- }
-
- /* check if whiteout exists in this branch: lookup .wh.foo */
- wh_lower_dentry = lookup_one_len(whname, lower_dir_dentry,
- namelen + UNIONFS_WHLEN);
+ /* check for whiteouts: stop lookup if found */
+ wh_lower_dentry = lookup_whiteout(name, lower_dir_dentry);
if (IS_ERR(wh_lower_dentry)) {
dput(first_lower_dentry);
unionfs_mntput(first_dentry, first_dentry_offset);
err = PTR_ERR(wh_lower_dentry);
goto out_free;
}
-
if (wh_lower_dentry->d_inode) {
- /* We found a whiteout so let's give up. */
- if (S_ISREG(wh_lower_dentry->d_inode->i_mode)) {
- dbend(dentry) = dbopaque(dentry) = bindex;
- dput(wh_lower_dentry);
- break;
- }
- err = -EIO;
- printk(KERN_ERR "unionfs: EIO: invalid whiteout "
- "entry type %d\n",
- wh_lower_dentry->d_inode->i_mode);
+ dbend(dentry) = dbopaque(dentry) = bindex;
dput(wh_lower_dentry);
- dput(first_lower_dentry);
- unionfs_mntput(first_dentry, first_dentry_offset);
- goto out_free;
+ break;
}
-
dput(wh_lower_dentry);
wh_lower_dentry = NULL;
@@ -424,7 +348,6 @@ out:
dput(first_lower_dentry);
}
}
- kfree(whname);
dput(parent_dentry);
if (err && (lookupmode == INTERPOSE_LOOKUP))
unionfs_unlock_dentry(dentry);
diff --git a/fs/unionfs/rename.c b/fs/unionfs/rename.c
index 5b3f1a3..85f96ee 100644
--- a/fs/unionfs/rename.c
+++ b/fs/unionfs/rename.c
@@ -62,17 +62,14 @@ out:
static int __unionfs_rename(struct inode *old_dir, struct dentry *old_dentry,
struct inode *new_dir, struct dentry *new_dentry,
- int bindex, struct dentry **wh_old)
+ int bindex)
{
int err = 0;
struct dentry *lower_old_dentry;
struct dentry *lower_new_dentry;
struct dentry *lower_old_dir_dentry;
struct dentry *lower_new_dir_dentry;
- struct dentry *lower_wh_dentry;
- struct dentry *lower_wh_dir_dentry;
struct dentry *trap;
- char *wh_name = NULL;
lower_new_dentry = unionfs_lower_dentry_idx(new_dentry, bindex);
lower_old_dentry = unionfs_lower_dentry_idx(old_dentry, bindex);
@@ -93,46 +90,14 @@ static int __unionfs_rename(struct inode *old_dir, struct dentry *old_dentry,
}
}
- wh_name = alloc_whname(new_dentry->d_name.name,
- new_dentry->d_name.len);
- if (unlikely(IS_ERR(wh_name))) {
- err = PTR_ERR(wh_name);
- goto out;
- }
-
- lower_wh_dentry = lookup_one_len(wh_name, lower_new_dentry->d_parent,
- new_dentry->d_name.len +
- UNIONFS_WHLEN);
- if (IS_ERR(lower_wh_dentry)) {
- err = PTR_ERR(lower_wh_dentry);
+ /* check for and remove whiteout, if any */
+ err = check_unlink_whiteout(new_dentry, lower_new_dentry, bindex);
+ if (err > 0) /* ignore if whiteout found and successfully removed */
+ err = 0;
+ if (err)
goto out;
- }
-
- if (lower_wh_dentry->d_inode) {
- /* get rid of the whiteout that is existing */
- if (lower_new_dentry->d_inode) {
- printk(KERN_ERR "unionfs: both a whiteout and a "
- "dentry exist when doing a rename!\n");
- err = -EIO;
-
- dput(lower_wh_dentry);
- goto out;
- }
-
- lower_wh_dir_dentry = lock_parent_wh(lower_wh_dentry);
- err = is_robranch_super(old_dentry->d_sb, bindex);
- if (!err)
- err = vfs_unlink(lower_wh_dir_dentry->d_inode,
- lower_wh_dentry);
-
- dput(lower_wh_dentry);
- unlock_dir(lower_wh_dir_dentry);
- if (err)
- goto out;
- } else {
- dput(lower_wh_dentry);
- }
+ /* check of old_dentry branch is writable */
err = is_robranch_super(old_dentry->d_sb, bindex);
if (err)
goto out;
@@ -142,28 +107,6 @@ static int __unionfs_rename(struct inode *old_dir, struct dentry *old_dentry,
lower_old_dir_dentry = dget_parent(lower_old_dentry);
lower_new_dir_dentry = dget_parent(lower_new_dentry);
- /*
- * ready to whiteout for old_dentry. caller will create the actual
- * whiteout, and must dput(*wh_old)
- */
- if (wh_old) {
- char *whname;
- whname = alloc_whname(old_dentry->d_name.name,
- old_dentry->d_name.len);
- err = PTR_ERR(whname);
- if (unlikely(IS_ERR(whname)))
- goto out_dput;
- *wh_old = lookup_one_len(whname, lower_old_dir_dentry,
- old_dentry->d_name.len +
- UNIONFS_WHLEN);
- kfree(whname);
- err = PTR_ERR(*wh_old);
- if (IS_ERR(*wh_old)) {
- *wh_old = NULL;
- goto out_dput;
- }
- }
-
/* see Documentation/filesystems/unionfs/issues.txt */
lockdep_off();
trap = lock_rename(lower_old_dir_dentry, lower_new_dir_dentry);
@@ -188,7 +131,6 @@ out_err_unlock:
unlock_rename(lower_old_dir_dentry, lower_new_dir_dentry);
lockdep_on();
-out_dput:
dput(lower_old_dir_dentry);
dput(lower_new_dir_dentry);
dput(lower_old_dentry);
@@ -203,8 +145,6 @@ out:
dbend(new_dentry) = bindex;
}
- kfree(wh_name);
-
return err;
}
@@ -227,7 +167,6 @@ static int do_unionfs_rename(struct inode *old_dir,
int local_err = 0;
int eio = 0;
int revert = 0;
- struct dentry *wh_old = NULL;
old_bstart = dbstart(old_dentry);
bwh_old = old_bstart;
@@ -239,7 +178,7 @@ static int do_unionfs_rename(struct inode *old_dir,
/* Rename source to destination. */
err = __unionfs_rename(old_dir, old_dentry, new_dir, new_dentry,
- old_bstart, &wh_old);
+ old_bstart);
if (err) {
if (!IS_COPYUP_ERR(err))
goto out;
@@ -282,7 +221,6 @@ static int do_unionfs_rename(struct inode *old_dir,
} else if (IS_COPYUP_ERR(err)) {
do_copyup = bindex - 1;
} else if (revert) {
- dput(wh_old);
goto revert;
}
}
@@ -301,11 +239,10 @@ static int do_unionfs_rename(struct inode *old_dir,
/* if copyup failed, try next branch to the left */
if (err)
continue;
- dput(wh_old);
bwh_old = bindex;
err = __unionfs_rename(old_dir, old_dentry,
new_dir, new_dentry,
- bindex, &wh_old);
+ bindex);
break;
}
}
@@ -323,38 +260,22 @@ static int do_unionfs_rename(struct inode *old_dir,
* (2) We did a copy_up
*/
if ((old_bstart != old_bend) || (do_copyup != -1)) {
- struct dentry *lower_parent;
- struct nameidata nd;
- if (!wh_old || wh_old->d_inode || bwh_old < 0) {
- printk(KERN_ERR "unionfs: rename error "
- "(wh_old=%p/%p bwh_old=%d)\n", wh_old,
- (wh_old ? wh_old->d_inode : NULL), bwh_old);
+ if (bwh_old < 0) {
+ printk(KERN_ERR "unionfs: rename error (bwh_old=%d)\n",
+ bwh_old);
err = -EIO;
goto out;
}
- err = init_lower_nd(&nd, LOOKUP_CREATE);
- if (unlikely(err < 0))
- goto out;
- lower_parent = lock_parent_wh(wh_old);
- local_err = vfs_create(lower_parent->d_inode, wh_old, S_IRUGO,
- &nd);
- unlock_dir(lower_parent);
- if (!local_err) {
- dbopaque(old_dentry) = bwh_old;
- } else {
- /*
- * we can't fix anything now, so we cop-out and use
- * -EIO.
- */
+ err = create_whiteout(old_dentry, bwh_old);
+ if (err) {
+ /* can't fix anything now, so we exit with -EIO */
printk(KERN_ERR "unionfs: can't create a whiteout for "
- "the source in rename!\n");
+ "%s in rename!\n", old_dentry->d_name.name);
err = -EIO;
}
- release_lower_nd(&nd, local_err);
}
out:
- dput(wh_old);
return err;
revert:
@@ -391,7 +312,7 @@ revert:
}
local_err = __unionfs_rename(new_dir, new_dentry,
- old_dir, old_dentry, old_bstart, NULL);
+ old_dir, old_dentry, old_bstart);
/* If we can't fix it, then we cop-out with -EIO. */
if (local_err) {
@@ -412,40 +333,6 @@ revert_out:
return err;
}
-static struct dentry *lookup_whiteout(struct dentry *dentry)
-{
- char *whname;
- int bindex = -1, bstart = -1, bend = -1;
- struct dentry *parent, *lower_parent, *wh_dentry;
-
- whname = alloc_whname(dentry->d_name.name, dentry->d_name.len);
- if (unlikely(IS_ERR(whname)))
- return (void *)whname;
-
- parent = dget_parent(dentry);
- unionfs_lock_dentry(parent, UNIONFS_DMUTEX_WHITEOUT);
- bstart = dbstart(parent);
- bend = dbend(parent);
- wh_dentry = ERR_PTR(-ENOENT);
- for (bindex = bstart; bindex <= bend; bindex++) {
- lower_parent = unionfs_lower_dentry_idx(parent, bindex);
- if (!lower_parent)
- continue;
- wh_dentry = lookup_one_len(whname, lower_parent,
- dentry->d_name.len + UNIONFS_WHLEN);
- if (IS_ERR(wh_dentry))
- continue;
- if (wh_dentry->d_inode)
- break;
- dput(wh_dentry);
- wh_dentry = ERR_PTR(-ENOENT);
- }
- unionfs_unlock_dentry(parent);
- dput(parent);
- kfree(whname);
- return wh_dentry;
-}
-
/*
* We can't copyup a directory, because it may involve huge numbers of
* children, etc. Doing that in the kernel would be bad, so instead we
@@ -511,7 +398,7 @@ int unionfs_rename(struct inode *old_dir, struct dentry *old_dentry,
* if new_dentry is already lower because of whiteout,
* simply override it even if the whited-out dir is not empty.
*/
- wh_dentry = lookup_whiteout(new_dentry);
+ wh_dentry = find_first_whiteout(new_dentry);
if (!IS_ERR(wh_dentry)) {
dput(wh_dentry);
} else if (new_dentry->d_inode) {
diff --git a/fs/unionfs/sioq.c b/fs/unionfs/sioq.c
index 2a8c88e..0ea8436 100644
--- a/fs/unionfs/sioq.c
+++ b/fs/unionfs/sioq.c
@@ -99,21 +99,3 @@ void __unionfs_unlink(struct work_struct *work)
args->err = vfs_unlink(u->parent, u->dentry);
complete(&args->comp);
}
-
-void __delete_whiteouts(struct work_struct *work)
-{
- struct sioq_args *args = container_of(work, struct sioq_args, work);
- struct deletewh_args *d = &args->deletewh;
-
- args->err = do_delete_whiteouts(d->dentry, d->bindex, d->namelist);
- complete(&args->comp);
-}
-
-void __is_opaque_dir(struct work_struct *work)
-{
- struct sioq_args *args = container_of(work, struct sioq_args, work);
-
- args->ret = lookup_one_len(UNIONFS_DIR_OPAQUE, args->is_opaque.dentry,
- sizeof(UNIONFS_DIR_OPAQUE) - 1);
- complete(&args->comp);
-}
diff --git a/fs/unionfs/subr.c b/fs/unionfs/subr.c
index 1f1db3d..dda2745 100644
--- a/fs/unionfs/subr.c
+++ b/fs/unionfs/subr.c
@@ -19,152 +19,6 @@
#include "union.h"
/*
- * Pass an unionfs dentry and an index. It will try to create a whiteout
- * for the filename in dentry, and will try in branch 'index'. On error,
- * it will proceed to a branch to the left.
- */
-int create_whiteout(struct dentry *dentry, int start)
-{
- int bstart, bend, bindex;
- struct dentry *lower_dir_dentry;
- struct dentry *lower_dentry;
- struct dentry *lower_wh_dentry;
- struct nameidata nd;
- char *name = NULL;
- int err = -EINVAL;
-
- verify_locked(dentry);
-
- bstart = dbstart(dentry);
- bend = dbend(dentry);
-
- /* create dentry's whiteout equivalent */
- name = alloc_whname(dentry->d_name.name, dentry->d_name.len);
- if (unlikely(IS_ERR(name))) {
- err = PTR_ERR(name);
- goto out;
- }
-
- for (bindex = start; bindex >= 0; bindex--) {
- lower_dentry = unionfs_lower_dentry_idx(dentry, bindex);
-
- if (!lower_dentry) {
- /*
- * if lower dentry is not present, create the
- * entire lower dentry directory structure and go
- * ahead. Since we want to just create whiteout, we
- * only want the parent dentry, and hence get rid of
- * this dentry.
- */
- lower_dentry = create_parents(dentry->d_inode,
- dentry,
- dentry->d_name.name,
- bindex);
- if (!lower_dentry || IS_ERR(lower_dentry)) {
- int ret = PTR_ERR(lower_dentry);
- if (!IS_COPYUP_ERR(ret))
- printk(KERN_ERR
- "unionfs: create_parents for "
- "whiteout failed: bindex=%d "
- "err=%d\n", bindex, ret);
- continue;
- }
- }
-
- lower_wh_dentry =
- lookup_one_len(name, lower_dentry->d_parent,
- dentry->d_name.len + UNIONFS_WHLEN);
- if (IS_ERR(lower_wh_dentry))
- continue;
-
- /*
- * The whiteout already exists. This used to be impossible,
- * but now is possible because of opaqueness.
- */
- if (lower_wh_dentry->d_inode) {
- dput(lower_wh_dentry);
- err = 0;
- goto out;
- }
-
- err = init_lower_nd(&nd, LOOKUP_CREATE);
- if (unlikely(err < 0))
- goto out;
- lower_dir_dentry = lock_parent_wh(lower_wh_dentry);
- err = is_robranch_super(dentry->d_sb, bindex);
- if (!err)
- err = vfs_create(lower_dir_dentry->d_inode,
- lower_wh_dentry,
- ~current->fs->umask & S_IRWXUGO,
- &nd);
- unlock_dir(lower_dir_dentry);
- dput(lower_wh_dentry);
- release_lower_nd(&nd, err);
-
- if (!err || !IS_COPYUP_ERR(err))
- break;
- }
-
- /* set dbopaque so that lookup will not proceed after this branch */
- if (!err)
- dbopaque(dentry) = bindex;
-
-out:
- kfree(name);
- return err;
-}
-
-int make_dir_opaque(struct dentry *dentry, int bindex)
-{
- int err = 0;
- struct dentry *lower_dentry, *diropq;
- struct inode *lower_dir;
- struct nameidata nd;
- kernel_cap_t orig_cap;
-
- /*
- * Opaque directory whiteout markers are special files (like regular
- * whiteouts), and should appear to the users as if they don't
- * exist. They should be created/deleted regardless of directory
- * search/create permissions, but only for the duration of this
- * creation of the .wh.__dir_opaque: file. Note, this does not
- * circumvent normal ->permission).
- */
- orig_cap = current->cap_effective;
- cap_raise(current->cap_effective, CAP_DAC_READ_SEARCH);
- cap_raise(current->cap_effective, CAP_DAC_OVERRIDE);
-
- lower_dentry = unionfs_lower_dentry_idx(dentry, bindex);
- lower_dir = lower_dentry->d_inode;
- BUG_ON(!S_ISDIR(dentry->d_inode->i_mode) ||
- !S_ISDIR(lower_dir->i_mode));
-
- mutex_lock(&lower_dir->i_mutex);
- diropq = lookup_one_len(UNIONFS_DIR_OPAQUE, lower_dentry,
- sizeof(UNIONFS_DIR_OPAQUE) - 1);
- if (IS_ERR(diropq)) {
- err = PTR_ERR(diropq);
- goto out;
- }
-
- err = init_lower_nd(&nd, LOOKUP_CREATE);
- if (unlikely(err < 0))
- goto out;
- if (!diropq->d_inode)
- err = vfs_create(lower_dir, diropq, S_IRUGO, &nd);
- if (!err)
- dbopaque(dentry) = bindex;
- release_lower_nd(&nd, err);
-
- dput(diropq);
-
-out:
- mutex_unlock(&lower_dir->i_mutex);
- current->cap_effective = orig_cap;
- return err;
-}
-
-/*
* returns the right n_link value based on the inode type
*/
int unionfs_get_nlinks(const struct inode *inode)
@@ -184,21 +38,6 @@ int unionfs_get_nlinks(const struct inode *inode)
return 1;
}
-/* construct whiteout filename */
-char *alloc_whname(const char *name, int len)
-{
- char *buf;
-
- buf = kmalloc(len + UNIONFS_WHLEN + 1, GFP_KERNEL);
- if (unlikely(!buf))
- return ERR_PTR(-ENOMEM);
-
- strcpy(buf, UNIONFS_WHPFX);
- strlcat(buf, name, len + UNIONFS_WHLEN + 1);
-
- return buf;
-}
-
/* copy a/m/ctime from the lower branch with the newest times */
void unionfs_copy_attr_times(struct inode *upper)
{
diff --git a/fs/unionfs/super.c b/fs/unionfs/super.c
index 9715529..3b6b65a 100644
--- a/fs/unionfs/super.c
+++ b/fs/unionfs/super.c
@@ -173,7 +173,7 @@ static int unionfs_statfs(struct dentry *dentry, struct kstatfs *buf)
*
* XXX: this restriction goes away with ODF.
*/
- buf->f_namelen -= UNIONFS_WHLEN;
+ unionfs_set_max_namelen(&buf->f_namelen);
/*
* reset two fields to avoid confusing user-land.
diff --git a/fs/unionfs/union.h b/fs/unionfs/union.h
index f5cf09b..9271b3b 100644
--- a/fs/unionfs/union.h
+++ b/fs/unionfs/union.h
@@ -306,18 +306,10 @@ extern void release_lower_nd(struct nameidata *nd, int err);
/* replicates the directory structure up to given dentry in given branch */
extern struct dentry *create_parents(struct inode *dir, struct dentry *dentry,
const char *name, int bindex);
-extern int make_dir_opaque(struct dentry *dir, int bindex);
/* partial lookup */
extern int unionfs_partial_lookup(struct dentry *dentry);
-/*
- * Pass an unionfs dentry and an index and it will try to create a whiteout
- * in branch 'index'.
- *
- * On error, it will proceed to a branch to the left
- */
-extern int create_whiteout(struct dentry *dentry, int start);
/* copies a file from dbstart to newbindex branch */
extern int copyup_file(struct inode *dir, struct file *file, int bstart,
int newbindex, loff_t size);
@@ -332,18 +324,25 @@ extern int copyup_dentry(struct inode *dir, struct dentry *dentry,
extern void unionfs_postcopyup_setmnt(struct dentry *dentry);
extern void unionfs_postcopyup_release(struct dentry *dentry);
-extern int remove_whiteouts(struct dentry *dentry,
- struct dentry *lower_dentry, int bindex);
-
-extern int do_delete_whiteouts(struct dentry *dentry, int bindex,
- struct unionfs_dir_state *namelist);
-
/* Is this directory empty: 0 if it is empty, -ENOTEMPTY if not. */
extern int check_empty(struct dentry *dentry,
struct unionfs_dir_state **namelist);
-/* Delete whiteouts from this directory in branch bindex. */
+/* whiteout and opaque directory helpers */
+extern char *alloc_whname(const char *name, int len);
+extern bool is_whiteout_name(char **namep, int *namelenp);
+extern bool is_validname(const char *name);
+extern struct dentry *lookup_whiteout(const char *name,
+ struct dentry *lower_parent);
+extern struct dentry *find_first_whiteout(struct dentry *dentry);
+extern int unlink_whiteout(struct dentry *wh_dentry);
+extern int check_unlink_whiteout(struct dentry *dentry,
+ struct dentry *lower_dentry, int bindex);
+extern int create_whiteout(struct dentry *dentry, int start);
extern int delete_whiteouts(struct dentry *dentry, int bindex,
struct unionfs_dir_state *namelist);
+extern int is_opaque_dir(struct dentry *dentry, int bindex);
+extern int make_dir_opaque(struct dentry *dir, int bindex);
+extern void unionfs_set_max_namelen(long *namelen);
extern void unionfs_reinterpose(struct dentry *this_dentry);
extern struct super_block *unionfs_duplicate_super(struct super_block *sb);
@@ -476,20 +475,9 @@ static inline int is_robranch(const struct dentry *dentry)
return is_robranch_idx(dentry, index);
}
-/* What do we use for whiteouts. */
-#define UNIONFS_WHPFX ".wh."
-#define UNIONFS_WHLEN 4
-/*
- * If a directory contains this file, then it is opaque. We start with the
- * .wh. flag so that it is blocked by lookup.
- */
-#define UNIONFS_DIR_OPAQUE_NAME "__dir_opaque"
-#define UNIONFS_DIR_OPAQUE UNIONFS_WHPFX UNIONFS_DIR_OPAQUE_NAME
-
/*
* EXTERNALS:
*/
-extern char *alloc_whname(const char *name, int len);
extern int check_branch(struct nameidata *nd);
extern int parse_branch_mode(const char *name, int *perms);
diff --git a/fs/unionfs/whiteout.c b/fs/unionfs/whiteout.c
new file mode 100644
index 0000000..b1768ed
--- /dev/null
+++ b/fs/unionfs/whiteout.c
@@ -0,0 +1,577 @@
+/*
+ * Copyright (c) 2003-2007 Erez Zadok
+ * Copyright (c) 2003-2006 Charles P. Wright
+ * Copyright (c) 2005-2007 Josef 'Jeff' Sipek
+ * Copyright (c) 2005-2006 Junjiro Okajima
+ * Copyright (c) 2005 Arun M. Krishnakumar
+ * Copyright (c) 2004-2006 David P. Quigley
+ * Copyright (c) 2003-2004 Mohammad Nayyer Zubair
+ * Copyright (c) 2003 Puja Gupta
+ * Copyright (c) 2003 Harikesavan Krishnan
+ * Copyright (c) 2003-2007 Stony Brook University
+ * Copyright (c) 2003-2007 The Research Foundation of SUNY
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include "union.h"
+
+/*
+ * whiteout and opaque directory helpers
+ */
+
+/* What do we use for whiteouts. */
+#define UNIONFS_WHPFX ".wh."
+#define UNIONFS_WHLEN 4
+/*
+ * If a directory contains this file, then it is opaque. We start with the
+ * .wh. flag so that it is blocked by lookup.
+ */
+#define UNIONFS_DIR_OPAQUE_NAME "__dir_opaque"
+#define UNIONFS_DIR_OPAQUE UNIONFS_WHPFX UNIONFS_DIR_OPAQUE_NAME
+
+/* construct whiteout filename */
+char *alloc_whname(const char *name, int len)
+{
+ char *buf;
+
+ buf = kmalloc(len + UNIONFS_WHLEN + 1, GFP_KERNEL);
+ if (unlikely(!buf))
+ return ERR_PTR(-ENOMEM);
+
+ strcpy(buf, UNIONFS_WHPFX);
+ strlcat(buf, name, len + UNIONFS_WHLEN + 1);
+
+ return buf;
+}
+
+/*
+ * XXX: this can be inline or CPP macro, but is here to keep all whiteout
+ * code in one place.
+ */
+void unionfs_set_max_namelen(long *namelen)
+{
+ *namelen -= UNIONFS_WHLEN;
+}
+
+/* check if @namep is a whiteout, update @namep and @namelenp accordingly */
+bool is_whiteout_name(char **namep, int *namelenp)
+{
+ if (*namelenp > UNIONFS_WHLEN &&
+ !strncmp(*namep, UNIONFS_WHPFX, UNIONFS_WHLEN)) {
+ *namep += UNIONFS_WHLEN;
+ *namelenp -= UNIONFS_WHLEN;
+ return true;
+ }
+ return false;
+}
+
+/* is the filename valid == !(whiteout for a file or opaque dir marker) */
+bool is_validname(const char *name)
+{
+ if (!strncmp(name, UNIONFS_WHPFX, UNIONFS_WHLEN))
+ return false;
+ if (!strncmp(name, UNIONFS_DIR_OPAQUE_NAME,
+ sizeof(UNIONFS_DIR_OPAQUE_NAME) - 1))
+ return false;
+ return true;
+}
+
+/*
+ * Look for a whiteout @name in @lower_parent directory. If error, return
+ * ERR_PTR. Caller must dput() the returned dentry if not an error.
+ *
+ * XXX: some callers can reuse the whname allocated buffer to avoid repeated
+ * free then re-malloc calls. Need to provide a different API for those
+ * callers.
+ */
+struct dentry *lookup_whiteout(const char *name, struct dentry *lower_parent)
+{
+ char *whname = NULL;
+ int err = 0, namelen;
+ struct dentry *wh_dentry = NULL;
+
+ namelen = strlen(name);
+ whname = alloc_whname(name, namelen);
+ if (unlikely(IS_ERR(whname))) {
+ err = PTR_ERR(whname);
+ goto out;
+ }
+
+ /* check if whiteout exists in this branch: lookup .wh.foo */
+ wh_dentry = lookup_one_len(whname, lower_parent, strlen(whname));
+ if (IS_ERR(wh_dentry)) {
+ err = PTR_ERR(wh_dentry);
+ goto out;
+ }
+
+ /* check if negative dentry (ENOENT) */
+ if (!wh_dentry->d_inode)
+ goto out;
+
+ /* whiteout found: check if valid type */
+ if (!S_ISREG(wh_dentry->d_inode->i_mode)) {
+ printk(KERN_ERR "unionfs: invalid whiteout %s entry type %d\n",
+ whname, wh_dentry->d_inode->i_mode);
+ dput(wh_dentry);
+ err = -EIO;
+ goto out;
+ }
+
+out:
+ kfree(whname);
+ if (err)
+ wh_dentry = ERR_PTR(err);
+ return wh_dentry;
+}
+
+/* find and return first whiteout in parent directory, else ENOENT */
+struct dentry *find_first_whiteout(struct dentry *dentry)
+{
+ int bindex, bstart, bend;
+ 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);
+
+ for (bindex = bstart; bindex <= bend; bindex++) {
+ lower_parent = unionfs_lower_dentry_idx(parent, bindex);
+ if (!lower_parent)
+ continue;
+ wh_dentry = lookup_whiteout(dentry->d_name.name, lower_parent);
+ if (IS_ERR(wh_dentry))
+ continue;
+ if (wh_dentry->d_inode)
+ break;
+ dput(wh_dentry);
+ wh_dentry = ERR_PTR(-ENOENT);
+ }
+ unionfs_unlock_dentry(parent);
+ dput(parent);
+
+ return wh_dentry;
+}
+
+/*
+ * Unlink a whiteout dentry. Returns 0 or -errno. Caller must hold and
+ * release dentry reference.
+ */
+int unlink_whiteout(struct dentry *wh_dentry)
+{
+ int err;
+ struct dentry *lower_dir_dentry;
+
+ /* dget and lock parent dentry */
+ lower_dir_dentry = lock_parent_wh(wh_dentry);
+
+ /* see Documentation/filesystems/unionfs/issues.txt */
+ lockdep_off();
+ err = vfs_unlink(lower_dir_dentry->d_inode, wh_dentry);
+ lockdep_on();
+ unlock_dir(lower_dir_dentry);
+
+ /*
+ * Whiteouts are special files and should be deleted no matter what
+ * (as if they never existed), in order to allow this create
+ * operation to succeed. This is especially important in sticky
+ * directories: a whiteout may have been created by one user, but
+ * the newly created file may be created by another user.
+ * Therefore, in order to maintain Unix semantics, if the vfs_unlink
+ * above failed, then we have to try to directly unlink the
+ * whiteout. Note: in the ODF version of unionfs, whiteout are
+ * handled much more cleanly.
+ */
+ if (err == -EPERM) {
+ struct inode *inode = lower_dir_dentry->d_inode;
+ err = inode->i_op->unlink(inode, wh_dentry);
+ }
+ if (err)
+ printk(KERN_ERR "unionfs: could not unlink whiteout %s, "
+ "err = %d\n", wh_dentry->d_name.name, err);
+
+ return err;
+
+}
+
+/*
+ * Helper function when creating new objects (create, symlink, mknod, etc.).
+ * Checks to see if there's a whiteout in @lower_dentry's parent directory,
+ * whose name is taken from @dentry. Then tries to remove that whiteout, if
+ * found. If <dentry,bindex> is a branch marked readonly, return -EROFS.
+ * If it finds both a regular file and a whiteout, return -EIO (this should
+ * never happen).
+ *
+ * Return 0 if no whiteout was found. Return 1 if one was found and
+ * successfully removed. Therefore a value >= 0 tells the caller that
+ * @lower_dentry belongs to a good branch to create the new object in).
+ * Return -ERRNO if an error occurred during whiteout lookup or in trying to
+ * unlink the whiteout.
+ */
+int check_unlink_whiteout(struct dentry *dentry, struct dentry *lower_dentry,
+ int bindex)
+{
+ int err;
+ struct dentry *wh_dentry = NULL;
+ struct dentry *lower_dir_dentry = NULL;
+
+ /* look for whiteout dentry first */
+ lower_dir_dentry = dget_parent(lower_dentry);
+ wh_dentry = lookup_whiteout(dentry->d_name.name, lower_dir_dentry);
+ dput(lower_dir_dentry);
+ if (IS_ERR(wh_dentry)) {
+ err = PTR_ERR(wh_dentry);
+ goto out;
+ }
+
+ if (!wh_dentry->d_inode) { /* no whiteout exists*/
+ err = 0;
+ goto out_dput;
+ }
+
+ /* check if regular file and whiteout were both found */
+ if (unlikely(lower_dentry->d_inode)) {
+ 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);
+ goto out_dput;
+ }
+
+ /* check if branch is writeable */
+ err = is_robranch_super(dentry->d_sb, bindex);
+ if (err)
+ goto out_dput;
+
+ /* .wh.foo has been found, so let's unlink it */
+ err = unlink_whiteout(wh_dentry);
+ if (!err)
+ err = 1; /* a whiteout was found and successfully removed */
+out_dput:
+ dput(wh_dentry);
+out:
+ return err;
+}
+
+/*
+ * Pass an unionfs dentry and an index. It will try to create a whiteout
+ * for the filename in dentry, and will try in branch 'index'. On error,
+ * it will proceed to a branch to the left.
+ */
+int create_whiteout(struct dentry *dentry, int start)
+{
+ int bstart, bend, bindex;
+ struct dentry *lower_dir_dentry;
+ struct dentry *lower_dentry;
+ struct dentry *lower_wh_dentry;
+ struct nameidata nd;
+ char *name = NULL;
+ int err = -EINVAL;
+
+ verify_locked(dentry);
+
+ bstart = dbstart(dentry);
+ bend = dbend(dentry);
+
+ /* create dentry's whiteout equivalent */
+ name = alloc_whname(dentry->d_name.name, dentry->d_name.len);
+ if (unlikely(IS_ERR(name))) {
+ err = PTR_ERR(name);
+ goto out;
+ }
+
+ for (bindex = start; bindex >= 0; bindex--) {
+ lower_dentry = unionfs_lower_dentry_idx(dentry, bindex);
+
+ if (!lower_dentry) {
+ /*
+ * if lower dentry is not present, create the
+ * entire lower dentry directory structure and go
+ * ahead. Since we want to just create whiteout, we
+ * only want the parent dentry, and hence get rid of
+ * this dentry.
+ */
+ lower_dentry = create_parents(dentry->d_inode,
+ dentry,
+ dentry->d_name.name,
+ bindex);
+ if (!lower_dentry || IS_ERR(lower_dentry)) {
+ int ret = PTR_ERR(lower_dentry);
+ if (!IS_COPYUP_ERR(ret))
+ printk(KERN_ERR
+ "unionfs: create_parents for "
+ "whiteout failed: bindex=%d "
+ "err=%d\n", bindex, ret);
+ continue;
+ }
+ }
+
+ lower_wh_dentry =
+ lookup_one_len(name, lower_dentry->d_parent,
+ dentry->d_name.len + UNIONFS_WHLEN);
+ if (IS_ERR(lower_wh_dentry))
+ continue;
+
+ /*
+ * The whiteout already exists. This used to be impossible,
+ * but now is possible because of opaqueness.
+ */
+ if (lower_wh_dentry->d_inode) {
+ dput(lower_wh_dentry);
+ err = 0;
+ goto out;
+ }
+
+ err = init_lower_nd(&nd, LOOKUP_CREATE);
+ if (unlikely(err < 0))
+ goto out;
+ lower_dir_dentry = lock_parent_wh(lower_wh_dentry);
+ err = is_robranch_super(dentry->d_sb, bindex);
+ if (!err)
+ err = vfs_create(lower_dir_dentry->d_inode,
+ lower_wh_dentry,
+ ~current->fs->umask & S_IRUGO,
+ &nd);
+ unlock_dir(lower_dir_dentry);
+ dput(lower_wh_dentry);
+ release_lower_nd(&nd, err);
+
+ if (!err || !IS_COPYUP_ERR(err))
+ break;
+ }
+
+ /* set dbopaque so that lookup will not proceed after this branch */
+ if (!err)
+ dbopaque(dentry) = bindex;
+
+out:
+ kfree(name);
+ return err;
+}
+
+/*
+ * Delete all of the whiteouts in a given directory for rmdir.
+ *
+ * lower directory inode should be locked
+ */
+static int do_delete_whiteouts(struct dentry *dentry, int bindex,
+ struct unionfs_dir_state *namelist)
+{
+ int err = 0;
+ struct dentry *lower_dir_dentry = NULL;
+ struct dentry *lower_dentry;
+ char *name = NULL, *p;
+ struct inode *lower_dir;
+ int i;
+ struct list_head *pos;
+ struct filldir_node *cursor;
+
+ /* Find out lower parent dentry */
+ lower_dir_dentry = unionfs_lower_dentry_idx(dentry, bindex);
+ BUG_ON(!S_ISDIR(lower_dir_dentry->d_inode->i_mode));
+ lower_dir = lower_dir_dentry->d_inode;
+ BUG_ON(!S_ISDIR(lower_dir->i_mode));
+
+ err = -ENOMEM;
+ name = __getname();
+ if (unlikely(!name))
+ goto out;
+ strcpy(name, UNIONFS_WHPFX);
+ p = name + UNIONFS_WHLEN;
+
+ err = 0;
+ for (i = 0; !err && i < namelist->size; i++) {
+ list_for_each(pos, &namelist->list[i]) {
+ cursor =
+ list_entry(pos, struct filldir_node,
+ file_list);
+ /* Only operate on whiteouts in this branch. */
+ if (cursor->bindex != bindex)
+ continue;
+ if (!cursor->whiteout)
+ continue;
+
+ strlcpy(p, cursor->name, PATH_MAX - UNIONFS_WHLEN);
+ lower_dentry =
+ lookup_one_len(name, lower_dir_dentry,
+ cursor->namelen +
+ UNIONFS_WHLEN);
+ if (IS_ERR(lower_dentry)) {
+ err = PTR_ERR(lower_dentry);
+ break;
+ }
+ if (lower_dentry->d_inode)
+ err = vfs_unlink(lower_dir, lower_dentry);
+ dput(lower_dentry);
+ if (err)
+ break;
+ }
+ }
+
+ __putname(name);
+
+ /* After all of the removals, we should copy the attributes once. */
+ fsstack_copy_attr_times(dentry->d_inode, lower_dir_dentry->d_inode);
+
+out:
+ return err;
+}
+
+
+void __delete_whiteouts(struct work_struct *work)
+{
+ struct sioq_args *args = container_of(work, struct sioq_args, work);
+ struct deletewh_args *d = &args->deletewh;
+
+ args->err = do_delete_whiteouts(d->dentry, d->bindex, d->namelist);
+ complete(&args->comp);
+}
+
+/* delete whiteouts in a dir (for rmdir operation) using sioq if necessary */
+int delete_whiteouts(struct dentry *dentry, int bindex,
+ struct unionfs_dir_state *namelist)
+{
+ int err;
+ struct super_block *sb;
+ struct dentry *lower_dir_dentry;
+ struct inode *lower_dir;
+ struct sioq_args args;
+
+ sb = dentry->d_sb;
+
+ BUG_ON(!S_ISDIR(dentry->d_inode->i_mode));
+ BUG_ON(bindex < dbstart(dentry));
+ BUG_ON(bindex > dbend(dentry));
+ err = is_robranch_super(sb, bindex);
+ if (err)
+ goto out;
+
+ lower_dir_dentry = unionfs_lower_dentry_idx(dentry, bindex);
+ BUG_ON(!S_ISDIR(lower_dir_dentry->d_inode->i_mode));
+ lower_dir = lower_dir_dentry->d_inode;
+ BUG_ON(!S_ISDIR(lower_dir->i_mode));
+
+ if (!permission(lower_dir, MAY_WRITE | MAY_EXEC, NULL)) {
+ err = do_delete_whiteouts(dentry, bindex, namelist);
+ } else {
+ args.deletewh.namelist = namelist;
+ args.deletewh.dentry = dentry;
+ args.deletewh.bindex = bindex;
+ run_sioq(__delete_whiteouts, &args);
+ err = args.err;
+ }
+
+out:
+ return err;
+}
+
+/****************************************************************************
+ * Opaque directory helpers *
+ ****************************************************************************/
+
+/*
+ * is_opaque_dir: returns 0 if it is NOT an opaque dir, 1 if it is, and
+ * -errno if an error occurred trying to figure this out.
+ */
+int is_opaque_dir(struct dentry *dentry, int bindex)
+{
+ int err = 0;
+ struct dentry *lower_dentry;
+ struct dentry *wh_lower_dentry;
+ struct inode *lower_inode;
+ struct sioq_args args;
+
+ lower_dentry = unionfs_lower_dentry_idx(dentry, bindex);
+ lower_inode = lower_dentry->d_inode;
+
+ BUG_ON(!S_ISDIR(lower_inode->i_mode));
+
+ mutex_lock(&lower_inode->i_mutex);
+
+ if (!permission(lower_inode, MAY_EXEC, NULL)) {
+ wh_lower_dentry =
+ lookup_one_len(UNIONFS_DIR_OPAQUE, lower_dentry,
+ sizeof(UNIONFS_DIR_OPAQUE) - 1);
+ } else {
+ args.is_opaque.dentry = lower_dentry;
+ run_sioq(__is_opaque_dir, &args);
+ wh_lower_dentry = args.ret;
+ }
+
+ mutex_unlock(&lower_inode->i_mutex);
+
+ if (IS_ERR(wh_lower_dentry)) {
+ err = PTR_ERR(wh_lower_dentry);
+ goto out;
+ }
+
+ /* This is an opaque dir iff wh_lower_dentry is positive */
+ err = !!wh_lower_dentry->d_inode;
+
+ dput(wh_lower_dentry);
+out:
+ return err;
+}
+
+void __is_opaque_dir(struct work_struct *work)
+{
+ struct sioq_args *args = container_of(work, struct sioq_args, work);
+
+ args->ret = lookup_one_len(UNIONFS_DIR_OPAQUE, args->is_opaque.dentry,
+ sizeof(UNIONFS_DIR_OPAQUE) - 1);
+ complete(&args->comp);
+}
+
+int make_dir_opaque(struct dentry *dentry, int bindex)
+{
+ int err = 0;
+ struct dentry *lower_dentry, *diropq;
+ struct inode *lower_dir;
+ struct nameidata nd;
+ kernel_cap_t orig_cap;
+
+ /*
+ * Opaque directory whiteout markers are special files (like regular
+ * whiteouts), and should appear to the users as if they don't
+ * exist. They should be created/deleted regardless of directory
+ * search/create permissions, but only for the duration of this
+ * creation of the .wh.__dir_opaque: file. Note, this does not
+ * circumvent normal ->permission).
+ */
+ orig_cap = current->cap_effective;
+ cap_raise(current->cap_effective, CAP_DAC_READ_SEARCH);
+ cap_raise(current->cap_effective, CAP_DAC_OVERRIDE);
+
+ lower_dentry = unionfs_lower_dentry_idx(dentry, bindex);
+ lower_dir = lower_dentry->d_inode;
+ BUG_ON(!S_ISDIR(dentry->d_inode->i_mode) ||
+ !S_ISDIR(lower_dir->i_mode));
+
+ mutex_lock(&lower_dir->i_mutex);
+ diropq = lookup_one_len(UNIONFS_DIR_OPAQUE, lower_dentry,
+ sizeof(UNIONFS_DIR_OPAQUE) - 1);
+ if (IS_ERR(diropq)) {
+ err = PTR_ERR(diropq);
+ goto out;
+ }
+
+ err = init_lower_nd(&nd, LOOKUP_CREATE);
+ if (unlikely(err < 0))
+ goto out;
+ if (!diropq->d_inode)
+ err = vfs_create(lower_dir, diropq, S_IRUGO, &nd);
+ if (!err)
+ dbopaque(dentry) = bindex;
+ release_lower_nd(&nd, err);
+
+ dput(diropq);
+
+out:
+ mutex_unlock(&lower_dir->i_mutex);
+ current->cap_effective = orig_cap;
+ return err;
+}
--
1.5.2.2
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@...r.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/
Powered by blists - more mailing lists