diff --git a/Documentation/filesystems/00-INDEX b/Documentation/filesystems/00-INDEX index 5717858..2ef035e 100644 --- a/Documentation/filesystems/00-INDEX +++ b/Documentation/filesystems/00-INDEX @@ -84,6 +84,8 @@ udf.txt - info and mount options for the UDF filesystem. ufs.txt - info on the ufs filesystem. +unionfs/ + - info on the unionfs filesystem vfat.txt - info on using the VFAT filesystem used in Windows NT and Windows 95 vfs.txt diff --git a/Documentation/filesystems/unionfs/00-INDEX b/Documentation/filesystems/unionfs/00-INDEX new file mode 100644 index 0000000..96fdf67 --- /dev/null +++ b/Documentation/filesystems/unionfs/00-INDEX @@ -0,0 +1,10 @@ +00-INDEX + - this file. +concepts.txt + - A brief introduction of concepts. +issues.txt + - A summary of known issues with unionfs. +rename.txt + - Information regarding rename operations. +usage.txt + - Usage information and examples. diff --git a/Documentation/filesystems/unionfs/concepts.txt b/Documentation/filesystems/unionfs/concepts.txt new file mode 100644 index 0000000..83d45b9 --- /dev/null +++ b/Documentation/filesystems/unionfs/concepts.txt @@ -0,0 +1,75 @@ +Unionfs 2.0 CONCEPTS: +===================== + +This file describes the concepts needed by a namespace unification file +system. + +Branch Priority: +================ + +Each branch is assigned a unique priority - starting from 0 (highest +priority). No two branches can have the same priority. + + +Branch Mode: +============ + +Each branch is assigned a mode - read-write or read-only. This allows +directories on media mounted read-write to be used in a read-only manner. + + +Whiteouts: +========== + +A whiteout removes a file name from the namespace. Whiteouts are needed when +one attempts to remove a file on a read-only branch. + +Suppose we have a two-branch union, where branch 0 is read-write and branch +1 is read-only. And a file 'foo' on branch 1: + +./b0/ +./b1/ +./b1/foo + +The unified view would simply be: + +./union/ +./union/foo + +Since 'foo' is stored on a read-only branch, it cannot be removed. A +whiteout is used to remove the name 'foo' from the unified namespace. Again, +since branch 1 is read-only, the whiteout cannot be created there. So, we +try on a higher priority (lower numerically) branch and create the whiteout +there. + +./b0/ +./b0/.wh.foo +./b1/ +./b1/foo + +Later, when Unionfs traverses branches (due to lookup or readdir), it +eliminate 'foo' from the namespace (as well as the whiteout itself.) + + +Duplicate Elimination: +====================== + +It is possible for files on different branches to have the same name. +Unionfs then has to select which instance of the file to show to the user. +Given the fact that each branch has a priority associated with it, the +simplest solution is to take the instance from the highest priority +(numerically lowest value) and "hide" the others. + + +Copyup: +======= + +When a change is made to the contents of a file's data or meta-data, they +have to be stored somewhere. The best way is to create a copy of the +original file on a branch that is writable, and then redirect the write +though to this copy. The copy must be made on a higher priority branch so +that lookup and readdir return this newer "version" of the file rather than +the original (see duplicate elimination). + + +For more information, see . diff --git a/Documentation/filesystems/unionfs/issues.txt b/Documentation/filesystems/unionfs/issues.txt new file mode 100644 index 0000000..a434fee --- /dev/null +++ b/Documentation/filesystems/unionfs/issues.txt @@ -0,0 +1,39 @@ +KNOWN Unionfs 2.0 ISSUES: +========================= + +1. The NFS server returns -EACCES for read-only exports, instead of -EROFS. + This means we can't reliably detect a read-only NFS export. + +2. Modifying a Unionfs branch directly, while the union is mounted, is + currently unsupported. We have tested Unionfs under such conditions, and + fixed any bugs we found (Unionfs comes with an extensive regression test + suite). However, it may still be possible that changes made to lower + branches directly could cause cache incoherency which, in the worst case, + may case an oops. We are currently addressing this problem for Unionfs + and also generically for all stackable file systems, by handing mmap and + introducing small VFS/MM changes that would allow a file system to handle + cache coherency correctly. + + Unionfs 2.0 has a temporary workaround for this. You can force Unionfs + to increase the superblock generation number, and hence purge all cached + Unionfs objects, which would then be re-gotten from the lower branches. + This should ensure cache consistency. To increase the generation number, + executed the command: + + mount -t unionfs -o remount,incgen none MOUNTPOINT + + Note that the older way of incrementing the generation number using an + ioctl, is no longer supported in Unionfs 2.0. Ioctls in general are not + encouraged. Plus, an ioctl is per-file concept, whereas the generation + number is a per-file-system concept. Worse, such an ioctl requires an + open file, which then has to be invalidated by the very nature of the + generation number increase (read: the old generation increase ioctl was + pretty racy). + +3. Unionfs should not use lookup_one_len() on the underlying f/s as it + confuses NFS. Currently, unionfs_lookup() passes lookup intents to the + lower file-system, this eliminates part of the problem. The remaining + calls to lookup_one_len may need to be changed to pass an intent. + + +For more information, see . diff --git a/Documentation/filesystems/unionfs/rename.txt b/Documentation/filesystems/unionfs/rename.txt new file mode 100644 index 0000000..e20bb82 --- /dev/null +++ b/Documentation/filesystems/unionfs/rename.txt @@ -0,0 +1,31 @@ +Rename is a complex beast. The following table shows which rename(2) operations +should succeed and which should fail. + +o: success +E: error (either unionfs or vfs) +X: EXDEV + +none = file does not exist +file = file is a file +dir = file is a empty directory +child= file is a non-empty directory +wh = file is a directory containing only whiteouts; this makes it logically + empty + + none file dir child wh +file o o E E E +dir o E o E o +child X E X E X +wh o E o E o + + +Renaming directories: +===================== + +Whenever a empty (either physically or logically) directory is being renamed, +the following sequence of events should take place: + +1) Remove whiteouts from both source and destination directory +2) Rename source to destination +3) Make destination opaque to prevent anything under it from showing up + diff --git a/Documentation/filesystems/unionfs/usage.txt b/Documentation/filesystems/unionfs/usage.txt new file mode 100644 index 0000000..13fbcea --- /dev/null +++ b/Documentation/filesystems/unionfs/usage.txt @@ -0,0 +1,90 @@ +Unionfs is a stackable unification file system, which can appear to merge +the contents of several directories (branches), while keeping their physical +content separate. Unionfs is useful for unified source tree management, +merged contents of split CD-ROM, merged separate software package +directories, data grids, and more. Unionfs allows any mix of read-only and +read-write branches, as well as insertion and deletion of branches anywhere +in the fan-out. To maintain Unix semantics, Unionfs handles elimination of +duplicates, partial-error conditions, and more. + +# mount -t unionfs -o branch-option[,union-options[,...]] none MOUNTPOINT + +The available branch-option for the mount command is: + + dirs=branch[=ro|=rw][:...] + +specifies a separated list of which directories compose the union. +Directories that come earlier in the list have a higher precedence than +those which come later. Additionally, read-only or read-write permissions of +the branch can be specified by appending =ro or =rw (default) to each +directory. + +Syntax: + + dirs=/branch1[=ro|=rw]:/branch2[=ro|=rw]:...:/branchN[=ro|=rw] + +Example: + + dirs=/writable_branch=rw:/read-only_branch=ro + + +DYNAMIC BRANCH MANAGEMENT AND REMOUNTS +====================================== + +You can remount a union and change its overall mode, or reconfigure the +branches, as follows. + +To downgrade a union from read-write to read-only: + +# mount -t unionfs -o remount,ro none MOUNTPOINT + +To upgrade a union from read-only to read-write: + +# mount -t unionfs -o remount,rw none MOUNTPOINT + +To delete a branch /foo, regardless where it is in the current union: + +# mount -t unionfs -o del=/foo none MOUNTPOINT + +To insert (add) a branch /foo before /bar: + +# mount -t unionfs -o remount,add=/bar:/foo none MOUNTPOINT + +To insert (add) a branch /foo (with the "rw" mode flag) before /bar: + +# mount -t unionfs -o remount,add=/bar:/foo=rw none MOUNTPOINT + +To insert (add) a branch /foo (in "rw" mode) at the very beginning (i.e., a +new highest-priority branch), you can use the above syntax, or use a short +hand version as follows: + +# mount -t unionfs -o remount,add=/foo none MOUNTPOINT + +To append a branch to the very end (new lowest-priority branch): + +# mount -t unionfs -o remount,add=:/foo none MOUNTPOINT + +To append a branch to the very end (new lowest-priority branch), in +read-only mode: + +# mount -t unionfs -o remount,add=:/foo:ro none MOUNTPOINT + +Finally, to change the mode of one existing branch, say /foo, from read-only +to read-write, and change /bar from read-write to read-only: + +# mount -t unionfs -o remount,mode=/foo=rw,mode=/bar=ro none MOUNTPOINT + + +CACHE CONSISTENCY +================= + +If you modify any file on any of the lower branches directly, while there is +a Unionfs 2.0 mounted above any of those branches, you should tell Unionfs +to purge its caches and re-get the objects. To do that, you have to +incremenet the generation number of the superblock using the following +command: + +# mount -t unionfs -o remount,remount,incgen none MOUNTPOINT + + +For more information, see . diff --git a/MAINTAINERS b/MAINTAINERS index 277877a..0b728ac 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -3364,6 +3364,13 @@ L: linux-kernel@vger.kernel.org W: http://www.kernel.dk S: Maintained +UNIONFS +P: Josef "Jeff" Sipek +M: jsipek@cs.sunysb.edu +L: unionfs@filesystems.org +W: http://unionfs.filesystems.org +S: Maintained + USB ACM DRIVER P: Oliver Neukum M: oliver@neukum.name diff --git a/fs/Kconfig b/fs/Kconfig index 3c4886b..a8cebe3 100644 --- a/fs/Kconfig +++ b/fs/Kconfig @@ -1034,6 +1034,41 @@ config CONFIGFS_FS endmenu +menu "Layered filesystems" + +config ECRYPT_FS + tristate "eCrypt filesystem layer support (EXPERIMENTAL)" + depends on EXPERIMENTAL && KEYS && CRYPTO && NET + help + Encrypted filesystem that operates on the VFS layer. See + to learn more about + eCryptfs. Userspace components are required and can be + obtained from . + + To compile this file system support as a module, choose M here: the + module will be called ecryptfs. + +config UNION_FS + tristate "Union file system (EXPERIMENTAL)" + depends on SLAB && EXPERIMENTAL + help + Unionfs is a stackable unification file system, which appears to + merge the contents of several directories (branches), while keeping + their physical content separate. + + See for details + +config UNION_FS_XATTR + bool "Unionfs extended attributes" + depends on UNION_FS + help + Extended attributes are name:value pairs associated with inodes by + the kernel or by users (see the attr(5) manual page). + + If unsure, say N. + +endmenu + menu "Miscellaneous filesystems" config ADFS_FS @@ -1086,18 +1121,6 @@ config AFFS_FS To compile this file system support as a module, choose M here: the module will be called affs. If unsure, say N. -config ECRYPT_FS - tristate "eCrypt filesystem layer support (EXPERIMENTAL)" - depends on EXPERIMENTAL && KEYS && CRYPTO && NET - help - Encrypted filesystem that operates on the VFS layer. See - to learn more about - eCryptfs. Userspace components are required and can be - obtained from . - - To compile this file system support as a module, choose M here: the - module will be called ecryptfs. - config HFS_FS tristate "Apple Macintosh file system support (EXPERIMENTAL)" depends on BLOCK && EXPERIMENTAL diff --git a/fs/Makefile b/fs/Makefile index 9edf411..b490b1a 100644 --- a/fs/Makefile +++ b/fs/Makefile @@ -114,3 +114,4 @@ obj-$(CONFIG_HPPFS) += hppfs/ obj-$(CONFIG_DEBUG_FS) += debugfs/ obj-$(CONFIG_OCFS2_FS) += ocfs2/ obj-$(CONFIG_GFS2_FS) += gfs2/ +obj-$(CONFIG_UNION_FS) += unionfs/ diff --git a/fs/drop_caches.c b/fs/drop_caches.c index 03ea769..6a7aa05 100644 --- a/fs/drop_caches.c +++ b/fs/drop_caches.c @@ -3,6 +3,7 @@ */ #include +#include #include #include #include @@ -12,7 +13,7 @@ /* A global variable is a bit ugly, but it keeps the code simple */ int sysctl_drop_caches; -static void drop_pagecache_sb(struct super_block *sb) +void drop_pagecache_sb(struct super_block *sb) { struct inode *inode; @@ -24,6 +25,7 @@ static void drop_pagecache_sb(struct super_block *sb) } spin_unlock(&inode_lock); } +EXPORT_SYMBOL(drop_pagecache_sb); void drop_pagecache(void) { diff --git a/fs/ecryptfs/inode.c b/fs/ecryptfs/inode.c index 1548be2..6655918 100644 --- a/fs/ecryptfs/inode.c +++ b/fs/ecryptfs/inode.c @@ -280,7 +280,9 @@ static struct dentry *ecryptfs_lookup(struct inode *dir, struct dentry *dentry, int rc = 0; struct dentry *lower_dir_dentry; struct dentry *lower_dentry; + struct dentry *dentry_save; struct vfsmount *lower_mnt; + struct vfsmount *mnt_save; char *encoded_name; unsigned int encoded_namelen; struct ecryptfs_crypt_stat *crypt_stat = NULL; @@ -308,9 +310,13 @@ static struct dentry *ecryptfs_lookup(struct inode *dir, struct dentry *dentry, } ecryptfs_printk(KERN_DEBUG, "encoded_name = [%s]; encoded_namelen " "= [%d]\n", encoded_name, encoded_namelen); - lower_dentry = lookup_one_len(encoded_name, lower_dir_dentry, - encoded_namelen - 1); + dentry_save = nd->dentry; + mnt_save = nd->mnt; + lower_dentry = lookup_one_len_nd(encoded_name, lower_dir_dentry, + (encoded_namelen - 1), nd); kfree(encoded_name); + nd->mnt = mnt_save; + nd->dentry = dentry_save; if (IS_ERR(lower_dentry)) { ecryptfs_printk(KERN_ERR, "ERR from lower_dentry\n"); rc = PTR_ERR(lower_dentry); diff --git a/fs/namei.c b/fs/namei.c index ee60cc4..2995fba 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -1292,8 +1292,8 @@ static struct dentry *lookup_hash(struct nameidata *nd) return __lookup_hash(&nd->last, nd->dentry, nd); } -/* SMP-safe */ -struct dentry * lookup_one_len(const char * name, struct dentry * base, int len) +struct dentry *lookup_one_len_nd(const char *name, struct dentry *base, + int len, struct nameidata *nd) { unsigned long hash; struct qstr this; @@ -1313,7 +1313,7 @@ struct dentry * lookup_one_len(const char * name, struct dentry * base, int len) } this.hash = end_name_hash(hash); - return __lookup_hash(&this, base, NULL); + return __lookup_hash(&this, base, nd); access: return ERR_PTR(-EACCES); } @@ -2758,7 +2758,7 @@ EXPORT_SYMBOL(follow_up); EXPORT_SYMBOL(get_write_access); /* binfmt_aout */ EXPORT_SYMBOL(getname); EXPORT_SYMBOL(lock_rename); -EXPORT_SYMBOL(lookup_one_len); +EXPORT_SYMBOL(lookup_one_len_nd); EXPORT_SYMBOL(page_follow_link_light); EXPORT_SYMBOL(page_put_link); EXPORT_SYMBOL(page_readlink); diff --git a/fs/unionfs/Makefile b/fs/unionfs/Makefile new file mode 100644 index 0000000..e6b2e0c --- /dev/null +++ b/fs/unionfs/Makefile @@ -0,0 +1,7 @@ +obj-$(CONFIG_UNION_FS) += unionfs.o + +unionfs-y := subr.o dentry.o file.o inode.o main.o super.o \ + branchman.o rdstate.o copyup.o dirhelper.o rename.o \ + unlink.o lookup.o commonfops.o dirfops.o sioq.o + +unionfs-$(CONFIG_UNION_FS_XATTR) += xattr.o diff --git a/fs/unionfs/branchman.c b/fs/unionfs/branchman.c new file mode 100644 index 0000000..6912be9 --- /dev/null +++ b/fs/unionfs/branchman.c @@ -0,0 +1,60 @@ +/* + * 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 State University of New York + * + * 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" + +/* return to userspace the branch indices containing the file in question + * + * 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 + */ +int unionfs_ioctl_queryfile(struct file *file, unsigned int cmd, + unsigned long arg) +{ + int err = 0; + fd_set branchlist; + + int bstart = 0, bend = 0, bindex = 0; + struct dentry *dentry, *hidden_dentry; + + dentry = file->f_dentry; + unionfs_lock_dentry(dentry); + if ((err = unionfs_partial_lookup(dentry))) + goto out; + bstart = dbstart(dentry); + bend = dbend(dentry); + + FD_ZERO(&branchlist); + + for (bindex = bstart; bindex <= bend; bindex++) { + hidden_dentry = unionfs_lower_dentry_idx(dentry, bindex); + if (!hidden_dentry) + continue; + if (hidden_dentry->d_inode) + FD_SET(bindex, &branchlist); + } + + err = copy_to_user((void __user *)arg, &branchlist, sizeof(fd_set)); + if (err) + err = -EFAULT; + +out: + unionfs_unlock_dentry(dentry); + return err < 0 ? err : bend; +} + diff --git a/fs/unionfs/commonfops.c b/fs/unionfs/commonfops.c new file mode 100644 index 0000000..c9df99d --- /dev/null +++ b/fs/unionfs/commonfops.c @@ -0,0 +1,687 @@ +/* + * 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 State University of New York + * + * 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" + +/* 1) Copyup the file + * 2) Rename the file to '.unionfs' - obviously + * stolen from NFS's silly rename + */ +static int copyup_deleted_file(struct file *file, struct dentry *dentry, + int bstart, int bindex) +{ + static unsigned int counter; + const int i_inosize = sizeof(dentry->d_inode->i_ino) * 2; + const int countersize = sizeof(counter) * 2; + const int nlen = sizeof(".unionfs") + i_inosize + countersize - 1; + char name[nlen + 1]; + + int err; + struct dentry *tmp_dentry = NULL; + struct dentry *hidden_dentry; + struct dentry *hidden_dir_dentry = NULL; + + hidden_dentry = unionfs_lower_dentry_idx(dentry, bstart); + + sprintf(name, ".unionfs%*.*lx", + i_inosize, i_inosize, hidden_dentry->d_inode->i_ino); + + /* + * Loop, looking for an unused temp name to copyup to. + * + * It's somewhat silly that we look for a free temp tmp name in the + * source branch (bstart) instead of the dest branch (bindex), where + * the final name will be created. We _will_ catch it if somehow + * the name exists in the dest branch, but it'd be nice to catch it + * sooner than later. + */ + tmp_dentry = NULL; + do { + char *suffix = name + nlen - countersize; + + dput(tmp_dentry); + counter++; + sprintf(suffix, "%*.*x", countersize, countersize, counter); + + printk(KERN_DEBUG "unionfs: trying to rename %s to %s\n", + dentry->d_name.name, name); + + tmp_dentry = lookup_one_len(name, hidden_dentry->d_parent, + UNIONFS_TMPNAM_LEN); + if (IS_ERR(tmp_dentry)) { + err = PTR_ERR(tmp_dentry); + goto out; + } + /* don't dput here because of do-while condition eval order */ + } 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, file->f_dentry->d_inode->i_size); + if (err) + goto out; + + /* bring it to the same state as an unlinked file */ + hidden_dentry = unionfs_lower_dentry_idx(dentry, dbstart(dentry)); + hidden_dir_dentry = lock_parent(hidden_dentry); + err = vfs_unlink(hidden_dir_dentry->d_inode, hidden_dentry); + unlock_dir(hidden_dir_dentry); + +out: + return err; +} + +/* + * Find new index of matching branch with an open file, since branches could + * have been added/deleted causing the one with open files to shift. + * + * @file: current file whose branches may have changed + * @bindex: index of branch within current file (could be old branch) + * @new_sb: the new superblock which may have new branch IDs + * Returns index of newly found branch (0 or greater), -1 otherwise. + */ +static int find_new_branch_index(struct file *file, int bindex, + struct super_block *new_sb) +{ + int old_branch_id = UNIONFS_F(file)->saved_branch_ids[bindex]; + int i; + + for (i = 0; i < sbmax(new_sb); i++) + if (old_branch_id == branch_id(new_sb, i)) + return i; + /* + * XXX: maybe we should BUG_ON if not found new branch index? + * (really that should never happen). + */ + return -1; +} + +/* put all references held by upper struct file and free lower file pointer + * array + */ +static void cleanup_file(struct file *file) +{ + int bindex, bstart, bend; + struct file **lf; + struct super_block *sb = file->f_dentry->d_sb; + + lf = UNIONFS_F(file)->lower_files; + bstart = fbstart(file); + bend = fbend(file); + + for (bindex = bstart; bindex <= bend; bindex++) { + if (unionfs_lower_file_idx(file, bindex)) { + int i; /* holds (possibly) updated branch index */ + i = find_new_branch_index(file, bindex, sb); + if (i < 0) + printk(KERN_ERR "unionfs: no supberlock for file %p\n", + file); + else { + unionfs_read_lock(sb); + branchput(sb, i); + unionfs_read_unlock(sb); + /* XXX: is it correct to use sb->s_root here? */ + unionfs_mntput(sb->s_root, i); + /* XXX: mntget b/c fput below will call mntput */ + unionfs_mntget(sb->s_root, bindex); + } + fput(unionfs_lower_file_idx(file, bindex)); + } + } + + UNIONFS_F(file)->lower_files = NULL; + kfree(lf); + kfree(UNIONFS_F(file)->saved_branch_ids); + /* set to NULL because caller needs to know if to kfree on error */ + UNIONFS_F(file)->saved_branch_ids = NULL; +} + +/* open all lower files for a given file */ +static int open_all_files(struct file *file) +{ + int bindex, bstart, bend, err = 0; + struct file *hidden_file; + struct dentry *hidden_dentry; + struct dentry *dentry = file->f_dentry; + struct super_block *sb = dentry->d_sb; + + bstart = dbstart(dentry); + bend = dbend(dentry); + + for (bindex = bstart; bindex <= bend; bindex++) { + hidden_dentry = unionfs_lower_dentry_idx(dentry, bindex); + if (!hidden_dentry) + continue; + + 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), + file->f_flags); + if (IS_ERR(hidden_file)) { + err = PTR_ERR(hidden_file); + goto out; + } else + unionfs_set_lower_file_idx(file, bindex, hidden_file); + } +out: + return err; +} + +/* open the highest priority file for a given upper file */ +static int open_highest_file(struct file *file, int willwrite) +{ + int bindex, bstart, bend, err = 0; + struct file *hidden_file; + struct dentry *hidden_dentry; + + struct dentry *dentry = file->f_dentry; + struct inode *parent_inode = dentry->d_parent->d_inode; + struct super_block *sb = dentry->d_sb; + size_t inode_size = dentry->d_inode->i_size; + + bstart = dbstart(dentry); + bend = dbend(dentry); + + hidden_dentry = unionfs_lower_dentry(dentry); + if (willwrite && IS_WRITE_FLAG(file->f_flags) && is_robranch(dentry)) { + for (bindex = bstart - 1; bindex >= 0; bindex--) { + err = copyup_file(parent_inode, file, bstart, bindex, + inode_size); + if (!err) + break; + } + atomic_set(&UNIONFS_F(file)->generation, + atomic_read(&UNIONFS_I(dentry->d_inode)->generation)); + goto out; + } + + 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)) { + err = PTR_ERR(hidden_file); + goto out; + } + unionfs_set_lower_file(file, hidden_file); + /* Fix up the position. */ + hidden_file->f_pos = file->f_pos; + + memcpy(&hidden_file->f_ra, &file->f_ra, sizeof(struct file_ra_state)); +out: + return err; +} + +static int do_delayed_copyup(struct file *file, struct dentry *dentry) +{ + int bindex, bstart, bend, err = 0; + struct inode *parent_inode = dentry->d_parent->d_inode; + loff_t inode_size = file->f_dentry->d_inode->i_size; + + bstart = fbstart(file); + bend = fbend(file); + + BUG_ON(!S_ISREG(file->f_dentry->d_inode->i_mode)); + + for (bindex = bstart - 1; bindex >= 0; bindex--) { + if (!d_deleted(file->f_dentry)) + err = copyup_file(parent_inode, file, bstart, + bindex, inode_size); + else + err = copyup_deleted_file(file, dentry, bstart, bindex); + + if (!err) + break; + } + if (!err && (bstart > fbstart(file))) { + 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); + } + } + fbend(file) = bend; + } + return err; +} + +/* + * Revalidate the struct file + * @file: file to revalidate + * @willwrite: 1 if caller may cause changes to the file; 0 otherwise. + */ +int unionfs_file_revalidate(struct file *file, int willwrite) +{ + struct super_block *sb; + struct dentry *dentry; + int sbgen, fgen, dgen; + int bstart, bend; + int size; + + int err = 0; + + dentry = file->f_dentry; + unionfs_lock_dentry(dentry); + sb = dentry->d_sb; + + /* first revalidate the dentry inside struct file */ + if (!__unionfs_d_revalidate_chain(dentry, NULL) && !d_deleted(dentry)) { + err = -ESTALE; + goto out_nofree; + } + + sbgen = atomic_read(&UNIONFS_SB(sb)->generation); + dgen = atomic_read(&UNIONFS_D(dentry)->generation); + fgen = atomic_read(&UNIONFS_F(file)->generation); + + BUG_ON(sbgen > dgen); + + /* There are two cases we are interested in. The first is if the + * generation is lower than the super-block. The second is if someone + * has copied up this file from underneath us, we also need to refresh + * things. + */ + if (!d_deleted(dentry) && + (sbgen > fgen || dbstart(dentry) != fbstart(file))) { + /* First we throw out the existing files. */ + cleanup_file(file); + + /* Now we reopen the file(s) as in unionfs_open. */ + bstart = fbstart(file) = dbstart(dentry); + bend = fbend(file) = dbend(dentry); + + size = sizeof(struct file *) * sbmax(sb); + UNIONFS_F(file)->lower_files = kzalloc(size, GFP_KERNEL); + if (!UNIONFS_F(file)->lower_files) { + err = -ENOMEM; + goto out; + } + size = sizeof(int) * sbmax(sb); + UNIONFS_F(file)->saved_branch_ids = kzalloc(size, GFP_KERNEL); + if (!UNIONFS_F(file)->saved_branch_ids) { + err = -ENOMEM; + goto out; + } + + if (S_ISDIR(dentry->d_inode->i_mode)) { + /* We need to open all the files. */ + err = open_all_files(file); + if (err) + goto out; + } else { + /* We only open the highest priority branch. */ + err = open_highest_file(file, willwrite); + if (err) + goto out; + } + atomic_set(&UNIONFS_F(file)->generation, + atomic_read(&UNIONFS_I(dentry->d_inode)-> + generation)); + } + + /* Copyup on the first write to a file on a readonly branch. */ + if (willwrite && IS_WRITE_FLAG(file->f_flags) && + !IS_WRITE_FLAG(unionfs_lower_file(file)->f_flags) && + is_robranch(dentry)) { + printk(KERN_DEBUG "Doing delayed copyup of a read-write " + "file on a read-only branch.\n"); + err = do_delayed_copyup(file, dentry); + } + +out: + if (err) { + kfree(UNIONFS_F(file)->lower_files); + kfree(UNIONFS_F(file)->saved_branch_ids); + } +out_nofree: + unionfs_unlock_dentry(dentry); + return err; +} + +/* unionfs_open helper function: open a directory */ +static int __open_dir(struct inode *inode, struct file *file) +{ + struct dentry *hidden_dentry; + struct file *hidden_file; + int bindex, bstart, bend; + + bstart = fbstart(file) = dbstart(file->f_dentry); + bend = fbend(file) = dbend(file->f_dentry); + + for (bindex = bstart; bindex <= bend; bindex++) { + hidden_dentry = unionfs_lower_dentry_idx(file->f_dentry, bindex); + if (!hidden_dentry) + continue; + + dget(hidden_dentry); + unionfs_mntget(file->f_dentry, bindex); + hidden_file = dentry_open(hidden_dentry, + unionfs_lower_mnt_idx(file->f_dentry, bindex), + file->f_flags); + if (IS_ERR(hidden_file)) + return PTR_ERR(hidden_file); + + unionfs_set_lower_file_idx(file, bindex, hidden_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; +} + +/* unionfs_open helper function: open a file */ +static int __open_file(struct inode *inode, struct file *file) +{ + struct dentry *hidden_dentry; + struct file *hidden_file; + int hidden_flags; + int bindex, bstart, bend; + + hidden_dentry = unionfs_lower_dentry(file->f_dentry); + hidden_flags = file->f_flags; + + bstart = fbstart(file) = dbstart(file->f_dentry); + bend = fbend(file) = dbend(file->f_dentry); + + /* check for the permission for hidden file. If the error is COPYUP_ERR, + * copyup the file. + */ + if (hidden_dentry->d_inode && is_robranch(file->f_dentry)) { + /* if the open will change the file, copy it up otherwise + * defer it. + */ + if (hidden_flags & O_TRUNC) { + int size = 0; + int err = -EROFS; + + /* copyup the file */ + for (bindex = bstart - 1; bindex >= 0; bindex--) { + err = copyup_file(file->f_dentry->d_parent->d_inode, + file, bstart, bindex, size); + if (!err) + break; + } + return err; + } else + hidden_flags &= ~(OPEN_WRITE_FLAGS); + } + + dget(hidden_dentry); + + /* dentry_open will decrement mnt refcnt if err. + * otherwise fput() will do an mntput() for us upon file close. + */ + unionfs_mntget(file->f_dentry, bstart); + hidden_file = dentry_open(hidden_dentry, + unionfs_lower_mnt_idx(file->f_dentry, bstart), + hidden_flags); + if (IS_ERR(hidden_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; +} + +int unionfs_open(struct inode *inode, struct file *file) +{ + int err = 0; + struct file *hidden_file = NULL; + struct dentry *dentry = NULL; + 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; + goto out_nofree; + } + fbstart(file) = -1; + fbend(file) = -1; + atomic_set(&UNIONFS_F(file)->generation, + atomic_read(&UNIONFS_I(inode)->generation)); + + size = sizeof(struct file *) * sbmax(inode->i_sb); + UNIONFS_F(file)->lower_files = kzalloc(size, GFP_KERNEL); + if (!UNIONFS_F(file)->lower_files) { + err = -ENOMEM; + goto out; + } + size = sizeof(int) * sbmax(inode->i_sb); + UNIONFS_F(file)->saved_branch_ids = kzalloc(size, GFP_KERNEL); + if (!UNIONFS_F(file)->saved_branch_ids) { + err = -ENOMEM; + goto out; + } + + dentry = file->f_dentry; + unionfs_lock_dentry(dentry); + + bstart = fbstart(file) = dbstart(dentry); + bend = fbend(file) = dbend(dentry); + + /* increment, so that we can flush appropriately */ + atomic_inc(&UNIONFS_I(dentry->d_inode)->totalopens); + + /* open all directories and make the unionfs file struct point to + * these hidden file structs + */ + if (S_ISDIR(inode->i_mode)) + err = __open_dir(inode, file); /* open a dir */ + else + err = __open_file(inode, file); /* open a file */ + + /* freeing the allocated resources, and fput the opened files */ + if (err) { + atomic_dec(&UNIONFS_I(dentry->d_inode)->totalopens); + for (bindex = bstart; bindex <= bend; bindex++) { + hidden_file = unionfs_lower_file_idx(file, bindex); + 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); + +out: + if (err) { + kfree(UNIONFS_F(file)->lower_files); + kfree(UNIONFS_F(file)->saved_branch_ids); + kfree(UNIONFS_F(file)); + } +out_nofree: + unionfs_read_unlock(inode->i_sb); + return err; +} + +/* release all lower object references & free the file info structure */ +int unionfs_file_release(struct inode *inode, struct file *file) +{ + struct file *hidden_file = NULL; + struct unionfs_file_info *fileinfo = UNIONFS_F(file); + struct unionfs_inode_info *inodeinfo = UNIONFS_I(inode); + 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); + bend = fbend(file); + + for (bindex = bstart; bindex <= bend; bindex++) { + hidden_file = unionfs_lower_file_idx(file, bindex); + + if (hidden_file) { + fput(hidden_file); + unionfs_read_lock(inode->i_sb); + branchput(inode->i_sb, bindex); + unionfs_read_unlock(inode->i_sb); + } + } + kfree(fileinfo->lower_files); + kfree(fileinfo->saved_branch_ids); + + if (fileinfo->rdstate) { + fileinfo->rdstate->access = jiffies; + printk(KERN_DEBUG "Saving rdstate with cookie %u [%d.%lld]\n", + fileinfo->rdstate->cookie, + fileinfo->rdstate->bindex, + (long long)fileinfo->rdstate->dirpos); + spin_lock(&inodeinfo->rdlock); + inodeinfo->rdcount++; + list_add_tail(&fileinfo->rdstate->cache, + &inodeinfo->readdircache); + mark_inode_dirty(inode); + spin_unlock(&inodeinfo->rdlock); + fileinfo->rdstate = NULL; + } + kfree(fileinfo); + unionfs_read_unlock(inode->i_sb); + return 0; +} + +/* pass the ioctl to the lower fs */ +static long do_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct file *hidden_file; + int err; + + hidden_file = unionfs_lower_file(file); + + err = security_file_ioctl(hidden_file, cmd, arg); + if (err) + goto out; + + err = -ENOTTY; + if (!hidden_file || !hidden_file->f_op) + goto out; + if (hidden_file->f_op->unlocked_ioctl) { + err = hidden_file->f_op->unlocked_ioctl(hidden_file, cmd, arg); + } else if (hidden_file->f_op->ioctl) { + lock_kernel(); + err = hidden_file->f_op->ioctl(hidden_file->f_dentry->d_inode, + hidden_file, cmd, arg); + unlock_kernel(); + } + +out: + unionfs_read_unlock(file->f_dentry->d_sb); + return err; +} + +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; + + /* check if asked for local commands */ + switch (cmd) { + case UNIONFS_IOCTL_INCGEN: + /* Increment the superblock generation count */ + printk("unionfs: incgen ioctl deprecated; use \"-o remount,incgen\"\n"); + err = -ENOSYS; + break; + + case UNIONFS_IOCTL_QUERYFILE: + /* Return list of branches containing the given file */ + err = unionfs_ioctl_queryfile(file, cmd, arg); + break; + + default: + /* pass the ioctl down */ + err = do_ioctl(file, cmd, arg); + break; + } + +out: + return err; +} + +int unionfs_flush(struct file *file, fl_owner_t id) +{ + int err = 0; + struct file *hidden_file = NULL; + 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; + + unionfs_lock_dentry(dentry); + + bstart = fbstart(file); + bend = fbend(file); + for (bindex = bstart; bindex <= bend; bindex++) { + hidden_file = unionfs_lower_file_idx(file, bindex); + + if (hidden_file && hidden_file->f_op && hidden_file->f_op->flush) { + err = hidden_file->f_op->flush(hidden_file, id); + if (err) + goto out_lock; + + /* if there are no more references to the dentry, dput it */ + if (d_deleted(dentry)) { + dput(unionfs_lower_dentry_idx(dentry, bindex)); + unionfs_set_lower_dentry_idx(dentry, bindex, NULL); + } + } + + } + +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 new file mode 100644 index 0000000..411553a --- /dev/null +++ b/fs/unionfs/copyup.c @@ -0,0 +1,776 @@ +/* + * 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 State University of New York* + * + * 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" + +static int copyup_named_dentry(struct inode *dir, struct dentry *dentry, + int bstart, int new_bindex, const char *name, + int namelen, struct file **copyup_file, + loff_t len); +static struct dentry *create_parents_named(struct inode *dir, + struct dentry *dentry, + const char *name, int bindex); + +/* For detailed explanation of copyup see: + * Documentation/filesystems/unionfs/concepts.txt + */ + +#ifdef CONFIG_UNION_FS_XATTR +/* copyup all extended attrs for a given dentry */ +static int copyup_xattrs(struct dentry *old_hidden_dentry, + struct dentry *new_hidden_dentry) +{ + int err = 0; + ssize_t list_size = -1; + char *name_list = NULL; + char *attr_value = NULL; + char *name_list_orig = NULL; + + list_size = vfs_listxattr(old_hidden_dentry, NULL, 0); + + if (list_size <= 0) { + err = list_size; + goto out; + } + + name_list = unionfs_xattr_alloc(list_size + 1, XATTR_LIST_MAX); + if (!name_list || IS_ERR(name_list)) { + err = PTR_ERR(name_list); + goto out; + } + list_size = vfs_listxattr(old_hidden_dentry, name_list, list_size); + attr_value = unionfs_xattr_alloc(XATTR_SIZE_MAX, XATTR_SIZE_MAX); + if (!attr_value || IS_ERR(attr_value)) { + err = PTR_ERR(name_list); + goto out; + } + name_list_orig = name_list; + while (*name_list) { + ssize_t size; + + /* Lock here since vfs_getxattr doesn't lock for us */ + mutex_lock(&old_hidden_dentry->d_inode->i_mutex); + size = vfs_getxattr(old_hidden_dentry, name_list, + attr_value, XATTR_SIZE_MAX); + mutex_unlock(&old_hidden_dentry->d_inode->i_mutex); + if (size < 0) { + err = size; + goto out; + } + + if (size > XATTR_SIZE_MAX) { + err = -E2BIG; + goto out; + } + /* Don't lock here since vfs_setxattr does it for us. */ + err = vfs_setxattr(new_hidden_dentry, name_list, attr_value, + size, 0); + + if (err < 0) + goto out; + name_list += strlen(name_list) + 1; + } + out: + name_list = name_list_orig; + + if (name_list) + unionfs_xattr_free(name_list, list_size + 1); + if (attr_value) + unionfs_xattr_free(attr_value, XATTR_SIZE_MAX); + /* It is no big deal if this fails, we just roll with the punches. */ + if (err == -ENOTSUPP || err == -EOPNOTSUPP) + err = 0; + return err; +} +#endif /* CONFIG_UNION_FS_XATTR */ + +/* Determine the mode based on the copyup flags, and the existing dentry. */ +static int copyup_permissions(struct super_block *sb, + struct dentry *old_hidden_dentry, + struct dentry *new_hidden_dentry) +{ + struct inode *i = old_hidden_dentry->d_inode; + struct iattr newattrs; + int err; + + newattrs.ia_atime = i->i_atime; + newattrs.ia_mtime = i->i_mtime; + newattrs.ia_ctime = i->i_ctime; + + newattrs.ia_gid = i->i_gid; + newattrs.ia_uid = i->i_uid; + + newattrs.ia_mode = i->i_mode; + + newattrs.ia_valid = ATTR_CTIME | ATTR_ATIME | ATTR_MTIME | + ATTR_ATIME_SET | ATTR_MTIME_SET | ATTR_FORCE | + ATTR_GID | ATTR_UID | ATTR_MODE; + + err = notify_change(new_hidden_dentry, &newattrs); + + return err; +} + +int copyup_dentry(struct inode *dir, struct dentry *dentry, + int bstart, int new_bindex, + struct file **copyup_file, loff_t len) +{ + return copyup_named_dentry(dir, dentry, bstart, new_bindex, + dentry->d_name.name, + dentry->d_name.len, copyup_file, len); +} + +/* create the new device/file/directory - use copyup_permission to copyup + * times, and mode + * + * if the object being copied up is a regular file, the file is only created, + * the contents have to be copied up separately + */ +static int __copyup_ndentry(struct dentry *old_hidden_dentry, + struct dentry *new_hidden_dentry, + struct dentry *new_hidden_parent_dentry, + char *symbuf) +{ + int err = 0; + umode_t old_mode = old_hidden_dentry->d_inode->i_mode; + struct sioq_args args; + + if (S_ISDIR(old_mode)) { + args.mkdir.parent = new_hidden_parent_dentry->d_inode; + args.mkdir.dentry = new_hidden_dentry; + args.mkdir.mode = old_mode; + + run_sioq(__unionfs_mkdir, &args); + err = args.err; + } else if (S_ISLNK(old_mode)) { + args.symlink.parent = new_hidden_parent_dentry->d_inode; + args.symlink.dentry = new_hidden_dentry; + args.symlink.symbuf = symbuf; + args.symlink.mode = old_mode; + + run_sioq(__unionfs_symlink, &args); + err = args.err; + } else if (S_ISBLK(old_mode) || S_ISCHR(old_mode) || + S_ISFIFO(old_mode) || S_ISSOCK(old_mode)) { + args.mknod.parent = new_hidden_parent_dentry->d_inode; + args.mknod.dentry = new_hidden_dentry; + args.mknod.mode = old_mode; + args.mknod.dev = old_hidden_dentry->d_inode->i_rdev; + + run_sioq(__unionfs_mknod, &args); + err = args.err; + } else if (S_ISREG(old_mode)) { + args.create.parent = new_hidden_parent_dentry->d_inode; + args.create.dentry = new_hidden_dentry; + args.create.mode = old_mode; + args.create.nd = NULL; + + run_sioq(__unionfs_create, &args); + err = args.err; + } else { + printk(KERN_ERR "Unknown inode type %d\n", + old_mode); + BUG(); + } + + return err; +} + +static int __copyup_reg_data(struct dentry *dentry, + struct dentry *new_hidden_dentry, int new_bindex, + struct dentry *old_hidden_dentry, int old_bindex, + struct file **copyup_file, loff_t len) +{ + struct super_block *sb = dentry->d_sb; + struct file *input_file; + struct file *output_file; + mm_segment_t old_fs; + char *buf = NULL; + ssize_t read_bytes, write_bytes; + loff_t size; + int err = 0; + + /* 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); + if (IS_ERR(input_file)) { + dput(old_hidden_dentry); + err = PTR_ERR(input_file); + goto out; + } + if (!input_file->f_op || !input_file->f_op->read) { + err = -EINVAL; + goto out_close_in; + } + + /* 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); + if (IS_ERR(output_file)) { + err = PTR_ERR(output_file); + goto out_close_in2; + } + if (!output_file->f_op || !output_file->f_op->write) { + err = -EINVAL; + goto out_close_out; + } + + /* allocating a buffer */ + buf = kmalloc(PAGE_SIZE, GFP_KERNEL); + if (!buf) { + err = -ENOMEM; + goto out_close_out; + } + + input_file->f_pos = 0; + output_file->f_pos = 0; + + old_fs = get_fs(); + set_fs(KERNEL_DS); + + size = len; + err = 0; + do { + if (len >= PAGE_SIZE) + size = PAGE_SIZE; + else if ((len < PAGE_SIZE) && (len > 0)) + size = len; + + len -= PAGE_SIZE; + + read_bytes = + input_file->f_op->read(input_file, + (char __user *)buf, size, + &input_file->f_pos); + if (read_bytes <= 0) { + err = read_bytes; + break; + } + + write_bytes = + output_file->f_op->write(output_file, + (char __user *)buf, + read_bytes, + &output_file->f_pos); + if ((write_bytes < 0) || (write_bytes < read_bytes)) { + err = write_bytes; + break; + } + } while ((read_bytes > 0) && (len > 0)); + + set_fs(old_fs); + + kfree(buf); + + if (err) + goto out_close_out; + if (copyup_file) { + *copyup_file = output_file; + goto out_close_in; + } + +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; +} + +/* dput the lower references for old and new dentry & clear a lower dentry + * pointer + */ +static void __clear(struct dentry *dentry, struct dentry *old_hidden_dentry, + int old_bstart, int old_bend, + struct dentry *new_hidden_dentry, int new_bindex) +{ + /* get rid of the hidden dentry and all its traces */ + unionfs_set_lower_dentry_idx(dentry, new_bindex, NULL); + set_dbstart(dentry, old_bstart); + set_dbend(dentry, old_bend); + + dput(new_hidden_dentry); + dput(old_hidden_dentry); +} + +/* copy up a dentry to a file of specified name */ +static int copyup_named_dentry(struct inode *dir, struct dentry *dentry, + int bstart, int new_bindex, const char *name, + int namelen, struct file **copyup_file, + loff_t len) +{ + struct dentry *new_hidden_dentry; + struct dentry *old_hidden_dentry = NULL; + struct super_block *sb; + int err = 0; + int old_bindex; + int old_bstart; + int old_bend; + struct dentry *new_hidden_parent_dentry = NULL; + mm_segment_t oldfs; + char *symbuf = NULL; + + verify_locked(dentry); + + old_bindex = bstart; + old_bstart = dbstart(dentry); + old_bend = dbend(dentry); + + BUG_ON(new_bindex < 0); + BUG_ON(new_bindex >= old_bindex); + + sb = dir->i_sb; + + if ((err = is_robranch_super(sb, new_bindex))) + goto out; + + /* Create the directory structure above this dentry. */ + new_hidden_dentry = create_parents_named(dir, dentry, name, new_bindex); + if (IS_ERR(new_hidden_dentry)) { + err = PTR_ERR(new_hidden_dentry); + goto out; + } + + old_hidden_dentry = unionfs_lower_dentry_idx(dentry, old_bindex); + /* we conditionally dput this old_hidden_dentry at end of function */ + dget(old_hidden_dentry); + + /* For symlinks, we must read the link before we lock the directory. */ + if (S_ISLNK(old_hidden_dentry->d_inode->i_mode)) { + + symbuf = kmalloc(PATH_MAX, GFP_KERNEL); + if (!symbuf) { + __clear(dentry, old_hidden_dentry, + old_bstart, old_bend, + new_hidden_dentry, new_bindex); + err = -ENOMEM; + goto out_free; + } + + oldfs = get_fs(); + set_fs(KERNEL_DS); + err = old_hidden_dentry->d_inode->i_op->readlink( + old_hidden_dentry, + (char __user *)symbuf, + PATH_MAX); + set_fs(oldfs); + if (err) { + __clear(dentry, old_hidden_dentry, + old_bstart, old_bend, + new_hidden_dentry, new_bindex); + goto out_free; + } + symbuf[err] = '\0'; + } + + /* Now we lock the parent, and create the object in the new branch. */ + new_hidden_parent_dentry = lock_parent(new_hidden_dentry); + + /* create the new inode */ + err = __copyup_ndentry(old_hidden_dentry, new_hidden_dentry, + new_hidden_parent_dentry, symbuf); + + if (err) { + __clear(dentry, old_hidden_dentry, + old_bstart, old_bend, + new_hidden_dentry, new_bindex); + goto out_unlock; + } + + /* We actually copyup the file here. */ + if (S_ISREG(old_hidden_dentry->d_inode->i_mode)) + err = __copyup_reg_data(dentry, new_hidden_dentry, new_bindex, + old_hidden_dentry, old_bindex, copyup_file, len); + if (err) + goto out_unlink; + + /* Set permissions. */ + if ((err = copyup_permissions(sb, old_hidden_dentry, new_hidden_dentry))) + goto out_unlink; + +#ifdef CONFIG_UNION_FS_XATTR + /* Selinux uses extended attributes for permissions. */ + if ((err = copyup_xattrs(old_hidden_dentry, new_hidden_dentry))) + goto out_unlink; +#endif + + /* do not allow files getting deleted to be reinterposed */ + if (!d_deleted(dentry)) + unionfs_reinterpose(dentry); + + goto out_unlock; + /****/ + +out_unlink: + /* copyup failed, because we possibly ran out of space or + * quota, or something else happened so let's unlink; we don't + * really care about the return value of vfs_unlink + */ + vfs_unlink(new_hidden_parent_dentry->d_inode, new_hidden_dentry); + + if (copyup_file) { + /* need to close the file */ + + fput(*copyup_file); + unionfs_read_lock(sb); + branchput(sb, new_bindex); + unionfs_read_unlock(sb); + } + + /* + * TODO: should we reset the error to something like -EIO? + * + * If we don't reset, the user may get some non-sensical errors, but + * on the other hand, if we reset to EIO, we guarantee that the user + * will get a "confusing" error message. + */ + +out_unlock: + unlock_dir(new_hidden_parent_dentry); + +out_free: + /* + * If old_hidden_dentry was a directory, we need to dput it. If it + * was a file, then it was already dput indirectly by other + * functions we call ablve which operate on regular files. + */ + if (old_hidden_dentry && old_hidden_dentry->d_inode && + S_ISDIR(old_hidden_dentry->d_inode->i_mode)) + dput(old_hidden_dentry); + kfree(symbuf); + +out: + return err; +} + +/* This function creates a copy of a file represented by 'file' which currently + * resides in branch 'bstart' to branch 'new_bindex.' The copy will be named + * "name". + */ +int copyup_named_file(struct inode *dir, struct file *file, char *name, + int bstart, int new_bindex, loff_t len) +{ + int err = 0; + struct file *output_file = NULL; + + err = copyup_named_dentry(dir, file->f_dentry, bstart, + new_bindex, name, strlen(name), &output_file, + len); + if (!err) { + fbstart(file) = new_bindex; + unionfs_set_lower_file_idx(file, new_bindex, output_file); + } + + return err; +} + +/* This function creates a copy of a file represented by 'file' which currently + * resides in branch 'bstart' to branch 'new_bindex'. + */ +int copyup_file(struct inode *dir, struct file *file, int bstart, + int new_bindex, loff_t len) +{ + int err = 0; + struct file *output_file = NULL; + + err = copyup_dentry(dir, file->f_dentry, bstart, new_bindex, + &output_file, len); + if (!err) { + fbstart(file) = new_bindex; + unionfs_set_lower_file_idx(file, new_bindex, output_file); + } + + return err; +} + +/* This function replicates the directory structure upto given dentry + * in the bindex branch. Can create directory structure recursively to the right + * also. + */ +struct dentry *create_parents(struct inode *dir, struct dentry *dentry, + int bindex) +{ + return create_parents_named(dir, dentry, dentry->d_name.name, bindex); +} + +static void __cleanup_dentry(struct dentry * dentry, int bindex, + int old_bstart, int old_bend) +{ + int loop_start; + int loop_end; + int new_bstart = -1; + int new_bend = -1; + int i; + + loop_start = min(old_bstart, bindex); + loop_end = max(old_bend, bindex); + + /* This loop sets the bstart and bend for the new dentry by + * traversing from left to right. It also dputs all negative + * dentries except bindex + */ + for (i = loop_start; i <= loop_end; i++) { + if (!unionfs_lower_dentry_idx(dentry, i)) + continue; + + if (i == bindex) { + new_bend = i; + if (new_bstart < 0) + new_bstart = i; + continue; + } + + if (!unionfs_lower_dentry_idx(dentry, i)->d_inode) { + dput(unionfs_lower_dentry_idx(dentry, i)); + unionfs_set_lower_dentry_idx(dentry, i, NULL); + + unionfs_mntput(dentry, i); + unionfs_set_lower_mnt_idx(dentry, i, NULL); + } else { + if (new_bstart < 0) + new_bstart = i; + new_bend = i; + } + } + + if (new_bstart < 0) + new_bstart = bindex; + if (new_bend < 0) + new_bend = bindex; + set_dbstart(dentry, new_bstart); + set_dbend(dentry, new_bend); + +} + +/* set lower inode ptr and update bstart & bend if necessary */ +static void __set_inode(struct dentry * upper, struct dentry * lower, + int bindex) +{ + unionfs_set_lower_inode_idx(upper->d_inode, bindex, + igrab(lower->d_inode)); + if (likely(ibstart(upper->d_inode) > bindex)) + ibstart(upper->d_inode) = bindex; + if (likely(ibend(upper->d_inode) < bindex)) + ibend(upper->d_inode) = bindex; + +} + +/* set lower dentry ptr and update bstart & bend if necessary */ +static void __set_dentry(struct dentry * upper, struct dentry * lower, + int bindex) +{ + unionfs_set_lower_dentry_idx(upper, bindex, lower); + if (likely(dbstart(upper) > bindex)) + set_dbstart(upper, bindex); + if (likely(dbend(upper) < bindex)) + set_dbend(upper, bindex); +} + +/* This function replicates the directory structure upto given dentry + * in the bindex branch. + */ +static struct dentry *create_parents_named(struct inode *dir, + struct dentry *dentry, + const char *name, int bindex) +{ + int err; + struct dentry *child_dentry; + struct dentry *parent_dentry; + struct dentry *hidden_parent_dentry = NULL; + struct dentry *hidden_dentry = NULL; + const char *childname; + unsigned int childnamelen; + + int old_kmalloc_size; + int kmalloc_size; + int num_dentry; + int count; + + int old_bstart; + int old_bend; + struct dentry **path = NULL; + struct dentry **tmp_path; + struct super_block *sb; + + verify_locked(dentry); + + /* There is no sense allocating any less than the minimum. */ + kmalloc_size = malloc_sizes[0].cs_size; + num_dentry = kmalloc_size / sizeof(struct dentry *); + + if ((err = is_robranch_super(dir->i_sb, bindex))) { + hidden_dentry = ERR_PTR(err); + goto out; + } + + old_bstart = dbstart(dentry); + old_bend = dbend(dentry); + + hidden_dentry = ERR_PTR(-ENOMEM); + path = kzalloc(kmalloc_size, GFP_KERNEL); + if (!path) + goto out; + + /* assume the negative dentry of unionfs as the parent dentry */ + parent_dentry = dentry; + + count = 0; + /* This loop finds the first parent that exists in the given branch. + * We start building the directory structure from there. At the end + * of the loop, the following should hold: + * - child_dentry is the first nonexistent child + * - parent_dentry is the first existent parent + * - path[0] is the = deepest child + * - path[count] is the first child to create + */ + do { + child_dentry = parent_dentry; + + /* find the parent directory dentry in unionfs */ + parent_dentry = child_dentry->d_parent; + unionfs_lock_dentry(parent_dentry); + + /* find out the hidden_parent_dentry in the given branch */ + hidden_parent_dentry = unionfs_lower_dentry_idx(parent_dentry, bindex); + + /* store the child dentry */ + path[count++] = child_dentry; + + /* grow path table */ + if (count == num_dentry) { + old_kmalloc_size = kmalloc_size; + kmalloc_size *= 2; + num_dentry = kmalloc_size / sizeof(struct dentry *); + + tmp_path = kzalloc(kmalloc_size, GFP_KERNEL); + if (!tmp_path) { + hidden_dentry = ERR_PTR(-ENOMEM); + goto out; + } + memcpy(tmp_path, path, old_kmalloc_size); + kfree(path); + path = tmp_path; + tmp_path = NULL; + } + + } while (!hidden_parent_dentry); + count--; + + sb = dentry->d_sb; + + /* This is basically while(child_dentry != dentry). This loop is + * horrible to follow and should be replaced with cleaner code. + */ + while (1) { + /* get hidden parent dir in the current branch */ + hidden_parent_dentry = unionfs_lower_dentry_idx(parent_dentry, bindex); + unionfs_unlock_dentry(parent_dentry); + + /* init the values to lookup */ + childname = child_dentry->d_name.name; + childnamelen = child_dentry->d_name.len; + + if (child_dentry != dentry) { + /* lookup child in the underlying file system */ + hidden_dentry = + lookup_one_len(childname, hidden_parent_dentry, + childnamelen); + if (IS_ERR(hidden_dentry)) + goto out; + } else { + + /* is the name a whiteout of the childname ? + * lookup the whiteout child in the underlying file system + */ + hidden_dentry = + lookup_one_len(name, hidden_parent_dentry, + strlen(name)); + if (IS_ERR(hidden_dentry)) + goto out; + + /* Replace the current dentry (if any) with the new one. */ + dput(unionfs_lower_dentry_idx(dentry, bindex)); + unionfs_set_lower_dentry_idx(dentry, bindex, hidden_dentry); + + __cleanup_dentry(dentry, bindex, old_bstart, old_bend); + break; + } + + if (hidden_dentry->d_inode) { + /* since this already exists we dput to avoid + * multiple references on the same dentry + */ + dput(hidden_dentry); + } else { + struct sioq_args args; + + /* its a negative dentry, create a new dir */ + hidden_parent_dentry = lock_parent(hidden_dentry); + + args.mkdir.parent = hidden_parent_dentry->d_inode; + args.mkdir.dentry = hidden_dentry; + args.mkdir.mode = child_dentry->d_inode->i_mode; + + run_sioq(__unionfs_mkdir, &args); + err = args.err; + + if (!err) + err = copyup_permissions(dir->i_sb, + child_dentry, hidden_dentry); + unlock_dir(hidden_parent_dentry); + if (err) { + dput(hidden_dentry); + hidden_dentry = ERR_PTR(err); + goto out; + } + + } + + __set_inode(child_dentry, hidden_dentry, bindex); + __set_dentry(child_dentry, hidden_dentry, bindex); + + parent_dentry = child_dentry; + child_dentry = path[--count]; + } +out: + kfree(path); + return hidden_dentry; +} + diff --git a/fs/unionfs/dentry.c b/fs/unionfs/dentry.c new file mode 100644 index 0000000..9eb143d --- /dev/null +++ b/fs/unionfs/dentry.c @@ -0,0 +1,333 @@ +/* + * 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 State University of New York + * + * 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" + + +/* + * Revalidate a single dentry. + * Assume that dentry's info node is locked. + * Assume that parent(s) are all valid already, but + * the child may not yet be valid. + * Returns 1 if valid, 0 otherwise. + */ +static int __unionfs_d_revalidate_one(struct dentry *dentry, struct nameidata *nd) +{ + int valid = 1; /* default is valid (1); invalid is 0. */ + struct dentry *hidden_dentry; + int bindex, bstart, bend; + int sbgen, dgen; + int positive = 0; + int locked = 0; + int interpose_flag; + + struct nameidata lowernd; /* TODO: be gentler to the stack */ + + if (nd) + memcpy(&lowernd, nd, sizeof(struct nameidata)); + else + memset(&lowernd, 0, sizeof(struct nameidata)); + + verify_locked(dentry); + + /* if the dentry is unhashed, do NOT revalidate */ + if (d_deleted(dentry)) { + printk(KERN_DEBUG "unhashed dentry being revalidated: %*s\n", + dentry->d_name.len, dentry->d_name.name); + goto out; + } + + BUG_ON(dbstart(dentry) == -1); + if (dentry->d_inode) + positive = 1; + dgen = atomic_read(&UNIONFS_D(dentry)->generation); + sbgen = atomic_read(&UNIONFS_SB(dentry->d_sb)->generation); + /* If we are working on an unconnected dentry, then there is no + * revalidation to be done, because this file does not exist within the + * namespace, and Unionfs operates on the namespace, not data. + */ + if (sbgen != dgen) { + struct dentry *result; + int pdgen; + + /* The root entry should always be valid */ + 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 */ + + /* Free the pointers for our inodes and this dentry. */ + bstart = dbstart(dentry); + bend = dbend(dentry); + if (bstart >= 0) { + struct dentry *hidden_dentry; + for (bindex = bstart; bindex <= bend; bindex++) { + hidden_dentry = + unionfs_lower_dentry_idx(dentry, bindex); + dput(hidden_dentry); + } + } + set_dbstart(dentry, -1); + set_dbend(dentry, -1); + + interpose_flag = INTERPOSE_REVAL_NEG; + if (positive) { + interpose_flag = INTERPOSE_REVAL; + /* + * During BRM, the VFS could already hold a lock on + * a file being read, so don't lock it again + * (deadlock), but if you lock it in this function, + * then release it here too. + */ + if (!mutex_is_locked(&dentry->d_inode->i_mutex)) { + mutex_lock(&dentry->d_inode->i_mutex); + locked = 1; + } + + bstart = ibstart(dentry->d_inode); + bend = ibend(dentry->d_inode); + if (bstart >= 0) { + struct inode *hidden_inode; + for (bindex = bstart; bindex <= bend; bindex++) { + hidden_inode = + unionfs_lower_inode_idx(dentry->d_inode, + bindex); + iput(hidden_inode); + } + } + kfree(UNIONFS_I(dentry->d_inode)->lower_inodes); + UNIONFS_I(dentry->d_inode)->lower_inodes = NULL; + ibstart(dentry->d_inode) = -1; + ibend(dentry->d_inode) = -1; + if (locked) + mutex_unlock(&dentry->d_inode->i_mutex); + } + + result = unionfs_lookup_backend(dentry, &lowernd, interpose_flag); + if (result) { + if (IS_ERR(result)) { + valid = 0; + goto out; + } + /* current unionfs_lookup_backend() doesn't return + * a valid dentry + */ + dput(dentry); + dentry = result; + } + + if (positive && UNIONFS_I(dentry->d_inode)->stale) { + make_bad_inode(dentry->d_inode); + d_drop(dentry); + valid = 0; + goto out; + } + goto out; + } + + /* The revalidation must occur across all branches */ + bstart = dbstart(dentry); + bend = dbend(dentry); + BUG_ON(bstart == -1); + for (bindex = bstart; bindex <= bend; bindex++) { + hidden_dentry = unionfs_lower_dentry_idx(dentry, bindex); + if (!hidden_dentry || !hidden_dentry->d_op + || !hidden_dentry->d_op->d_revalidate) + continue; + if (!hidden_dentry->d_op->d_revalidate(hidden_dentry, &lowernd)) + valid = 0; + } + + if (!dentry->d_inode) + valid = 0; + + if (valid) { + fsstack_copy_attr_all(dentry->d_inode, + unionfs_lower_inode(dentry->d_inode), + unionfs_get_nlinks); + fsstack_copy_inode_size(dentry->d_inode, + unionfs_lower_inode(dentry->d_inode)); + } + +out: + return valid; +} + +/* + * Revalidate a parent chain of dentries, then the actual node. + * Assumes that dentry is locked, but will lock all parents if/when needed. + */ +int __unionfs_d_revalidate_chain(struct dentry *dentry, struct nameidata *nd) +{ + int valid = 0; /* default is invalid (0); valid is 1. */ + 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; + dgen = atomic_read(&UNIONFS_D(dtmp)->generation); + 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 (!chain) { + printk("unionfs: no more memory in %s\n", __FUNCTION__); + goto out; + } + + /* + * lock all dentries in chain, in child to parent order. + * if failed, then sleep for a little, then retry. + */ + dtmp = dentry->d_parent; + for (i=chain_len-1; i>=0; i--) { + chain[i] = dget(dtmp); + dtmp = dtmp->d_parent; + } + + /* + * call __unionfs_d_revalidate() on each dentry, but in parent to + * child order. + */ + for (i=0; id_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); + } + unionfs_unlock_dentry(chain[i]); + + if (!valid) { + goto out_free; + } + } + + + out_this: + /* finally, lock this dentry and revalidate it */ + verify_locked(dentry); + dgen = atomic_read(&UNIONFS_D(dentry)->generation); + saved_bstart = dbstart(dentry); + saved_bend = dbend(dentry); + valid = __unionfs_d_revalidate_one(dentry, nd); + + if (valid && chain_len > 0 && sbgen != dgen) { + for (bindex = saved_bstart; bindex <= saved_bend; bindex++) + unionfs_mntput(dentry, bindex); + } + + out_free: + /* unlock/dput all dentries in chain and return status */ + if (chain_len > 0) { + for (i=0; id_name.len, dentry->d_name.name); + goto out; + } else if (dbstart(dentry) < 0) { + /* this is due to a failed lookup */ + printk(KERN_DEBUG "dentry without hidden dentries : %.*s", + dentry->d_name.len, dentry->d_name.name); + goto out_free; + } + + /* Release all the hidden dentries */ + bstart = dbstart(dentry); + bend = dbend(dentry); + for (bindex = bstart; bindex <= bend; bindex++) { + dput(unionfs_lower_dentry_idx(dentry, bindex)); + unionfs_mntput(dentry, bindex); + + unionfs_set_lower_dentry_idx(dentry, bindex, NULL); + unionfs_set_lower_mnt_idx(dentry, bindex, NULL); + } + /* free private data (unionfs_dentry_info) here */ + kfree(UNIONFS_D(dentry)->lower_paths); + UNIONFS_D(dentry)->lower_paths = NULL; + +out_free: + /* No need to unlock it, because it is disappeared. */ + free_dentry_private_data(UNIONFS_D(dentry)); + dentry->d_fsdata = NULL; /* just to be safe */ + +out: + return; +} + +struct dentry_operations unionfs_dops = { + .d_revalidate = unionfs_d_revalidate, + .d_release = unionfs_d_release, +}; + diff --git a/fs/unionfs/dirfops.c b/fs/unionfs/dirfops.c new file mode 100644 index 0000000..6ff32a0 --- /dev/null +++ b/fs/unionfs/dirfops.c @@ -0,0 +1,267 @@ +/* + * 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 State University of New York + * + * 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" + +/* Make sure our rdstate is playing by the rules. */ +static void verify_rdstate_offset(struct unionfs_dir_state *rdstate) +{ + BUG_ON(rdstate->offset >= DIREOF); + BUG_ON(rdstate->cookie >= MAXRDCOOKIE); +} + +struct unionfs_getdents_callback { + struct unionfs_dir_state *rdstate; + void *dirent; + int entries_written; + int filldir_called; + int filldir_error; + filldir_t filldir; + struct super_block *sb; +}; + +/* based on generic filldir in fs/readir.c */ +static int unionfs_filldir(void *dirent, const char *name, 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; + + buf->filldir_called++; + + if ((namelen > UNIONFS_WHLEN) && + !strncmp(name, UNIONFS_WHPFX, UNIONFS_WHLEN)) { + name += UNIONFS_WHLEN; + namelen -= UNIONFS_WHLEN; + is_wh_entry = 1; + } + + found = find_filldir_node(buf->rdstate, name, namelen); + + if (found) + goto out; + + /* if 'name' isn't a whiteout, filldir it. */ + if (!is_wh_entry) { + off_t pos = rdstate2offset(buf->rdstate); + u64 unionfs_ino = ino; + + if (!err) { + err = buf->filldir(buf->dirent, name, namelen, pos, + unionfs_ino, d_type); + buf->rdstate->offset++; + verify_rdstate_offset(buf->rdstate); + } + } + /* If we did fill it, stuff it in our hash, otherwise return an error */ + if (err) { + buf->filldir_error = err; + goto out; + } + buf->entries_written++; + if ((err = add_filldir_node(buf->rdstate, name, namelen, + buf->rdstate->bindex, is_wh_entry))) + buf->filldir_error = err; + +out: + return err; +} + +static int unionfs_readdir(struct file *file, void *dirent, filldir_t filldir) +{ + int err = 0; + struct file *hidden_file = NULL; + struct inode *inode = NULL; + struct unionfs_getdents_callback buf; + struct unionfs_dir_state *uds; + int bend; + loff_t offset; + + unionfs_read_lock(file->f_dentry->d_sb); + if ((err = unionfs_file_revalidate(file, 0))) + goto out; + + inode = file->f_dentry->d_inode; + + uds = UNIONFS_F(file)->rdstate; + if (!uds) { + if (file->f_pos == DIREOF) { + goto out; + } else if (file->f_pos > 0) { + uds = find_rdstate(inode, file->f_pos); + if (!uds) { + err = -ESTALE; + goto out; + } + UNIONFS_F(file)->rdstate = uds; + } else { + init_rdstate(file); + uds = UNIONFS_F(file)->rdstate; + } + } + bend = fbend(file); + + while (uds->bindex <= bend) { + hidden_file = unionfs_lower_file_idx(file, uds->bindex); + if (!hidden_file) { + uds->bindex++; + uds->dirpos = 0; + continue; + } + + /* prepare callback buffer */ + buf.filldir_called = 0; + buf.filldir_error = 0; + buf.entries_written = 0; + buf.dirent = dirent; + buf.filldir = filldir; + buf.rdstate = uds; + buf.sb = inode->i_sb; + + /* Read starting from where we last left off. */ + offset = vfs_llseek(hidden_file, uds->dirpos, SEEK_SET); + if (offset < 0) { + err = offset; + goto out; + } + err = vfs_readdir(hidden_file, unionfs_filldir, &buf); + + /* Save the position for when we continue. */ + offset = vfs_llseek(hidden_file, 0, SEEK_CUR); + if (offset < 0) { + err = offset; + goto out; + } + uds->dirpos = offset; + + /* Copy the atime. */ + fsstack_copy_attr_atime(inode, hidden_file->f_dentry->d_inode); + + if (err < 0) + goto out; + + if (buf.filldir_error) + break; + + if (!buf.entries_written) { + uds->bindex++; + uds->dirpos = 0; + } + } + + if (!buf.filldir_error && uds->bindex >= bend) { + /* Save the number of hash entries for next time. */ + UNIONFS_I(inode)->hashsize = uds->hashentries; + free_rdstate(uds); + UNIONFS_F(file)->rdstate = NULL; + file->f_pos = DIREOF; + } else + file->f_pos = rdstate2offset(uds); + +out: + unionfs_read_unlock(file->f_dentry->d_sb); + return err; +} + +/* This is not meant to be a generic repositioning function. If you do + * things that aren't supported, then we return EINVAL. + * + * What is allowed: + * (1) seeking to the same position that you are currently at + * This really has no effect, but returns where you are. + * (2) seeking to the beginning of the file + * This throws out all state, and lets you begin again. + */ +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; + + rdstate = UNIONFS_F(file)->rdstate; + + /* We let users seek to their current position, but not anywhere else. */ + if (!offset) { + switch (origin) { + case SEEK_SET: + if (rdstate) { + free_rdstate(rdstate); + UNIONFS_F(file)->rdstate = NULL; + } + init_rdstate(file); + err = 0; + break; + case SEEK_CUR: + err = file->f_pos; + break; + case SEEK_END: + /* Unsupported, because we would break everything. */ + err = -EINVAL; + break; + } + } else { + switch (origin) { + case SEEK_SET: + if (rdstate) { + if (offset == rdstate2offset(rdstate)) + err = offset; + else if (file->f_pos == DIREOF) + err = DIREOF; + else + err = -EINVAL; + } else { + rdstate = find_rdstate(file->f_dentry->d_inode, + offset); + if (rdstate) { + UNIONFS_F(file)->rdstate = rdstate; + err = rdstate->offset; + } else + err = -EINVAL; + } + break; + case SEEK_CUR: + case SEEK_END: + /* Unsupported, because we would break everything. */ + err = -EINVAL; + break; + } + } + +out: + unionfs_read_unlock(file->f_dentry->d_sb); + return err; +} + +/* Trimmed directory options, we shouldn't pass everything down since + * we don't want to operate on partial directories. + */ +struct file_operations unionfs_dir_fops = { + .llseek = unionfs_dir_llseek, + .read = generic_read_dir, + .readdir = unionfs_readdir, + .unlocked_ioctl = unionfs_ioctl, + .open = unionfs_open, + .release = unionfs_file_release, + .flush = unionfs_flush, +}; + diff --git a/fs/unionfs/dirhelper.c b/fs/unionfs/dirhelper.c new file mode 100644 index 0000000..bd15eb4 --- /dev/null +++ b/fs/unionfs/dirhelper.c @@ -0,0 +1,276 @@ +/* + * 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 State University of New York + * + * 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" + +/* Delete all of the whiteouts in a given directory for rmdir. + * + * hidden directory inode should be locked + */ +int do_delete_whiteouts(struct dentry *dentry, int bindex, + struct unionfs_dir_state *namelist) +{ + int err = 0; + struct dentry *hidden_dir_dentry = NULL; + struct dentry *hidden_dentry; + char *name = NULL, *p; + struct inode *hidden_dir; + + int i; + struct list_head *pos; + struct filldir_node *cursor; + + /* Find out hidden parent dentry */ + hidden_dir_dentry = unionfs_lower_dentry_idx(dentry, bindex); + BUG_ON(!S_ISDIR(hidden_dir_dentry->d_inode->i_mode)); + hidden_dir = hidden_dir_dentry->d_inode; + BUG_ON(!S_ISDIR(hidden_dir->i_mode)); + + err = -ENOMEM; + name = __getname(); + if (!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); + hidden_dentry = + lookup_one_len(name, hidden_dir_dentry, + cursor->namelen + UNIONFS_WHLEN); + if (IS_ERR(hidden_dentry)) { + err = PTR_ERR(hidden_dentry); + break; + } + if (hidden_dentry->d_inode) + err = vfs_unlink(hidden_dir, hidden_dentry); + dput(hidden_dentry); + if (err) + break; + } + } + + __putname(name); + + /* After all of the removals, we should copy the attributes once. */ + fsstack_copy_attr_times(dentry->d_inode, hidden_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 *hidden_dir_dentry; + struct inode *hidden_dir; + + struct sioq_args args; + + sb = dentry->d_sb; + unionfs_read_lock(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; + + hidden_dir_dentry = unionfs_lower_dentry_idx(dentry, bindex); + BUG_ON(!S_ISDIR(hidden_dir_dentry->d_inode->i_mode)); + hidden_dir = hidden_dir_dentry->d_inode; + BUG_ON(!S_ISDIR(hidden_dir->i_mode)); + + mutex_lock(&hidden_dir->i_mutex); + if (!permission(hidden_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; + } + mutex_unlock(&hidden_dir->i_mutex); + +out: + unionfs_read_unlock(sb); + return err; +} + +#define RD_NONE 0 +#define RD_CHECK_EMPTY 1 +/* The callback structure for check_empty. */ +struct unionfs_rdutil_callback { + int err; + int filldir_called; + struct unionfs_dir_state *rdstate; + int mode; +}; + +/* This filldir function makes sure only whiteouts exist within a directory. */ +static int readdir_util_callback(void *dirent, const char *name, int namelen, + loff_t offset, u64 ino, unsigned int d_type) +{ + int err = 0; + struct unionfs_rdutil_callback *buf = dirent; + int whiteout = 0; + struct filldir_node *found; + + buf->filldir_called = 1; + + if (name[0] == '.' && (namelen == 1 || (name[1] == '.' && namelen == 2))) + goto out; + + if (namelen > UNIONFS_WHLEN && + !strncmp(name, UNIONFS_WHPFX, UNIONFS_WHLEN)) { + namelen -= UNIONFS_WHLEN; + name += UNIONFS_WHLEN; + whiteout = 1; + } + + found = find_filldir_node(buf->rdstate, name, namelen); + /* If it was found in the table there was a previous whiteout. */ + if (found) + goto out; + + /* If it wasn't found and isn't a whiteout, the directory isn't empty. */ + err = -ENOTEMPTY; + if ((buf->mode == RD_CHECK_EMPTY) && !whiteout) + goto out; + + err = add_filldir_node(buf->rdstate, name, namelen, + buf->rdstate->bindex, whiteout); + +out: + buf->err = err; + return err; +} + +/* Is a directory logically empty? */ +int check_empty(struct dentry *dentry, struct unionfs_dir_state **namelist) +{ + int err = 0; + struct dentry *hidden_dentry = NULL; + struct super_block *sb; + struct file *hidden_file; + struct unionfs_rdutil_callback *buf = NULL; + int bindex, bstart, bend, bopaque; + + sb = dentry->d_sb; + + unionfs_read_lock(sb); + + BUG_ON(!S_ISDIR(dentry->d_inode->i_mode)); + + if ((err = unionfs_partial_lookup(dentry))) + goto out; + + bstart = dbstart(dentry); + bend = dbend(dentry); + bopaque = dbopaque(dentry); + if (0 <= bopaque && bopaque < bend) + bend = bopaque; + + buf = kmalloc(sizeof(struct unionfs_rdutil_callback), GFP_KERNEL); + if (!buf) { + err = -ENOMEM; + goto out; + } + buf->err = 0; + buf->mode = RD_CHECK_EMPTY; + buf->rdstate = alloc_rdstate(dentry->d_inode, bstart); + if (!buf->rdstate) { + err = -ENOMEM; + goto out; + } + + /* Process the hidden directories with rdutil_callback as a filldir. */ + for (bindex = bstart; bindex <= bend; bindex++) { + hidden_dentry = unionfs_lower_dentry_idx(dentry, bindex); + if (!hidden_dentry) + continue; + if (!hidden_dentry->d_inode) + continue; + if (!S_ISDIR(hidden_dentry->d_inode->i_mode)) + continue; + + 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; + } + + do { + buf->filldir_called = 0; + buf->rdstate->bindex = bindex; + err = vfs_readdir(hidden_file, + readdir_util_callback, buf); + if (buf->err) + err = buf->err; + } while ((err >= 0) && buf->filldir_called); + + /* 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; + } + +out: + if (buf) { + if (namelist && !err) + *namelist = buf->rdstate; + else if (buf->rdstate) + free_rdstate(buf->rdstate); + kfree(buf); + } + + unionfs_read_unlock(sb); + + return err; +} + diff --git a/fs/unionfs/fanout.h b/fs/unionfs/fanout.h new file mode 100644 index 0000000..9e4a35f --- /dev/null +++ b/fs/unionfs/fanout.h @@ -0,0 +1,231 @@ +/* + * Copyright (c) 2003-2007 Erez Zadok + * Copyright (c) 2003-2006 Charles P. Wright + * Copyright (c) 2005-2007 Josef 'Jeff' Sipek + * 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 State University of New York + * + * 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. + */ + +#ifndef _FANOUT_H_ +#define _FANOUT_H_ + +/* Inode to private data */ +static inline struct unionfs_inode_info *UNIONFS_I(const struct inode *inode) +{ + return container_of(inode, struct unionfs_inode_info, vfs_inode); +} + +#define ibstart(ino) (UNIONFS_I(ino)->bstart) +#define ibend(ino) (UNIONFS_I(ino)->bend) + +/* Superblock to private data */ +#define UNIONFS_SB(super) ((struct unionfs_sb_info *)(super)->s_fs_info) +#define sbstart(sb) 0 +#define sbend(sb) (UNIONFS_SB(sb)->bend) +#define sbmax(sb) (UNIONFS_SB(sb)->bend + 1) +#define sbhbid(sb) (UNIONFS_SB(sb)->high_branch_id) + +/* File to private Data */ +#define UNIONFS_F(file) ((struct unionfs_file_info *)((file)->private_data)) +#define fbstart(file) (UNIONFS_F(file)->bstart) +#define fbend(file) (UNIONFS_F(file)->bend) + +/* macros to manipulate branch IDs in stored in our superblock */ +static inline int branch_id(struct super_block *sb, int index) +{ + return UNIONFS_SB(sb)->data[index].branch_id; +} + +static inline void set_branch_id(struct super_block *sb, int index, int val) +{ + UNIONFS_SB(sb)->data[index].branch_id = val; +} + +static inline void new_branch_id(struct super_block *sb, int index) +{ + set_branch_id(sb, index, ++UNIONFS_SB(sb)->high_branch_id); +} + +/* File to lower file. */ +static inline struct file *unionfs_lower_file(const struct file *f) +{ + return UNIONFS_F(f)->lower_files[fbstart(f)]; +} + +static inline struct file *unionfs_lower_file_idx(const struct file *f, int index) +{ + return UNIONFS_F(f)->lower_files[index]; +} + +static inline void unionfs_set_lower_file_idx(struct file *f, int index, struct file *val) +{ + UNIONFS_F(f)->lower_files[index] = val; + /* save branch ID (may be redundant?) */ + UNIONFS_F(f)->saved_branch_ids[index] = + branch_id((f)->f_dentry->d_sb, index); +} + +static inline void unionfs_set_lower_file(struct file *f, struct file *val) +{ + unionfs_set_lower_file_idx((f), fbstart(f), (val)); +} + +/* Inode to lower inode. */ +static inline struct inode *unionfs_lower_inode(const struct inode *i) +{ + return UNIONFS_I(i)->lower_inodes[ibstart(i)]; +} + +static inline struct inode *unionfs_lower_inode_idx(const struct inode *i, int index) +{ + return UNIONFS_I(i)->lower_inodes[index]; +} + +static inline void unionfs_set_lower_inode_idx(struct inode *i, int index, + struct inode *val) +{ + UNIONFS_I(i)->lower_inodes[index] = val; +} + +static inline void unionfs_set_lower_inode(struct inode *i, struct inode *val) +{ + UNIONFS_I(i)->lower_inodes[ibstart(i)] = val; +} + +/* Superblock to lower superblock. */ +static inline struct super_block *unionfs_lower_super(const struct super_block *sb) +{ + return UNIONFS_SB(sb)->data[sbstart(sb)].sb; +} + +static inline struct super_block *unionfs_lower_super_idx(const struct super_block *sb, int index) +{ + return UNIONFS_SB(sb)->data[index].sb; +} + +static inline void unionfs_set_lower_super_idx(struct super_block *sb, int index, + struct super_block *val) +{ + UNIONFS_SB(sb)->data[index].sb = val; +} + +static inline void unionfs_set_lower_super(struct super_block *sb, struct super_block *val) +{ + UNIONFS_SB(sb)->data[sbstart(sb)].sb = val; +} + +/* Branch count macros. */ +static inline int branch_count(const struct super_block *sb, int index) +{ + return atomic_read(&UNIONFS_SB(sb)->data[index].open_files); +} + +static inline void set_branch_count(struct super_block *sb, int index, int val) +{ + atomic_set(&UNIONFS_SB(sb)->data[index].open_files, val); +} + +static inline void branchget(struct super_block *sb, int index) +{ + atomic_inc(&UNIONFS_SB(sb)->data[index].open_files); +} + +static inline void branchput(struct super_block *sb, int index) +{ + atomic_dec(&UNIONFS_SB(sb)->data[index].open_files); +} + +/* Dentry macros */ +static inline struct unionfs_dentry_info *UNIONFS_D(const struct dentry *dent) +{ + return dent->d_fsdata; +} + +static inline int dbstart(const struct dentry *dent) +{ + return UNIONFS_D(dent)->bstart; +} + +static inline void set_dbstart(struct dentry *dent, int val) +{ + UNIONFS_D(dent)->bstart = val; +} + +static inline int dbend(const struct dentry *dent) +{ + return UNIONFS_D(dent)->bend; +} + +static inline void set_dbend(struct dentry *dent, int val) +{ + UNIONFS_D(dent)->bend = val; +} + +static inline int dbopaque(const struct dentry *dent) +{ + return UNIONFS_D(dent)->bopaque; +} + +static inline void set_dbopaque(struct dentry *dent, int val) +{ + UNIONFS_D(dent)->bopaque = val; +} + +static inline void unionfs_set_lower_dentry_idx(struct dentry *dent, int index, + struct dentry *val) +{ + UNIONFS_D(dent)->lower_paths[index].dentry = val; +} + +static inline struct dentry *unionfs_lower_dentry_idx(const struct dentry *dent, int index) +{ + return UNIONFS_D(dent)->lower_paths[index].dentry; +} + +static inline struct dentry *unionfs_lower_dentry(const struct dentry *dent) +{ + return unionfs_lower_dentry_idx(dent, dbstart(dent)); +} + +static inline void unionfs_set_lower_mnt_idx(struct dentry *dent, int index, + struct vfsmount *mnt) +{ + UNIONFS_D(dent)->lower_paths[index].mnt = mnt; +} + +static inline struct vfsmount *unionfs_lower_mnt_idx(const struct dentry *dent, int index) +{ + return UNIONFS_D(dent)->lower_paths[index].mnt; +} + +static inline struct vfsmount *unionfs_lower_mnt(const struct dentry *dent) +{ + return unionfs_lower_mnt_idx(dent,dbstart(dent)); +} + +/* Macros for locking a dentry. */ +static inline void unionfs_lock_dentry(struct dentry *d) +{ + mutex_lock(&UNIONFS_D(d)->lock); +} + +static inline void unionfs_unlock_dentry(struct dentry *d) +{ + mutex_unlock(&UNIONFS_D(d)->lock); +} + +static inline void verify_locked(struct dentry *d) +{ + BUG_ON(!mutex_is_locked(&UNIONFS_D(d)->lock)); +} + +#endif /* _FANOUT_H */ diff --git a/fs/unionfs/file.c b/fs/unionfs/file.c new file mode 100644 index 0000000..84d6bab --- /dev/null +++ b/fs/unionfs/file.c @@ -0,0 +1,266 @@ +/* + * 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 State University of New York + * + * 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" + +/******************* + * File Operations * + *******************/ + +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; + + hidden_file = unionfs_lower_file(file); + /* always set hidden position to this one */ + hidden_file->f_pos = file->f_pos; + + memcpy(&hidden_file->f_ra, &file->f_ra, sizeof(struct file_ra_state)); + + if (hidden_file->f_op && hidden_file->f_op->llseek) + err = hidden_file->f_op->llseek(hidden_file, offset, origin); + else + err = generic_file_llseek(hidden_file, offset, origin); + + if (err < 0) + goto out; + if (err != file->f_pos) { + file->f_pos = err; + file->f_version++; + } +out: + unionfs_read_unlock(file->f_dentry->d_sb); + return err; +} + +static ssize_t unionfs_read(struct file * file, char __user * buf, + size_t count, loff_t * ppos) +{ + struct file *hidden_file; + loff_t pos = *ppos; + int err; + + unionfs_read_lock(file->f_dentry->d_sb); + if ((err = unionfs_file_revalidate(file, 0))) + goto out; + + err = -EINVAL; + hidden_file = unionfs_lower_file(file); + if (!hidden_file->f_op || !hidden_file->f_op->read) + goto out; + + err = hidden_file->f_op->read(hidden_file, buf, count, &pos); + *ppos = pos; + +out: + unionfs_read_unlock(file->f_dentry->d_sb); + return err; +} + +static ssize_t __unionfs_write(struct file * file, const char __user * buf, + size_t count, loff_t * ppos) +{ + int err = -EINVAL; + struct file *hidden_file = NULL; + struct inode *inode; + struct inode *hidden_inode; + loff_t pos = *ppos; + int bstart, bend; + + inode = file->f_dentry->d_inode; + + bstart = fbstart(file); + bend = fbend(file); + + BUG_ON(bstart == -1); + + hidden_file = unionfs_lower_file(file); + hidden_inode = hidden_file->f_dentry->d_inode; + + if (!hidden_file->f_op || !hidden_file->f_op->write) + goto out; + + /* adjust for append -- seek to the end of the file */ + if (file->f_flags & O_APPEND) + pos = inode->i_size; + + err = hidden_file->f_op->write(hidden_file, buf, count, &pos); + + /* + * copy ctime and mtime from lower layer attributes + * atime is unchanged for both layers + */ + if (err >= 0) + fsstack_copy_attr_times(inode, hidden_inode); + + *ppos = pos; + + /* update this inode's size */ + if (pos > inode->i_size) + inode->i_size = pos; +out: + return err; +} + +static ssize_t unionfs_write(struct file * file, const char __user * buf, + size_t count, loff_t * ppos) +{ + 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; +} + +static int unionfs_file_readdir(struct file *file, void *dirent, + filldir_t filldir) +{ + return -ENOTDIR; +} + +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; + goto out; + } + + hidden_file = unionfs_lower_file(file); + + if (!hidden_file->f_op || !hidden_file->f_op->poll) + goto out; + + mask = hidden_file->f_op->poll(hidden_file, wait); + +out: + unionfs_read_unlock(file->f_dentry->d_sb); + return mask; +} + +static int __do_mmap(struct file *file, struct vm_area_struct *vma) +{ + int err; + struct file *hidden_file; + + hidden_file = unionfs_lower_file(file); + + err = -ENODEV; + if (!hidden_file->f_op || !hidden_file->f_op->mmap) + goto out; + + vma->vm_file = hidden_file; + err = hidden_file->f_op->mmap(hidden_file, vma); + get_file(hidden_file); /* make sure it doesn't get freed on us */ + fput(file); /* no need to keep extra ref on ours */ +out: + return err; +} + +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))) + goto out; + + err = __do_mmap(file, vma); + +out: + unionfs_read_unlock(file->f_dentry->d_sb); + return err; +} + +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; + + hidden_file = unionfs_lower_file(file); + + err = -EINVAL; + if (!hidden_file->f_op || !hidden_file->f_op->fsync) + goto out; + + mutex_lock(&hidden_file->f_dentry->d_inode->i_mutex); + err = hidden_file->f_op->fsync(hidden_file, hidden_file->f_dentry, + datasync); + mutex_unlock(&hidden_file->f_dentry->d_inode->i_mutex); + +out: + unionfs_read_unlock(file->f_dentry->d_sb); + return err; +} + +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; + + hidden_file = unionfs_lower_file(file); + + if (hidden_file->f_op && hidden_file->f_op->fasync) + err = hidden_file->f_op->fasync(fd, hidden_file, flag); + +out: + unionfs_read_unlock(file->f_dentry->d_sb); + return err; +} + +struct file_operations unionfs_main_fops = { + .llseek = unionfs_llseek, + .read = unionfs_read, + .write = unionfs_write, + .readdir = unionfs_file_readdir, + .poll = unionfs_poll, + .unlocked_ioctl = unionfs_ioctl, + .mmap = unionfs_mmap, + .open = unionfs_open, + .flush = unionfs_flush, + .release = unionfs_file_release, + .fsync = unionfs_fsync, + .fasync = unionfs_fasync, +}; + diff --git a/fs/unionfs/inode.c b/fs/unionfs/inode.c new file mode 100644 index 0000000..97dad8c --- /dev/null +++ b/fs/unionfs/inode.c @@ -0,0 +1,1016 @@ +/* + * 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 State University of New York + * + * 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" + +static int unionfs_create(struct inode *parent, struct dentry *dentry, + int mode, struct nameidata *nd) +{ + int err = 0; + struct dentry *hidden_dentry = NULL; + struct dentry *wh_dentry = NULL; + struct dentry *new_hidden_dentry; + struct dentry *hidden_parent_dentry = NULL; + int bindex = 0, bstart; + char *name = NULL; + int valid = 0; + + /* + * We have to read-lock the superblock rwsem, and we have to + * revalidate the parent dentry and this one. A branch-management + * operation could have taken place, mid-way through a VFS operation + * that eventually reaches here. So we have to ensure consistency, + * just as we do with the file operations. + * + * XXX: we may need to do this for all other inode ops that take a + * dentry. + */ + unionfs_read_lock(dentry->d_sb); + unionfs_lock_dentry(dentry); + + unionfs_lock_dentry(dentry->d_parent); + valid = __unionfs_d_revalidate_chain(dentry->d_parent, nd); + unionfs_unlock_dentry(dentry->d_parent); + if (!valid) { + err = -ENOENT; /* same as what real_lookup does */ + goto out; + } + valid = __unionfs_d_revalidate_chain(dentry, nd); + /* + * 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); + + /* We start out in the leftmost branch. */ + bstart = dbstart(dentry); + hidden_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 (IS_ERR(name)) { + err = PTR_ERR(name); + goto out; + } + + wh_dentry = lookup_one_len(name, hidden_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) { + /* .wh.foo has been found. */ + /* First truncate it and then rename it to foo (hence having + * the same overall effect as a normal create. + */ + struct dentry *hidden_dir_dentry; + struct iattr newattrs; + + mutex_lock(&wh_dentry->d_inode->i_mutex); + newattrs.ia_valid = ATTR_CTIME | ATTR_MODE | ATTR_ATIME + | ATTR_MTIME | ATTR_UID | ATTR_GID | ATTR_FORCE + | ATTR_KILL_SUID | ATTR_KILL_SGID; + + newattrs.ia_mode = mode & ~current->fs->umask; + newattrs.ia_uid = current->fsuid; + newattrs.ia_gid = current->fsgid; + + if (wh_dentry->d_inode->i_size != 0) { + newattrs.ia_valid |= ATTR_SIZE; + newattrs.ia_size = 0; + } + + err = notify_change(wh_dentry, &newattrs); + + mutex_unlock(&wh_dentry->d_inode->i_mutex); + + if (err) + printk(KERN_WARNING "unionfs: %s:%d: notify_change " + "failed: %d, ignoring..\n", + __FILE__, __LINE__, err); + + new_hidden_dentry = unionfs_lower_dentry(dentry); + dget(new_hidden_dentry); + + hidden_dir_dentry = dget_parent(wh_dentry); + lock_rename(hidden_dir_dentry, hidden_dir_dentry); + + if (!(err = is_robranch_super(dentry->d_sb, bstart))) { + err = vfs_rename(hidden_dir_dentry->d_inode, + wh_dentry, + hidden_dir_dentry->d_inode, + new_hidden_dentry); + } + if (!err) { + fsstack_copy_attr_times(parent, + new_hidden_dentry->d_parent->d_inode); + fsstack_copy_inode_size(parent, + new_hidden_dentry->d_parent->d_inode); + parent->i_nlink = unionfs_get_nlinks(parent); + } + + unlock_rename(hidden_dir_dentry, hidden_dir_dentry); + dput(hidden_dir_dentry); + + dput(new_hidden_dentry); + + if (err) { + /* exit if the error returned was NOT -EROFS */ + if (!IS_COPYUP_ERR(err)) + goto out; + /* We were not able to create the file in this + * branch, so, we try to create it in one branch to + * left + */ + bstart--; + } else { + /* reset the unionfs dentry to point to the .wh.foo + * entry. + */ + + /* Discard any old reference. */ + dput(unionfs_lower_dentry(dentry)); + + /* Trade one reference to another. */ + unionfs_set_lower_dentry_idx(dentry, bstart, wh_dentry); + wh_dentry = NULL; + + err = unionfs_interpose(dentry, parent->i_sb, 0); + goto out; + } + } + + for (bindex = bstart; bindex >= 0; bindex--) { + hidden_dentry = unionfs_lower_dentry_idx(dentry, bindex); + if (!hidden_dentry) { + /* if hidden_dentry is NULL, create the entire + * dentry directory structure in branch 'bindex'. + * hidden_dentry will NOT be null when bindex == bstart + * because lookup passed as a negative unionfs dentry + * pointing to a lone negative underlying dentry */ + hidden_dentry = create_parents(parent, dentry, bindex); + if (!hidden_dentry || IS_ERR(hidden_dentry)) { + if (IS_ERR(hidden_dentry)) + err = PTR_ERR(hidden_dentry); + continue; + } + } + + hidden_parent_dentry = lock_parent(hidden_dentry); + if (IS_ERR(hidden_parent_dentry)) { + err = PTR_ERR(hidden_parent_dentry); + goto out; + } + /* We shouldn't create things in a read-only branch. */ + if (!(err = is_robranch_super(dentry->d_sb, bindex))) + err = vfs_create(hidden_parent_dentry->d_inode, + hidden_dentry, mode, nd); + + if (err || !hidden_dentry->d_inode) { + unlock_dir(hidden_parent_dentry); + + /* break out of for loop if the error wasn't -EROFS */ + if (!IS_COPYUP_ERR(err)) + break; + } else { + err = unionfs_interpose(dentry, parent->i_sb, 0); + if (!err) { + fsstack_copy_attr_times(parent, + hidden_parent_dentry->d_inode); + fsstack_copy_inode_size(parent, + hidden_parent_dentry->d_inode); + /* update number of links on parent directory */ + parent->i_nlink = unionfs_get_nlinks(parent); + } + unlock_dir(hidden_parent_dentry); + break; + } + } + +out: + dput(wh_dentry); + kfree(name); + + unionfs_unlock_dentry(dentry); + unionfs_read_unlock(dentry->d_sb); + return err; +} + +static struct dentry *unionfs_lookup(struct inode *parent, + struct dentry *dentry, + struct nameidata *nd) +{ + struct path path_save; + struct dentry *ret; + + /* save the dentry & vfsmnt from namei */ + if (nd) { + path_save.dentry = nd->dentry; + path_save.mnt = nd->mnt; + } + + /* The locking is done by unionfs_lookup_backend. */ + ret = unionfs_lookup_backend(dentry, nd, INTERPOSE_LOOKUP); + + /* restore the dentry & vfsmnt in namei */ + if (nd) { + nd->dentry = path_save.dentry; + nd->mnt = path_save.mnt; + } + + return ret; +} + +static int unionfs_link(struct dentry *old_dentry, struct inode *dir, + struct dentry *new_dentry) +{ + int err = 0; + struct dentry *hidden_old_dentry = NULL; + struct dentry *hidden_new_dentry = NULL; + struct dentry *hidden_dir_dentry = NULL; + struct dentry *whiteout_dentry; + char *name = NULL; + + BUG_ON(!is_valid_dentry(new_dentry)); + BUG_ON(!is_valid_dentry(old_dentry)); + + double_lock_dentry(new_dentry, old_dentry); + + hidden_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 (IS_ERR(name)) { + err = PTR_ERR(name); + goto out; + } + + whiteout_dentry = lookup_one_len(name, hidden_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() */ + hidden_dir_dentry = lock_parent(whiteout_dentry); + err = is_robranch_super(new_dentry->d_sb, dbstart(new_dentry)); + if (!err) + err = vfs_unlink(hidden_dir_dentry->d_inode, + whiteout_dentry); + + fsstack_copy_attr_times(dir, hidden_dir_dentry->d_inode); + dir->i_nlink = unionfs_get_nlinks(dir); + unlock_dir(hidden_dir_dentry); + hidden_dir_dentry = NULL; + dput(whiteout_dentry); + if (err) + goto out; + } + + if (dbstart(old_dentry) != dbstart(new_dentry)) { + hidden_new_dentry = + create_parents(dir, new_dentry, dbstart(old_dentry)); + err = PTR_ERR(hidden_new_dentry); + if (IS_COPYUP_ERR(err)) + goto docopyup; + if (!hidden_new_dentry || IS_ERR(hidden_new_dentry)) + goto out; + } + hidden_new_dentry = unionfs_lower_dentry(new_dentry); + hidden_old_dentry = unionfs_lower_dentry(old_dentry); + + BUG_ON(dbstart(old_dentry) != dbstart(new_dentry)); + hidden_dir_dentry = lock_parent(hidden_new_dentry); + if (!(err = is_robranch(old_dentry))) + err = vfs_link(hidden_old_dentry, hidden_dir_dentry->d_inode, + hidden_new_dentry); + unlock_dir(hidden_dir_dentry); + +docopyup: + if (IS_COPYUP_ERR(err)) { + int old_bstart = dbstart(old_dentry); + int bindex; + + for (bindex = old_bstart - 1; bindex >= 0; bindex--) { + err = + copyup_dentry(old_dentry->d_parent-> + d_inode, old_dentry, + old_bstart, bindex, NULL, + old_dentry->d_inode->i_size); + if (!err) { + hidden_new_dentry = + create_parents(dir, new_dentry, bindex); + hidden_old_dentry = unionfs_lower_dentry(old_dentry); + hidden_dir_dentry = + lock_parent(hidden_new_dentry); + /* do vfs_link */ + err = vfs_link(hidden_old_dentry, + hidden_dir_dentry->d_inode, + hidden_new_dentry); + unlock_dir(hidden_dir_dentry); + goto check_link; + } + } + goto out; + } + +check_link: + if (err || !hidden_new_dentry->d_inode) + goto out; + + /* Its a hard link, so use the same inode */ + new_dentry->d_inode = igrab(old_dentry->d_inode); + d_instantiate(new_dentry, new_dentry->d_inode); + fsstack_copy_attr_all(dir, hidden_new_dentry->d_parent->d_inode, + unionfs_get_nlinks); + fsstack_copy_inode_size(dir, hidden_new_dentry->d_parent->d_inode); + + /* propagate number of hard-links */ + old_dentry->d_inode->i_nlink = unionfs_get_nlinks(old_dentry->d_inode); + +out: + if (!new_dentry->d_inode) + d_drop(new_dentry); + + kfree(name); + + unionfs_unlock_dentry(new_dentry); + unionfs_unlock_dentry(old_dentry); + + return err; +} + +static int unionfs_symlink(struct inode *dir, struct dentry *dentry, + const char *symname) +{ + int err = 0; + struct dentry *hidden_dentry = NULL; + struct dentry *whiteout_dentry = NULL; + struct dentry *hidden_dir_dentry = NULL; + umode_t mode; + int bindex = 0, bstart; + char *name = NULL; + + BUG_ON(!is_valid_dentry(dentry)); + + unionfs_lock_dentry(dentry); + + /* We start out in the leftmost branch. */ + bstart = dbstart(dentry); + + hidden_dentry = unionfs_lower_dentry(dentry); + + /* check if whiteout exists in this branch, i.e. lookup .wh.foo + * first. If present, delete it + */ + name = alloc_whname(dentry->d_name.name, dentry->d_name.len); + if (IS_ERR(name)) { + err = PTR_ERR(name); + goto out; + } + + whiteout_dentry = + lookup_one_len(name, hidden_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 { + /* found a .wh.foo entry, unlink it and then call vfs_symlink() */ + hidden_dir_dentry = lock_parent(whiteout_dentry); + + if (!(err = is_robranch_super(dentry->d_sb, bstart))) + err = vfs_unlink(hidden_dir_dentry->d_inode, + whiteout_dentry); + dput(whiteout_dentry); + + fsstack_copy_attr_times(dir, hidden_dir_dentry->d_inode); + /* propagate number of hard-links */ + dir->i_nlink = unionfs_get_nlinks(dir); + + unlock_dir(hidden_dir_dentry); + + if (err) { + /* exit if the error returned was NOT -EROFS */ + if (!IS_COPYUP_ERR(err)) + goto out; + /* should now try to create symlink in the another branch */ + bstart--; + } + } + + /* deleted whiteout if it was present, now do a normal vfs_symlink() + * with possible recursive directory creation + */ + for (bindex = bstart; bindex >= 0; bindex--) { + hidden_dentry = unionfs_lower_dentry_idx(dentry, bindex); + if (!hidden_dentry) { + /* if hidden_dentry is NULL, create the entire + * dentry directory structure in branch 'bindex'. + * hidden_dentry will NOT be null when bindex == + * bstart because lookup passed as a negative + * unionfs dentry pointing to a lone negative + * underlying dentry + */ + hidden_dentry = create_parents(dir, dentry, bindex); + if (!hidden_dentry || IS_ERR(hidden_dentry)) { + if (IS_ERR(hidden_dentry)) + err = PTR_ERR(hidden_dentry); + + printk(KERN_DEBUG "hidden dentry NULL (or error)" + "for bindex = %d\n", bindex); + continue; + } + } + + hidden_dir_dentry = lock_parent(hidden_dentry); + + if (!(err = is_robranch_super(dentry->d_sb, bindex))) { + mode = S_IALLUGO; + err = + vfs_symlink(hidden_dir_dentry->d_inode, + hidden_dentry, symname, mode); + } + unlock_dir(hidden_dir_dentry); + + if (err || !hidden_dentry->d_inode) { + /* break out of for loop if error returned was NOT -EROFS */ + if (!IS_COPYUP_ERR(err)) + break; + } else { + err = unionfs_interpose(dentry, dir->i_sb, 0); + if (!err) { + fsstack_copy_attr_times(dir, + hidden_dir_dentry->d_inode); + fsstack_copy_inode_size(dir, + hidden_dir_dentry->d_inode); + /* update number of links on parent directory */ + dir->i_nlink = unionfs_get_nlinks(dir); + } + break; + } + } + +out: + if (!dentry->d_inode) + d_drop(dentry); + + kfree(name); + unionfs_unlock_dentry(dentry); + return err; +} + +static int unionfs_mkdir(struct inode *parent, struct dentry *dentry, int mode) +{ + int err = 0; + struct dentry *hidden_dentry = NULL, *whiteout_dentry = NULL; + struct dentry *hidden_parent_dentry = NULL; + int bindex = 0, bstart; + char *name = NULL; + int whiteout_unlinked = 0; + struct sioq_args args; + + BUG_ON(!is_valid_dentry(dentry)); + + unionfs_lock_dentry(dentry); + bstart = dbstart(dentry); + + hidden_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 (IS_ERR(name)) { + err = PTR_ERR(name); + goto out; + } + + whiteout_dentry = lookup_one_len(name, hidden_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 { + hidden_parent_dentry = lock_parent(whiteout_dentry); + + /* found a.wh.foo entry, remove it then do vfs_mkdir */ + if (!(err = is_robranch_super(dentry->d_sb, bstart))) { + args.unlink.parent = hidden_parent_dentry->d_inode; + args.unlink.dentry = whiteout_dentry; + run_sioq(__unionfs_unlink, &args); + err = args.err; + } + dput(whiteout_dentry); + + unlock_dir(hidden_parent_dentry); + + if (err) { + /* exit if the error returned was NOT -EROFS */ + if (!IS_COPYUP_ERR(err)) + goto out; + bstart--; + } else + whiteout_unlinked = 1; + } + + for (bindex = bstart; bindex >= 0; bindex--) { + int i; + int bend = dbend(dentry); + + if (is_robranch_super(dentry->d_sb, bindex)) + continue; + + hidden_dentry = unionfs_lower_dentry_idx(dentry, bindex); + if (!hidden_dentry) { + hidden_dentry = create_parents(parent, dentry, bindex); + if (!hidden_dentry || IS_ERR(hidden_dentry)) { + printk(KERN_DEBUG "hidden dentry NULL for " + "bindex = %d\n", bindex); + continue; + } + } + + hidden_parent_dentry = lock_parent(hidden_dentry); + + if (IS_ERR(hidden_parent_dentry)) { + err = PTR_ERR(hidden_parent_dentry); + goto out; + } + + err = vfs_mkdir(hidden_parent_dentry->d_inode, hidden_dentry, mode); + + unlock_dir(hidden_parent_dentry); + + /* did the mkdir suceed? */ + if (err) + break; + + for (i = bindex + 1; i < bend; i++) { + if (unionfs_lower_dentry_idx(dentry, i)) { + dput(unionfs_lower_dentry_idx(dentry, i)); + unionfs_set_lower_dentry_idx(dentry, i, NULL); + } + } + set_dbend(dentry, bindex); + + err = unionfs_interpose(dentry, parent->i_sb, 0); + if (!err) { + fsstack_copy_attr_times(parent, + hidden_parent_dentry->d_inode); + fsstack_copy_inode_size(parent, + hidden_parent_dentry->d_inode); + + /* update number of links on parent directory */ + parent->i_nlink = unionfs_get_nlinks(parent); + } + + err = make_dir_opaque(dentry, dbstart(dentry)); + if (err) { + printk(KERN_ERR "mkdir: error creating " + ".wh.__dir_opaque: %d\n", err); + goto out; + } + + /* we are done! */ + break; + } + +out: + if (!dentry->d_inode) + d_drop(dentry); + + kfree(name); + + unionfs_unlock_dentry(dentry); + return err; +} + +static int unionfs_mknod(struct inode *dir, struct dentry *dentry, int mode, + dev_t dev) +{ + int err = 0; + struct dentry *hidden_dentry = NULL, *whiteout_dentry = NULL; + struct dentry *hidden_parent_dentry = NULL; + int bindex = 0, bstart; + char *name = NULL; + int whiteout_unlinked = 0; + + BUG_ON(!is_valid_dentry(dentry)); + + unionfs_lock_dentry(dentry); + bstart = dbstart(dentry); + + hidden_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 (IS_ERR(name)) { + err = PTR_ERR(name); + goto out; + } + + whiteout_dentry = lookup_one_len(name, hidden_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 { + /* found .wh.foo, unlink it */ + hidden_parent_dentry = lock_parent(whiteout_dentry); + + /* found a.wh.foo entry, remove it then do vfs_mkdir */ + if (!(err = is_robranch_super(dentry->d_sb, bstart))) + err = vfs_unlink(hidden_parent_dentry->d_inode, + whiteout_dentry); + dput(whiteout_dentry); + + unlock_dir(hidden_parent_dentry); + + if (err) { + if (!IS_COPYUP_ERR(err)) + goto out; + + bstart--; + } else + whiteout_unlinked = 1; + } + + for (bindex = bstart; bindex >= 0; bindex--) { + if (is_robranch_super(dentry->d_sb, bindex)) + continue; + + hidden_dentry = unionfs_lower_dentry_idx(dentry, bindex); + if (!hidden_dentry) { + hidden_dentry = create_parents(dir, dentry, bindex); + if (IS_ERR(hidden_dentry)) { + printk(KERN_DEBUG + "failed to create parents on %d, err = %ld\n", + bindex, PTR_ERR(hidden_dentry)); + continue; + } + } + + hidden_parent_dentry = lock_parent(hidden_dentry); + if (IS_ERR(hidden_parent_dentry)) { + err = PTR_ERR(hidden_parent_dentry); + goto out; + } + + err = vfs_mknod(hidden_parent_dentry->d_inode, + hidden_dentry, mode, dev); + + if (err) { + unlock_dir(hidden_parent_dentry); + break; + } + + err = unionfs_interpose(dentry, dir->i_sb, 0); + if (!err) { + fsstack_copy_attr_times(dir, + hidden_parent_dentry->d_inode); + fsstack_copy_inode_size(dir, + hidden_parent_dentry->d_inode); + /* update number of links on parent directory */ + dir->i_nlink = unionfs_get_nlinks(dir); + } + unlock_dir(hidden_parent_dentry); + + break; + } + +out: + if (!dentry->d_inode) + d_drop(dentry); + + kfree(name); + + unionfs_unlock_dentry(dentry); + return err; +} + +static int unionfs_readlink(struct dentry *dentry, char __user * buf, + int bufsiz) +{ + int err; + struct dentry *hidden_dentry; + + BUG_ON(!is_valid_dentry(dentry)); + + unionfs_lock_dentry(dentry); + hidden_dentry = unionfs_lower_dentry(dentry); + + if (!hidden_dentry->d_inode->i_op || + !hidden_dentry->d_inode->i_op->readlink) { + err = -EINVAL; + goto out; + } + + err = hidden_dentry->d_inode->i_op->readlink(hidden_dentry, buf, bufsiz); + if (err > 0) + fsstack_copy_attr_atime(dentry->d_inode, hidden_dentry->d_inode); + +out: + unionfs_unlock_dentry(dentry); + return err; +} + +/* We don't lock the dentry here, because readlink does the heavy lifting. */ +static void *unionfs_follow_link(struct dentry *dentry, struct nameidata *nd) +{ + char *buf; + int len = PAGE_SIZE, err; + mm_segment_t old_fs; + + BUG_ON(!is_valid_dentry(dentry)); + + /* This is freed by the put_link method assuming a successful call. */ + buf = kmalloc(len, GFP_KERNEL); + if (!buf) { + err = -ENOMEM; + goto out; + } + + /* read the symlink, and then we will follow it */ + old_fs = get_fs(); + set_fs(KERNEL_DS); + err = dentry->d_inode->i_op->readlink(dentry, (char __user *)buf, len); + set_fs(old_fs); + if (err < 0) { + kfree(buf); + buf = NULL; + goto out; + } + buf[err] = 0; + nd_set_link(nd, buf); + err = 0; + +out: + return ERR_PTR(err); +} + +static void unionfs_put_link(struct dentry *dentry, struct nameidata *nd, + void *cookie) +{ + kfree(nd_get_link(nd)); +} + +/* Basically copied from the kernel vfs permission(), but we've changed + * the following: + * (1) the IS_RDONLY check is skipped, and + * (2) if you set the mount option `mode=nfsro', we assume that -EACCES + * means that the export is read-only and we should check standard Unix + * permissions. This means that NFS ACL checks (or other advanced + * permission features) are bypassed. Note however, that we do call + * security_inode_permission, and therefore security inside SELinux, etc. + * are performed. + */ +static int inode_permission(struct inode *inode, int mask, struct nameidata *nd, + int bindex) +{ + int retval, submask; + + if (mask & MAY_WRITE) { + /* The first branch is allowed to be really readonly. */ + if (bindex == 0) { + umode_t mode = inode->i_mode; + if (IS_RDONLY(inode) && + (S_ISREG(mode) || S_ISDIR(mode) || S_ISLNK(mode))) + return -EROFS; + } + /* + * Nobody gets write access to an immutable file. + */ + if (IS_IMMUTABLE(inode)) + return -EACCES; + } + + /* Ordinary permission routines do not understand MAY_APPEND. */ + submask = mask & ~MAY_APPEND; + if (inode->i_op && inode->i_op->permission) { + 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)) { + 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); + + if (retval && retval != -EROFS) /* ignore EROFS */ + return retval; + + retval = security_inode_permission(inode, mask, nd); + return ((retval == -EROFS) ? 0 : retval); /* ignore EROFS */ +} + +static int unionfs_permission(struct inode *inode, int mask, + struct nameidata *nd) +{ + struct inode *hidden_inode = NULL; + int err = 0; + int bindex, bstart, bend; + const int is_file = !S_ISDIR(inode->i_mode); + const int write_mask = (mask & MAY_WRITE) && !(mask & MAY_READ); + + unionfs_read_lock(inode->i_sb); + + bstart = ibstart(inode); + bend = ibend(inode); + if (bstart < 0 || bend < 0) { + /* + * With branch-management, we can get a stale inode here. + * If so, we return ESTALE back to link_path_walk, which + * would discard the dcache entry and re-lookup the + * dentry+inode. This should be equivalent to issuing + * __unionfs_d_revalidate_chain on nd.dentry here. + */ + err = -ESTALE; /* force revalidate */ + goto out; + } + + for (bindex = bstart; bindex <= bend; bindex++) { + hidden_inode = unionfs_lower_inode_idx(inode, bindex); + if (!hidden_inode) + continue; + + /* check the condition for D-F-D underlying files/directories, + * we dont have to check for files, if we are checking for + * directories. + */ + if (!is_file && !S_ISDIR(hidden_inode->i_mode)) + continue; + + /* We use our own special version of permission, such that + * only the first branch returns -EROFS. + */ + err = inode_permission(hidden_inode, mask, nd, bindex); + + /* The permissions are an intersection of the overall directory + * permissions, so we fail if one fails. + */ + if (err) + goto out; + + /* only the leftmost file matters. */ + if (is_file || write_mask) { + if (is_file && write_mask) { + err = get_write_access(hidden_inode); + if (!err) + put_write_access(hidden_inode); + } + break; + } + } + +out: + unionfs_read_unlock(inode->i_sb); + return err; +} + +static int unionfs_setattr(struct dentry *dentry, struct iattr *ia) +{ + int err = 0; + struct dentry *hidden_dentry; + struct inode *inode = NULL; + struct inode *hidden_inode = NULL; + int bstart, bend, bindex; + int i; + int copyup = 0; + + BUG_ON(!is_valid_dentry(dentry)); + + unionfs_lock_dentry(dentry); + bstart = dbstart(dentry); + bend = dbend(dentry); + inode = dentry->d_inode; + + for (bindex = bstart; (bindex <= bend) || (bindex == bstart); bindex++) { + hidden_dentry = unionfs_lower_dentry_idx(dentry, bindex); + if (!hidden_dentry) + continue; + BUG_ON(hidden_dentry->d_inode == NULL); + + /* If the file is on a read only branch */ + if (is_robranch_super(dentry->d_sb, bindex) + || IS_RDONLY(hidden_dentry->d_inode)) { + if (copyup || (bindex != bstart)) + continue; + /* Only if its the leftmost file, copyup the file */ + for (i = bstart - 1; i >= 0; i--) { + loff_t size = dentry->d_inode->i_size; + if (ia->ia_valid & ATTR_SIZE) + size = ia->ia_size; + err = copyup_dentry(dentry->d_parent->d_inode, + dentry, bstart, i, NULL, + size); + + if (!err) { + copyup = 1; + hidden_dentry = unionfs_lower_dentry(dentry); + break; + } + /* if error is in the leftmost branch, pass it up */ + if (i == 0) + goto out; + } + + } + err = notify_change(hidden_dentry, ia); + if (err) + goto out; + break; + } + + /* get the size from the first hidden inode */ + hidden_inode = unionfs_lower_inode(dentry->d_inode); + fsstack_copy_attr_all(inode, hidden_inode, unionfs_get_nlinks); + fsstack_copy_inode_size(inode, hidden_inode); + +out: + unionfs_unlock_dentry(dentry); + return err; +} + +struct inode_operations unionfs_symlink_iops = { + .readlink = unionfs_readlink, + .permission = unionfs_permission, + .follow_link = unionfs_follow_link, + .setattr = unionfs_setattr, + .put_link = unionfs_put_link, +}; + +struct inode_operations unionfs_dir_iops = { + .create = unionfs_create, + .lookup = unionfs_lookup, + .link = unionfs_link, + .unlink = unionfs_unlink, + .symlink = unionfs_symlink, + .mkdir = unionfs_mkdir, + .rmdir = unionfs_rmdir, + .mknod = unionfs_mknod, + .rename = unionfs_rename, + .permission = unionfs_permission, + .setattr = unionfs_setattr, +#ifdef CONFIG_UNION_FS_XATTR + .setxattr = unionfs_setxattr, + .getxattr = unionfs_getxattr, + .removexattr = unionfs_removexattr, + .listxattr = unionfs_listxattr, +#endif +}; + +struct inode_operations unionfs_main_iops = { + .permission = unionfs_permission, + .setattr = unionfs_setattr, +#ifdef CONFIG_UNION_FS_XATTR + .setxattr = unionfs_setxattr, + .getxattr = unionfs_getxattr, + .removexattr = unionfs_removexattr, + .listxattr = unionfs_listxattr, +#endif +}; + diff --git a/fs/unionfs/lookup.c b/fs/unionfs/lookup.c new file mode 100644 index 0000000..0fc5993 --- /dev/null +++ b/fs/unionfs/lookup.c @@ -0,0 +1,523 @@ +/* + * 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 State University of New York + * + * 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" + +/* 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 int is_opaque_dir(struct dentry *dentry, int bindex) +{ + int err = 0; + struct dentry *hidden_dentry; + struct dentry *wh_hidden_dentry; + struct inode *hidden_inode; + struct sioq_args args; + + hidden_dentry = unionfs_lower_dentry_idx(dentry, bindex); + hidden_inode = hidden_dentry->d_inode; + + BUG_ON(!S_ISDIR(hidden_inode->i_mode)); + + mutex_lock(&hidden_inode->i_mutex); + + if (!permission(hidden_inode, MAY_EXEC, NULL)) + wh_hidden_dentry = lookup_one_len(UNIONFS_DIR_OPAQUE, hidden_dentry, + sizeof(UNIONFS_DIR_OPAQUE) - 1); + else { + args.is_opaque.dentry = hidden_dentry; + run_sioq(__is_opaque_dir, &args); + wh_hidden_dentry = args.ret; + } + + mutex_unlock(&hidden_inode->i_mutex); + + if (IS_ERR(wh_hidden_dentry)) { + err = PTR_ERR(wh_hidden_dentry); + goto out; + } + + /* This is an opaque dir iff wh_hidden_dentry is positive */ + err = !!wh_hidden_dentry->d_inode; + + dput(wh_hidden_dentry); +out: + return err; +} + +struct dentry *unionfs_lookup_backend(struct dentry *dentry, struct nameidata *nd, + int lookupmode) +{ + int err = 0; + struct dentry *hidden_dentry = NULL; + struct dentry *wh_hidden_dentry = NULL; + struct dentry *hidden_dir_dentry = NULL; + struct dentry *parent_dentry = NULL; + int bindex, bstart, bend, bopaque; + int dentry_count = 0; /* Number of positive dentries. */ + int first_dentry_offset = -1; /* -1 is uninitialized */ + struct dentry *first_dentry = NULL; + struct dentry *first_hidden_dentry = NULL; + struct vfsmount *first_hidden_mnt = NULL; + int locked_parent = 0; + int locked_child = 0; + int allocated_new_info = 0; + + int opaque; + char *whname = NULL; + const char *name; + int namelen; + + /* We should already have a lock on this dentry in the case of a + * partial lookup, or a revalidation. Otherwise it is returned from + * new_dentry_private_data already locked. + */ + if (lookupmode == INTERPOSE_PARTIAL || lookupmode == INTERPOSE_REVAL || + lookupmode == INTERPOSE_REVAL_NEG) + verify_locked(dentry); + else { + BUG_ON(UNIONFS_D(dentry) != NULL); + locked_child = 1; + } + if (lookupmode != INTERPOSE_PARTIAL) { + if ((err = new_dentry_private_data(dentry))) + goto out; + allocated_new_info = 1; + } + /* must initialize dentry operations */ + dentry->d_op = &unionfs_dops; + + parent_dentry = dget_parent(dentry); + /* We never partial lookup the root directory. */ + if (parent_dentry != dentry) { + unionfs_lock_dentry(parent_dentry); + locked_parent = 1; + } else { + dput(parent_dentry); + parent_dentry = NULL; + goto out; + } + + name = dentry->d_name.name; + namelen = dentry->d_name.len; + + /* No dentries should get created for possible whiteout names. */ + if (!is_validname(name)) { + err = -EPERM; + goto out_free; + } + + /* Now start the actual lookup procedure. */ + bstart = dbstart(parent_dentry); + bend = dbend(parent_dentry); + bopaque = dbopaque(parent_dentry); + BUG_ON(bstart < 0); + + /* It would be ideal if we could convert partial lookups to only have + * to do this work when they really need to. It could probably improve + * performance quite a bit, and maybe simplify the rest of the code. + */ + if (lookupmode == INTERPOSE_PARTIAL) { + bstart++; + if ((bopaque != -1) && (bopaque < bend)) + bend = bopaque; + } + + for (bindex = bstart; bindex <= bend; bindex++) { + hidden_dentry = unionfs_lower_dentry_idx(dentry, bindex); + if (lookupmode == INTERPOSE_PARTIAL && hidden_dentry) + continue; + BUG_ON(hidden_dentry != NULL); + + hidden_dir_dentry = unionfs_lower_dentry_idx(parent_dentry, bindex); + + /* if the parent hidden dentry does not exist skip this */ + if (!(hidden_dir_dentry && hidden_dir_dentry->d_inode)) + continue; + + /* also skip it if the parent isn't a directory. */ + if (!S_ISDIR(hidden_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 (IS_ERR(whname)) { + err = PTR_ERR(whname); + goto out_free; + } + } + + /* check if whiteout exists in this branch: lookup .wh.foo */ + wh_hidden_dentry = lookup_one_len(whname, hidden_dir_dentry, + namelen + UNIONFS_WHLEN); + if (IS_ERR(wh_hidden_dentry)) { + dput(first_hidden_dentry); + unionfs_mntput(first_dentry, first_dentry_offset); + err = PTR_ERR(wh_hidden_dentry); + goto out_free; + } + + if (wh_hidden_dentry->d_inode) { + /* We found a whiteout so lets give up. */ + if (S_ISREG(wh_hidden_dentry->d_inode->i_mode)) { + set_dbend(dentry, bindex); + set_dbopaque(dentry, bindex); + dput(wh_hidden_dentry); + break; + } + err = -EIO; + printk(KERN_NOTICE "EIO: Invalid whiteout entry type" + " %d.\n", wh_hidden_dentry->d_inode->i_mode); + dput(wh_hidden_dentry); + dput(first_hidden_dentry); + unionfs_mntput(first_dentry, first_dentry_offset); + goto out_free; + } + + dput(wh_hidden_dentry); + wh_hidden_dentry = NULL; + + /* Now do regular lookup; lookup foo */ + nd->dentry = unionfs_lower_dentry_idx(dentry, bindex); + /* FIXME: fix following line for mount point crossing */ + nd->mnt = unionfs_lower_mnt_idx(parent_dentry, bindex); + + hidden_dentry = lookup_one_len_nd(name, hidden_dir_dentry, + namelen, nd); + if (IS_ERR(hidden_dentry)) { + dput(first_hidden_dentry); + unionfs_mntput(first_dentry, first_dentry_offset); + err = PTR_ERR(hidden_dentry); + goto out_free; + } + + /* Store the first negative dentry specially, because if they + * are all negative we need this for future creates. + */ + if (!hidden_dentry->d_inode) { + if (!first_hidden_dentry && (dbstart(dentry) == -1)) { + first_hidden_dentry = hidden_dentry; + /* FIXME: following line needs to be changed + * to allow mountpoint crossing + */ + first_dentry = parent_dentry; + first_hidden_mnt = unionfs_mntget(parent_dentry, bindex); + first_dentry_offset = bindex; + } else + dput(hidden_dentry); + + continue; + } + + /* number of positive dentries */ + dentry_count++; + + /* store underlying dentry */ + if (dbstart(dentry) == -1) + set_dbstart(dentry, bindex); + unionfs_set_lower_dentry_idx(dentry, bindex, hidden_dentry); + /* FIXME: the following line needs to get fixed to allow + * mountpoint crossing + */ + unionfs_set_lower_mnt_idx(dentry, bindex, + unionfs_mntget(parent_dentry, bindex)); + set_dbend(dentry, bindex); + + /* update parent directory's atime with the bindex */ + fsstack_copy_attr_atime(parent_dentry->d_inode, + hidden_dir_dentry->d_inode); + + /* We terminate file lookups here. */ + if (!S_ISDIR(hidden_dentry->d_inode->i_mode)) { + if (lookupmode == INTERPOSE_PARTIAL) + continue; + if (dentry_count == 1) + goto out_positive; + /* This can only happen with mixed D-*-F-* */ + BUG_ON(!S_ISDIR(unionfs_lower_dentry(dentry)->d_inode->i_mode)); + continue; + } + + opaque = is_opaque_dir(dentry, bindex); + if (opaque < 0) { + dput(first_hidden_dentry); + unionfs_mntput(first_dentry, first_dentry_offset); + err = opaque; + goto out_free; + } else if (opaque) { + set_dbend(dentry, bindex); + set_dbopaque(dentry, bindex); + break; + } + } + + if (dentry_count) + goto out_positive; + else + goto out_negative; + +out_negative: + if (lookupmode == INTERPOSE_PARTIAL) + goto out; + + /* If we've only got negative dentries, then use the leftmost one. */ + if (lookupmode == INTERPOSE_REVAL) { + if (dentry->d_inode) + UNIONFS_I(dentry->d_inode)->stale = 1; + + goto out; + } + /* This should only happen if we found a whiteout. */ + if (first_dentry_offset == -1) { + nd->dentry = dentry; + /* FIXME: fix following line for mount point crossing */ + nd->mnt = unionfs_lower_mnt_idx(parent_dentry, bindex); + + first_hidden_dentry = lookup_one_len_nd(name, hidden_dir_dentry, + namelen, nd); + first_dentry_offset = bindex; + if (IS_ERR(first_hidden_dentry)) { + err = PTR_ERR(first_hidden_dentry); + goto out; + } + + /* FIXME: the following line needs to be changed to allow + * mountpoint crossing + */ + first_dentry = dentry; + first_hidden_mnt = unionfs_mntget(dentry, bindex); + } + unionfs_set_lower_dentry_idx(dentry, first_dentry_offset, first_hidden_dentry); + unionfs_set_lower_mnt_idx(dentry, first_dentry_offset, first_hidden_mnt); + set_dbstart(dentry, first_dentry_offset); + set_dbend(dentry, first_dentry_offset); + + if (lookupmode == INTERPOSE_REVAL_NEG) + BUG_ON(dentry->d_inode != NULL); + else + d_add(dentry, NULL); + goto out; + +/* This part of the code is for positive dentries. */ +out_positive: + BUG_ON(dentry_count <= 0); + + /* If we're holding onto the first negative dentry & corresponding + * vfsmount - throw it out. + */ + dput(first_hidden_dentry); + unionfs_mntput(first_dentry, first_dentry_offset); + + /* Partial lookups need to reinterpose, or throw away older negs. */ + if (lookupmode == INTERPOSE_PARTIAL) { + if (dentry->d_inode) { + unionfs_reinterpose(dentry); + goto out; + } + + /* This somehow turned positive, so it is as if we had a + * negative revalidation. + */ + lookupmode = INTERPOSE_REVAL_NEG; + + update_bstart(dentry); + bstart = dbstart(dentry); + bend = dbend(dentry); + } + + err = unionfs_interpose(dentry, dentry->d_sb, lookupmode); + if (err) + goto out_drop; + + goto out; + +out_drop: + d_drop(dentry); + +out_free: + /* should dput all the underlying dentries on error condition */ + bstart = dbstart(dentry); + if (bstart >= 0) { + bend = dbend(dentry); + for (bindex = bstart; bindex <= bend; bindex++) { + dput(unionfs_lower_dentry_idx(dentry, bindex)); + unionfs_mntput(dentry, bindex); + } + } + kfree(UNIONFS_D(dentry)->lower_paths); + UNIONFS_D(dentry)->lower_paths = NULL; + set_dbstart(dentry, -1); + set_dbend(dentry, -1); + +out: + if (!err && UNIONFS_D(dentry)) { + BUG_ON(dbend(dentry) > UNIONFS_D(dentry)->bcount); + BUG_ON(dbend(dentry) > sbmax(dentry->d_sb)); + BUG_ON(dbstart(dentry) < 0); + } + kfree(whname); + if (locked_parent) + unionfs_unlock_dentry(parent_dentry); + dput(parent_dentry); + if (locked_child || (err && allocated_new_info)) + unionfs_unlock_dentry(dentry); + return ERR_PTR(err); +} + +/* This is a utility function that fills in a unionfs dentry.*/ +int unionfs_partial_lookup(struct dentry *dentry) +{ + struct dentry *tmp; + struct nameidata nd = { .flags = 0 }; + + tmp = unionfs_lookup_backend(dentry, &nd, INTERPOSE_PARTIAL); + if (!tmp) + return 0; + if (IS_ERR(tmp)) + return PTR_ERR(tmp); + /* need to change the interface */ + BUG_ON(tmp != dentry); + return -ENOSYS; +} + +/* The dentry cache is just so we have properly sized dentries. */ +static struct kmem_cache *unionfs_dentry_cachep; +int unionfs_init_dentry_cache(void) +{ + unionfs_dentry_cachep = kmem_cache_create("unionfs_dentry", + sizeof(struct unionfs_dentry_info), 0, + SLAB_RECLAIM_ACCOUNT, NULL, NULL); + + return (unionfs_dentry_cachep ? 0 : -ENOMEM); +} + +void unionfs_destroy_dentry_cache(void) +{ + if (unionfs_dentry_cachep) + kmem_cache_destroy(unionfs_dentry_cachep); +} + +void free_dentry_private_data(struct unionfs_dentry_info *udi) +{ + if (!udi) + return; + kmem_cache_free(unionfs_dentry_cachep, udi); +} + +/* allocate new dentry private data, free old one if necessary */ +int new_dentry_private_data(struct dentry *dentry) +{ + int newsize; + int oldsize = 0; + struct unionfs_dentry_info *info = UNIONFS_D(dentry); + int unlock_on_err = 0; + + spin_lock(&dentry->d_lock); + if (!info) { + dentry->d_fsdata = kmem_cache_alloc(unionfs_dentry_cachep, + GFP_ATOMIC); + info = UNIONFS_D(dentry); + + if (!info) + goto out; + + mutex_init(&info->lock); + mutex_lock(&info->lock); + unlock_on_err = 1; + + info->lower_paths = NULL; + } else + oldsize = sizeof(struct path) * info->bcount; + + info->bstart = -1; + info->bend = -1; + info->bopaque = -1; + info->bcount = sbmax(dentry->d_sb); + atomic_set(&info->generation, + atomic_read(&UNIONFS_SB(dentry->d_sb)->generation)); + newsize = sizeof(struct path) * sbmax(dentry->d_sb); + + /* Don't reallocate when we already have enough space. */ + /* It would be ideal if we could actually use the slab macros to + * determine what our object sizes is, but those are not exported. + */ + if (oldsize) { + int minsize = malloc_sizes[0].cs_size; + + if (!newsize || ((oldsize < newsize) && (newsize > minsize))) { + kfree(info->lower_paths); + info->lower_paths = NULL; + } + } + + if (!info->lower_paths && newsize) { + info->lower_paths = kmalloc(newsize, GFP_ATOMIC); + if (!info->lower_paths) + goto out_free; + } + + memset(info->lower_paths, 0, (oldsize > newsize ? oldsize : newsize)); + + spin_unlock(&dentry->d_lock); + return 0; + +out_free: + kfree(info->lower_paths); + if (unlock_on_err) + mutex_unlock(&info->lock); + +out: + free_dentry_private_data(info); + dentry->d_fsdata = NULL; + spin_unlock(&dentry->d_lock); + return -ENOMEM; +} + +/* scan through the lower dentry objects, and set bstart to reflect the + * starting branch + */ +void update_bstart(struct dentry *dentry) +{ + int bindex; + int bstart = dbstart(dentry); + int bend = dbend(dentry); + struct dentry *hidden_dentry; + + for (bindex = bstart; bindex <= bend; bindex++) { + hidden_dentry = unionfs_lower_dentry_idx(dentry, bindex); + if (!hidden_dentry) + continue; + if (hidden_dentry->d_inode) { + set_dbstart(dentry, bindex); + break; + } + dput(hidden_dentry); + unionfs_set_lower_dentry_idx(dentry, bindex, NULL); + } +} + diff --git a/fs/unionfs/main.c b/fs/unionfs/main.c new file mode 100644 index 0000000..4fffafa --- /dev/null +++ b/fs/unionfs/main.c @@ -0,0 +1,703 @@ +/* + * 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 State University of New York + * + * 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" +#include +#include + +/* sb we pass is unionfs's super_block */ +int unionfs_interpose(struct dentry *dentry, struct super_block *sb, int flag) +{ + struct inode *hidden_inode; + struct dentry *hidden_dentry; + int err = 0; + struct inode *inode; + int is_negative_dentry = 1; + int bindex, bstart, bend; + + verify_locked(dentry); + + bstart = dbstart(dentry); + bend = dbend(dentry); + + /* Make sure that we didn't get a negative dentry. */ + for (bindex = bstart; bindex <= bend; bindex++) { + if (unionfs_lower_dentry_idx(dentry, bindex) && + unionfs_lower_dentry_idx(dentry, bindex)->d_inode) { + is_negative_dentry = 0; + break; + } + } + BUG_ON(is_negative_dentry); + + /* We allocate our new inode below, by calling iget. + * iget will call our read_inode which will initialize some + * of the new inode's fields + */ + + /* On revalidate we've already got our own inode and just need + * to fix it up. + */ + if (flag == INTERPOSE_REVAL) { + inode = dentry->d_inode; + UNIONFS_I(inode)->bstart = -1; + UNIONFS_I(inode)->bend = -1; + atomic_set(&UNIONFS_I(inode)->generation, + atomic_read(&UNIONFS_SB(sb)->generation)); + + UNIONFS_I(inode)->lower_inodes = + kcalloc(sbmax(sb), sizeof(struct inode *), GFP_KERNEL); + if (!UNIONFS_I(inode)->lower_inodes) { + err = -ENOMEM; + goto out; + } + } else { + /* get unique inode number for unionfs */ + inode = iget(sb, iunique(sb, UNIONFS_ROOT_INO)); + if (!inode) { + err = -EACCES; + goto out; + } + + if (atomic_read(&inode->i_count) > 1) + goto skip; + } + + for (bindex = bstart; bindex <= bend; bindex++) { + hidden_dentry = unionfs_lower_dentry_idx(dentry, bindex); + if (!hidden_dentry) { + unionfs_set_lower_inode_idx(inode, bindex, NULL); + continue; + } + + /* Initialize the hidden inode to the new hidden inode. */ + if (!hidden_dentry->d_inode) + continue; + + unionfs_set_lower_inode_idx(inode, bindex, + igrab(hidden_dentry->d_inode)); + } + + ibstart(inode) = dbstart(dentry); + ibend(inode) = dbend(dentry); + + /* Use attributes from the first branch. */ + hidden_inode = unionfs_lower_inode(inode); + + /* Use different set of inode ops for symlinks & directories */ + if (S_ISLNK(hidden_inode->i_mode)) + inode->i_op = &unionfs_symlink_iops; + else if (S_ISDIR(hidden_inode->i_mode)) + inode->i_op = &unionfs_dir_iops; + + /* Use different set of file ops for directories */ + if (S_ISDIR(hidden_inode->i_mode)) + inode->i_fop = &unionfs_dir_fops; + + /* properly initialize special inodes */ + if (S_ISBLK(hidden_inode->i_mode) || S_ISCHR(hidden_inode->i_mode) || + S_ISFIFO(hidden_inode->i_mode) || S_ISSOCK(hidden_inode->i_mode)) + init_special_inode(inode, hidden_inode->i_mode, + hidden_inode->i_rdev); + /* Fix our inode's address operations to that of the lower inode + * (Unionfs is FiST-Lite) + */ + if (inode->i_mapping->a_ops != hidden_inode->i_mapping->a_ops) + inode->i_mapping->a_ops = hidden_inode->i_mapping->a_ops; + + /* all well, copy inode attributes */ + fsstack_copy_attr_all(inode, hidden_inode, unionfs_get_nlinks); + fsstack_copy_inode_size(inode, hidden_inode); + +skip: + /* only (our) lookup wants to do a d_add */ + switch (flag) { + case INTERPOSE_DEFAULT: + case INTERPOSE_REVAL_NEG: + d_instantiate(dentry, inode); + break; + case INTERPOSE_LOOKUP: + err = PTR_ERR(d_splice_alias(inode, dentry)); + break; + case INTERPOSE_REVAL: + /* Do nothing. */ + break; + default: + printk(KERN_ERR "Invalid interpose flag passed!"); + BUG(); + } + +out: + return err; +} + +void unionfs_reinterpose(struct dentry *dentry) +{ + struct dentry *hidden_dentry; + struct inode *inode; + int bindex, bstart, bend; + + verify_locked(dentry); + + /* This is pre-allocated inode */ + inode = dentry->d_inode; + + bstart = dbstart(dentry); + bend = dbend(dentry); + for (bindex = bstart; bindex <= bend; bindex++) { + hidden_dentry = unionfs_lower_dentry_idx(dentry, bindex); + if (!hidden_dentry) + continue; + + if (!hidden_dentry->d_inode) + continue; + if (unionfs_lower_inode_idx(inode, bindex)) + continue; + unionfs_set_lower_inode_idx(inode, bindex, + igrab(hidden_dentry->d_inode)); + } + ibstart(inode) = dbstart(dentry); + ibend(inode) = dbend(dentry); +} + +/* make sure the branch we just looked up (nd) makes sense: + * + * 1) we're not trying to stack unionfs on top of unionfs + * 2) it exists + * 3) is a directory + */ +int check_branch(struct nameidata *nd) +{ + if (!strcmp(nd->dentry->d_sb->s_type->name, "unionfs")) + return -EINVAL; + if (!nd->dentry->d_inode) + return -ENOENT; + if (!S_ISDIR(nd->dentry->d_inode->i_mode)) + return -ENOTDIR; + return 0; +} + +/* checks if two hidden_dentries have overlapping branches */ +static int is_branch_overlap(struct dentry *dent1, struct dentry *dent2) +{ + struct dentry *dent = NULL; + + dent = dent1; + while ((dent != dent2) && (dent->d_parent != dent)) + dent = dent->d_parent; + + if (dent == dent2) + return 1; + + dent = dent2; + while ((dent != dent1) && (dent->d_parent != dent)) + dent = dent->d_parent; + + return (dent == dent1); +} + +/* + * Parse branch mode helper function + */ +int __parse_branch_mode(const char *name) +{ + if (!name) + return 0; + if (!strcmp(name, "ro")) + return MAY_READ; + if (!strcmp(name, "rw")) + return (MAY_READ | MAY_WRITE); + return 0; +} + +/* + * Parse "ro" or "rw" options, but default to "rw" of no mode options + * was specified. + */ +int parse_branch_mode(const char *name) +{ + int perms = __parse_branch_mode(name); + if (perms == 0) + perms = MAY_READ | MAY_WRITE; + return perms; +} + +/* parse the dirs= mount argument */ +static int parse_dirs_option(struct super_block *sb, struct unionfs_dentry_info + *hidden_root_info, char *options) +{ + struct nameidata nd; + char *name; + int err = 0; + int branches = 1; + int bindex = 0; + int i = 0; + int j = 0; + + struct dentry *dent1; + struct dentry *dent2; + + if (options[0] == '\0') { + printk(KERN_WARNING "unionfs: no branches specified\n"); + err = -EINVAL; + goto out; + } + + /* Each colon means we have a separator, this is really just a rough + * guess, since strsep will handle empty fields for us. + */ + for (i = 0; options[i]; i++) + if (options[i] == ':') + branches++; + + /* allocate space for underlying pointers to hidden dentry */ + UNIONFS_SB(sb)->data = kcalloc(branches, + sizeof(struct unionfs_data), GFP_KERNEL); + if (!UNIONFS_SB(sb)->data) { + err = -ENOMEM; + goto out; + } + + hidden_root_info->lower_paths = kcalloc(branches, + sizeof(struct path), GFP_KERNEL); + if (!hidden_root_info->lower_paths) { + err = -ENOMEM; + goto out; + } + + /* now parsing a string such as "b1:b2=rw:b3=ro:b4" */ + branches = 0; + while ((name = strsep(&options, ":")) != NULL) { + int perms; + char *mode = strchr(name, '='); + + if (!name || !*name) + continue; + + branches++; + + /* strip off '=' if any */ + if (mode) + *mode++ = '\0'; + + perms = parse_branch_mode(mode); + if (!bindex && !(perms & MAY_WRITE)) { + err = -EINVAL; + goto out; + } + + err = path_lookup(name, LOOKUP_FOLLOW, &nd); + if (err) { + printk(KERN_WARNING "unionfs: error accessing " + "hidden directory '%s' (error %d)\n", name, err); + goto out; + } + + if ((err = check_branch(&nd))) { + printk(KERN_WARNING "unionfs: hidden directory " + "'%s' is not a valid branch\n", name); + path_release(&nd); + goto out; + } + + hidden_root_info->lower_paths[bindex].dentry = nd.dentry; + hidden_root_info->lower_paths[bindex].mnt = nd.mnt; + + unionfs_write_lock(sb); + set_branchperms(sb, bindex, perms); + set_branch_count(sb, bindex, 0); + new_branch_id(sb, bindex); + unionfs_write_unlock(sb); + + if (hidden_root_info->bstart < 0) + hidden_root_info->bstart = bindex; + hidden_root_info->bend = bindex; + bindex++; + } + + if (branches == 0) { + printk(KERN_WARNING "unionfs: no branches specified\n"); + err = -EINVAL; + goto out; + } + + BUG_ON(branches != (hidden_root_info->bend + 1)); + + /* ensure that no overlaps exist in the branches */ + for (i = 0; i < branches; i++) { + for (j = i + 1; j < branches; j++) { + dent1 = hidden_root_info->lower_paths[i].dentry; + dent2 = hidden_root_info->lower_paths[j].dentry; + + if (is_branch_overlap(dent1, dent2)) { + printk(KERN_WARNING "unionfs: branches %d and " + "%d overlap\n", i, j); + err = -EINVAL; + goto out; + } + } + } + +out: + if (err) { + for (i = 0; i < branches; i++) + if (hidden_root_info->lower_paths[i].dentry) { + dput(hidden_root_info->lower_paths[i].dentry); + /* initializing: can't use unionfs_mntput here */ + mntput(hidden_root_info->lower_paths[i].mnt); + } + + kfree(hidden_root_info->lower_paths); + kfree(UNIONFS_SB(sb)->data); + + /* MUST clear the pointers to prevent potential double free if + * the caller dies later on + */ + hidden_root_info->lower_paths = NULL; + UNIONFS_SB(sb)->data = NULL; + } + return err; +} + +/* + * Parse mount options. See the manual page for usage instructions. + * + * Returns the dentry object of the lower-level (hidden) directory; + * We want to mount our stackable file system on top of that hidden directory. + */ +static struct unionfs_dentry_info *unionfs_parse_options(struct super_block *sb, + char *options) +{ + struct unionfs_dentry_info *hidden_root_info; + char *optname; + int err = 0; + int bindex; + int dirsfound = 0; + + /* allocate private data area */ + err = -ENOMEM; + hidden_root_info = + kzalloc(sizeof(struct unionfs_dentry_info), GFP_KERNEL); + if (!hidden_root_info) + goto out_error; + hidden_root_info->bstart = -1; + hidden_root_info->bend = -1; + hidden_root_info->bopaque = -1; + + while ((optname = strsep(&options, ",")) != NULL) { + char *optarg; + char *endptr; + int intval; + + if (!optname || !*optname) + continue; + + optarg = strchr(optname, '='); + if (optarg) + *optarg++ = '\0'; + + /* All of our options take an argument now. Insert ones that + * don't, above this check. + */ + if (!optarg) { + printk("unionfs: %s requires an argument.\n", optname); + err = -EINVAL; + goto out_error; + } + + if (!strcmp("dirs", optname)) { + if (++dirsfound > 1) { + printk(KERN_WARNING + "unionfs: multiple dirs specified\n"); + err = -EINVAL; + goto out_error; + } + err = parse_dirs_option(sb, hidden_root_info, optarg); + if (err) + goto out_error; + continue; + } + + /* All of these options require an integer argument. */ + intval = simple_strtoul(optarg, &endptr, 0); + if (*endptr) { + printk(KERN_WARNING + "unionfs: invalid %s option '%s'\n", + optname, optarg); + err = -EINVAL; + goto out_error; + } + + err = -EINVAL; + printk(KERN_WARNING + "unionfs: unrecognized option '%s'\n", optname); + goto out_error; + } + if (dirsfound != 1) { + printk(KERN_WARNING "unionfs: dirs option required\n"); + err = -EINVAL; + goto out_error; + } + goto out; + +out_error: + if (hidden_root_info && hidden_root_info->lower_paths) { + for (bindex = hidden_root_info->bstart; + bindex >= 0 && bindex <= hidden_root_info->bend; + bindex++) { + struct dentry *d; + struct vfsmount *m; + + d = hidden_root_info->lower_paths[bindex].dentry; + m = hidden_root_info->lower_paths[bindex].mnt; + + dput(d); + /* initializing: can't use unionfs_mntput here */ + mntput(m); + } + } + + kfree(hidden_root_info->lower_paths); + kfree(hidden_root_info); + + kfree(UNIONFS_SB(sb)->data); + UNIONFS_SB(sb)->data = NULL; + + hidden_root_info = ERR_PTR(err); +out: + return hidden_root_info; +} + +/* our custom d_alloc_root workalike + * + * we can't use d_alloc_root if we want to use our own interpose function + * unchanged, so we simply call our own "fake" d_alloc_root + */ +static struct dentry *unionfs_d_alloc_root(struct super_block *sb) +{ + struct dentry *ret = NULL; + + if (sb) { + static const struct qstr name = {.name = "/",.len = 1 }; + + ret = d_alloc(NULL, &name); + if (ret) { + ret->d_op = &unionfs_dops; + ret->d_sb = sb; + ret->d_parent = ret; + } + } + return ret; +} + +static int unionfs_read_super(struct super_block *sb, void *raw_data, + int silent) +{ + int err = 0; + + struct unionfs_dentry_info *hidden_root_info = NULL; + int bindex, bstart, bend; + + if (!raw_data) { + printk(KERN_WARNING + "unionfs_read_super: missing data argument\n"); + err = -EINVAL; + goto out; + } + + /* Allocate superblock private data */ + sb->s_fs_info = kzalloc(sizeof(struct unionfs_sb_info), GFP_KERNEL); + if (!UNIONFS_SB(sb)) { + printk(KERN_WARNING "%s: out of memory\n", __FUNCTION__); + err = -ENOMEM; + goto out; + } + + UNIONFS_SB(sb)->bend = -1; + atomic_set(&UNIONFS_SB(sb)->generation, 1); + init_rwsem(&UNIONFS_SB(sb)->rwsem); + UNIONFS_SB(sb)->high_branch_id = -1; /* -1 == invalid branch ID */ + + hidden_root_info = unionfs_parse_options(sb, raw_data); + if (IS_ERR(hidden_root_info)) { + printk(KERN_WARNING + "unionfs_read_super: error while parsing options " + "(err = %ld)\n", PTR_ERR(hidden_root_info)); + err = PTR_ERR(hidden_root_info); + hidden_root_info = NULL; + goto out_free; + } + if (hidden_root_info->bstart == -1) { + err = -ENOENT; + goto out_free; + } + + /* set the hidden superblock field of upper superblock */ + bstart = hidden_root_info->bstart; + BUG_ON(bstart != 0); + sbend(sb) = bend = hidden_root_info->bend; + for (bindex = bstart; bindex <= bend; bindex++) { + struct dentry *d; + + 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; + + /* See comment next to the definition of unionfs_d_alloc_root */ + sb->s_root = unionfs_d_alloc_root(sb); + if (!sb->s_root) { + err = -ENOMEM; + goto out_dput; + } + + /* link the upper and lower dentries */ + sb->s_root->d_fsdata = NULL; + if ((err = new_dentry_private_data(sb->s_root))) + goto out_freedpd; + + /* Set the hidden dentries for s_root */ + for (bindex = bstart; bindex <= bend; bindex++) { + struct dentry *d; + struct vfsmount *m; + + d = hidden_root_info->lower_paths[bindex].dentry; + m = hidden_root_info->lower_paths[bindex].mnt; + + unionfs_set_lower_dentry_idx(sb->s_root, bindex, d); + unionfs_set_lower_mnt_idx(sb->s_root, bindex, m); + } + set_dbstart(sb->s_root, bstart); + set_dbend(sb->s_root, bend); + + /* Set the generation number to one, since this is for the mount. */ + atomic_set(&UNIONFS_D(sb->s_root)->generation, 1); + + /* call interpose to create the upper level inode */ + err = unionfs_interpose(sb->s_root, sb, 0); + unionfs_unlock_dentry(sb->s_root); + if (!err) + goto out; + /* else fall through */ + +out_freedpd: + if (UNIONFS_D(sb->s_root)) { + kfree(UNIONFS_D(sb->s_root)->lower_paths); + free_dentry_private_data(UNIONFS_D(sb->s_root)); + } + dput(sb->s_root); + +out_dput: + if (hidden_root_info && !IS_ERR(hidden_root_info)) { + for (bindex = hidden_root_info->bstart; + bindex <= hidden_root_info->bend; bindex++) { + struct dentry *d; + struct vfsmount *m; + + d = hidden_root_info->lower_paths[bindex].dentry; + m = hidden_root_info->lower_paths[bindex].mnt; + + dput(d); + /* initializing: can't use unionfs_mntput here */ + mntput(m); + } + kfree(hidden_root_info->lower_paths); + kfree(hidden_root_info); + hidden_root_info = NULL; + } + +out_free: + kfree(UNIONFS_SB(sb)->data); + kfree(UNIONFS_SB(sb)); + sb->s_fs_info = NULL; + +out: + if (hidden_root_info && !IS_ERR(hidden_root_info)) { + kfree(hidden_root_info->lower_paths); + kfree(hidden_root_info); + } + return err; +} + +static int unionfs_get_sb(struct file_system_type *fs_type, + int flags, const char *dev_name, + void *raw_data, struct vfsmount *mnt) +{ + return get_sb_nodev(fs_type, flags, raw_data, unionfs_read_super, mnt); +} + +static struct file_system_type unionfs_fs_type = { + .owner = THIS_MODULE, + .name = "unionfs", + .get_sb = unionfs_get_sb, + .kill_sb = generic_shutdown_super, + .fs_flags = FS_REVAL_DOT, +}; + +static int __init init_unionfs_fs(void) +{ + int err; + printk("Registering unionfs " UNIONFS_VERSION "\n"); + + if ((err = unionfs_init_filldir_cache())) + goto out; + if ((err = unionfs_init_inode_cache())) + goto out; + if ((err = unionfs_init_dentry_cache())) + goto out; + if ((err = init_sioq())) + goto out; + err = register_filesystem(&unionfs_fs_type); +out: + if (err) { + stop_sioq(); + unionfs_destroy_filldir_cache(); + unionfs_destroy_inode_cache(); + unionfs_destroy_dentry_cache(); + } + return err; +} + +static void __exit exit_unionfs_fs(void) +{ + stop_sioq(); + unionfs_destroy_filldir_cache(); + unionfs_destroy_inode_cache(); + unionfs_destroy_dentry_cache(); + unregister_filesystem(&unionfs_fs_type); + printk("Completed unionfs module unload.\n"); +} + +MODULE_AUTHOR("Erez Zadok, Filesystems and Storage Lab, Stony Brook University" + " (http://www.fsl.cs.sunysb.edu)"); +MODULE_DESCRIPTION("Unionfs " UNIONFS_VERSION + " (http://unionfs.filesystems.org)"); +MODULE_LICENSE("GPL"); + +module_init(init_unionfs_fs); +module_exit(exit_unionfs_fs); + diff --git a/fs/unionfs/rdstate.c b/fs/unionfs/rdstate.c new file mode 100644 index 0000000..b67a86a --- /dev/null +++ b/fs/unionfs/rdstate.c @@ -0,0 +1,275 @@ +/* + * 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 State University of New York + * + * 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" + +/* This file contains the routines for maintaining readdir state. */ + +/* There are two structures here, rdstate which is a hash table + * of the second structure which is a filldir_node. + */ + +/* This is a struct kmem_cache for filldir nodes, because we allocate a lot + * of them and they shouldn't waste memory. If the node has a small name + * (as defined by the dentry structure), then we use an inline name to + * preserve kmalloc space. + */ +static struct kmem_cache *unionfs_filldir_cachep; + +int unionfs_init_filldir_cache(void) +{ + unionfs_filldir_cachep = + kmem_cache_create("unionfs_filldir", sizeof(struct filldir_node), 0, + SLAB_RECLAIM_ACCOUNT, NULL, NULL); + + return (unionfs_filldir_cachep ? 0 : -ENOMEM); +} + +void unionfs_destroy_filldir_cache(void) +{ + if (unionfs_filldir_cachep) + kmem_cache_destroy(unionfs_filldir_cachep); +} + +/* This is a tuning parameter that tells us roughly how big to make the + * hash table in directory entries per page. This isn't perfect, but + * at least we get a hash table size that shouldn't be too overloaded. + * The following averages are based on my home directory. + * 14.44693 Overall + * 12.29 Single Page Directories + * 117.93 Multi-page directories + */ +#define DENTPAGE 4096 +#define DENTPERONEPAGE 12 +#define DENTPERPAGE 118 +#define MINHASHSIZE 1 +static int guesstimate_hash_size(struct inode *inode) +{ + struct inode *hidden_inode; + int bindex; + int hashsize = MINHASHSIZE; + + if (UNIONFS_I(inode)->hashsize > 0) + return UNIONFS_I(inode)->hashsize; + + for (bindex = ibstart(inode); bindex <= ibend(inode); bindex++) { + if (!(hidden_inode = unionfs_lower_inode_idx(inode, bindex))) + continue; + + if (hidden_inode->i_size == DENTPAGE) + hashsize += DENTPERONEPAGE; + else + hashsize += (hidden_inode->i_size / DENTPAGE) * DENTPERPAGE; + } + + return hashsize; +} + +int init_rdstate(struct file *file) +{ + BUG_ON(sizeof(loff_t) != (sizeof(unsigned int) + sizeof(unsigned int))); + BUG_ON(UNIONFS_F(file)->rdstate != NULL); + + UNIONFS_F(file)->rdstate = alloc_rdstate(file->f_dentry->d_inode, + fbstart(file)); + + return (UNIONFS_F(file)->rdstate ? 0 : -ENOMEM); +} + +struct unionfs_dir_state *find_rdstate(struct inode *inode, loff_t fpos) +{ + struct unionfs_dir_state *rdstate = NULL; + struct list_head *pos; + + spin_lock(&UNIONFS_I(inode)->rdlock); + list_for_each(pos, &UNIONFS_I(inode)->readdircache) { + struct unionfs_dir_state *r = + list_entry(pos, struct unionfs_dir_state, cache); + if (fpos == rdstate2offset(r)) { + UNIONFS_I(inode)->rdcount--; + list_del(&r->cache); + rdstate = r; + break; + } + } + spin_unlock(&UNIONFS_I(inode)->rdlock); + return rdstate; +} + +struct unionfs_dir_state *alloc_rdstate(struct inode *inode, int bindex) +{ + int i = 0; + int hashsize; + unsigned long mallocsize = sizeof(struct unionfs_dir_state); + struct unionfs_dir_state *rdstate; + + hashsize = guesstimate_hash_size(inode); + mallocsize += hashsize * sizeof(struct list_head); + mallocsize = __roundup_pow_of_two(mallocsize); + + /* This should give us about 500 entries anyway. */ + if (mallocsize > PAGE_SIZE) + mallocsize = PAGE_SIZE; + + hashsize = (mallocsize - + sizeof(struct unionfs_dir_state)) / sizeof(struct list_head); + + rdstate = kmalloc(mallocsize, GFP_KERNEL); + if (!rdstate) + return NULL; + + spin_lock(&UNIONFS_I(inode)->rdlock); + if (UNIONFS_I(inode)->cookie >= (MAXRDCOOKIE - 1)) + UNIONFS_I(inode)->cookie = 1; + else + UNIONFS_I(inode)->cookie++; + + rdstate->cookie = UNIONFS_I(inode)->cookie; + spin_unlock(&UNIONFS_I(inode)->rdlock); + rdstate->offset = 1; + rdstate->access = jiffies; + rdstate->bindex = bindex; + rdstate->dirpos = 0; + rdstate->hashentries = 0; + rdstate->size = hashsize; + for (i = 0; i < rdstate->size; i++) + INIT_LIST_HEAD(&rdstate->list[i]); + + return rdstate; +} + +static void free_filldir_node(struct filldir_node *node) +{ + if (node->namelen >= DNAME_INLINE_LEN_MIN) + kfree(node->name); + kmem_cache_free(unionfs_filldir_cachep, node); +} + +void free_rdstate(struct unionfs_dir_state *state) +{ + struct filldir_node *tmp; + int i; + + for (i = 0; i < state->size; i++) { + struct list_head *head = &(state->list[i]); + struct list_head *pos, *n; + + /* traverse the list and deallocate space */ + list_for_each_safe(pos, n, head) { + tmp = list_entry(pos, struct filldir_node, file_list); + list_del(&tmp->file_list); + free_filldir_node(tmp); + } + } + + kfree(state); +} + +struct filldir_node *find_filldir_node(struct unionfs_dir_state *rdstate, + const char *name, int namelen) +{ + int index; + unsigned int hash; + struct list_head *head; + struct list_head *pos; + struct filldir_node *cursor = NULL; + int found = 0; + + BUG_ON(namelen <= 0); + + hash = full_name_hash(name, namelen); + index = hash % rdstate->size; + + head = &(rdstate->list[index]); + list_for_each(pos, head) { + cursor = list_entry(pos, struct filldir_node, file_list); + + if (cursor->namelen == namelen && cursor->hash == hash && + !strncmp(cursor->name, name, namelen)) { + /* a duplicate exists, and hence no need to create + * entry to the list + */ + found = 1; + + /* if the duplicate is in this branch, then the file + * system is corrupted. + */ + if (cursor->bindex == rdstate->bindex) { + printk(KERN_DEBUG "Possible I/O error " + "unionfs_filldir: a file is duplicated " + "in the same branch %d: %s\n", + rdstate->bindex, cursor->name); + } + break; + } + } + + if (!found) + cursor = NULL; + + return cursor; +} + +int add_filldir_node(struct unionfs_dir_state *rdstate, const char *name, + int namelen, int bindex, int whiteout) +{ + struct filldir_node *new; + unsigned int hash; + int index; + int err = 0; + struct list_head *head; + + BUG_ON(namelen <= 0); + + hash = full_name_hash(name, namelen); + index = hash % rdstate->size; + head = &(rdstate->list[index]); + + new = kmem_cache_alloc(unionfs_filldir_cachep, GFP_KERNEL); + if (!new) { + err = -ENOMEM; + goto out; + } + + INIT_LIST_HEAD(&new->file_list); + new->namelen = namelen; + new->hash = hash; + new->bindex = bindex; + new->whiteout = whiteout; + + if (namelen < DNAME_INLINE_LEN_MIN) + new->name = new->iname; + else { + new->name = kmalloc(namelen + 1, GFP_KERNEL); + if (!new->name) { + kmem_cache_free(unionfs_filldir_cachep, new); + new = NULL; + goto out; + } + } + + memcpy(new->name, name, namelen); + new->name[namelen] = '\0'; + + rdstate->hashentries++; + + list_add(&(new->file_list), head); +out: + return err; +} + diff --git a/fs/unionfs/rename.c b/fs/unionfs/rename.c new file mode 100644 index 0000000..0044492 --- /dev/null +++ b/fs/unionfs/rename.c @@ -0,0 +1,448 @@ +/* + * 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 State University of New York + * + * 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" + +static int do_rename(struct inode *old_dir, struct dentry *old_dentry, + struct inode *new_dir, struct dentry *new_dentry, + int bindex, struct dentry **wh_old) +{ + int err = 0; + struct dentry *hidden_old_dentry; + struct dentry *hidden_new_dentry; + struct dentry *hidden_old_dir_dentry; + struct dentry *hidden_new_dir_dentry; + struct dentry *hidden_wh_dentry; + struct dentry *hidden_wh_dir_dentry; + char *wh_name = NULL; + + hidden_new_dentry = unionfs_lower_dentry_idx(new_dentry, bindex); + hidden_old_dentry = unionfs_lower_dentry_idx(old_dentry, bindex); + + if (!hidden_new_dentry) { + hidden_new_dentry = + create_parents(new_dentry->d_parent->d_inode, new_dentry, bindex); + if (IS_ERR(hidden_new_dentry)) { + printk(KERN_DEBUG "error creating directory tree for" + " rename, bindex = %d, err = %ld\n", + bindex, PTR_ERR(hidden_new_dentry)); + err = PTR_ERR(hidden_new_dentry); + goto out; + } + } + + wh_name = alloc_whname(new_dentry->d_name.name, new_dentry->d_name.len); + if (IS_ERR(wh_name)) { + err = PTR_ERR(wh_name); + goto out; + } + + hidden_wh_dentry = lookup_one_len(wh_name, hidden_new_dentry->d_parent, + new_dentry->d_name.len + UNIONFS_WHLEN); + if (IS_ERR(hidden_wh_dentry)) { + err = PTR_ERR(hidden_wh_dentry); + goto out; + } + + if (hidden_wh_dentry->d_inode) { + /* get rid of the whiteout that is existing */ + if (hidden_new_dentry->d_inode) { + printk(KERN_WARNING "Both a whiteout and a dentry" + " exist when doing a rename!\n"); + err = -EIO; + + dput(hidden_wh_dentry); + goto out; + } + + hidden_wh_dir_dentry = lock_parent(hidden_wh_dentry); + if (!(err = is_robranch_super(old_dentry->d_sb, bindex))) + err = vfs_unlink(hidden_wh_dir_dentry->d_inode, + hidden_wh_dentry); + + dput(hidden_wh_dentry); + unlock_dir(hidden_wh_dir_dentry); + if (err) + goto out; + } else + dput(hidden_wh_dentry); + + dget(hidden_old_dentry); + hidden_old_dir_dentry = dget_parent(hidden_old_dentry); + hidden_new_dir_dentry = dget_parent(hidden_new_dentry); + + lock_rename(hidden_old_dir_dentry, hidden_new_dir_dentry); + + err = is_robranch_super(old_dentry->d_sb, bindex); + if (err) + goto out_unlock; + + /* 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 (IS_ERR(whname)) + goto out_unlock; + *wh_old = lookup_one_len(whname, hidden_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_unlock; + } + } + + err = vfs_rename(hidden_old_dir_dentry->d_inode, hidden_old_dentry, + hidden_new_dir_dentry->d_inode, hidden_new_dentry); + +out_unlock: + unlock_rename(hidden_old_dir_dentry, hidden_new_dir_dentry); + + dput(hidden_old_dir_dentry); + dput(hidden_new_dir_dentry); + dput(hidden_old_dentry); + +out: + if (!err) { + /* Fixup the newdentry. */ + if (bindex < dbstart(new_dentry)) + set_dbstart(new_dentry, bindex); + else if (bindex > dbend(new_dentry)) + set_dbend(new_dentry, bindex); + } + + kfree(wh_name); + + return err; +} + +static int do_unionfs_rename(struct inode *old_dir, + struct dentry *old_dentry, + struct inode *new_dir, + struct dentry *new_dentry) +{ + 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; + struct dentry *wh_old = NULL; + + 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 = do_rename(old_dir, old_dentry, new_dir, new_dentry, old_bstart, + &wh_old); + if (err) { + if (!IS_COPYUP_ERR(err)) + goto out; + do_copyup = old_bstart - 1; + } else + revert = 1; + + /* Unlink all instances of destination that exist to the left of + * bstart of source. On error, revert back, goto out. + */ + for (bindex = old_bstart - 1; bindex >= new_bstart; bindex--) { + struct dentry *unlink_dentry; + struct dentry *unlink_dir_dentry; + + unlink_dentry = unionfs_lower_dentry_idx(new_dentry, bindex); + if (!unlink_dentry) + continue; + + unlink_dir_dentry = lock_parent(unlink_dentry); + if (!(err = is_robranch_super(old_dir->i_sb, bindex))) + err = vfs_unlink(unlink_dir_dentry->d_inode, + unlink_dentry); + + fsstack_copy_attr_times(new_dentry->d_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); + + unlock_dir(unlink_dir_dentry); + if (!err) { + if (bindex != new_bstart) { + dput(unlink_dentry); + unionfs_set_lower_dentry_idx(new_dentry, bindex, NULL); + } + } else if (IS_COPYUP_ERR(err)) { + do_copyup = bindex - 1; + } else if (revert) { + dput(wh_old); + goto revert; + } + } + + if (do_copyup != -1) { + for (bindex = do_copyup; bindex >= 0; bindex--) { + /* copyup the file into some left directory, so that + * you can rename it + */ + err = copyup_dentry(old_dentry->d_parent->d_inode, + old_dentry, old_bstart, bindex, NULL, + old_dentry->d_inode->i_size); + if (!err) { + dput(wh_old); + bwh_old = bindex; + err = do_rename(old_dir, old_dentry, new_dir, + new_dentry, bindex, &wh_old); + break; + } + } + } + + /* make it opaque */ + if (S_ISDIR(old_dentry->d_inode->i_mode)) { + err = make_dir_opaque(old_dentry, dbstart(old_dentry)); + if (err) + goto revert; + } + + /* Create whiteout for source, only if: + * (1) There is more than one underlying instance of source. + * (2) We did a copy_up + */ + if ((old_bstart != old_bend) || (do_copyup != -1)) { + struct dentry *hidden_parent; + BUG_ON(!wh_old || wh_old->d_inode || bwh_old < 0); + hidden_parent = lock_parent(wh_old); + local_err = vfs_create(hidden_parent->d_inode, wh_old, S_IRUGO, + NULL); + unlock_dir(hidden_parent); + if (!local_err) + set_dbopaque(old_dentry, bwh_old); + else { + /* We can't fix anything now, so we cop-out and use -EIO. */ + printk(KERN_ERR "We can't create a whiteout for the " + "source in rename!\n"); + err = -EIO; + } + } + +out: + dput(wh_old); + return err; + +revert: + /* Do revert here. */ + local_err = unionfs_refresh_hidden_dentry(new_dentry, old_bstart); + if (local_err) { + printk(KERN_WARNING "Revert failed in rename: the new refresh " + "failed.\n"); + eio = -EIO; + } + + local_err = unionfs_refresh_hidden_dentry(old_dentry, old_bstart); + if (local_err) { + printk(KERN_WARNING "Revert failed in rename: the old refresh " + "failed.\n"); + eio = -EIO; + goto revert_out; + } + + if (!unionfs_lower_dentry_idx(new_dentry, bindex) || + !unionfs_lower_dentry_idx(new_dentry, bindex)->d_inode) { + printk(KERN_WARNING "Revert failed in rename: the object " + "disappeared from under us!\n"); + eio = -EIO; + goto revert_out; + } + + if (unionfs_lower_dentry_idx(old_dentry, bindex) && + unionfs_lower_dentry_idx(old_dentry, bindex)->d_inode) { + printk(KERN_WARNING "Revert failed in rename: the object was " + "created underneath us!\n"); + eio = -EIO; + goto revert_out; + } + + local_err = do_rename(new_dir, new_dentry, old_dir, old_dentry, old_bstart, + NULL); + + /* If we can't fix it, then we cop-out with -EIO. */ + if (local_err) { + printk(KERN_WARNING "Revert failed in rename!\n"); + eio = -EIO; + } + + local_err = unionfs_refresh_hidden_dentry(new_dentry, bindex); + if (local_err) + eio = -EIO; + local_err = unionfs_refresh_hidden_dentry(old_dentry, bindex); + if (local_err) + eio = -EIO; + +revert_out: + if (eio) + err = eio; + return err; +} + +static struct dentry *lookup_whiteout(struct dentry *dentry) +{ + char *whname; + int bindex = -1, bstart = -1, bend = -1; + struct dentry *parent, *hidden_parent, *wh_dentry; + + whname = alloc_whname(dentry->d_name.name, dentry->d_name.len); + if (IS_ERR(whname)) + return (void *)whname; + + parent = dget_parent(dentry); + unionfs_lock_dentry(parent); + bstart = dbstart(parent); + bend = dbend(parent); + wh_dentry = ERR_PTR(-ENOENT); + for (bindex = bstart; bindex <= bend; bindex++) { + hidden_parent = unionfs_lower_dentry_idx(parent, bindex); + if (!hidden_parent) + continue; + wh_dentry = lookup_one_len(whname, hidden_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 let the userspace recurse and ask us + * to copy up each file separately + */ +static int may_rename_dir(struct dentry *dentry) +{ + int err, bstart; + + err = check_empty(dentry, NULL); + if (err == -ENOTEMPTY) { + if (is_robranch(dentry)) + return -EXDEV; + } else if (err) + return err; + + bstart = dbstart(dentry); + if (dbend(dentry) == bstart || dbopaque(dentry) == bstart) + return 0; + + set_dbstart(dentry, bstart + 1); + err = check_empty(dentry, NULL); + set_dbstart(dentry, bstart); + if (err == -ENOTEMPTY) + err = -EXDEV; + return err; +} + +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; + + BUG_ON(!is_valid_dentry(old_dentry)); + BUG_ON(!is_valid_dentry(new_dentry)); + + double_lock_dentry(old_dentry, new_dentry); + + if (!S_ISDIR(old_dentry->d_inode->i_mode)) + err = unionfs_partial_lookup(old_dentry); + else + err = may_rename_dir(old_dentry); + + if (err) + goto out; + + err = unionfs_partial_lookup(new_dentry); + if (err) + goto out; + + /* + * if new_dentry is already hidden because of whiteout, + * simply override it even if the whiteouted dir is not empty. + */ + wh_dentry = lookup_whiteout(new_dentry); + if (!IS_ERR(wh_dentry)) + dput(wh_dentry); + else if (new_dentry->d_inode) { + if (S_ISDIR(old_dentry->d_inode->i_mode) != + S_ISDIR(new_dentry->d_inode->i_mode)) { + err = S_ISDIR(old_dentry->d_inode->i_mode) ? + -ENOTDIR : -EISDIR; + goto out; + } + + if (S_ISDIR(new_dentry->d_inode->i_mode)) { + struct unionfs_dir_state *namelist; + /* check if this unionfs directory is empty or not */ + err = check_empty(new_dentry, &namelist); + if (err) + goto out; + + if (!is_robranch(new_dentry)) + err = delete_whiteouts(new_dentry, + dbstart(new_dentry), + namelist); + + free_rdstate(namelist); + + if (err) + goto out; + } + } + err = do_unionfs_rename(old_dir, old_dentry, new_dir, new_dentry); + +out: + if (err) + /* clear the new_dentry stuff created */ + d_drop(new_dentry); + else + /* force re-lookup since the dir on ro branch is not renamed, + and hidden dentries still indicate the un-renamed ones. */ + if (S_ISDIR(old_dentry->d_inode->i_mode)) + atomic_dec(&UNIONFS_D(old_dentry)->generation); + + unionfs_unlock_dentry(new_dentry); + unionfs_unlock_dentry(old_dentry); + return err; +} + diff --git a/fs/unionfs/sioq.c b/fs/unionfs/sioq.c new file mode 100644 index 0000000..c0d89a3 --- /dev/null +++ b/fs/unionfs/sioq.c @@ -0,0 +1,123 @@ +/* + * 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 State University of New York + * + * 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" + +/* Super-user IO work Queue - sometimes we need to perform actions which + * would fail due to the unix permissions on the parent directory (e.g., + * rmdir a directory which appears empty, but in reality contains + * whiteouts). + */ + +static struct workqueue_struct *superio_workqueue; + +int __init init_sioq(void) +{ + int err; + + superio_workqueue = create_workqueue("unionfs_siod"); + if (!IS_ERR(superio_workqueue)) + return 0; + + err = PTR_ERR(superio_workqueue); + printk(KERN_ERR "create_workqueue failed %d\n", err); + superio_workqueue = NULL; + return err; +} + +void stop_sioq(void) +{ + if (superio_workqueue) + destroy_workqueue(superio_workqueue); +} + +void run_sioq(work_func_t func, struct sioq_args *args) +{ + INIT_WORK(&args->work, func); + + init_completion(&args->comp); + while (!queue_work(superio_workqueue, &args->work)) { + /* TODO: do accounting if needed */ + schedule(); + } + wait_for_completion(&args->comp); +} + +void __unionfs_create(struct work_struct *work) +{ + struct sioq_args *args = container_of(work, struct sioq_args, work); + struct create_args *c = &args->create; + + args->err = vfs_create(c->parent, c->dentry, c->mode, c->nd); + complete(&args->comp); +} + +void __unionfs_mkdir(struct work_struct *work) +{ + struct sioq_args *args = container_of(work, struct sioq_args, work); + struct mkdir_args *m = &args->mkdir; + + args->err = vfs_mkdir(m->parent, m->dentry, m->mode); + complete(&args->comp); +} + +void __unionfs_mknod(struct work_struct *work) +{ + struct sioq_args *args = container_of(work, struct sioq_args, work); + struct mknod_args *m = &args->mknod; + + args->err = vfs_mknod(m->parent, m->dentry, m->mode, m->dev); + complete(&args->comp); +} + +void __unionfs_symlink(struct work_struct *work) +{ + struct sioq_args *args = container_of(work, struct sioq_args, work); + struct symlink_args *s = &args->symlink; + + args->err = vfs_symlink(s->parent, s->dentry, s->symbuf, s->mode); + complete(&args->comp); +} + +void __unionfs_unlink(struct work_struct *work) +{ + struct sioq_args *args = container_of(work, struct sioq_args, work); + struct unlink_args *u = &args->unlink; + + 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/sioq.h b/fs/unionfs/sioq.h new file mode 100644 index 0000000..20e3b0c --- /dev/null +++ b/fs/unionfs/sioq.h @@ -0,0 +1,78 @@ +#ifndef _SIOQ_H +#define _SIOQ_H + +struct deletewh_args { + struct unionfs_dir_state *namelist; + struct dentry *dentry; + int bindex; +}; + +struct is_opaque_args { + struct dentry *dentry; +}; + +struct create_args { + struct inode *parent; + struct dentry *dentry; + umode_t mode; + struct nameidata *nd; +}; + +struct mkdir_args { + struct inode *parent; + struct dentry *dentry; + umode_t mode; +}; + +struct mknod_args { + struct inode *parent; + struct dentry *dentry; + umode_t mode; + dev_t dev; +}; + +struct symlink_args { + struct inode *parent; + struct dentry *dentry; + char *symbuf; + umode_t mode; +}; + +struct unlink_args { + struct inode *parent; + struct dentry *dentry; +}; + + +struct sioq_args { + struct completion comp; + struct work_struct work; + int err; + void *ret; + + union { + struct deletewh_args deletewh; + struct is_opaque_args is_opaque; + struct create_args create; + struct mkdir_args mkdir; + struct mknod_args mknod; + struct symlink_args symlink; + struct unlink_args unlink; + }; +}; + +extern int __init init_sioq(void); +extern __exit void stop_sioq(void); +extern void run_sioq(work_func_t func, struct sioq_args *args); + +/* Extern definitions for our privlege escalation helpers */ +extern void __unionfs_create(struct work_struct *work); +extern void __unionfs_mkdir(struct work_struct *work); +extern void __unionfs_mknod(struct work_struct *work); +extern void __unionfs_symlink(struct work_struct *work); +extern void __unionfs_unlink(struct work_struct *work); +extern void __delete_whiteouts(struct work_struct *work); +extern void __is_opaque_dir(struct work_struct *work); + +#endif /* _SIOQ_H */ + diff --git a/fs/unionfs/subr.c b/fs/unionfs/subr.c new file mode 100644 index 0000000..d274752 --- /dev/null +++ b/fs/unionfs/subr.c @@ -0,0 +1,233 @@ +/* + * 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 State University of New York + * + * 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" + +/* 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 *hidden_dir_dentry; + struct dentry *hidden_dentry; + struct dentry *hidden_wh_dentry; + 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 (IS_ERR(name)) { + err = PTR_ERR(name); + goto out; + } + + for (bindex = start; bindex >= 0; bindex--) { + hidden_dentry = unionfs_lower_dentry_idx(dentry, bindex); + + if (!hidden_dentry) { + /* if hidden dentry is not present, create the entire + * hidden 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. + */ + hidden_dentry = create_parents(dentry->d_inode, + dentry, bindex); + if (!hidden_dentry || IS_ERR(hidden_dentry)) { + printk(KERN_DEBUG "create_parents failed for " + "bindex = %d\n", bindex); + continue; + } + } + + hidden_wh_dentry = lookup_one_len(name, hidden_dentry->d_parent, + dentry->d_name.len + UNIONFS_WHLEN); + if (IS_ERR(hidden_wh_dentry)) + continue; + + /* The whiteout already exists. This used to be impossible, but + * now is possible because of opaqueness. + */ + if (hidden_wh_dentry->d_inode) { + dput(hidden_wh_dentry); + err = 0; + goto out; + } + + hidden_dir_dentry = lock_parent(hidden_wh_dentry); + if (!(err = is_robranch_super(dentry->d_sb, bindex))) { + err = vfs_create(hidden_dir_dentry->d_inode, + hidden_wh_dentry, + ~current->fs->umask & S_IRWXUGO, NULL); + + } + unlock_dir(hidden_dir_dentry); + dput(hidden_wh_dentry); + + if (!err || !IS_COPYUP_ERR(err)) + break; + } + + /* set dbopaque so that lookup will not proceed after this branch */ + if (!err) + set_dbopaque(dentry, bindex); + +out: + kfree(name); + return err; +} + +/* This is a helper function for rename, which ends up with hosed over dentries + * when it needs to revert. + */ +int unionfs_refresh_hidden_dentry(struct dentry *dentry, int bindex) +{ + struct dentry *hidden_dentry; + struct dentry *hidden_parent; + int err = 0; + + verify_locked(dentry); + + unionfs_lock_dentry(dentry->d_parent); + hidden_parent = unionfs_lower_dentry_idx(dentry->d_parent, bindex); + unionfs_unlock_dentry(dentry->d_parent); + + BUG_ON(!S_ISDIR(hidden_parent->d_inode->i_mode)); + + hidden_dentry = lookup_one_len(dentry->d_name.name, hidden_parent, + dentry->d_name.len); + if (IS_ERR(hidden_dentry)) { + err = PTR_ERR(hidden_dentry); + goto out; + } + + dput(unionfs_lower_dentry_idx(dentry, bindex)); + iput(unionfs_lower_inode_idx(dentry->d_inode, bindex)); + unionfs_set_lower_inode_idx(dentry->d_inode, bindex, NULL); + + if (!hidden_dentry->d_inode) { + dput(hidden_dentry); + unionfs_set_lower_dentry_idx(dentry, bindex, NULL); + } else { + unionfs_set_lower_dentry_idx(dentry, bindex, hidden_dentry); + unionfs_set_lower_inode_idx(dentry->d_inode, bindex, + igrab(hidden_dentry->d_inode)); + } + +out: + return err; +} + +int make_dir_opaque(struct dentry *dentry, int bindex) +{ + int err = 0; + struct dentry *hidden_dentry, *diropq; + struct inode *hidden_dir; + + hidden_dentry = unionfs_lower_dentry_idx(dentry, bindex); + hidden_dir = hidden_dentry->d_inode; + BUG_ON(!S_ISDIR(dentry->d_inode->i_mode) || + !S_ISDIR(hidden_dir->i_mode)); + + mutex_lock(&hidden_dir->i_mutex); + diropq = lookup_one_len(UNIONFS_DIR_OPAQUE, hidden_dentry, + sizeof(UNIONFS_DIR_OPAQUE) - 1); + if (IS_ERR(diropq)) { + err = PTR_ERR(diropq); + goto out; + } + + if (!diropq->d_inode) + err = vfs_create(hidden_dir, diropq, S_IRUGO, NULL); + if (!err) + set_dbopaque(dentry, bindex); + + dput(diropq); + +out: + mutex_unlock(&hidden_dir->i_mutex); + return err; +} + +/* returns the sum of the n_link values of all the underlying inodes of the + * passed inode + */ +int unionfs_get_nlinks(struct inode *inode) +{ + int sum_nlinks = 0; + int dirs = 0; + int bindex; + struct inode *hidden_inode; + + /* don't bother to do all the work since we're unlinked */ + if (inode->i_nlink == 0) + return 0; + + if (!S_ISDIR(inode->i_mode)) + return unionfs_lower_inode(inode)->i_nlink; + + for (bindex = ibstart(inode); bindex <= ibend(inode); bindex++) { + hidden_inode = unionfs_lower_inode_idx(inode, bindex); + + /* ignore files */ + if (!hidden_inode || !S_ISDIR(hidden_inode->i_mode)) + continue; + + BUG_ON(hidden_inode->i_nlink < 0); + + /* A deleted directory. */ + if (hidden_inode->i_nlink == 0) + continue; + dirs++; + + /* + * A broken directory... + * + * Some filesystems don't properly set the number of links + * on empty directories + */ + if (hidden_inode->i_nlink == 1) + sum_nlinks += 2; + else + sum_nlinks += (hidden_inode->i_nlink - 2); + } + + return (!dirs ? 0 : sum_nlinks + 2); +} + +/* construct whiteout filename */ +char *alloc_whname(const char *name, int len) +{ + char *buf; + + buf = kmalloc(len + UNIONFS_WHLEN + 1, GFP_KERNEL); + if (!buf) + return ERR_PTR(-ENOMEM); + + strcpy(buf, UNIONFS_WHPFX); + strlcat(buf, name, len + UNIONFS_WHLEN + 1); + + return buf; +} + diff --git a/fs/unionfs/super.c b/fs/unionfs/super.c new file mode 100644 index 0000000..02c0cc8 --- /dev/null +++ b/fs/unionfs/super.c @@ -0,0 +1,965 @@ +/* + * 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 State University of New York + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include "union.h" + +/* The inode cache is used with alloc_inode for both our inode info and the + * vfs inode. + */ +static struct kmem_cache *unionfs_inode_cachep; + +static void unionfs_read_inode(struct inode *inode) +{ + static struct address_space_operations unionfs_empty_aops; + int size; + struct unionfs_inode_info *info = UNIONFS_I(inode); + + if (!info) { + printk(KERN_ERR "No kernel memory when allocating inode " + "private data!\n"); + BUG(); + } + + memset(info, 0, offsetof(struct unionfs_inode_info, vfs_inode)); + info->bstart = -1; + info->bend = -1; + atomic_set(&info->generation, + atomic_read(&UNIONFS_SB(inode->i_sb)->generation)); + spin_lock_init(&info->rdlock); + info->rdcount = 1; + info->hashsize = -1; + INIT_LIST_HEAD(&info->readdircache); + + size = sbmax(inode->i_sb) * sizeof(struct inode *); + info->lower_inodes = kzalloc(size, GFP_KERNEL); + if (!info->lower_inodes) { + printk(KERN_ERR "No kernel memory when allocating lower-" + "pointer array!\n"); + BUG(); + } + + inode->i_version++; + inode->i_op = &unionfs_main_iops; + inode->i_fop = &unionfs_main_fops; + + /* I don't think ->a_ops is ever allowed to be NULL */ + inode->i_mapping->a_ops = &unionfs_empty_aops; +} + +static void unionfs_put_inode(struct inode *inode) +{ + /* + * This is really funky stuff: + * Basically, if i_count == 1, iput will then decrement it and this + * inode will be destroyed. It is currently holding a reference to the + * hidden inode. Therefore, it needs to release that reference by + * calling iput on the hidden inode. iput() _will_ do it for us (by + * calling our clear_inode), but _only_ if i_nlink == 0. The problem + * is, NFS keeps i_nlink == 1 for silly_rename'd files. So we must for + * our i_nlink to 0 here to trick iput() into calling our clear_inode. + */ + + if (atomic_read(&inode->i_count) == 1) + inode->i_nlink = 0; +} + +/* + * we now define delete_inode, because there are two VFS paths that may + * destroy an inode: one of them calls clear inode before doing everything + * else that's needed, and the other is fine. This way we truncate the inode + * size (and its pages) and then clear our own inode, which will do an iput + * on our and the lower inode. + */ +static void unionfs_delete_inode(struct inode *inode) +{ + inode->i_size = 0; /* every f/s seems to do that */ + + clear_inode(inode); +} + +/* final actions when unmounting a file system */ +static void unionfs_put_super(struct super_block *sb) +{ + int bindex, bstart, bend; + struct unionfs_sb_info *spd; + int leaks = 0; + + spd = UNIONFS_SB(sb); + if (!spd) + return; + + bstart = sbstart(sb); + bend = sbend(sb); + + /* Make sure we have no leaks of branchget/branchput. */ + for (bindex = bstart; bindex <= bend; bindex++) + if (branch_count(sb, bindex) != 0) { + printk("unionfs: branch %d has %d references left!\n", + bindex, branch_count(sb,bindex)); + leaks = 1; + } + BUG_ON(leaks != 0); + + kfree(spd->data); + kfree(spd); + sb->s_fs_info = NULL; +} + +/* Since people use this to answer the "How big of a file can I write?" + * question, we report the size of the highest priority branch as the size of + * the union. + */ +static int unionfs_statfs(struct dentry *dentry, struct kstatfs *buf) +{ + int err = 0; + struct super_block *sb, *hidden_sb; + + BUG_ON(!is_valid_dentry(dentry)); + + 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; + buf->f_namelen -= UNIONFS_WHLEN; + + memset(&buf->f_fsid, 0, sizeof(__kernel_fsid_t)); + memset(&buf->f_spare, 0, sizeof(buf->f_spare)); + + return err; +} + +/* handle mode changing during remount */ +static noinline int do_remount_mode_option(char *optarg, int cur_branches, + struct unionfs_data *new_data, + struct path *new_lower_paths) +{ + int err = -EINVAL; + int perms, idx; + char *modename = strchr(optarg, '='); + struct nameidata nd; + + /* by now, optarg contains the branch name */ + if (!*optarg) { + printk("unionfs: no branch specified for mode change.\n"); + goto out; + } + if (!modename) { + printk("unionfs: branch \"%s\" requires a mode.\n", optarg); + goto out; + } + *modename++ = '\0'; + perms = __parse_branch_mode(modename); + if (perms == 0) { + printk("unionfs: invalid mode \"%s\" for \"%s\".\n", + modename, optarg); + goto out; + } + + /* + * Find matching branch index. For now, this assumes that nothing + * has been mounted on top of this Unionfs stack. Once we have /odf + * and cache-coherency resolved, we'll address the branch-path + * uniqueness. + */ + err = path_lookup(optarg, LOOKUP_FOLLOW, &nd); + if (err) { + printk(KERN_WARNING "unionfs: error accessing " + "hidden directory \"%s\" (error %d)\n", + optarg, err); + goto out; + } + for (idx=0; idx 0) { + err = -EBUSY; + goto out; + } + + /* + * Now we have to delete the branch. First, release any handles it + * has. Then, move the remaining array indexes past "idx" in + * new_data and new_lower_paths one to the left. Finally, adjust + * cur_branches. + */ + pathput(&new_lower_paths[idx]); + + if (idx < cur_branches - 1) { + /* if idx==cur_branches-1, we delete last branch: easy */ + memmove(&new_data[idx], &new_data[idx+1], + (cur_branches - 1 - idx) * sizeof(struct unionfs_data)); + memmove(&new_lower_paths[idx], &new_lower_paths[idx+1], + (cur_branches - 1 - idx) * sizeof(struct path)); + } + + err = 0; +out: + return err; +} + +/* handle branch insertion during remount */ +static noinline int do_remount_add_option(char *optarg, int cur_branches, + struct unionfs_data *new_data, + struct path *new_lower_paths, + int *high_branch_id) +{ + int err = -EINVAL; + int perms; + int idx = 0; /* default: insert at beginning */ + char *new_branch , *modename = NULL; + struct nameidata nd; + + /* + * optarg can be of several forms: + * + * /bar:/foo insert /foo before /bar + * /bar:/foo=ro insert /foo in ro mode before /bar + * /foo insert /foo in the beginning (prepend) + * :/foo insert /foo at the end (append) + */ + if (*optarg == ':') { /* append? */ + new_branch = optarg + 1; /* skip ':' */ + idx = cur_branches; + goto found_insertion_point; + } + new_branch = strchr(optarg, ':'); + if (!new_branch) { /* prepend? */ + new_branch = optarg; + goto found_insertion_point; + } + *new_branch++ = '\0'; /* holds path+mode of new branch */ + + /* + * Find matching branch index. For now, this assumes that nothing + * has been mounted on top of this Unionfs stack. Once we have /odf + * and cache-coherency resolved, we'll address the branch-path + * uniqueness. + */ + err = path_lookup(optarg, LOOKUP_FOLLOW, &nd); + if (err) { + printk(KERN_WARNING "unionfs: error accessing " + "hidden directory \"%s\" (error %d)\n", + optarg, err); + goto out; + } + for (idx=0; idx < cur_branches; idx++) + if (nd.mnt == new_lower_paths[idx].mnt && + nd.dentry == new_lower_paths[idx].dentry) + break; + path_release(&nd); /* no longer needed */ + if (idx == cur_branches) { + printk(KERN_WARNING "unionfs: branch \"%s\" " + "not found\n", optarg); + err = -ENOENT; + goto out; + } + + /* + * At this point idx will hold the index where the new branch should + * be inserted before. + */ +found_insertion_point: + /* find the mode for the new branch */ + if (new_branch) + modename = strchr(new_branch, '='); + if (modename) + *modename++ = '\0'; + perms = parse_branch_mode(modename); + + if (!new_branch || !*new_branch) { + printk(KERN_WARNING "unionfs: null new branch\n"); + err = -EINVAL; + goto out; + } + err = path_lookup(new_branch, LOOKUP_FOLLOW, &nd); + if (err) { + printk(KERN_WARNING "unionfs: error accessing " + "hidden directory \"%s\" (error %d)\n", + new_branch, err); + goto out; + } + /* it's probably safe to check_mode the new branch to insert */ + if ((err = check_branch(&nd))) { + printk(KERN_WARNING "unionfs: hidden directory " + "\"%s\" is not a valid branch\n", optarg); + path_release(&nd); + goto out; + } + + /* + * Now we have to insert the new branch. But first, move the bits + * to make space for the new branch, if needed. Finally, adjust + * cur_branches. + * We don't release nd here; it's kept until umount/remount. + */ + if (idx < cur_branches) { + /* if idx==cur_branches, we append: easy */ + memmove(&new_data[idx+1], &new_data[idx], + (cur_branches - idx) * sizeof(struct unionfs_data)); + memmove(&new_lower_paths[idx+1], &new_lower_paths[idx], + (cur_branches - idx) * sizeof(struct path)); + } + new_lower_paths[idx].dentry = nd.dentry; + new_lower_paths[idx].mnt = nd.mnt; + + new_data[idx].sb = nd.dentry->d_sb; + atomic_set(&new_data[idx].open_files, 0); + new_data[idx].branchperms = perms; + new_data[idx].branch_id = ++*high_branch_id; /* assign new branch ID */ + + err = 0; +out: + return err; +} + + +/* + * Support branch management options on remount. + * + * See Documentation/filesystems/unionfs/ for details. + * + * @flags: numeric mount options + * @options: mount options string + * + * This function can rearrange a mounted union dynamically, adding and + * removing branches, including changing branch modes. Clearly this has to + * be done safely and atomically. Luckily, the VFS already calls this + * function with lock_super(sb) and lock_kernel() held, preventing + * concurrent mixing of new mounts, remounts, and unmounts. Moreover, + * do_remount_sb(), our caller function, already called shrink_dcache_sb(sb) + * to purge dentries/inodes from our superblock, and also called + * fsync_super(sb) to purge any dirty pages. So we're good. + * + * XXX: however, our remount code may also need to invalidate mapped pages + * so as to force them to be re-gotten from the (newly reconfigured) lower + * branches. This has to wait for proper mmap and cache coherency support + * in the VFS. + * + */ +static int unionfs_remount_fs(struct super_block *sb, int *flags, + char *options) +{ + int err = 0; + int i; + char *optionstmp, *tmp_to_free; /* kstrdup'ed of "options" */ + char *optname; + int cur_branches; /* no. of current branches */ + int new_branches; /* no. of branches actually left in the end */ + int add_branches; /* est. no. of branches to add */ + int del_branches; /* est. no. of branches to del */ + int max_branches; /* max possible no. of branches */ + struct unionfs_data *new_data = NULL, *tmp_data = NULL; + struct path *new_lower_paths = NULL, *tmp_lower_paths = NULL; + int new_high_branch_id; /* new high branch ID */ + + unionfs_write_lock(sb); + + /* + * The VFS will take care of "ro" and "rw" flags, and we can safely + * ignore MS_SILENT, but anything else left over is an error. So we + * need to check if any other flags may have been passed (none are + * allowed/supported as of now). + */ + if ((*flags & ~(MS_RDONLY | MS_SILENT)) != 0) { + printk(KERN_WARNING + "unionfs: remount flags 0x%x unsupported\n", *flags); + err = -EINVAL; + goto out_error; + } + + /* + * If 'options' is NULL, it's probably because the user just changed + * the union to a "ro" or "rw" and the VFS took care of it. So + * nothing to do and we're done. + */ + if (!options || options[0] == '\0') + goto out_error; + + /* + * Find out how many branches we will have in the end, counting + * "add" and "del" commands. Copy the "options" string because + * strsep modifies the string and we need it later. + */ + optionstmp = tmp_to_free = kstrdup(options, GFP_KERNEL); + if (!optionstmp) { + err = -ENOMEM; + goto out_free; + } + new_branches = cur_branches = sbmax(sb); /* current no. branches */ + add_branches = del_branches = 0; + new_high_branch_id = sbhbid(sb); /* save current high_branch_id */ + while ((optname = strsep(&optionstmp, ",")) != NULL) { + char *optarg; + + if (!optname || !*optname) + continue; + + optarg = strchr(optname, '='); + if (optarg) + *optarg++ = '\0'; + + if (!strcmp("add", optname)) + add_branches++; + else if (!strcmp("del", optname)) + del_branches++; + } + kfree(tmp_to_free); + /* after all changes, will we have at least one branch left? */ + if ((new_branches + add_branches - del_branches) < 1) { + printk(KERN_WARNING + "unionfs: no branches left after remount\n"); + err = -EINVAL; + goto out_free; + } + + /* + * Since we haven't actually parsed all the add/del options, nor + * have we checked them for errors, we don't know for sure how many + * branches we will have after all changes have taken place. In + * fact, the total number of branches left could be less than what + * we have now. So we need to allocate space for a temporary + * placeholder that is at least as large as the maximum number of + * branches we *could* have, which is the current number plus all + * the additions. Once we're done with these temp placeholders, we + * may have to re-allocate the final size, copy over from the temp, + * and then free the temps (done near the end of this function). + */ + max_branches = cur_branches + add_branches; + /* allocate space for new pointers to hidden dentry */ + tmp_data = kcalloc(max_branches, + sizeof(struct unionfs_data), GFP_KERNEL); + if (!tmp_data) { + err = -ENOMEM; + goto out_free; + } + /* allocate space for new pointers to lower paths */ + tmp_lower_paths = kcalloc(max_branches, + sizeof(struct path), GFP_KERNEL); + if (!tmp_lower_paths) { + err = -ENOMEM; + goto out_free; + } + /* copy current info into new placeholders, incrementing refcnts */ + memcpy(tmp_data, UNIONFS_SB(sb)->data, + cur_branches * sizeof(struct unionfs_data)); + memcpy(tmp_lower_paths, UNIONFS_D(sb->s_root)->lower_paths, + cur_branches * sizeof(struct path)); + for (i=0; i UNIONFS_MAX_BRANCHES) { + printk("unionfs: command exceeds %d branches\n", + UNIONFS_MAX_BRANCHES); + err = -E2BIG; + goto out_release; + } + continue; + } + if (!strcmp("del", optname)) { + err = do_remount_del_option(optarg, new_branches, + tmp_data, + tmp_lower_paths); + if (err) + goto out_release; + new_branches--; + continue; + } + if (!strcmp("mode", optname)) { + err = do_remount_mode_option(optarg, new_branches, + tmp_data, + tmp_lower_paths); + if (err) + goto out_release; + continue; + } + + /* + * When you use "mount -o remount,ro", mount(8) will + * reportedly pass the original dirs= string from + * /proc/mounts. So for now, we have to ignore dirs= and + * not consider it an error, unless we want to allow users + * to pass dirs= in remount. Note that to allow the VFS to + * actually process the ro/rw remount options, we have to + * return 0 from this function. + */ + if (!strcmp("dirs", optname)) { + printk(KERN_WARNING + "unionfs: remount ignoring option \"%s\".\n", + optname); + continue; + } + + err = -EINVAL; + printk(KERN_WARNING + "unionfs: unrecognized option \"%s\"\n", optname); + goto out_release; + } + +out_no_change: + + /****************************************************************** + * WE'RE ALMOST DONE: see if we need to allocate a small-sized new + * vector, copy the vectors to their correct place, release the + * refcnt of the older ones, and return. + * Also handle invalidating any pgaes that will have to be re-read. + *******************************************************************/ + + /* + * Allocate space for actual pointers, if needed. By the time we + * finish this block of code, new_branches and new_lower_paths will + * have the correct size. None of this code below would be needed + * if the kernel had a realloc() function, at least one capable of + * shrinking/truncating an allocation's size (hint, hint). + */ + if (new_branches < max_branches) { + + /* allocate space for new pointers to hidden dentry */ + new_data = kcalloc(new_branches, + sizeof(struct unionfs_data), GFP_KERNEL); + if (!new_data) { + err = -ENOMEM; + goto out_release; + } + /* allocate space for new pointers to lower paths */ + new_lower_paths = kcalloc(new_branches, + sizeof(struct path), GFP_KERNEL); + if (!new_lower_paths) { + err = -ENOMEM; + goto out_release; + } + /* + * copy current info into new placeholders, incrementing + * refcounts. + */ + memcpy(new_data, tmp_data, + new_branches * sizeof(struct unionfs_data)); + memcpy(new_lower_paths, tmp_lower_paths, + new_branches * sizeof(struct path)); + /* + * Since we already hold various refcnts on the objects, we + * don't need to redo it here. Just free the older memory + * and re-point the pointers. + */ + kfree(tmp_data); + kfree(tmp_lower_paths); + /* no need to nullify pointers here */ + } else { + /* number of branches didn't change, no need to re-alloc */ + new_data = tmp_data; + new_lower_paths = tmp_lower_paths; + } + + /* + * OK, just before we actually put the new set of branches in place, + * we need to ensure that our own f/s has no dirty objects left. + * Luckily, do_remount_sb() already calls shrink_dcache_sb(sb) and + * fsync_super(sb), taking care of dentries, inodes, and dirty + * pages. So all that's left is for us to invalidate any leftover + * (non-dirty) pages to ensure that they will be re-read from the + * new lower branches (and to support mmap). + */ + + /* + * No we call drop_pagecache_sb() to invalidate all pages in this + * super. This function calls invalidate_inode_pages(mapping), + * which calls invalidate_mapping_pages(): the latter, however, will + * not invalidate pages which are dirty, locked, under writeback, or + * mapped into pagetables. We shouldn't have to worry about dirty + * or under-writeback pages, because do_remount_sb() called + * fsync_super() which would not have returned until all dirty pages + * were flushed. + * + * But do w have to worry about locked pages? Is there any chance + * that in here we'll get locked pages? + * + * XXX: what about pages mapped into pagetables? Are these pages + * which user processes may have mmap(2)'ed? If so, then we need to + * invalidate those too, no? Maybe we'll have to write our own + * version of invalidate_mapping_pages() which also handled mapped + * pages. + * + * XXX: Alternatively, maybe we should call truncate_inode_pages(), + * which use two passes over the pages list, and will truncate all + * pages. + */ + drop_pagecache_sb(sb); + + /* copy new vectors into their correct place */ + tmp_data = UNIONFS_SB(sb)->data; + UNIONFS_SB(sb)->data = new_data; + new_data = NULL; /* so don't free good pointers below */ + tmp_lower_paths = UNIONFS_D(sb->s_root)->lower_paths; + UNIONFS_D(sb->s_root)->lower_paths = new_lower_paths; + new_lower_paths = NULL; /* so don't free good pointers below */ + + /* update our unionfs_sb_info and root dentry index of last branch */ + i = sbmax(sb); /* save no. of branches to release at end */ + sbend(sb) = new_branches - 1; + set_dbend(sb->s_root, new_branches - 1); + UNIONFS_D(sb->s_root)->bcount = new_branches; + new_branches = i; /* no. of branches to release below */ + + /* maxbytes may have changed */ + sb->s_maxbytes = unionfs_lower_super_idx(sb, 0)->s_maxbytes; + /* update high branch ID */ + sbhbid(sb) = new_high_branch_id; + + /* update our sb->generation for revalidating objects */ + i = atomic_inc_return(&UNIONFS_SB(sb)->generation); + atomic_set(&UNIONFS_D(sb->s_root)->generation, i); + atomic_set(&UNIONFS_I(sb->s_root->d_inode)->generation, i); + if (!(*flags & MS_SILENT)) + printk("unionfs: new generation number %d\n", i); + err = 0; /* reset to success */ + + /* + * The code above falls through to the next label, and releases the + * refcnts of the older ones (stored in tmp_*): if we fell through + * here, it means success. However, if we jump directly to this + * label from any error above, then an error occurred after we + * grabbed various refcnts, and so we have to release the + * temporarily constructed structures. + */ +out_release: + /* no need to cleanup/release anything in tmp_data */ + if (tmp_lower_paths) + for (i=0; ireaddircache) { + rdstate = list_entry(pos, struct unionfs_dir_state, cache); + list_del(&rdstate->cache); + free_rdstate(rdstate); + } + + /* Decrement a reference to a hidden_inode, which was incremented + * by our read_inode when it was created initially. + */ + bstart = ibstart(inode); + bend = ibend(inode); + if (bstart >= 0) { + for (bindex = bstart; bindex <= bend; bindex++) { + hidden_inode = unionfs_lower_inode_idx(inode, bindex); + if (!hidden_inode) + continue; + iput(hidden_inode); + } + } + + kfree(UNIONFS_I(inode)->lower_inodes); + UNIONFS_I(inode)->lower_inodes = NULL; +} + +static struct inode *unionfs_alloc_inode(struct super_block *sb) +{ + struct unionfs_inode_info *i; + + i = kmem_cache_alloc(unionfs_inode_cachep, GFP_KERNEL); + if (!i) + return NULL; + + /* memset everything up to the inode to 0 */ + memset(i, 0, offsetof(struct unionfs_inode_info, vfs_inode)); + + i->vfs_inode.i_version = 1; + return &i->vfs_inode; +} + +static void unionfs_destroy_inode(struct inode *inode) +{ + kmem_cache_free(unionfs_inode_cachep, UNIONFS_I(inode)); +} + +/* unionfs inode cache constructor */ +static void init_once(void *v, struct kmem_cache * cachep, unsigned long flags) +{ + struct unionfs_inode_info *i = v; + + if ((flags & (SLAB_CTOR_VERIFY | SLAB_CTOR_CONSTRUCTOR)) == + SLAB_CTOR_CONSTRUCTOR) + inode_init_once(&i->vfs_inode); +} + +int unionfs_init_inode_cache(void) +{ + int err = 0; + + unionfs_inode_cachep = + kmem_cache_create("unionfs_inode_cache", + sizeof(struct unionfs_inode_info), 0, + SLAB_RECLAIM_ACCOUNT, init_once, NULL); + if (!unionfs_inode_cachep) + err = -ENOMEM; + return err; +} + +void unionfs_destroy_inode_cache(void) +{ + if (unionfs_inode_cachep) + kmem_cache_destroy(unionfs_inode_cachep); +} + +/* Called when we have a dirty inode, right here we only throw out + * parts of our readdir list that are too old. + */ +static int unionfs_write_inode(struct inode *inode, int sync) +{ + struct list_head *pos, *n; + struct unionfs_dir_state *rdstate; + + spin_lock(&UNIONFS_I(inode)->rdlock); + list_for_each_safe(pos, n, &UNIONFS_I(inode)->readdircache) { + rdstate = list_entry(pos, struct unionfs_dir_state, cache); + /* We keep this list in LRU order. */ + if ((rdstate->access + RDCACHE_JIFFIES) > jiffies) + break; + UNIONFS_I(inode)->rdcount--; + list_del(&rdstate->cache); + free_rdstate(rdstate); + } + spin_unlock(&UNIONFS_I(inode)->rdlock); + + return 0; +} + +/* + * Used only in nfs, to kill any pending RPC tasks, so that subsequent + * code can actually succeed and won't leave tasks that need handling. + */ +static void unionfs_umount_begin(struct vfsmount *mnt, int flags) +{ + struct super_block *sb, *hidden_sb; + struct vfsmount *hidden_mnt; + int bindex, bstart, bend; + + if (!(flags & MNT_FORCE)) + /* we are not being MNT_FORCEd, therefore we should emulate + * old behaviour + */ + return; + + sb = mnt->mnt_sb; + + bstart = sbstart(sb); + 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) + hidden_sb->s_op->umount_begin(hidden_mnt, flags); + } +} + +static int unionfs_show_options(struct seq_file *m, struct vfsmount *mnt) +{ + struct super_block *sb = mnt->mnt_sb; + int ret = 0; + char *tmp_page; + char *path; + int bindex, bstart, bend; + int perms; + + unionfs_lock_dentry(sb->s_root); + + tmp_page = (char*) __get_free_page(GFP_KERNEL); + if (!tmp_page) { + ret = -ENOMEM; + goto out; + } + + bstart = sbstart(sb); + bend = sbend(sb); + + seq_printf(m, ",dirs="); + for (bindex = bstart; bindex <= bend; bindex++) { + path = d_path(unionfs_lower_dentry_idx(sb->s_root, bindex), + unionfs_lower_mnt_idx(sb->s_root, bindex), tmp_page, + PAGE_SIZE); + if (IS_ERR(path)) { + ret = PTR_ERR(path); + goto out; + } + + unionfs_read_lock(sb); + perms = branchperms(sb, bindex); + unionfs_read_unlock(sb); + + seq_printf(m, "%s=%s", path, + perms & MAY_WRITE ? "rw" : "ro"); + if (bindex != bend) + seq_printf(m, ":"); + } + +out: + free_page((unsigned long) tmp_page); + + unionfs_unlock_dentry(sb->s_root); + + return ret; +} + +struct super_operations unionfs_sops = { + .read_inode = unionfs_read_inode, + .put_inode = unionfs_put_inode, + .delete_inode = unionfs_delete_inode, + .put_super = unionfs_put_super, + .statfs = unionfs_statfs, + .remount_fs = unionfs_remount_fs, + .clear_inode = unionfs_clear_inode, + .umount_begin = unionfs_umount_begin, + .show_options = unionfs_show_options, + .write_inode = unionfs_write_inode, + .alloc_inode = unionfs_alloc_inode, + .destroy_inode = unionfs_destroy_inode, +}; + diff --git a/fs/unionfs/union.h b/fs/unionfs/union.h new file mode 100644 index 0000000..b6fa0a2 --- /dev/null +++ b/fs/unionfs/union.h @@ -0,0 +1,489 @@ +/* + * Copyright (c) 2003-2007 Erez Zadok + * Copyright (c) 2003-2006 Charles P. Wright + * Copyright (c) 2005-2007 Josef 'Jeff' Sipek + * 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 State University of New York + * + * 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. + */ + +#ifndef _UNION_H_ +#define _UNION_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +/* the file system name */ +#define UNIONFS_NAME "unionfs" + +/* unionfs root inode number */ +#define UNIONFS_ROOT_INO 1 + +/* number of characters while generating unique temporary file names */ +#define UNIONFS_TMPNAM_LEN 12 + +/* number of times we try to get a unique temporary file name */ +#define GET_TMPNAM_MAX_RETRY 5 + +/* maximum number of branches we support, to avoid memory blowup */ +#define UNIONFS_MAX_BRANCHES 128 + +/* Operations vectors defined in specific files. */ +extern struct file_operations unionfs_main_fops; +extern struct file_operations unionfs_dir_fops; +extern struct inode_operations unionfs_main_iops; +extern struct inode_operations unionfs_dir_iops; +extern struct inode_operations unionfs_symlink_iops; +extern struct super_operations unionfs_sops; +extern struct dentry_operations unionfs_dops; + +/* How long should an entry be allowed to persist */ +#define RDCACHE_JIFFIES (5*HZ) + +/* file private data. */ +struct unionfs_file_info { + int bstart; + int bend; + atomic_t generation; + + struct unionfs_dir_state *rdstate; + struct file **lower_files; + int *saved_branch_ids; /* IDs of branches when file was opened */ +}; + +/* unionfs inode data in memory */ +struct unionfs_inode_info { + int bstart; + int bend; + atomic_t generation; + int stale; + /* Stuff for readdir over NFS. */ + spinlock_t rdlock; + struct list_head readdircache; + int rdcount; + int hashsize; + int cookie; + + /* The hidden inodes */ + struct inode **lower_inodes; + /* to keep track of reads/writes for unlinks before closes */ + atomic_t totalopens; + + struct inode vfs_inode; +}; + +/* unionfs dentry data in memory */ +struct unionfs_dentry_info { + /* The semaphore is used to lock the dentry as soon as we get into a + * unionfs function from the VFS. Our lock ordering is that children + * go before their parents. + */ + struct mutex lock; + int bstart; + int bend; + int bopaque; + int bcount; + atomic_t generation; + struct path *lower_paths; +}; + +/* These are the pointers to our various objects. */ +struct unionfs_data { + struct super_block *sb; + atomic_t open_files; /* number of open files on branch */ + int branchperms; + int branch_id; /* unique branch ID at re/mount time */ +}; + +/* unionfs super-block data in memory */ +struct unionfs_sb_info { + int bend; + + atomic_t generation; + struct rw_semaphore rwsem; /* protects access to data+id fields */ + int high_branch_id; /* last unique branch ID given */ + struct unionfs_data *data; +}; + +/* + * structure for making the linked list of entries by readdir on left branch + * to compare with entries on right branch + */ +struct filldir_node { + struct list_head file_list; /* list for directory entries */ + char *name; /* name entry */ + int hash; /* name hash */ + int namelen; /* name len since name is not 0 terminated */ + + /* we can check for duplicate whiteouts and files in the same branch + * in order to return -EIO. + */ + int bindex; + + /* is this a whiteout entry? */ + int whiteout; + + /* Inline name, so we don't need to separately kmalloc small ones */ + char iname[DNAME_INLINE_LEN_MIN]; +}; + +/* Directory hash table. */ +struct unionfs_dir_state { + unsigned int cookie; /* The cookie, which is based off of rdversion */ + unsigned int offset; /* The entry we have returned. */ + int bindex; + loff_t dirpos; /* The offset within the lower level directory. */ + int size; /* How big is the hash table? */ + int hashentries; /* How many entries have been inserted? */ + unsigned long access; + + /* This cache list is used when the inode keeps us around. */ + struct list_head cache; + struct list_head list[0]; +}; + +/* include miscellaneous macros */ +#include "fanout.h" +#include "sioq.h" + +/* Cache creation/deletion routines. */ +void unionfs_destroy_filldir_cache(void); +int unionfs_init_filldir_cache(void); +int unionfs_init_inode_cache(void); +void unionfs_destroy_inode_cache(void); +int unionfs_init_dentry_cache(void); +void unionfs_destroy_dentry_cache(void); + +/* Initialize and free readdir-specific state. */ +int init_rdstate(struct file *file); +struct unionfs_dir_state *alloc_rdstate(struct inode *inode, int bindex); +struct unionfs_dir_state *find_rdstate(struct inode *inode, loff_t fpos); +void free_rdstate(struct unionfs_dir_state *state); +int add_filldir_node(struct unionfs_dir_state *rdstate, const char *name, + int namelen, int bindex, int whiteout); +struct filldir_node *find_filldir_node(struct unionfs_dir_state *rdstate, + const char *name, int namelen); + +struct dentry **alloc_new_dentries(int objs); +struct unionfs_data *alloc_new_data(int objs); + +/* We can only use 32-bits of offset for rdstate --- blech! */ +#define DIREOF (0xfffff) +#define RDOFFBITS 20 /* This is the number of bits in DIREOF. */ +#define MAXRDCOOKIE (0xfff) +/* Turn an rdstate into an offset. */ +static inline off_t rdstate2offset(struct unionfs_dir_state *buf) +{ + off_t tmp; + tmp = ((buf->cookie & MAXRDCOOKIE) << RDOFFBITS) + | (buf->offset & DIREOF); + return tmp; +} + +#define unionfs_read_lock(sb) down_read(&UNIONFS_SB(sb)->rwsem) +#define unionfs_read_unlock(sb) up_read(&UNIONFS_SB(sb)->rwsem) +#define unionfs_write_lock(sb) down_write(&UNIONFS_SB(sb)->rwsem) +#define unionfs_write_unlock(sb) up_write(&UNIONFS_SB(sb)->rwsem) + +static inline void double_lock_dentry(struct dentry *d1, struct dentry *d2) +{ + if (d2 < d1) { + struct dentry *tmp = d1; + d1 = d2; + d2 = tmp; + } + unionfs_lock_dentry(d1); + unionfs_lock_dentry(d2); +} + +extern int new_dentry_private_data(struct dentry *dentry); +void free_dentry_private_data(struct unionfs_dentry_info *udi); +void update_bstart(struct dentry *dentry); + +/* + * EXTERNALS: + */ + +/* replicates the directory structure upto given dentry in given branch */ +extern struct dentry *create_parents(struct inode *dir, struct dentry *dentry, + 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); +extern int copyup_named_file(struct inode *dir, struct file *file, + char *name, int bstart, int new_bindex, + loff_t len); +/* copies a dentry from dbstart to newbindex branch */ +extern int copyup_dentry(struct inode *dir, struct dentry *dentry, int bstart, + int new_bindex, struct file **copyup_file, loff_t len); + +extern int remove_whiteouts(struct dentry *dentry, struct dentry *hidden_dentry, + int bindex); + +extern int do_delete_whiteouts(struct dentry *dentry, int bindex, + struct unionfs_dir_state *namelist); + +extern int unionfs_get_nlinks(struct inode *inode); + +/* 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. */ +extern int delete_whiteouts(struct dentry *dentry, int bindex, + struct unionfs_dir_state *namelist); + +/* Re-lookup a hidden dentry. */ +extern int unionfs_refresh_hidden_dentry(struct dentry *dentry, int bindex); + +extern void unionfs_reinterpose(struct dentry *this_dentry); +extern struct super_block *unionfs_duplicate_super(struct super_block *sb); + +/* Locking functions. */ +extern int unionfs_setlk(struct file *file, int cmd, struct file_lock *fl); +extern int unionfs_getlk(struct file *file, struct file_lock *fl); + +/* Common file operations. */ +extern int unionfs_file_revalidate(struct file *file, int 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 long unionfs_ioctl(struct file *file, unsigned int cmd, + unsigned long arg); + +/* Inode operations */ +extern int unionfs_rename(struct inode *old_dir, struct dentry *old_dentry, + struct inode *new_dir, struct dentry *new_dentry); +int unionfs_unlink(struct inode *dir, struct dentry *dentry); +int unionfs_rmdir(struct inode *dir, struct dentry *dentry); + +int __unionfs_d_revalidate_chain(struct dentry *dentry, struct nameidata *nd); + +/* The values for unionfs_interpose's flag. */ +#define INTERPOSE_DEFAULT 0 +#define INTERPOSE_LOOKUP 1 +#define INTERPOSE_REVAL 2 +#define INTERPOSE_REVAL_NEG 3 +#define INTERPOSE_PARTIAL 4 + +extern int unionfs_interpose(struct dentry *this_dentry, struct super_block *sb, + int flag); + +/* Branch management ioctls. */ +int unionfs_ioctl_incgen(struct file *file, unsigned int cmd, + unsigned long arg); +int unionfs_ioctl_queryfile(struct file *file, unsigned int cmd, + unsigned long arg); + +#ifdef CONFIG_UNION_FS_XATTR +/* Extended attribute functions. */ +extern void *unionfs_xattr_alloc(size_t size, size_t limit); +extern void unionfs_xattr_free(void *ptr, size_t size); + +extern ssize_t unionfs_getxattr(struct dentry *dentry, const char *name, + void *value, size_t size); +extern int unionfs_removexattr(struct dentry *dentry, const char *name); +extern ssize_t unionfs_listxattr(struct dentry *dentry, char *list, + size_t size); +extern int unionfs_setxattr(struct dentry *dentry, const char *name, + const void *value, size_t size, int flags); +#endif /* CONFIG_UNION_FS_XATTR */ + +/* The root directory is unhashed, but isn't deleted. */ +static inline int d_deleted(struct dentry *d) +{ + return d_unhashed(d) && (d != d->d_sb->s_root); +} + +struct dentry *unionfs_lookup_backend(struct dentry *dentry, struct nameidata *nd, int lookupmode); + +/* unionfs_permission, check if we should bypass error to facilitate copyup */ +#define IS_COPYUP_ERR(err) ((err) == -EROFS) + +/* unionfs_open, check if we need to copyup the file */ +#define OPEN_WRITE_FLAGS (O_WRONLY | O_RDWR | O_APPEND) +#define IS_WRITE_FLAG(flag) ((flag) & OPEN_WRITE_FLAGS) + +static inline int branchperms(const struct super_block *sb, int index) +{ + BUG_ON(index < 0); + + return UNIONFS_SB(sb)->data[index].branchperms; +} + +static inline int set_branchperms(struct super_block *sb, int index, int perms) +{ + BUG_ON(index < 0); + + UNIONFS_SB(sb)->data[index].branchperms = perms; + + return perms; +} + +/* Is this file on a read-only branch? */ +static inline int is_robranch_super(const struct super_block *sb, int index) +{ + 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? */ +static inline int is_robranch_idx(const struct dentry *dentry, int index) +{ + int err = 0; + + 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; +} + +static inline int is_robranch(const struct dentry *dentry) +{ + int index; + + index = UNIONFS_D(dentry)->bstart; + BUG_ON(index < 0); + + return is_robranch_idx(dentry, index); +} + +/* + * Check if dentry is valid or not, as per our generation numbers. + * @dentry: dentry to check. + * Returns 1 (valid) or 0 (invalid/stale). + */ +static inline int is_valid_dentry(struct dentry *dentry) +{ + BUG_ON(!UNIONFS_D(dentry)); + BUG_ON(!UNIONFS_SB(dentry->d_sb)); + return (atomic_read(&UNIONFS_D(dentry)->generation) == + atomic_read(&UNIONFS_SB(dentry->d_sb)->generation)); +} + +/* 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 + +#ifndef DEFAULT_POLLMASK +#define DEFAULT_POLLMASK (POLLIN | POLLOUT | POLLRDNORM | POLLWRNORM) +#endif + +/* + * 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); +extern int parse_branch_mode(const char *name); + +/* These two functions are here because it is kind of daft to copy and paste the + * contents of the two functions to 32+ places in unionfs + */ +static inline struct dentry *lock_parent(struct dentry *dentry) +{ + struct dentry *dir = dget(dentry->d_parent); + + mutex_lock(&dir->d_inode->i_mutex); + return dir; +} + +static inline void unlock_dir(struct dentry *dir) +{ + mutex_unlock(&dir->d_inode->i_mutex); + dput(dir); +} + +extern int make_dir_opaque(struct dentry *dir, int bindex); + +static inline struct vfsmount *unionfs_mntget(struct dentry *dentry, int bindex) +{ + struct vfsmount *mnt; + if (!dentry) { + if (bindex < 0) + return NULL; + BUG_ON(bindex < 0); + } + mnt = unionfs_lower_mnt_idx(dentry, bindex); + if (!mnt) { + if (bindex < 0) + return NULL; + BUG_ON(mnt && bindex < 0); + } + mnt = mntget(mnt); + return mnt; +} + +static inline void unionfs_mntput(struct dentry *dentry, int bindex) +{ + struct vfsmount *mnt; + if (!dentry) { + if (bindex < 0) + return; + BUG_ON(dentry && bindex < 0); + } + mnt = unionfs_lower_mnt_idx(dentry, bindex); + if (!mnt) { + if (bindex < 0) + return; + BUG_ON(mnt && bindex < 0); + } + mntput(mnt); +} +#endif /* not _UNION_H_ */ diff --git a/fs/unionfs/unlink.c b/fs/unionfs/unlink.c new file mode 100644 index 0000000..dd1dd9c --- /dev/null +++ b/fs/unionfs/unlink.c @@ -0,0 +1,166 @@ +/* + * 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 State University of New York + * + * 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" + +/* unlink a file by creating a whiteout */ +static int unionfs_unlink_whiteout(struct inode *dir, struct dentry *dentry) +{ + struct dentry *hidden_dentry; + struct dentry *hidden_dir_dentry; + int bindex; + int err = 0; + + if ((err = unionfs_partial_lookup(dentry))) + goto out; + + bindex = dbstart(dentry); + + hidden_dentry = unionfs_lower_dentry_idx(dentry, bindex); + if (!hidden_dentry) + goto out; + + hidden_dir_dentry = lock_parent(hidden_dentry); + + /* avoid destroying the hidden inode if the file is in use */ + dget(hidden_dentry); + if (!(err = is_robranch_super(dentry->d_sb, bindex))) + err = vfs_unlink(hidden_dir_dentry->d_inode, hidden_dentry); + dput(hidden_dentry); + fsstack_copy_attr_times(dir, hidden_dir_dentry->d_inode); + unlock_dir(hidden_dir_dentry); + + if (err && !IS_COPYUP_ERR(err)) + goto out; + + if (err) { + if (dbstart(dentry) == 0) + goto out; + + err = create_whiteout(dentry, dbstart(dentry) - 1); + } else if (dbopaque(dentry) != -1) + /* There is a hidden lower-priority file with the same name. */ + err = create_whiteout(dentry, dbopaque(dentry)); + else + err = create_whiteout(dentry, dbstart(dentry)); + +out: + if (!err) + dentry->d_inode->i_nlink--; + + /* We don't want to leave negative leftover dentries for revalidate. */ + if (!err && (dbopaque(dentry) != -1)) + update_bstart(dentry); + + return err; +} + +int unionfs_unlink(struct inode *dir, struct dentry *dentry) +{ + int err = 0; + + BUG_ON(!is_valid_dentry(dentry)); + + unionfs_lock_dentry(dentry); + + err = unionfs_unlink_whiteout(dir, dentry); + /* call d_drop so the system "forgets" about us */ + if (!err) + d_drop(dentry); + + unionfs_unlock_dentry(dentry); + return err; +} + +static int unionfs_rmdir_first(struct inode *dir, struct dentry *dentry, + struct unionfs_dir_state *namelist) +{ + int err; + struct dentry *hidden_dentry; + struct dentry *hidden_dir_dentry = NULL; + + /* Here we need to remove whiteout entries. */ + err = delete_whiteouts(dentry, dbstart(dentry), namelist); + if (err) + goto out; + + hidden_dentry = unionfs_lower_dentry(dentry); + + hidden_dir_dentry = lock_parent(hidden_dentry); + + /* avoid destroying the hidden inode if the file is in use */ + dget(hidden_dentry); + if (!(err = is_robranch(dentry))) + err = vfs_rmdir(hidden_dir_dentry->d_inode, hidden_dentry); + dput(hidden_dentry); + + fsstack_copy_attr_times(dir, hidden_dir_dentry->d_inode); + /* propagate number of hard-links */ + dentry->d_inode->i_nlink = unionfs_get_nlinks(dentry->d_inode); + +out: + if (hidden_dir_dentry) + unlock_dir(hidden_dir_dentry); + return err; +} + +int unionfs_rmdir(struct inode *dir, struct dentry *dentry) +{ + int err = 0; + struct unionfs_dir_state *namelist = NULL; + + BUG_ON(!is_valid_dentry(dentry)); + + unionfs_lock_dentry(dentry); + + /* check if this unionfs directory is empty or not */ + err = check_empty(dentry, &namelist); + if (err) + goto out; + + err = unionfs_rmdir_first(dir, dentry, namelist); + /* create whiteout */ + if (!err) + err = create_whiteout(dentry, dbstart(dentry)); + else { + int new_err; + + if (dbstart(dentry) == 0) + goto out; + + /* exit if the error returned was NOT -EROFS */ + if (!IS_COPYUP_ERR(err)) + goto out; + + new_err = create_whiteout(dentry, dbstart(dentry) - 1); + if (new_err != -EEXIST) + err = new_err; + } + +out: + /* call d_drop so the system "forgets" about us */ + if (!err) + d_drop(dentry); + + if (namelist) + free_rdstate(namelist); + + unionfs_unlock_dentry(dentry); + return err; +} + diff --git a/fs/unionfs/xattr.c b/fs/unionfs/xattr.c new file mode 100644 index 0000000..6e1f4bd --- /dev/null +++ b/fs/unionfs/xattr.c @@ -0,0 +1,131 @@ +/* + * 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 State University of New York* + * + * 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" + +/* This is lifted from fs/xattr.c */ +void *unionfs_xattr_alloc(size_t size, size_t limit) +{ + void *ptr; + + if (size > limit) + return ERR_PTR(-E2BIG); + + if (!size) /* size request, no buffer is needed */ + return NULL; + else if (size <= PAGE_SIZE) + ptr = kmalloc(size, GFP_KERNEL); + else + ptr = vmalloc(size); + if (!ptr) + return ERR_PTR(-ENOMEM); + return ptr; +} + +void unionfs_xattr_free(void *ptr, size_t size) +{ + if (!size) /* size request, no buffer was needed */ + return; + else if (size <= PAGE_SIZE) + kfree(ptr); + else + vfree(ptr); +} + +/* BKL held by caller. + * dentry->d_inode->i_mutex locked + */ +ssize_t unionfs_getxattr(struct dentry * dentry, const char *name, void *value, + size_t size) +{ + struct dentry *hidden_dentry = NULL; + int err = -EOPNOTSUPP; + + BUG_ON(!is_valid_dentry(dentry)); + + unionfs_lock_dentry(dentry); + + hidden_dentry = unionfs_lower_dentry(dentry); + + err = vfs_getxattr(hidden_dentry, (char*) name, value, size); + + unionfs_unlock_dentry(dentry); + return err; +} + +/* BKL held by caller. + * dentry->d_inode->i_mutex locked + */ +int unionfs_setxattr(struct dentry *dentry, const char *name, const void *value, + size_t size, int flags) +{ + struct dentry *hidden_dentry = NULL; + int err = -EOPNOTSUPP; + + BUG_ON(!is_valid_dentry(dentry)); + + unionfs_lock_dentry(dentry); + hidden_dentry = unionfs_lower_dentry(dentry); + + err = vfs_setxattr(hidden_dentry, (char*) name, (void*) value, size, flags); + + unionfs_unlock_dentry(dentry); + return err; +} + +/* BKL held by caller. + * dentry->d_inode->i_mutex locked + */ +int unionfs_removexattr(struct dentry *dentry, const char *name) +{ + struct dentry *hidden_dentry = NULL; + int err = -EOPNOTSUPP; + + BUG_ON(!is_valid_dentry(dentry)); + + unionfs_lock_dentry(dentry); + hidden_dentry = unionfs_lower_dentry(dentry); + + err = vfs_removexattr(hidden_dentry, (char*) name); + + unionfs_unlock_dentry(dentry); + return err; +} + +/* BKL held by caller. + * dentry->d_inode->i_mutex locked + */ +ssize_t unionfs_listxattr(struct dentry * dentry, char *list, size_t size) +{ + struct dentry *hidden_dentry = NULL; + int err = -EOPNOTSUPP; + char *encoded_list = NULL; + + BUG_ON(!is_valid_dentry(dentry)); + + unionfs_lock_dentry(dentry); + + hidden_dentry = unionfs_lower_dentry(dentry); + + encoded_list = list; + err = vfs_listxattr(hidden_dentry, encoded_list, size); + + unionfs_unlock_dentry(dentry); + return err; +} + diff --git a/include/linux/magic.h b/include/linux/magic.h index a9c6567..a6751f6 100644 --- a/include/linux/magic.h +++ b/include/linux/magic.h @@ -35,6 +35,8 @@ #define REISER2FS_SUPER_MAGIC_STRING "ReIsEr2Fs" #define REISER2FS_JR_SUPER_MAGIC_STRING "ReIsEr3Fs" +#define UNIONFS_SUPER_MAGIC 0xf15f083d + #define SMB_SUPER_MAGIC 0x517B #define USBDEVICE_SUPER_MAGIC 0x9fa2 diff --git a/include/linux/mm.h b/include/linux/mm.h index 60e0e4a..c680669 100644 --- a/include/linux/mm.h +++ b/include/linux/mm.h @@ -1157,6 +1157,7 @@ int drop_caches_sysctl_handler(struct ctl_table *, int, struct file *, void __user *, size_t *, loff_t *); unsigned long shrink_slab(unsigned long scanned, gfp_t gfp_mask, unsigned long lru_pages); +extern void drop_pagecache_sb(struct super_block *); void drop_pagecache(void); void drop_slab(void); diff --git a/include/linux/namei.h b/include/linux/namei.h index d39a5a6..92b422b 100644 --- a/include/linux/namei.h +++ b/include/linux/namei.h @@ -3,6 +3,7 @@ #include #include +#include struct vfsmount; @@ -81,7 +82,15 @@ extern struct file *lookup_instantiate_filp(struct nameidata *nd, struct dentry extern struct file *nameidata_to_filp(struct nameidata *nd, int flags); extern void release_open_intent(struct nameidata *); -extern struct dentry * lookup_one_len(const char *, struct dentry *, int); +extern struct dentry *lookup_one_len_nd(const char *, + struct dentry *, int, struct nameidata *); + +/* SMP-safe */ +static inline struct dentry *lookup_one_len(const char *name, + struct dentry *dir, int len) +{ + return lookup_one_len_nd(name, dir, len, NULL); +} extern int follow_down(struct vfsmount **, struct dentry **); extern int follow_up(struct vfsmount **, struct dentry **); @@ -99,4 +108,16 @@ static inline char *nd_get_link(struct nameidata *nd) return nd->saved_names[nd->depth]; } +static inline void pathget(struct path *path) +{ + mntget(path->mnt); + dget(path->dentry); +} + +static inline void pathput(struct path *path) +{ + dput(path->dentry); + mntput(path->mnt); +} + #endif /* _LINUX_NAMEI_H */ diff --git a/include/linux/union_fs.h b/include/linux/union_fs.h new file mode 100644 index 0000000..b724031 --- /dev/null +++ b/include/linux/union_fs.h @@ -0,0 +1,18 @@ +#ifndef _LINUX_UNION_FS_H +#define _LINUX_UNION_FS_H + +#define UNIONFS_VERSION "2.0" +/* + * DEFINITIONS FOR USER AND KERNEL CODE: + */ +# define UNIONFS_IOCTL_INCGEN _IOR(0x15, 11, int) +# define UNIONFS_IOCTL_QUERYFILE _IOR(0x15, 15, int) + +/* We don't support normal remount, but unionctl uses it. */ +# define UNIONFS_REMOUNT_MAGIC 0x4a5a4380 + +/* should be at least LAST_USED_UNIONFS_PERMISSION<<1 */ +#define MAY_NFSRO 16 + +#endif /* _LINUX_UNIONFS_H */ +