GIT f4d2082d6bf00663eb320db5c493de7671a5b9bb git+ssh://master.kernel.org/pub/scm/linux/kernel/git/jsipek/unionfs.git commit f4d2082d6bf00663eb320db5c493de7671a5b9bb Author: Josef 'Jeff' Sipek Date: Mon Jul 23 11:28:40 2007 -0400 Unionfs: Remove slab destructors from kmem_cache_create() Signed-off-by: Josef 'Jeff' Sipek commit 35e95a0f4f3a3e2f9c433b5a2f8cb0c26d39135a Author: Erez Zadok Date: Fri Jul 20 23:36:11 2007 -0400 Unionfs: Set copied up open file mode to read-write When we copyup a file, we used to set the new file's open mode to write only. But we need read permission too. We need write permission because we're creating a new file; and we need read permission because the original file had to have been readable. If we don't set the new files permission to BOTH read and write, we get -EBADF when trying calling vfs_read/vfs_write on a copied up file. (This fix is somewhat related to unionfs_readpage fixes that use vfs_read instead of calling the lower readpage.) Signed-off-by: Erez Zadok Signed-off-by: Josef 'Jeff' Sipek commit 7bbb6cc14278ff3f7f048de7817644b2907ffb4c Author: Erez Zadok Date: Sat Jul 21 21:03:39 2007 -0400 Unionfs: Rewrite do_unionfs_readpage to use vfs_read In do_unionfs_readpage, we used to call the lower file system's ->readpage. However, some file systems (e.g., tmpfs) don't implement ->readpage, causing a NULL pointer dereference under certain conditions, especially under severe memory pressure. This patch reimplements do_unionfs_readpage using vfs_read, which makes the code simpler and more reliable, as we depend on the VFS to do most of the hard work (even if this implementation might be a bit slower). This fix also makes sense because it makes the mmap code in unionfs more symmetric with unionfs_commit_write --- which uses vfs_write(). Signed-off-by: Erez Zadok Signed-off-by: Josef 'Jeff' Sipek commit 801933f6974be2dc35802e0f1554dc745108f2bf Author: Erez Zadok Date: Fri Jul 20 23:34:28 2007 -0400 Unionfs: Fix minor typo in a comment Signed-off-by: Erez Zadok Signed-off-by: Josef 'Jeff' Sipek commit 5968725161a9c8d09cfb59e411ab2f30fd21f959 Author: Erez Zadok Date: Fri Jul 20 23:34:13 2007 -0400 Unionfs: Use splice_read file method instead of sendfile Signed-off-by: Erez Zadok Signed-off-by: Josef 'Jeff' Sipek commit 368e8b2d69e148956b418db7923ff12cfcbbea57 Author: Erez Zadok Date: Fri Jul 20 23:33:29 2007 -0400 Unionfs: Fix renaming symlinks on readonly branches bug Signed-off-by: Erez Zadok Signed-off-by: Josef 'Jeff' Sipek commit 13cabf50715d00271721180852e5848a9c23fe91 Author: Erez Zadok Date: Fri Jul 20 23:32:49 2007 -0400 Unionfs: Prevent deadlock with branch-management code Don't grab the superblock read-lock in unionfs_permission, which prevents a deadlock with the branch-management "add branch" code (which grabbed the write lock). It is safe to not grab the read lock here, because even with branch management taking place, there is no chance that unionfs_permission, or anything it calls, will use stale branch information. Signed-off-by: Erez Zadok Signed-off-by: Josef 'Jeff' Sipek commit 3640f2db35be9a03c9d6bcfbc3564c15fad4fb66 Author: Adrian Bunk Date: Mon Jul 2 14:27:57 2007 -0400 [PATCH] unionfs: make functions static This patch makes some needlessly global functions static. Signed-off-by: Adrian Bunk Signed-off-by: Josef 'Jeff' Sipek commit 9d55401b71e5db67221dd7a200d0be5b5c73b082 Author: Erez Zadok Date: Fri Jun 29 02:02:11 2007 -0400 Unionfs: Remove unnecessary BUG_ON in unionfs_follow_link Signed-off-by: Erez Zadok Signed-off-by: Josef 'Jeff' Sipek commit c02dd8b6ae7cbb5b4e4a2bdb67ba7e2ddeedce94 Author: Erez Zadok Date: Fri Jun 29 02:03:17 2007 -0400 Unionfs: Clarification comment for unionfs_lookup Signed-off-by: Erez Zadok Signed-off-by: Josef 'Jeff' Sipek commit ef6e1855396f6348f21a769f8a31bfee3d08c81d Author: Erez Zadok Date: Fri Jun 29 01:49:08 2007 -0400 Unionfs: Use file->f_path instead of file->f_dentry Signed-off-by: Erez Zadok Signed-off-by: Josef 'Jeff' Sipek commit 5453d631e9a076830df69fa90f8513b95dab6b32 Author: Josef 'Jeff' Sipek Date: Fri Jun 29 01:53:16 2007 -0400 Unionfs: Add missing unlock call in unionfs_file_release Signed-off-by: Josef 'Jeff' Sipek commit 2b2c237ff7ed33316371da7b407355fae2bcebcf Author: Erez Zadok Date: Fri Jun 29 01:32:54 2007 -0400 Unionfs: Convert all instances of "hidden" to "lower" The Unionfs code contained many references to symbols whose name had the string "hidden" in it, referring to, for example, a "hidden inode" below the Unionfs inode. The term "hidden" was there for historical reasons and was a misnomer, implying that the objects at Unionfs's lower branches were somehow hidden or unavailable. This was not just incorrect, but confusing. The lower objects are just that: lower. They are not hidden from users. In fact, users can 'cd' to lower branches and manipulate files directly there. This long patch renames all such mis-named symbols, and re-indents the code as needed to comply with coding standards. [jsipek: fixed up few rejects] Signed-off-by: Erez Zadok Signed-off-by: Josef 'Jeff' Sipek commit 51a571cb07174a1a5658ccddc1279be2e7844026 Author: Josef 'Jeff' Sipek Date: Fri Jun 8 11:08:33 2007 -0400 Unionfs: Remove superfluous check for NULL pointer Since we use containers and the struct inode is _inside_ the unionfs_inode_info structure, UNIONFS_I will always (given a non-NULL inode pointer), return a valid non-NULL pointer. Signed-off-by: Josef 'Jeff' Sipek commit c02045201a303d51646e809599f58a73eed119fd Author: Josef 'Jeff' Sipek Date: Fri Jun 8 17:04:40 2007 -0400 Unionfs: Change the semantics of sb info's rwsem This rw semaphore is used to make sure that a branch management operation... 1) will not begin before all currently in-flight operations complete 2) any new operations do not execute until the currently running branch management operation completes TODO: rename the functions unionfs_{read,write}_{,un}lock() to something more descriptive. Signed-off-by: Josef 'Jeff' Sipek commit 0f4c63d1f1357d801be82674edf395e0f6f14310 Author: Josef 'Jeff' Sipek Date: Fri Jun 8 10:58:21 2007 -0400 Unionfs: Add BUG_ONs to unionfs_lower_* Signed-off-by: Josef 'Jeff' Sipek commit 2970166213ef09ae1c05ad7eb173728316bf7786 Author: Josef 'Jeff' Sipek Date: Fri Jun 8 10:58:22 2007 -0400 Unionfs: Change free_dentry_private_info to take a struct dentry This makes it more symmetric with new_dentry_private_info. Signed-off-by: Josef 'Jeff' Sipek commit 2fcd54b14260560b298915e3c3238d6c00347f01 Author: Josef 'Jeff' Sipek Date: Fri Jun 8 10:58:24 2007 -0400 Unionfs: Cleanup new_dentry_private_data Signed-off-by: Josef 'Jeff' Sipek commit 15729e0988c6f9d72e6650f19ba5d2a42d8141f2 Author: Erez Zadok Date: Fri Jun 8 10:39:11 2007 -0400 Unionfs: Revalidate dentries passed to all inode/super operations Be sure to properly revalidate all dentry chains passed to all inode and super_block operations. Remove the older BUG_ON test is_valid_dentry(). This should help improve cache-coherency. Signed-off-by: Erez Zadok Signed-off-by: Josef 'Jeff' Sipek commit ea16395aa1e43f3db405210e167f31c3bdf91c17 Author: Erez Zadok Date: Fri Jun 8 10:36:23 2007 -0400 Unionfs: merge find_new_branch_index and branch_id_to_idx into one function Useful code cleanup and consolidation between the ODF code and non-ODF code. Signed-off-by: Erez Zadok Signed-off-by: Josef 'Jeff' Sipek commit bc3af40f204c57f061be77016f3cd2447d9e33ee Author: Yiannis Pericleous Date: Fri Jun 8 10:29:29 2007 -0400 Unionfs: mmap implementation Signed-off-by: Shaya Potter Signed-off-by: Erez Zadok Signed-off-by: Yiannis Pericleous Signed-off-by: Josef 'Jeff' Sipek commit 03681088bfba51c3d89e4df087adf4d4a533c2f1 Author: Erez Zadok Date: Thu Jun 7 17:42:59 2007 -0400 Unionfs: Remove unnecessary #define UNIONFS_TMPNAM_LEN is used in only one place, and we have calculate the length of the string to begin with. Signed-off-by: Erez Zadok Signed-off-by: Josef 'Jeff' Sipek commit 4a66b3477a1954b9e1ee2d37bbc3344b34b7d4f6 Author: Erez Zadok Date: Thu Jun 7 17:41:10 2007 -0400 Unionfs: Add standard copyright comment to include/linux/union_fs.h Signed-off-by: Erez Zadok Signed-off-by: Josef 'Jeff' Sipek commit cad7386018d658f60eefb010e046c6644666f2a2 Author: Erez Zadok Date: Thu Jun 7 17:40:22 2007 -0400 MAINTAINERS: Add Erez Zadok as a maintainer of Unionfs Signed-off-by: Erez Zadok Signed-off-by: Josef 'Jeff' Sipek commit 8ffc2cbb54c7dcfa0e158ef50fa2efd03383a31b Author: Erez Zadok Date: Thu Jun 7 17:28:37 2007 -0400 Unionfs: call statfs on lower file system properly Get the correct lower dentry to use to statfs the first branch (always), Signed-off-by: Erez Zadok Signed-off-by: Josef 'Jeff' Sipek commit eb2cbdbe7790b2f723c352e7ce2fc0b753fa30a1 Author: Erez Zadok Date: Thu Jun 7 17:45:12 2007 -0400 Unionfs: Set lower inodes correctly after branch management succeeds Signed-off-by: Erez Zadok Signed-off-by: Josef 'Jeff' Sipek commit be37142db3f25cdba3ec92726231f6120d2d6966 Author: Erez Zadok Date: Thu Jun 7 17:05:07 2007 -0400 Unionfs: Retry lookup for different silly-renamed files When we have to copyup an open-but-unlinked file, we have to give it a temporary name, similar to NFS's silly-renamed files. So we generate temporary file names until we find one that doesn't exist, and use it. The code had a bug where if the silly-renamed file name already existed, Unionfs would oops upon copyup to that temp name. Signed-off-by: Erez Zadok Signed-off-by: Josef 'Jeff' Sipek commit 9b72436b9eed4cbe20a57c40a8a885ed01a87b6d Author: Erez Zadok Date: Thu Jun 7 17:05:00 2007 -0400 Unionfs: Don't revalidate dropped dentries This fixes a harmless but annoying message that unionfs prints if a dropped dentry is being revalidated, which could happen if you unlink open files. Signed-off-by: Erez Zadok Signed-off-by: Josef 'Jeff' Sipek commit 2c3ff89fb2e8478709839f4a7c6f57ed3cd1d4ce Author: Randy Dunlap Date: Wed Jun 6 20:38:19 2007 -0400 [PATCH] unionfs section mismatch Fix section marker in header file: WARNING: fs/unionfs/unionfs.o(.init.text+0x56): Section mismatch: reference to .exit.text:stop_sioq (between 'init_module' and 'init_sioq') Signed-off-by: Randy Dunlap Signed-off-by: Josef 'Jeff' Sipek commit 4e5615aba43671eb6a1efe0931ce14a5c6ffa467 Author: Josef 'Jeff' Sipek Date: Sun Jun 3 22:06:33 2007 -0400 Unionfs: Fix lock leak in unionfs_ioctl Signed-off-by: Josef 'Jeff' Sipek commit 068a024634001da2469b5aaf8f29c19f9ad47b69 Author: Erez Zadok Date: Wed May 23 18:27:24 2007 -0400 Unionfs: Correctly decrement refcounts of mnt's upon branch management The old logic was broken in one place, which another place tried to "fix" incorrectly. Also added detailed comments to explain the new/correct logic. Signed-off-by: Erez Zadok Signed-off-by: Josef 'Jeff' Sipek commit e84a6cc65b1f1429a873484ee68ec61cf1cd4b25 Author: Yiannis Pericleous Date: Wed May 23 18:25:47 2007 -0400 Unionfs: Removed a trailing whitespace Signed-off-by: Yiannis Pericleous Signed-off-by: Josef 'Jeff' Sipek commit 0c362e7dd9d349c874a9c2676a8b0c6cbcf9490b Author: Josef 'Jeff' Sipek Date: Wed May 23 19:51:47 2007 -0400 Unionfs: Actually catch bad use of unionfs_mnt{get,put} Signed-off-by: Josef 'Jeff' Sipek commit cdd1b78919bf8837602ade609c6af2e96c508a6a Author: Erez Zadok Date: Wed May 23 17:57:31 2007 -0400 Unionfs: Remove defunct unionfs_put_inode super op Removed old workaround code that was needed to get mmap working, is no longer needed with recent kernels. Signed-off-by: Erez Zadok Signed-off-by: Josef 'Jeff' Sipek commit 506b0622007710fdaa574a82133ca26243921dcc Author: Erez Zadok Date: Wed May 23 17:55:28 2007 -0400 Unionfs: Documentation update regarding overlapping branches and new lookup code Added detailed comment and updated documentation to explain why overlapping branches are disallowed, and better explain the cache coherency issues. Signed-off-by: Erez Zadok Signed-off-by: Josef 'Jeff' Sipek commit a2ea8e183ff43ed6d7743d038249ca10104b3dc2 Author: Erez Zadok Date: Wed May 23 17:48:11 2007 -0400 Unionfs: Disallow setting leftmost branch to readonly Unionfs requires the leftmost branch to be writeable for copyup to work properly and simply. If, through branch-management commands (add, delete, or mode change), the leftmost branch will becomes readonly, then return an error (and tell the user to use "remount,ro" if they want a readonly union). [jsipek: fixed up to apply cleanly] Signed-off-by: Erez Zadok Signed-off-by: Josef 'Jeff' Sipek commit 4d3dbb8e0ba0f60efcaa6e80106edf620fccd405 Author: Josef 'Jeff' Sipek Date: Wed May 23 17:17:31 2007 -0400 Unionfs: Use krealloc instead of open-coding the functionality Change the branch management code to use krealloc instead of playing tricks with kmalloc/memcpy/kfree. Signed-off-by: Josef 'Jeff' Sipek commit e9851f3a4769f61d8f77627cbaff96153e772352 Author: Josef 'Jeff' Sipek Date: Wed May 23 17:06:08 2007 -0400 Unionfs: Call realloc unconditionally krealloc already checks if the new size is greater than the old size. Therefore, we can call realloc unconditionally - making the code simpler and cleaner. Signed-off-by: Josef 'Jeff' Sipek commit 663b9cb91a04af89734db93b9d9b1316a082c4e2 Author: Josef 'Jeff' Sipek Date: Wed May 23 16:44:54 2007 -0400 Unionfs: Don't leak resources when copyup fails partially Original-patch-by: Erez Zadok Signed-off-by: Josef 'Jeff' Sipek commit c2fe9c92c1debaec0ffdea352a671dcd63f0ffe9 Author: Erez Zadok Date: Wed May 23 16:33:24 2007 -0400 Unionfs: Prefix external functions with 'extern' properly Signed-off-by: Erez Zadok Signed-off-by: Josef 'Jeff' Sipek commit b0e1c7e8b1d541ea7239e71f9ec9a9050eda4f03 Author: Josef 'Jeff' Sipek Date: Wed May 23 16:25:21 2007 -0400 Unionfs: Combine unionfs_write with __unionfs_write. The __unionfs_write helper function was used only by unionfs_write, and there is really no reason why they should not be combined. Signed-off-by: Josef 'Jeff' Sipek commit 30f7614e1bccebf1c809bee10fa746823fe09cbf Author: Josef 'Jeff' Sipek Date: Sun May 20 17:12:48 2007 -0400 Unionfs: Move unionfs_query_file to commonfops.c Moved unionfs_query_file closer to its one user in commonfops.c. Additionally, it can now become static, and branchman.c can be removed as it is empty. Signed-off-by: Josef 'Jeff' Sipek commit 3742a0d99f24c94a3b4454903530a041c2db2a0d Author: Erez Zadok Date: Sun May 20 16:44:52 2007 -0400 Unionfs: Rename our "do_rename" to __unionfs_rename To avoid confusion with the VFS function do_rename, and to help ctags, rename our utility (static) function do_rename to __unionfs_rename. Signed-off-by: Erez Zadok Signed-off-by: Josef 'Jeff' Sipek commit c988ffaac2f81c7e2d8c1b83171150f66fb4adce Author: Erez Zadok Date: Sun May 20 16:23:01 2007 -0400 Unionfs: Rename Unionfs's double_lock_dentry to avoid confusion To avoid potential confusion with a VFS function, rename our version of double_lock_dentry to unionfs_double_lock_dentry. Signed-off-by: Erez Zadok Signed-off-by: Josef 'Jeff' Sipek commit 082ea7cb86318038bb6cd61b0801af001df8e2d9 Author: Erez Zadok Date: Sun May 20 16:03:00 2007 -0400 Unionfs: Consistent pointer declaration spacing Change instances of "foo * var" to "foo *var" for consistency. Signed-off-by: Erez Zadok Signed-off-by: Josef 'Jeff' Sipek commit ca58203b70c9cfcec8840b8eee370b3a5a4e2c18 Author: Erez Zadok Date: Sun May 20 16:46:21 2007 -0400 Unionfs: Added numerous comments Audited entire code for documentation. Added comments at top of functions where it felt necessary (i.e., function's name and size don't make it clear what it may be doing precisely). Reformatted some long comments. Fixed a few comment typos and spelling errors. Signed-off-by: Erez Zadok Signed-off-by: Josef 'Jeff' Sipek commit 5cd5e3b4f4c65fad03e83c2fcf8de8442e0d63b7 Author: Erez Zadok Date: Sun May 20 15:50:59 2007 -0400 Unionfs: Cleanup of strings and comments Includes: - consistent style for multi-line comments - spell-check of all strings and comments Signed-off-by: Erez Zadok Signed-off-by: Josef 'Jeff' Sipek commit e74d69504378c91e54cac75253b5a7e980314195 Author: Erez Zadok Date: Sun May 20 15:21:08 2007 -0400 Unionfs: Add missing copyright notices Signed-off-by: Erez Zadok Signed-off-by: Josef 'Jeff' Sipek commit 2a72fe8dc6d7d8fcb17f4813e26980fa22b4dbb1 Author: Erez Zadok Date: Sun May 20 15:36:32 2007 -0400 Unionfs: Every printk should prefix with "unionfs: " consistently Signed-off-by: Erez Zadok Signed-off-by: Josef 'Jeff' Sipek commit becbd87e659b852e341eb213777b6a4637e067ef Author: Erez Zadok Date: Sun May 20 16:09:06 2007 -0400 Unionfs: Coding style fixes Includes: - indentation fixes - 80 column wrapping - removing unnecessary braces - removing trailing whitespace Signed-off-by: Erez Zadok Signed-off-by: Josef 'Jeff' Sipek commit 1d203c7a8e9eda38a81a36ba3d5325a3547126d7 Author: Erez Zadok Date: Sun May 20 15:18:38 2007 -0400 Unionfs: Tiny documentation fixups Signed-off-by: Erez Zadok Signed-off-by: Josef 'Jeff' Sipek commit 40c691ee7b33b0e83fec09280efd6a44bd6ff588 Author: Josef 'Jeff' Sipek Date: Sat May 19 03:14:30 2007 -0400 Unionfs: SLAB_CTOR_CONSTRUCTOR has been removed from vanilla Signed-off-by: Josef 'Jeff' Sipek commit e568f288d01488d40bc0c4618038d00789c454c4 Author: Pekka Enberg Date: Sun May 13 12:55:44 2007 -0400 [PATCH] unionfs: fix slab abuses with krealloc This changes unionfs to use krealloc() for reallocating memory so that we don't need to play tricks with slab internals. Cc: Josef Sipek Signed-off-by: Pekka Enberg Signed-off-by: Andrew Morton Signed-off-by: Josef 'Jeff' Sipek commit d2c015733d51cf38836a79b15f3ef4bc7497b8f6 Author: Adrian Bunk Date: Sat May 5 15:28:33 2007 -0400 [PATCH] fix unionfs compilation On Sat, May 05, 2007 at 01:49:55AM -0700, Andrew Morton wrote: >... > Changes since 2.6.21-rc7-mm2: >... > git-unionfs.patch >... > git trees >... <-- snip --> ... CC fs/unionfs/super.o /home/bunk/linux/kernel-2.6/linux-2.6.21-mm1/fs/unionfs/super.c: In function ‘init_once’: /home/bunk/linux/kernel-2.6/linux-2.6.21-mm1/fs/unionfs/super.c:822: error: ‘SLAB_CTOR_VERIFY’ undeclared (first use in this function) /home/bunk/linux/kernel-2.6/linux-2.6.21-mm1/fs/unionfs/super.c:822: error: (Each undeclared identifier is reported only once /home/bunk/linux/kernel-2.6/linux-2.6.21-mm1/fs/unionfs/super.c:822: error: for each function it appears in.) make[3]: *** [fs/unionfs/super.o] Error 1 <-- snip --> Signed-off-by: Adrian Bunk Signed-off-by: Josef 'Jeff' Sipek commit f96e79a4697a56fbc2cdf2fbe68fb2808c8a8328 Author: Adrian Brunyate Date: Thu Apr 26 18:51:16 2007 -0400 Unionfs: Check remount options for being NULL Signed-off-by: Adrian Brunyate Signed-off-by: Josef 'Jeff' Sipek commit 07453b93e09affda72fc8aae5c92c150ecc48f23 Author: Adrian Brunyate Date: Thu Apr 26 18:51:09 2007 -0400 Unionfs: Accept MS_SILENT during remount [jsipek: whitespace cleanup] Signed-off-by: Adrian Brunyate Signed-off-by: Josef 'Jeff' Sipek commit 793ebde7dfb573012ffd2b4f761454f0b847e20c Author: Erez Zadok Date: Fri Mar 23 13:10:46 2007 -0400 Unionfs: Don't inline do_remount_{add,del,mode}_option gcc4 decided to inline do_remount_{add,del,mode}_option creating an 600 byte stack abuser on a x86_64 test box. Reported by: Josef 'Jeff' Sipek Signed-off-by: Erez Zadok Signed-off-by: Josef 'Jeff' Sipek commit a9e97f8ff8bbd561b14812db8cae4347bb007cd3 Author: Erez Zadok Date: Fri Mar 23 13:07:14 2007 -0400 Unionfs: Added several BUG_ONs to assert dentry validity This should help catch races between the VFS and the branch-management code. Signed-off-by: Erez Zadok Signed-off-by: Josef 'Jeff' Sipek commit a95512f8be86abb2085c4d0957439bafd73b8b92 Author: Erez Zadok Date: Thu Mar 22 20:17:48 2007 -0400 Unionfs: Properly handle stale inodes passed to unionfs_permission Signed-off-by: Erez Zadok Signed-off-by: Josef 'Jeff' Sipek commit b961da11f469f9f4c9c9c568430e8eeb6ccd9333 Author: Erez Zadok Date: Thu Mar 22 20:09:07 2007 -0400 Unionfs: Pass lowernd to lower ->revalidate function Signed-off-by: Erez Zadok Signed-off-by: Josef 'Jeff' Sipek commit 16c61c2c04c2b7d4ea401070adb5d0bab27bd043 Author: Erez Zadok Date: Thu Mar 22 20:08:30 2007 -0400 Unionfs: vfsmount reference counting fixes Signed-off-by: Erez Zadok Signed-off-by: Josef 'Jeff' Sipek commit 81d9763af32a6b21f5d6be2c9728933d39d4746a Author: Erez Zadok Date: Thu Mar 22 20:01:56 2007 -0400 Unionfs: unionfs_create needs to revalidate the dentry 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 unionfs_create(). So we have to ensure consistency, just as we do with the file operations. Signed-off-by: Erez Zadok Signed-off-by: Josef 'Jeff' Sipek commit d9fdce24f33ab47a2696f6dedda7d84f86b0bfec Author: Erez Zadok Date: Thu Mar 22 19:47:43 2007 -0400 Unionfs: Decrement totalopens counter on error in unionfs_open Signed-off-by: Erez Zadok Signed-off-by: Josef 'Jeff' Sipek commit df06ce8679831679233ec33fdcd06b34a4024798 Author: Josef 'Jeff' Sipek Date: Sun Mar 18 16:30:45 2007 -0400 Unionfs: Document unionfs_d_release locking Signed-off-by: Josef 'Jeff' Sipek commit 7223285bffd128853ca654a472dbb7931eb81bc0 Author: Josef 'Jeff' Sipek Date: Sun Mar 18 16:22:03 2007 -0400 Unionfs: Remove the older incgen ioctl The new remount code now has the "incgen" functionality. Signed-off-by: Josef 'Jeff' Sipek commit 835a08b87ce0aa480718a784df74fa557b2065e7 Author: Erez Zadok Date: Thu Mar 22 19:32:40 2007 -0400 Unionfs: Grab the unionfs sb private data lock around branch info users Locking/concurrency/race fixes. Use the unionfs superblock rwsem, and grab the read lock around every op that uses branch-related information, such as branch counters. Grab the write rwsem lock in operations which attempt to change branch information, such as when adding/deleting branches. This will, for example, cause branch-management remount commands (which are infrequent) to block a bit until all in-progress file operations on open files are done. Signed-off-by: Erez Zadok [jsipek: whitespace fixes & more locks/unlocks] Signed-off-by: Josef 'Jeff' Sipek commit e8090102469c0b6d31b2abf4031ac6baf46f4282 Author: Erez Zadok Date: Mon Mar 19 21:02:55 2007 -0400 Unionfs: Rewrite unionfs_d_revalidate Rewrite unionfs_d_revalidate code to avoid stack-unfriendly recursion: split into a call to revalidate just one dentry, and an interative driver function to revalidate an entire dentry-parent chain. Fix vfsmount ref leaks which prevented lower f/s from being unmounted after generation increment, esp. during heavy loads. Fix one deadlock between revalidation code and VFS. Better documentation of what the code does. Signed-off-by: Erez Zadok [jsipek: compile & whitespace fixes] Signed-off-by: Josef 'Jeff' Sipek commit 8a9481b691b16de9f26e12192d566bbfba969613 Author: Erez Zadok Date: Mon Mar 19 20:57:49 2007 -0400 Unionfs: Introduce unionfs_mnt{get,put} Helper inline functions to perform Unionfs's mntget/put ops on lower branches. Signed-off-by: Erez Zadok [jsipek: cleanup branching in unionfs_mnt{get,put} and compile fixes] Signed-off-by: Josef 'Jeff' Sipek commit aa4dfacd51a43ed96cc3c57c157c7388aba784e4 Author: Erez Zadok Date: Mon Mar 19 20:44:12 2007 -0400 Unionfs: Bulk of branch-management remount code Signed-off-by: Erez Zadok Signed-off-by: Josef 'Jeff' Sipek commit 9879ff781a680f7a8e27cbe757a97be35a8940cf Author: Erez Zadok Date: Thu Mar 22 19:43:29 2007 -0400 Unionfs: Introduce branch-id code Each branch gets a unique ID, which helps during branch additions, deletions, and changes, to locate where branches were moved to, and perform proper reference-counting. This is useful even if the same directory was added more than once to union. Signed-off-by: Erez Zadok Signed-off-by: Josef 'Jeff' Sipek commit 80c0775e22eec00ebdb0d793e331af3652bc3f85 Author: Erez Zadok Date: Mon Apr 9 09:57:51 2007 -0400 Unionfs: Actually verify if dentry's info node is locked Signed-off-by: Erez Zadok Signed-off-by: Josef 'Jeff' Sipek commit 1c137353164a4b1b2c998947c482b47c947cc053 Author: Erez Zadok Date: Mon Mar 19 03:01:49 2007 -0400 Unionfs: Provide more helpful info on branch leaks during unmount Signed-off-by: Erez Zadok [jsipek: no need to take a read lock on the superblock private data] Signed-off-by: Josef 'Jeff' Sipek commit e63d820b9db139c3f3b49f8243b4910b49fa5229 Author: Erez Zadok Date: Mon Mar 19 00:46:03 2007 -0400 Unionfs: Rename unionfs_data sbcount field to more appropriate open_files Signed-off-by: Erez Zadok Signed-off-by: Josef 'Jeff' Sipek commit 8616d619f52e363380c3bbee251d4a84ac2b2d20 Author: Josef 'Jeff' Sipek Date: Sun Mar 18 19:37:51 2007 -0400 Unionfs: Proper comment on rwsem field Signed-off-by: Josef 'Jeff' Sipek commit 3d633ef3c3a268a01fef0443eac6f5e010b30158 Author: Erez Zadok Date: Fri Mar 23 17:17:07 2007 -0400 Unionfs: Documentation updates for branch-management Describe dynamic branch-management introduced by subsequent patches. Signed-off-by: Erez Zadok Signed-off-by: Josef 'Jeff' Sipek commit 5ea1f40846d5c73a7985727c575c7fdec84a307a Author: Erez Zadok Date: Wed May 23 20:39:35 2007 -0400 fs: Export drop_pagecache_sb symbol Signed-off-by: Erez Zadok Signed-off-by: Josef 'Jeff' Sipek commit fa4e14578ed91f27d97a62f2ededa0afa97fad64 Author: Erez Zadok Date: Wed May 23 20:42:50 2007 -0400 fs: Introduce path{get,put} Signed-off-by: Erez Zadok Signed-off-by: Josef 'Jeff' Sipek commit 69a530b959d3aa82b005412beeece47fb90d70e3 Author: Randy Dunlap Date: Mon Mar 19 19:15:22 2007 -0400 [PATCH] Unionfs: sioq not __exit stop_sioq() is called from both __init and __exit functions, so it shouldn't be marked __exit. Reported on the kernelnewbies mailing list, but no patch offered there. Signed-off-by: Randy Dunlap Signed-off-by: Josef 'Jeff' Sipek commit 3c2ff3b4c8a80010bd32e7314d89cb381f54f605 Author: Josef 'Jeff' Sipek Date: Sat Mar 17 19:42:25 2007 -0400 MAINTAINERS: Unionfs URL update Signed-off-by: Josef 'Jeff' Sipek commit 0d715e13b670f953a6c00f2ccc02451d70b61757 Author: Erez Zadok Date: Sun Mar 4 18:02:11 2007 -0500 fs/unionfs/: Fix unlocking in error paths Signed-off-by: Erez Zadok Signed-off-by: Josef 'Jeff' Sipek commit ff68f281711e219c9128442e593ffde4796f69c3 Author: Erez Zadok Date: Sun Mar 4 15:21:14 2007 -0500 fs/unionfs/: Fix dentry leak in copyup_named_dentry When we chmod a directory on a readonly branch, and have to copy it up, we forget to dput(). If this was a file, it gets dput indirectly through other functions we call, but not if it was a directory. Signed-off-by: Erez Zadok Signed-off-by: Josef 'Jeff' Sipek commit 2f9ef3c26554d48c08fcdf4beed057236bc00492 Author: Erez Zadok Date: Fri Mar 2 13:10:56 2007 -0500 fs/unionfs/: mntput in __cleanup_dentry This fixes a mnt refleak which occured during copyup when directory hierarchy was recreated on a writable branch. Signed-off-by: Erez Zadok Signed-off-by: Josef 'Jeff' Sipek commit e2b5c50da52b9f72edcc4895ffadf3536ad30996 Author: Erez Zadok Date: Sun Mar 4 00:41:51 2007 -0500 fs/unionfs/: Fix copyup_deleted_file dentry leak Signed-off-by: Erez Zadok Signed-off-by: Josef 'Jeff' Sipek commit 8dec30072499a2e27f5ad2319f00ac5fbf880a60 Author: Josef 'Jeff' Sipek Date: Fri Mar 2 13:14:01 2007 -0500 fs/unionfs/: Miscellaneous coding style fixes Signed-off-by: Josef 'Jeff' Sipek commit ed2b301fd7fe4f29949986f59728a596be23a32d Author: Josef 'Jeff' Sipek Date: Tue Feb 27 14:27:25 2007 -0500 fs/unionfs/: Check return value of d_path Signed-off-by: Josef 'Jeff' Sipek commit 24aae1cb4c05d2d2f97ec38ff5eeb0fcca2c88e2 Author: Josef 'Jeff' Sipek Date: Sun Feb 25 17:32:46 2007 -0500 fs/unionfs/: Use SEEK_{SET,CUR} instead of hardcoded values Signed-off-by: Josef 'Jeff' Sipek commit 1e9af38410dd722d2c0702e6efde891d1508a4c5 Author: Josef 'Jeff' Sipek Date: Sun Feb 25 17:30:34 2007 -0500 fs/unionfs/: Remove alloc_filldir_node alloc_filldir_node was used only once. Additionally, all the arguments passed to it were ignored wasting stack space for no reason whatsoever. Signed-off-by: Josef 'Jeff' Sipek commit 979e78210d7d8fb8e4011d1e7f4f27e415045c7a Author: Josef 'Jeff' Sipek Date: Sat Feb 24 16:47:45 2007 -0500 fs/unionfs/: Rename unionfs_d_revalidate_wrap Follow the convention of "foo" calling "__foo". Signed-off-by: Josef 'Jeff' Sipek commit 2016a57f0401504ba118040a2e9d18a2cf8b7068 Author: Josef 'Jeff' Sipek Date: Sat Feb 24 16:46:08 2007 -0500 fs/unionfs/: Several small cleanups in unionfs_interpose 1) No need to lock the inode - lockdep was complaining about potential circular dependency 2) No need to use temporary variable for iunique() inode number 3) Removed unneeded comment Signed-off-by: Josef 'Jeff' Sipek commit 500839c825dcfab4d9009f5a2121227a0751b348 Author: Josef 'Jeff' Sipek Date: Fri Feb 23 01:04:09 2007 -0500 fs/unionfs/: Don't grab dentry private data mutex in unionfs_d_release Grabbing the UNIONFS_D(dentry)->lock is completely unnecessary and there are no other references; we are about to free the object anyway. Additionally, grabbing the mutex produces warning when the slab object is reused - as it was freed while there still was a reference to it. Signed-off-by: Erez Zadok Signed-off-by: Josef 'Jeff' Sipek commit a61fd16c528b285274778227b857d0d8b0f829a0 Author: Erez Zadok Date: Fri Mar 2 13:35:33 2007 -0500 fs/unionfs/: Fix a memory leak in unionfs_read_super Signed-off-by: Erez Zadok Signed-off-by: Josef 'Jeff' Sipek commit 6ee97f10c76a60cae42b601810099f0a9191f003 Author: Erez Zadok Date: Fri Mar 2 13:00:57 2007 -0500 fs/unionfs: Fix a memory leak & null pointer dereference Signed-off-by: Erez Zadok Signed-off-by: Josef 'Jeff' Sipek commit 9c2b8ab1479f7421ece9a04451f19e122ae0259e Author: Adrian Bunk Date: Wed Feb 21 01:30:10 2007 -0500 [PATCH] UNION_FS must depend on SLAB On Sat, Feb 17, 2007 at 09:51:46PM -0800, Andrew Morton wrote: >... > Changes since 2.6.20-mm1: >... > git-unionfs.patch >... > git trees >... <-- snip --> ... CC fs/unionfs/copyup.o /home/bunk/linux/kernel-2.6/linux-2.6.20-mm2/fs/unionfs/copyup.c: In function 'create_parents_named': /home/bunk/linux/kernel-2.6/linux-2.6.20-mm2/fs/unionfs/copyup.c:620: error: 'malloc_sizes' undeclared (first use in this function) /home/bunk/linux/kernel-2.6/linux-2.6.20-mm2/fs/unionfs/copyup.c:620: error: (Each undeclared identifier is reported only once /home/bunk/linux/kernel-2.6/linux-2.6.20-mm2/fs/unionfs/copyup.c:620: error: for each function it appears in.) make[3]: *** [fs/unionfs/copyup.o] Error 1 <-- snip --> Signed-off-by: Adrian Bunk Signed-off-by: Josef 'Jeff' Sipek commit f21307a4bde92c5faacaa268e62473e575b5f0d9 Author: Josef 'Jeff' Sipek Date: Sat Feb 17 03:25:15 2007 -0500 fs/unionfs/: Remove unused structure members & macros This patch removes: - hidden_mnt pointer from struct unionfs_data - mount_flag from struct unionfs_sb_info - mount_flag related macros Signed-off-by: Josef 'Jeff' Sipek commit 7243463ab38a73d0aff4580bb44df0ad47ec121f Author: Michael Halcrow Date: Fri Feb 16 14:09:25 2007 -0500 eCryptfs: convert lookup_one_len() to lookup_one_len_nd() Call the new lookup_one_len_nd() rather than lookup_one_len(). This fixes an oops when stacked on NFS. Note that there are still some issues with eCryptfs on NFS having to do with directory deletion (I'm not getting an oops, just an -EBUSY). Signed-off-by: Michael Halcrow Signed-off-by: Andrew Morton Signed-off-by: Josef 'Jeff' Sipek commit 53b72afba4e9cb6d370c4db31b8f654e5a6b92e2 Author: Erez Zadok Date: Mon Feb 12 12:36:38 2007 -0500 Unionfs: Documentation update Be little gentler & updated the URLs Signed-off-by: Erez Zadok Signed-off-by: Josef 'Jeff' Sipek commit 3965ce96e34a86a06794d3313fb3834ab633f20c Author: Josef 'Jeff' Sipek Date: Mon Feb 19 23:35:32 2007 -0500 fs/: Move eCryptfs & Unionfs config options into a sub-menu Using The Misc filesystems sub-menu for layered/stackable filesystems only makes it harder for users to find eCryptfs/Unionfs. Additionally, the menu can be easily turned into a menuconfig, which could be used to turn on any VFS/VM functionality required by layered filesystems (there is none at the moment). Signed-off-by: Josef 'Jeff' Sipek Signed-off-by: Michael Halcrow commit 568c2fbd85bc8522f749a8babc5eed16b0834e65 Author: Josef 'Jeff' Sipek Date: Thu Feb 1 12:14:03 2007 -0500 fs/unionfs/: Use __roundup_pow_of_two instead of custom rounding code Signed-off-by: Josef 'Jeff' Sipek commit ae18b7894900520b6b092d2e6514b1881463a8ed Author: Josef 'Jeff' Sipek Date: Sun Jan 28 14:20:19 2007 -0500 fs/unionfs/: Don't duplicate the struct nameidata The only fields that we have to watch out for are the dentry and vfsmount. Additionally, this makes Unionfs gentler on the stack as nameidata is rather large. Signed-off-by: Josef 'Jeff' Sipek commit 7caae594ce24507c734619139d75688adb73c558 Author: Josef 'Jeff' Sipek Date: Sun Jan 28 14:20:49 2007 -0500 fs/unionfs/: Andrew Morton's comments - rename {,un}lock_dentry to unionfs_{,un}lock_dentry - few minor coding style fixes - removed prototypes from .c files - replaced dbstart macros etc with static inlines - replaced UNIONFS_D(d)->sem semaphore with a mutex - renamed sioq struct workqueue to superio_workqueue - made unionfs_get_nlinks and alloc_whname not inlined Signed-off-by: Josef 'Jeff' Sipek commit 9d243a996fae919fa741ab37b3ac6ad58a150958 Author: Adrian Bunk Date: Thu Jan 25 03:15:59 2007 -0500 fs/unionfs/: possible cleanups This patch contains the following possible cleanups: - every function should #include the headers containing the prototypes of it's global functions - static functions in C files shouldn't be marked "inline", gcc should know best when to inline them - make needlessly global code static - #if 0 the following unused global function: - stale_inode.c: is_stale_inode() Signed-off-by: Adrian Bunk [removed stale inode related fixes as stale_inode.c is gone] Signed-off-by: Josef 'Jeff' Sipek commit cfc13556f82e344da5bce964a8f7ff2002ccb908 Author: Josef 'Jeff' Sipek Date: Sun Jan 28 15:52:43 2007 -0500 fs/unionfs/: Remove stale_inode.c The stale inode operations were heavily based on bad inode operations. This patch removes stale_inode.c and converts all users of stale_inode_ops to bad_inode_ops as there seems to be no reason to return ESTALE instead of EIO. This is the more appropriate than porting the bad_inode.c fix (commit be6aab0e9fa6d3c6d75aa1e38ac972d8b4ee82b8) to stale_inode.c. Signed-off-by: Josef 'Jeff' Sipek commit a578cf4adad305c90a63310a4158130a09402ae6 Author: Josef "Jeff" Sipek Date: Sun Jan 28 15:11:23 2007 -0500 Unionfs: Extended Attributes support Extended attribute support. Signed-off-by: Josef "Jeff" Sipek Signed-off-by: David Quigley Signed-off-by: Erez Zadok commit 785fa8a083a17b380bc1b5d2e17074ef3d6bafde Author: Josef "Jeff" Sipek Date: Sun Jan 28 15:10:20 2007 -0500 Unionfs: Kconfig and Makefile This patch contains the changes to fs Kconfig file, Makefiles, and Maintainers file for Unionfs. Signed-off-by: Josef "Jeff" Sipek Signed-off-by: David Quigley Signed-off-by: Erez Zadok commit dfd5b692cba15cd1050af070ba9a5b3c373ece04 Author: Josef "Jeff" Sipek Date: Sun Jan 21 18:47:57 2007 -0500 Unionfs: Unlink This patch provides unlink functionality for Unionfs. Signed-off-by: Josef "Jeff" Sipek Signed-off-by: David Quigley Signed-off-by: Erez Zadok commit b1dd3e4547dfbf0d6beaa2f420eee751fde410ac Author: Josef "Jeff" Sipek Date: Sun Jan 21 18:47:43 2007 -0500 Unionfs: Include file Global include file - can be included from userspace by utilities. Signed-off-by: Josef "Jeff" Sipek Signed-off-by: David Quigley Signed-off-by: Erez Zadok commit 6d02f66a7dcb4c16d586252657c4c664f06a650b Author: Josef "Jeff" Sipek Date: Sun Jan 28 15:05:29 2007 -0500 Unionfs: Internal include file This patch contains an internal Unionfs include file. The include file is specific to kernel code only, and therefore is separate from include/linux/unionfs.h. Signed-off-by: Josef "Jeff" Sipek Signed-off-by: David Quigley Signed-off-by: Erez Zadok commit f5efead8599a1aba565b5835c358b4d5cce2683a Author: Josef "Jeff" Sipek Date: Sun Jan 21 18:46:56 2007 -0500 Unionfs: Helper macros/inlines This patch contains many macros and inline functions used thoughout Unionfs. Signed-off-by: Josef "Jeff" Sipek Signed-off-by: David Quigley Signed-off-by: Erez Zadok commit 7c66d5e549f4b7770061887228c0b71583cf8c1f Author: Josef "Jeff" Sipek Date: Sun Jan 28 15:05:07 2007 -0500 Unionfs: Handling of stale inodes Provides nicer handling of stale inodes. Signed-off-by: Josef "Jeff" Sipek Signed-off-by: David Quigley Signed-off-by: Erez Zadok commit b721617b1e2bffeacb212ec95c83d875e2c96de3 Author: Josef "Jeff" Sipek Date: Sun Jan 21 18:46:47 2007 -0500 Unionfs: Superblock operations This patch contains the superblock operations for Unionfs. Signed-off-by: Josef "Jeff" Sipek Signed-off-by: David Quigley Signed-off-by: Erez Zadok commit 236cb70bb5318c589c9faf40d7c53e50d0a732ed Author: Josef "Jeff" Sipek Date: Sun Jan 21 18:46:39 2007 -0500 Unionfs: Miscellaneous helper functions This patch contains miscellaneous helper functions used thoughout Unionfs. Signed-off-by: Josef "Jeff" Sipek Signed-off-by: David Quigley Signed-off-by: Erez Zadok commit 6f680d868f2ab835a97b891ca2ef535c4a7a47b5 Author: Josef "Jeff" Sipek Date: Sun Jan 21 18:46:11 2007 -0500 Unionfs: Privileged operations workqueue Workqueue & helper functions used to perform privileged operations on behalf of the user process. Signed-off-by: Josef "Jeff" Sipek Signed-off-by: David Quigley Signed-off-by: Erez Zadok commit 5c43cc8ff8767e8cb8505aef66f9eda60dc3e7f7 Author: Josef "Jeff" Sipek Date: Sun Jan 21 18:45:56 2007 -0500 Unionfs: Rename This patch provides rename functionality for Unionfs. Signed-off-by: Josef "Jeff" Sipek Signed-off-by: David Quigley Signed-off-by: Erez Zadok commit 1f8cee2a469ffdaa7580dd0122b1e4ebe2480326 Author: Josef "Jeff" Sipek Date: Sun Jan 21 18:45:48 2007 -0500 Unionfs: Readdir state This file contains the routines for maintaining readdir state. Signed-off-by: Josef "Jeff" Sipek Signed-off-by: David Quigley Signed-off-by: Erez Zadok commit d33ab6500b460e1cfd703b0a2b81fbe7dce8e2c0 Author: Josef "Jeff" Sipek Date: Sun Jan 21 18:45:28 2007 -0500 Unionfs: Main module functions Module init & cleanup code, as well as interposition functions. Signed-off-by: Josef "Jeff" Sipek Signed-off-by: David Quigley Signed-off-by: Erez Zadok commit 235c3f768df2202a61f2b2410f54c5124c7fe96f Author: Josef "Jeff" Sipek Date: Sun Jan 21 18:45:13 2007 -0500 Unionfs: Lookup helper functions This patch provides helper functions for the lookup operations in Unionfs. Signed-off-by: Josef "Jeff" Sipek Signed-off-by: David Quigley Signed-off-by: Erez Zadok commit 268a4b66db1c7a810cf12f56ab88cc32949a6cd6 Author: Josef "Jeff" Sipek Date: Sun Jan 21 18:44:54 2007 -0500 Unionfs: Inode operations This patch provides the inode operations for Unionfs. Signed-off-by: Josef "Jeff" Sipek Signed-off-by: David Quigley Signed-off-by: Erez Zadok commit b02c5ae1aea5396103d3482ed6372574292fe30e Author: Josef "Jeff" Sipek Date: Sun Jan 21 18:44:35 2007 -0500 Unionfs: Directory manipulation helper functions This patch contains directory manipulation helper functions. Signed-off-by: Josef "Jeff" Sipek Signed-off-by: David Quigley Signed-off-by: Erez Zadok commit 935a0d31711a7ee099866d67593bf3188dd5a884 Author: Josef "Jeff" Sipek Date: Sun Jan 21 18:44:24 2007 -0500 Unionfs: Directory file operations This patch provides directory file operations. Signed-off-by: Josef "Jeff" Sipek Signed-off-by: David Quigley Signed-off-by: Erez Zadok commit 83897ebc81a4f7d809fa430993b1f8f18a23c96c Author: Josef "Jeff" Sipek Date: Sun Jan 21 18:44:05 2007 -0500 Unionfs: File operations This patch provides the file operations for Unionfs. Signed-off-by: Josef "Jeff" Sipek Signed-off-by: David Quigley Signed-off-by: Erez Zadok commit f74cfeba8231794d457f43b95d06d32283c4ce5f Author: Josef "Jeff" Sipek Date: Sun Jan 28 15:05:21 2007 -0500 Unionfs: Dentry operations This patch contains the dentry operations for Unionfs. Signed-off-by: Josef "Jeff" Sipek Signed-off-by: David Quigley Signed-off-by: Erez Zadok commit b611201bdfb5a2476eb2350662acd9d79bf031e0 Author: Josef "Jeff" Sipek Date: Sun Jan 21 18:42:32 2007 -0500 Unionfs: Copyup Functionality This patch contains the functions used to perform copyup operations in unionfs. Signed-off-by: Josef "Jeff" Sipek Signed-off-by: David Quigley Signed-off-by: Erez Zadok commit 8b857d34a9b628e691b6cd191330c261a3f70244 Author: Josef "Jeff" Sipek Date: Sun Jan 21 18:42:16 2007 -0500 Unionfs: Common file operations This patch contains helper functions used through the rest of the code which pertains to files. Signed-off-by: Josef "Jeff" Sipek Signed-off-by: David Quigley Signed-off-by: Erez Zadok commit 1403fd212d9c67d9928715c6a9fd383781658050 Author: Josef "Jeff" Sipek Date: Sun Jan 21 18:42:01 2007 -0500 Unionfs: Branch management functionality This patch contains the ioctls to increase the union generation and to query which branch a file exists on. Signed-off-by: Josef "Jeff" Sipek Signed-off-by: David Quigley Signed-off-by: Erez Zadok commit e3e19acd6944cd03e69349ba7e63715ca8d4b4ea Author: Josef "Jeff" Sipek Date: Sun Apr 29 15:36:03 2007 -0400 lookup_one_len_nd - lookup_one_len with nameidata argument This patch renames lookup_one_len to lookup_one_len_nd, and adds a nameidata argument. An inline function, lookup_one_len (which calls lookup_one_len_nd with nd == NULL) preserves original behavior. The following Unionfs patches depend on this one. Signed-off-by: Josef "Jeff" Sipek commit d99006b8f959d3826b7c9f9551fc549d1965aaa0 Author: Josef "Jeff" Sipek Date: Mon Feb 19 23:31:35 2007 -0500 Unionfs: Documentation This patch contains documentation for Unionfs. You will find several files outlining basic unification concepts and rename semantics. Signed-off-by: Josef "Jeff" Sipek Signed-off-by: David Quigley Signed-off-by: Erez Zadok Signed-off-by: Andrew Morton --- Documentation/filesystems/00-INDEX | 2 Documentation/filesystems/unionfs/00-INDEX | 10 Documentation/filesystems/unionfs/concepts.txt | 75 Documentation/filesystems/unionfs/issues.txt | 39 Documentation/filesystems/unionfs/rename.txt | 31 Documentation/filesystems/unionfs/usage.txt | 90 + MAINTAINERS | 9 fs/Kconfig | 47 fs/Makefile | 1 fs/drop_caches.c | 4 fs/ecryptfs/inode.c | 10 fs/namei.c | 4 fs/stack.c | 19 fs/unionfs/Makefile | 7 fs/unionfs/commonfops.c | 750 +++++++++ fs/unionfs/copyup.c | 807 ++++++++++ fs/unionfs/dentry.c | 353 ++++ fs/unionfs/dirfops.c | 276 +++ fs/unionfs/dirhelper.c | 273 +++ fs/unionfs/fanout.h | 308 ++++ fs/unionfs/file.c | 149 + fs/unionfs/inode.c | 1134 +++++++++++++++ fs/unionfs/lookup.c | 551 +++++++ fs/unionfs/main.c | 729 +++++++++ fs/unionfs/mmap.c | 316 ++++ fs/unionfs/rdstate.c | 282 +++ fs/unionfs/rename.c | 477 ++++++ fs/unionfs/sioq.c | 118 + fs/unionfs/sioq.h | 91 + fs/unionfs/subr.c | 238 +++ fs/unionfs/super.c | 1002 +++++++++++++ fs/unionfs/union.h | 466 ++++++ fs/unionfs/unlink.c | 176 ++ fs/unionfs/xattr.c | 161 ++ include/linux/fs_stack.h | 19 include/linux/magic.h | 2 include/linux/mm.h | 1 include/linux/namei.h | 13 include/linux/union_fs.h | 29 39 files changed, 9046 insertions(+), 23 deletions(-) diff -puN Documentation/filesystems/00-INDEX~git-unionfs Documentation/filesystems/00-INDEX --- a/Documentation/filesystems/00-INDEX~git-unionfs +++ a/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 -puN /dev/null Documentation/filesystems/unionfs/00-INDEX --- /dev/null +++ a/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 -puN /dev/null Documentation/filesystems/unionfs/concepts.txt --- /dev/null +++ a/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 -puN /dev/null Documentation/filesystems/unionfs/issues.txt --- /dev/null +++ a/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, because it could cause a cache incoherency between + the union layer and the lower file systems (for that reason, Unionfs + currently prohibits using branches which overlap with each other, even + partially). 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. + + 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 -puN /dev/null Documentation/filesystems/unionfs/rename.txt --- /dev/null +++ a/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 -puN /dev/null Documentation/filesystems/unionfs/usage.txt --- /dev/null +++ a/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 +increment the generation number of the superblock using the following +command: + +# mount -t unionfs -o remount,incgen none MOUNTPOINT + + +For more information, see . diff -puN MAINTAINERS~git-unionfs MAINTAINERS --- a/MAINTAINERS~git-unionfs +++ a/MAINTAINERS @@ -3711,6 +3711,15 @@ L: linux-kernel@vger.kernel.org W: http://www.kernel.dk S: Maintained +UNIONFS +P: Erez Zadok +M: ezk@cs.sunysb.edu +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 -puN fs/Kconfig~git-unionfs fs/Kconfig --- a/fs/Kconfig~git-unionfs +++ a/fs/Kconfig @@ -1030,6 +1030,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 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 @@ -1082,18 +1117,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 -puN fs/Makefile~git-unionfs fs/Makefile --- a/fs/Makefile~git-unionfs +++ a/fs/Makefile @@ -118,3 +118,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 -puN fs/drop_caches.c~git-unionfs fs/drop_caches.c --- a/fs/drop_caches.c~git-unionfs +++ a/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 sup } spin_unlock(&inode_lock); } +EXPORT_SYMBOL(drop_pagecache_sb); void drop_pagecache(void) { diff -puN fs/ecryptfs/inode.c~git-unionfs fs/ecryptfs/inode.c --- a/fs/ecryptfs/inode.c~git-unionfs +++ a/fs/ecryptfs/inode.c @@ -280,7 +280,9 @@ static struct dentry *ecryptfs_lookup(st 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; int encoded_namelen; struct ecryptfs_crypt_stat *crypt_stat = NULL; @@ -308,9 +310,13 @@ static struct dentry *ecryptfs_lookup(st } 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 -puN fs/namei.c~git-unionfs fs/namei.c --- a/fs/namei.c~git-unionfs +++ a/fs/namei.c @@ -1369,7 +1369,7 @@ struct dentry *lookup_one_len(const char err = permission(base->d_inode, MAY_EXEC, NULL); if (err) return ERR_PTR(err); - return __lookup_hash(&this, base, NULL); + return __lookup_hash(&this, base, nd); } /** @@ -2813,7 +2813,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 -puN fs/stack.c~git-unionfs fs/stack.c --- a/fs/stack.c~git-unionfs +++ a/fs/stack.c @@ -1,8 +1,20 @@ +/* + * Copyright (c) 2006-2007 Erez Zadok + * Copyright (c) 2006-2007 Josef 'Jeff' Sipek + * Copyright (c) 2006-2007 Stony Brook University + * Copyright (c) 2006-2007 The Research Foundation of SUNY + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + #include #include #include -/* does _NOT_ require i_mutex to be held. +/* + * does _NOT_ require i_mutex to be held. * * This function cannot be inlined since i_size_{read,write} is rather * heavy-weight on 32-bit systems @@ -14,11 +26,12 @@ void fsstack_copy_inode_size(struct inod } EXPORT_SYMBOL_GPL(fsstack_copy_inode_size); -/* copy all attributes; get_nlinks is optional way to override the i_nlink +/* + * copy all attributes; get_nlinks is optional way to override the i_nlink * copying */ void fsstack_copy_attr_all(struct inode *dest, const struct inode *src, - int (*get_nlinks)(struct inode *)) + int (*get_nlinks)(struct inode *)) { dest->i_mode = src->i_mode; dest->i_uid = src->i_uid; diff -puN /dev/null fs/unionfs/Makefile --- /dev/null +++ a/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 \ + rdstate.o copyup.o dirhelper.o rename.o unlink.o \ + lookup.o commonfops.o dirfops.o sioq.o mmap.o + +unionfs-$(CONFIG_UNION_FS_XATTR) += xattr.o diff -puN /dev/null fs/unionfs/commonfops.c --- /dev/null +++ a/fs/unionfs/commonfops.c @@ -0,0 +1,750 @@ +/* + * Copyright (c) 2003-2007 Erez Zadok + * Copyright (c) 2003-2006 Charles P. Wright + * Copyright (c) 2005-2007 Josef 'Jeff' Sipek + * Copyright (c) 2005-2006 Junjiro Okajima + * Copyright (c) 2005 Arun M. Krishnakumar + * Copyright (c) 2004-2006 David P. Quigley + * Copyright (c) 2003-2004 Mohammad Nayyer Zubair + * Copyright (c) 2003 Puja Gupta + * Copyright (c) 2003 Harikesavan Krishnan + * Copyright (c) 2003-2007 Stony Brook University + * Copyright (c) 2003-2007 The Research Foundation of SUNY + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include "union.h" + +/* + * 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 *lower_dentry; + struct dentry *lower_dir_dentry = NULL; + + lower_dentry = unionfs_lower_dentry_idx(dentry, bstart); + + sprintf(name, ".unionfs%*.*lx", + i_inosize, i_inosize, lower_dentry->d_inode->i_ino); + +retry: + /* + * 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, lower_dentry->d_parent, + nlen); + if (IS_ERR(tmp_dentry)) { + err = PTR_ERR(tmp_dentry); + goto out; + } + } 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 == -EEXIST) + goto retry; + else if (err) + goto out; + + /* bring it to the same state as an unlinked file */ + lower_dentry = unionfs_lower_dentry_idx(dentry, dbstart(dentry)); + lower_dir_dentry = lock_parent(lower_dentry); + err = vfs_unlink(lower_dir_dentry->d_inode, lower_dentry); + unlock_dir(lower_dir_dentry); + +out: + return err; +} + +/* + * 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)) { + /* + * Find new index of matching branch with an open + * file, since branches could have been added or + * deleted causing the one with open files to shift. + */ + int i; /* holds (possibly) updated branch index */ + int old_bid; + + old_bid = UNIONFS_F(file)->saved_branch_ids[bindex]; + i = branch_id_to_idx(sb, old_bid); + if (i < 0) + printk(KERN_ERR "unionfs: no superblock for " + "file %p\n", file); + else { + /* decrement count of open files */ + branchput(sb, i); + /* + * fput will perform an mntput for us on the + * correct branch. Although we're using the + * file's old branch configuration, bindex, + * which is the old index, correctly points + * to the right branch in the file's branch + * list. In other words, we're going to + * mntput the correct branch even if + * branches have been added/removed. + */ + 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 *lower_file; + struct dentry *lower_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++) { + lower_dentry = unionfs_lower_dentry_idx(dentry, bindex); + if (!lower_dentry) + continue; + + dget(lower_dentry); + unionfs_mntget(dentry, bindex); + branchget(sb, bindex); + + lower_file = + dentry_open(lower_dentry, + unionfs_lower_mnt_idx(dentry, bindex), + file->f_flags); + if (IS_ERR(lower_file)) { + err = PTR_ERR(lower_file); + goto out; + } else + unionfs_set_lower_file_idx(file, bindex, lower_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 *lower_file; + struct dentry *lower_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); + + lower_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(lower_dentry); + unionfs_mntget(dentry, bstart); + branchget(sb, bstart); + lower_file = dentry_open(lower_dentry, + unionfs_lower_mnt_idx(dentry, bstart), + file->f_flags); + if (IS_ERR(lower_file)) { + err = PTR_ERR(lower_file); + goto out; + } + unionfs_set_lower_file(file, lower_file); + /* Fix up the position. */ + lower_file->f_pos = file->f_pos; + + memcpy(&lower_file->f_ra, &file->f_ra, sizeof(struct file_ra_state)); +out: + return err; +} + +/* perform a delayed copyup of a read-write file on a read-only branch */ +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)) { + branchput(dentry->d_sb, bindex); + 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, + * but not unhashed dentries. + */ + if (!d_deleted(dentry) && + !__unionfs_d_revalidate_chain(dentry, NULL)) { + 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 "unionfs: 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 *lower_dentry; + struct file *lower_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++) { + lower_dentry = + unionfs_lower_dentry_idx(file->f_dentry, bindex); + if (!lower_dentry) + continue; + + dget(lower_dentry); + unionfs_mntget(file->f_dentry, bindex); + lower_file = dentry_open(lower_dentry, + unionfs_lower_mnt_idx(file->f_dentry, + bindex), + file->f_flags); + if (IS_ERR(lower_file)) + return PTR_ERR(lower_file); + + unionfs_set_lower_file_idx(file, bindex, lower_file); + + /* + * The branchget goes after the open, because otherwise + * we would miss the reference on release. + */ + branchget(inode->i_sb, bindex); + } + + return 0; +} + +/* unionfs_open helper function: open a file */ +static int __open_file(struct inode *inode, struct file *file) +{ + struct dentry *lower_dentry; + struct file *lower_file; + int lower_flags; + int bindex, bstart, bend; + + lower_dentry = unionfs_lower_dentry(file->f_dentry); + lower_flags = file->f_flags; + + bstart = fbstart(file) = dbstart(file->f_dentry); + bend = fbend(file) = dbend(file->f_dentry); + + /* + * check for the permission for lower file. If the error is + * COPYUP_ERR, copyup the file. + */ + if (lower_dentry->d_inode && is_robranch(file->f_dentry)) { + /* + * if the open will change the file, copy it up otherwise + * defer it. + */ + if (lower_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 + lower_flags &= ~(OPEN_WRITE_FLAGS); + } + + dget(lower_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); + lower_file = + dentry_open(lower_dentry, + unionfs_lower_mnt_idx(file->f_dentry, bstart), + lower_flags); + if (IS_ERR(lower_file)) + return PTR_ERR(lower_file); + + unionfs_set_lower_file(file, lower_file); + branchget(inode->i_sb, bstart); + + return 0; +} + +int unionfs_open(struct inode *inode, struct file *file) +{ + int err = 0; + struct file *lower_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 lower 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++) { + lower_file = unionfs_lower_file_idx(file, bindex); + if (!lower_file) + continue; + + branchput(file->f_dentry->d_sb, bindex); + /* fput calls dput for lower_dentry */ + fput(lower_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 + * + * No need to grab sb info's rwsem. + */ +int unionfs_file_release(struct inode *inode, struct file *file) +{ + struct file *lower_file = NULL; + struct unionfs_file_info *fileinfo; + struct unionfs_inode_info *inodeinfo; + struct super_block *sb = inode->i_sb; + int bindex, bstart, bend; + int fgen; + int err; + + unionfs_read_lock(sb); + /* + * Yes, we have to revalidate this file even if it's being released. + * This is important for open-but-unlinked files, as well as mmap + * support. + */ + if ((err = unionfs_file_revalidate(file, 1))) + goto out; + fileinfo = UNIONFS_F(file); + BUG_ON(file->f_dentry->d_inode != inode); + inodeinfo = UNIONFS_I(inode); + + /* fput all the lower files */ + fgen = atomic_read(&fileinfo->generation); + bstart = fbstart(file); + bend = fbend(file); + + for (bindex = bstart; bindex <= bend; bindex++) { + lower_file = unionfs_lower_file_idx(file, bindex); + + if (lower_file) { + fput(lower_file); + branchput(inode->i_sb, bindex); + } + } + kfree(fileinfo->lower_files); + kfree(fileinfo->saved_branch_ids); + + if (fileinfo->rdstate) { + fileinfo->rdstate->access = jiffies; + printk(KERN_DEBUG "unionfs: 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); +out: + unionfs_read_unlock(sb); + return err; +} + +/* pass the ioctl to the lower fs */ +static long do_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct file *lower_file; + int err; + + lower_file = unionfs_lower_file(file); + + err = security_file_ioctl(lower_file, cmd, arg); + if (err) + goto out; + + err = -ENOTTY; + if (!lower_file || !lower_file->f_op) + goto out; + if (lower_file->f_op->unlocked_ioctl) { + err = lower_file->f_op->unlocked_ioctl(lower_file, cmd, arg); + } else if (lower_file->f_op->ioctl) { + lock_kernel(); + err = lower_file->f_op->ioctl(lower_file->f_dentry->d_inode, + lower_file, cmd, arg); + unlock_kernel(); + } + +out: + return err; +} + +/* + * return to user-space 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 + */ +static 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, *lower_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++) { + lower_dentry = unionfs_lower_dentry_idx(dentry, bindex); + if (!lower_dentry) + continue; + if (lower_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; +} + +long unionfs_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + long err; + + unionfs_read_lock(file->f_path.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: + unionfs_read_unlock(file->f_path.dentry->d_sb); + return err; +} + +int unionfs_flush(struct file *file, fl_owner_t id) +{ + int err = 0; + struct file *lower_file = NULL; + struct dentry *dentry = file->f_path.dentry; + int bindex, bstart, bend; + + unionfs_read_lock(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++) { + lower_file = unionfs_lower_file_idx(file, bindex); + + if (lower_file && lower_file->f_op && + lower_file->f_op->flush) { + err = lower_file->f_op->flush(lower_file, id); + if (err) + goto out_lock; + + /* if there are no more refs 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(dentry->d_sb); + return err; +} diff -puN /dev/null fs/unionfs/copyup.c --- /dev/null +++ a/fs/unionfs/copyup.c @@ -0,0 +1,807 @@ +/* + * Copyright (c) 2003-2007 Erez Zadok + * Copyright (c) 2003-2006 Charles P. Wright + * Copyright (c) 2005-2007 Josef 'Jeff' Sipek + * Copyright (c) 2005-2006 Junjiro Okajima + * Copyright (c) 2005 Arun M. Krishnakumar + * Copyright (c) 2004-2006 David P. Quigley + * Copyright (c) 2003-2004 Mohammad Nayyer Zubair + * Copyright (c) 2003 Puja Gupta + * Copyright (c) 2003 Harikesavan Krishnan + * Copyright (c) 2003-2007 Stony Brook University + * Copyright (c) 2003-2007 The Research Foundation of SUNY + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include "union.h" + +/* + * For detailed explanation of copyup see: + * Documentation/filesystems/unionfs/concepts.txt + */ + +/* forward definitions */ +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); + +#ifdef CONFIG_UNION_FS_XATTR +/* copyup all extended attrs for a given dentry */ +static int copyup_xattrs(struct dentry *old_lower_dentry, + struct dentry *new_lower_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_lower_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_lower_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_lower_dentry->d_inode->i_mutex); + size = vfs_getxattr(old_lower_dentry, name_list, + attr_value, XATTR_SIZE_MAX); + mutex_unlock(&old_lower_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_lower_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_lower_dentry, + struct dentry *new_lower_dentry) +{ + struct inode *i = old_lower_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_lower_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_lower_dentry, + struct dentry *new_lower_dentry, + struct dentry *new_lower_parent_dentry, + char *symbuf) +{ + int err = 0; + umode_t old_mode = old_lower_dentry->d_inode->i_mode; + struct sioq_args args; + + if (S_ISDIR(old_mode)) { + args.mkdir.parent = new_lower_parent_dentry->d_inode; + args.mkdir.dentry = new_lower_dentry; + args.mkdir.mode = old_mode; + + run_sioq(__unionfs_mkdir, &args); + err = args.err; + } else if (S_ISLNK(old_mode)) { + args.symlink.parent = new_lower_parent_dentry->d_inode; + args.symlink.dentry = new_lower_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_lower_parent_dentry->d_inode; + args.mknod.dentry = new_lower_dentry; + args.mknod.mode = old_mode; + args.mknod.dev = old_lower_dentry->d_inode->i_rdev; + + run_sioq(__unionfs_mknod, &args); + err = args.err; + } else if (S_ISREG(old_mode)) { + args.create.parent = new_lower_parent_dentry->d_inode; + args.create.dentry = new_lower_dentry; + args.create.mode = old_mode; + args.create.nd = NULL; + + run_sioq(__unionfs_create, &args); + err = args.err; + } else { + printk(KERN_ERR "unionfs: unknown inode type %d\n", + old_mode); + BUG(); + } + + return err; +} + +static int __copyup_reg_data(struct dentry *dentry, + struct dentry *new_lower_dentry, int new_bindex, + struct dentry *old_lower_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); + branchget(sb, old_bindex); + input_file = dentry_open(old_lower_dentry, + unionfs_lower_mnt_idx(dentry, old_bindex), + O_RDONLY | O_LARGEFILE); + if (IS_ERR(input_file)) { + dput(old_lower_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_lower_dentry); + unionfs_mntget(dentry, new_bindex); + branchget(sb, new_bindex); + output_file = dentry_open(new_lower_dentry, + unionfs_lower_mnt_idx(dentry, new_bindex), + O_RDWR | 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) + err = output_file->f_op->fsync(output_file, + new_lower_dentry, 0); + + 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: + branchput(sb, new_bindex); + +out_close_in: + fput(input_file); + +out: + branchput(sb, old_bindex); + + 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_lower_dentry, + int old_bstart, int old_bend, + struct dentry *new_lower_dentry, int new_bindex) +{ + /* get rid of the lower 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_lower_dentry); + dput(old_lower_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_lower_dentry; + struct dentry *old_lower_dentry = NULL; + struct super_block *sb; + int err = 0; + int old_bindex; + int old_bstart; + int old_bend; + struct dentry *new_lower_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_lower_dentry = + create_parents_named(dir, dentry, name, new_bindex); + if (IS_ERR(new_lower_dentry)) { + err = PTR_ERR(new_lower_dentry); + goto out; + } + + old_lower_dentry = unionfs_lower_dentry_idx(dentry, old_bindex); + /* we conditionally dput this old_lower_dentry at end of function */ + dget(old_lower_dentry); + + /* For symlinks, we must read the link before we lock the directory. */ + if (S_ISLNK(old_lower_dentry->d_inode->i_mode)) { + + symbuf = kmalloc(PATH_MAX, GFP_KERNEL); + if (!symbuf) { + __clear(dentry, old_lower_dentry, + old_bstart, old_bend, + new_lower_dentry, new_bindex); + err = -ENOMEM; + goto out_free; + } + + oldfs = get_fs(); + set_fs(KERNEL_DS); + err = old_lower_dentry->d_inode->i_op->readlink( + old_lower_dentry, + (char __user *)symbuf, + PATH_MAX); + set_fs(oldfs); + if (err < 0) { + __clear(dentry, old_lower_dentry, + old_bstart, old_bend, + new_lower_dentry, new_bindex); + goto out_free; + } + symbuf[err] = '\0'; + } + + /* Now we lock the parent, and create the object in the new branch. */ + new_lower_parent_dentry = lock_parent(new_lower_dentry); + + /* create the new inode */ + err = __copyup_ndentry(old_lower_dentry, new_lower_dentry, + new_lower_parent_dentry, symbuf); + + if (err) { + __clear(dentry, old_lower_dentry, + old_bstart, old_bend, + new_lower_dentry, new_bindex); + goto out_unlock; + } + + /* We actually copyup the file here. */ + if (S_ISREG(old_lower_dentry->d_inode->i_mode)) + err = __copyup_reg_data(dentry, new_lower_dentry, new_bindex, + old_lower_dentry, old_bindex, + copyup_file, len); + if (err) + goto out_unlink; + + /* Set permissions. */ + if ((err = copyup_permissions(sb, old_lower_dentry, + new_lower_dentry))) + goto out_unlink; + +#ifdef CONFIG_UNION_FS_XATTR + /* Selinux uses extended attributes for permissions. */ + if ((err = copyup_xattrs(old_lower_dentry, new_lower_dentry))) + goto out_unlink; +#endif + + /* do not allow files getting deleted to be re-interposed */ + 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_lower_parent_dentry->d_inode, new_lower_dentry); + + if (copyup_file) { + /* need to close the file */ + + fput(*copyup_file); + branchput(sb, new_bindex); + } + + /* + * TODO: should we reset the error to something like -EIO? + * + * If we don't reset, the user may get some nonsensical 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_lower_parent_dentry); + +out_free: + /* + * If old_lower_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 above which operate on regular files. + */ + if (old_lower_dentry && old_lower_dentry->d_inode && + (S_ISDIR(old_lower_dentry->d_inode->i_mode) || + S_ISLNK(old_lower_dentry->d_inode->i_mode))) + dput(old_lower_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 up-to 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); +} + +/* purge a dentry's lower-branch states (dput/mntput, etc.) */ +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 up-to 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 *lower_parent_dentry = NULL; + struct dentry *lower_dentry = NULL; + const char *childname; + unsigned int childnamelen; + + int nr_dentry; + int count = 0; + + int old_bstart; + int old_bend; + struct dentry **path = NULL; + struct super_block *sb; + + verify_locked(dentry); + + if ((err = is_robranch_super(dir->i_sb, bindex))) { + lower_dentry = ERR_PTR(err); + goto out; + } + + old_bstart = dbstart(dentry); + old_bend = dbend(dentry); + + lower_dentry = ERR_PTR(-ENOMEM); + + /* There is no sense allocating any less than the minimum. */ + nr_dentry = 1; + path = kmalloc(nr_dentry * sizeof(struct dentry *), GFP_KERNEL); + if (!path) + goto out; + + /* assume the negative dentry of unionfs as the parent dentry */ + parent_dentry = dentry; + + /* + * 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 lower_parent_dentry in the given branch */ + lower_parent_dentry = + unionfs_lower_dentry_idx(parent_dentry, bindex); + + /* grow path table */ + if (count == nr_dentry) { + void *p; + + nr_dentry *= 2; + p = krealloc(path, nr_dentry * sizeof(struct dentry *), GFP_KERNEL); + if (!p) { + lower_dentry = ERR_PTR(-ENOMEM); + goto out; + } + path = p; + } + + /* store the child dentry */ + path[count++] = child_dentry; + } while (!lower_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 lower parent dir in the current branch */ + lower_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 */ + lower_dentry = + lookup_one_len(childname, lower_parent_dentry, + childnamelen); + if (IS_ERR(lower_dentry)) + goto out; + } else { + + /* + * is the name a whiteout of the child name ? + * lookup the whiteout child in the underlying file + * system + */ + lower_dentry = + lookup_one_len(name, lower_parent_dentry, + strlen(name)); + if (IS_ERR(lower_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, + lower_dentry); + + __cleanup_dentry(dentry, bindex, old_bstart, old_bend); + break; + } + + if (lower_dentry->d_inode) { + /* + * since this already exists we dput to avoid + * multiple references on the same dentry + */ + dput(lower_dentry); + } else { + struct sioq_args args; + + /* its a negative dentry, create a new dir */ + lower_parent_dentry = lock_parent(lower_dentry); + + args.mkdir.parent = lower_parent_dentry->d_inode; + args.mkdir.dentry = lower_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, + lower_dentry); + unlock_dir(lower_parent_dentry); + if (err) { + struct inode *inode = lower_dentry->d_inode; + /* + * If we get here, it means that we created a new + * dentry+inode, but copying permissions failed. + * Therefore, we should delete this inode and dput + * the dentry so as not to leave cruft behind. + * + * XXX: call dentry_iput() instead, but then we have + * to export that symbol. + */ + if (lower_dentry->d_op && lower_dentry->d_op->d_iput) + lower_dentry->d_op->d_iput(lower_dentry, + inode); + else + iput(inode); + lower_dentry->d_inode = NULL; + + dput(lower_dentry); + lower_dentry = ERR_PTR(err); + goto out; + } + + } + + __set_inode(child_dentry, lower_dentry, bindex); + __set_dentry(child_dentry, lower_dentry, bindex); + + parent_dentry = child_dentry; + child_dentry = path[--count]; + } +out: + /* cleanup any leftover locks from the do/while loop above */ + if (IS_ERR(lower_dentry)) + while (count) + unionfs_unlock_dentry(path[count--]); + kfree(path); + return lower_dentry; +} diff -puN /dev/null fs/unionfs/dentry.c --- /dev/null +++ a/fs/unionfs/dentry.c @@ -0,0 +1,353 @@ +/* + * Copyright (c) 2003-2007 Erez Zadok + * Copyright (c) 2003-2006 Charles P. Wright + * Copyright (c) 2005-2007 Josef 'Jeff' Sipek + * Copyright (c) 2005-2006 Junjiro Okajima + * Copyright (c) 2005 Arun M. Krishnakumar + * Copyright (c) 2004-2006 David P. Quigley + * Copyright (c) 2003-2004 Mohammad Nayyer Zubair + * Copyright (c) 2003 Puja Gupta + * Copyright (c) 2003 Harikesavan Krishnan + * Copyright (c) 2003-2007 Stony Brook University + * Copyright (c) 2003-2007 The Research Foundation of SUNY + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include "union.h" + +/* + * 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 *lower_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 "unionfs: 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 *lower_dentry; + for (bindex = bstart; bindex <= bend; bindex++) { + lower_dentry = + unionfs_lower_dentry_idx(dentry, + bindex); + dput(lower_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 *lower_inode; + for (bindex = bstart; bindex <= bend; + bindex++) { + lower_inode = + unionfs_lower_inode_idx( + dentry->d_inode, + bindex); + iput(lower_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++) { + lower_dentry = unionfs_lower_dentry_idx(dentry, bindex); + if (!lower_dentry || !lower_dentry->d_op + || !lower_dentry->d_op->d_revalidate) + continue; + if (!lower_dentry->d_op->d_revalidate(lower_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); + valid = __unionfs_d_revalidate_one(dentry, nd); + + /* + * If __unionfs_d_revalidate_one() succeeded above, then it will + * have incremented the refcnt of the mnt's, but also the branch + * indices of the dentry will have been updated (to take into + * account any branch insertions/deletion. So the current + * dbstart/dbend match the current, and new, indices of the mnts + * which __unionfs_d_revalidate_one has incremented. Note: the "if" + * test below does not depend on whether chain_len was 0 or greater. + */ + if (valid && sbgen != dgen) + for (bindex = dbstart(dentry); + bindex <= dbend(dentry); + bindex++) + unionfs_mntput(dentry, bindex); + +out_free: + /* unlock/dput all dentries in chain and return status */ + if (chain_len > 0) { + for (i=0; id_sb); + + unionfs_lock_dentry(dentry); + err = __unionfs_d_revalidate_chain(dentry, nd); + unionfs_unlock_dentry(dentry); + + unionfs_read_unlock(dentry->d_sb); + + return err; +} + +/* + * At this point no one can reference this dentry, so we don't have to be + * careful about concurrent access. + */ +static void unionfs_d_release(struct dentry *dentry) +{ + int bindex, bstart, bend; + + unionfs_read_lock(dentry->d_sb); + + /* this could be a negative dentry, so check first */ + if (!UNIONFS_D(dentry)) { + printk(KERN_DEBUG "unionfs: dentry without private data: %.*s", + dentry->d_name.len, dentry->d_name.name); + goto out; + } else if (dbstart(dentry) < 0) { + /* this is due to a failed lookup */ + printk(KERN_DEBUG "unionfs: dentry without lower " + "dentries: %.*s", + dentry->d_name.len, dentry->d_name.name); + goto out_free; + } + + /* Release all the lower 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(dentry); + +out: + unionfs_read_unlock(dentry->d_sb); + return; +} + +struct dentry_operations unionfs_dops = { + .d_revalidate = unionfs_d_revalidate, + .d_release = unionfs_d_release, +}; diff -puN /dev/null fs/unionfs/dirfops.c --- /dev/null +++ a/fs/unionfs/dirfops.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 SUNY + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include "union.h" + +/* 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 *lower_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_path.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) { + lower_file = unionfs_lower_file_idx(file, uds->bindex); + if (!lower_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(lower_file, uds->dirpos, SEEK_SET); + if (offset < 0) { + err = offset; + goto out; + } + err = vfs_readdir(lower_file, unionfs_filldir, &buf); + + /* Save the position for when we continue. */ + offset = vfs_llseek(lower_file, 0, SEEK_CUR); + if (offset < 0) { + err = offset; + goto out; + } + uds->dirpos = offset; + + /* Copy the atime. */ + fsstack_copy_attr_atime(inode, lower_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_path.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_path.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_path.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 -puN /dev/null fs/unionfs/dirhelper.c --- /dev/null +++ a/fs/unionfs/dirhelper.c @@ -0,0 +1,273 @@ +/* + * Copyright (c) 2003-2007 Erez Zadok + * Copyright (c) 2003-2006 Charles P. Wright + * Copyright (c) 2005-2007 Josef 'Jeff' Sipek + * Copyright (c) 2005-2006 Junjiro Okajima + * Copyright (c) 2005 Arun M. Krishnakumar + * Copyright (c) 2004-2006 David P. Quigley + * Copyright (c) 2003-2004 Mohammad Nayyer Zubair + * Copyright (c) 2003 Puja Gupta + * Copyright (c) 2003 Harikesavan Krishnan + * Copyright (c) 2003-2007 Stony Brook University + * Copyright (c) 2003-2007 The Research Foundation of SUNY + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include "union.h" + +/* + * Delete all of the whiteouts in a given directory for rmdir. + * + * lower directory inode should be locked + */ +int do_delete_whiteouts(struct dentry *dentry, int bindex, + struct unionfs_dir_state *namelist) +{ + int err = 0; + struct dentry *lower_dir_dentry = NULL; + struct dentry *lower_dentry; + char *name = NULL, *p; + struct inode *lower_dir; + + int i; + struct list_head *pos; + struct filldir_node *cursor; + + /* Find out lower parent dentry */ + lower_dir_dentry = unionfs_lower_dentry_idx(dentry, bindex); + BUG_ON(!S_ISDIR(lower_dir_dentry->d_inode->i_mode)); + lower_dir = lower_dir_dentry->d_inode; + BUG_ON(!S_ISDIR(lower_dir->i_mode)); + + err = -ENOMEM; + name = __getname(); + if (!name) + goto out; + strcpy(name, UNIONFS_WHPFX); + p = name + UNIONFS_WHLEN; + + err = 0; + for (i = 0; !err && i < namelist->size; i++) { + list_for_each(pos, &namelist->list[i]) { + cursor = + list_entry(pos, struct filldir_node, + file_list); + /* Only operate on whiteouts in this branch. */ + if (cursor->bindex != bindex) + continue; + if (!cursor->whiteout) + continue; + + strcpy(p, cursor->name); + lower_dentry = + lookup_one_len(name, lower_dir_dentry, + cursor->namelen + + UNIONFS_WHLEN); + if (IS_ERR(lower_dentry)) { + err = PTR_ERR(lower_dentry); + break; + } + if (lower_dentry->d_inode) + err = vfs_unlink(lower_dir, lower_dentry); + dput(lower_dentry); + if (err) + break; + } + } + + __putname(name); + + /* After all of the removals, we should copy the attributes once. */ + fsstack_copy_attr_times(dentry->d_inode, lower_dir_dentry->d_inode); + +out: + return err; +} + +/* delete whiteouts in a dir (for rmdir operation) using sioq if necessary */ +int delete_whiteouts(struct dentry *dentry, int bindex, + struct unionfs_dir_state *namelist) +{ + int err; + struct super_block *sb; + struct dentry *lower_dir_dentry; + struct inode *lower_dir; + + struct sioq_args args; + + sb = dentry->d_sb; + + BUG_ON(!S_ISDIR(dentry->d_inode->i_mode)); + BUG_ON(bindex < dbstart(dentry)); + BUG_ON(bindex > dbend(dentry)); + err = is_robranch_super(sb, bindex); + if (err) + goto out; + + lower_dir_dentry = unionfs_lower_dentry_idx(dentry, bindex); + BUG_ON(!S_ISDIR(lower_dir_dentry->d_inode->i_mode)); + lower_dir = lower_dir_dentry->d_inode; + BUG_ON(!S_ISDIR(lower_dir->i_mode)); + + mutex_lock(&lower_dir->i_mutex); + if (!permission(lower_dir, MAY_WRITE | MAY_EXEC, NULL)) + err = do_delete_whiteouts(dentry, bindex, namelist); + else { + args.deletewh.namelist = namelist; + args.deletewh.dentry = dentry; + args.deletewh.bindex = bindex; + run_sioq(__delete_whiteouts, &args); + err = args.err; + } + mutex_unlock(&lower_dir->i_mutex); + +out: + 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 *lower_dentry = NULL; + struct super_block *sb; + struct file *lower_file; + struct unionfs_rdutil_callback *buf = NULL; + int bindex, bstart, bend, bopaque; + + sb = dentry->d_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 lower directories with rdutil_callback as a filldir. */ + for (bindex = bstart; bindex <= bend; bindex++) { + lower_dentry = unionfs_lower_dentry_idx(dentry, bindex); + if (!lower_dentry) + continue; + if (!lower_dentry->d_inode) + continue; + if (!S_ISDIR(lower_dentry->d_inode->i_mode)) + continue; + + dget(lower_dentry); + unionfs_mntget(dentry, bindex); + branchget(sb, bindex); + lower_file = + dentry_open(lower_dentry, + unionfs_lower_mnt_idx(dentry, bindex), + O_RDONLY); + if (IS_ERR(lower_file)) { + err = PTR_ERR(lower_file); + dput(lower_dentry); + branchput(sb, bindex); + goto out; + } + + do { + buf->filldir_called = 0; + buf->rdstate->bindex = bindex; + err = vfs_readdir(lower_file, + readdir_util_callback, buf); + if (buf->err) + err = buf->err; + } while ((err >= 0) && buf->filldir_called); + + /* fput calls dput for lower_dentry */ + fput(lower_file); + branchput(sb, bindex); + + if (err < 0) + goto out; + } + +out: + if (buf) { + if (namelist && !err) + *namelist = buf->rdstate; + else if (buf->rdstate) + free_rdstate(buf->rdstate); + kfree(buf); + } + + + return err; +} diff -puN /dev/null fs/unionfs/fanout.h --- /dev/null +++ a/fs/unionfs/fanout.h @@ -0,0 +1,308 @@ +/* + * 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 SUNY + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _FANOUT_H_ +#define _FANOUT_H_ + +/* + * Inode to private data + * + * Since we use containers and the struct inode is _inside_ the + * unionfs_inode_info structure, UNIONFS_I will always (given a non-NULL + * inode pointer), return a valid non-NULL pointer. + */ +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) +{ + BUG_ON(!sb || index < 0); + return UNIONFS_SB(sb)->data[index].branch_id; +} + +static inline void set_branch_id(struct super_block *sb, int index, int val) +{ + BUG_ON(!sb || index < 0); + UNIONFS_SB(sb)->data[index].branch_id = val; +} + +static inline void new_branch_id(struct super_block *sb, int index) +{ + BUG_ON(!sb || index < 0); + set_branch_id(sb, index, ++UNIONFS_SB(sb)->high_branch_id); +} + +/* + * Find new index of matching branch with an existing superblock a a known + * (possibly old) id. This is needed because branches could have been + * added/deleted causing the branchs of any open files to shift. + * + * @sb: the new superblock which may have new/different branch IDs + * @id: the old/existing id we're looking for + * Returns index of newly found branch (0 or greater), -1 otherwise. + */ +static inline int branch_id_to_idx(struct super_block *sb, int id) +{ + int i; + for (i = 0; i < sbmax(sb); i++) { + if (branch_id(sb, i) == id) + return i; + } + /* + * XXX: maybe we should BUG_ON if not found new branch index? + * (really that should never happen). + */ + printk(KERN_WARNING "unionfs: cannot find branch with id %d\n", id); + return -1; +} + +/* File to lower file. */ +static inline struct file *unionfs_lower_file(const struct file *f) +{ + BUG_ON(!f); + return UNIONFS_F(f)->lower_files[fbstart(f)]; +} + +static inline struct file *unionfs_lower_file_idx(const struct file *f, + int index) +{ + BUG_ON(!f || index < 0); + return UNIONFS_F(f)->lower_files[index]; +} + +static inline void unionfs_set_lower_file_idx(struct file *f, int index, + struct file *val) +{ + BUG_ON(!f || index < 0); + 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) +{ + BUG_ON(!f); + unionfs_set_lower_file_idx((f), fbstart(f), (val)); +} + +/* Inode to lower inode. */ +static inline struct inode *unionfs_lower_inode(const struct inode *i) +{ + BUG_ON(!i); + return UNIONFS_I(i)->lower_inodes[ibstart(i)]; +} + +static inline struct inode *unionfs_lower_inode_idx(const struct inode *i, + int index) +{ + BUG_ON(!i || index < 0); + return UNIONFS_I(i)->lower_inodes[index]; +} + +static inline void unionfs_set_lower_inode_idx(struct inode *i, int index, + struct inode *val) +{ + BUG_ON(!i || index < 0); + UNIONFS_I(i)->lower_inodes[index] = val; +} + +static inline void unionfs_set_lower_inode(struct inode *i, struct inode *val) +{ + BUG_ON(!i); + 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) +{ + BUG_ON(!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) +{ + BUG_ON(!sb || index < 0); + 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) +{ + BUG_ON(!sb || index < 0); + UNIONFS_SB(sb)->data[index].sb = val; +} + +static inline void unionfs_set_lower_super(struct super_block *sb, + struct super_block *val) +{ + BUG_ON(!sb); + UNIONFS_SB(sb)->data[sbstart(sb)].sb = val; +} + +/* Branch count macros. */ +static inline int branch_count(const struct super_block *sb, int index) +{ + BUG_ON(!sb || index < 0); + return atomic_read(&UNIONFS_SB(sb)->data[index].open_files); +} + +static inline void set_branch_count(struct super_block *sb, int index, int val) +{ + BUG_ON(!sb || index < 0); + atomic_set(&UNIONFS_SB(sb)->data[index].open_files, val); +} + +static inline void branchget(struct super_block *sb, int index) +{ + BUG_ON(!sb || index < 0); + atomic_inc(&UNIONFS_SB(sb)->data[index].open_files); +} + +static inline void branchput(struct super_block *sb, int index) +{ + BUG_ON(!sb || index < 0); + atomic_dec(&UNIONFS_SB(sb)->data[index].open_files); +} + +/* Dentry macros */ +static inline struct unionfs_dentry_info *UNIONFS_D(const struct dentry *dent) +{ + BUG_ON(!dent); + return dent->d_fsdata; +} + +static inline int dbstart(const struct dentry *dent) +{ + BUG_ON(!dent); + return UNIONFS_D(dent)->bstart; +} + +static inline void set_dbstart(struct dentry *dent, int val) +{ + BUG_ON(!dent); + UNIONFS_D(dent)->bstart = val; +} + +static inline int dbend(const struct dentry *dent) +{ + BUG_ON(!dent); + return UNIONFS_D(dent)->bend; +} + +static inline void set_dbend(struct dentry *dent, int val) +{ + BUG_ON(!dent); + UNIONFS_D(dent)->bend = val; +} + +static inline int dbopaque(const struct dentry *dent) +{ + BUG_ON(!dent); + return UNIONFS_D(dent)->bopaque; +} + +static inline void set_dbopaque(struct dentry *dent, int val) +{ + BUG_ON(!dent); + UNIONFS_D(dent)->bopaque = val; +} + +static inline void unionfs_set_lower_dentry_idx(struct dentry *dent, int index, + struct dentry *val) +{ + BUG_ON(!dent || index < 0); + UNIONFS_D(dent)->lower_paths[index].dentry = val; +} + +static inline struct dentry *unionfs_lower_dentry_idx( + const struct dentry *dent, + int index) +{ + BUG_ON(!dent || index < 0); + return UNIONFS_D(dent)->lower_paths[index].dentry; +} + +static inline struct dentry *unionfs_lower_dentry(const struct dentry *dent) +{ + BUG_ON(!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) +{ + BUG_ON(!dent || index < 0); + UNIONFS_D(dent)->lower_paths[index].mnt = mnt; +} + +static inline struct vfsmount *unionfs_lower_mnt_idx( + const struct dentry *dent, + int index) +{ + BUG_ON(!dent || index < 0); + return UNIONFS_D(dent)->lower_paths[index].mnt; +} + +static inline struct vfsmount *unionfs_lower_mnt(const struct dentry *dent) +{ + BUG_ON(!dent); + return unionfs_lower_mnt_idx(dent, dbstart(dent)); +} + +/* Macros for locking a dentry. */ +static inline void unionfs_lock_dentry(struct dentry *d) +{ + BUG_ON(!d); + mutex_lock(&UNIONFS_D(d)->lock); +} + +static inline void unionfs_unlock_dentry(struct dentry *d) +{ + BUG_ON(!d); + mutex_unlock(&UNIONFS_D(d)->lock); +} + +static inline void verify_locked(struct dentry *d) +{ + BUG_ON(!d); + BUG_ON(!mutex_is_locked(&UNIONFS_D(d)->lock)); +} + +#endif /* _FANOUT_H */ diff -puN /dev/null fs/unionfs/file.c --- /dev/null +++ a/fs/unionfs/file.c @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2003-2007 Erez Zadok + * Copyright (c) 2003-2006 Charles P. Wright + * Copyright (c) 2005-2007 Josef 'Jeff' Sipek + * Copyright (c) 2005-2006 Junjiro Okajima + * Copyright (c) 2005 Arun M. Krishnakumar + * Copyright (c) 2004-2006 David P. Quigley + * Copyright (c) 2003-2004 Mohammad Nayyer Zubair + * Copyright (c) 2003 Puja Gupta + * Copyright (c) 2003 Harikesavan Krishnan + * Copyright (c) 2003-2007 Stony Brook University + * Copyright (c) 2003-2007 The Research Foundation of SUNY + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include "union.h" + +/******************* + * File Operations * + *******************/ + +static ssize_t unionfs_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + int err; + + unionfs_read_lock(file->f_path.dentry->d_sb); + + if ((err = unionfs_file_revalidate(file, 0))) + goto out; + + err = do_sync_read(file, buf, count, ppos); + + if (err >= 0) + touch_atime(unionfs_lower_mnt(file->f_path.dentry), + unionfs_lower_dentry(file->f_path.dentry)); + +out: + unionfs_read_unlock(file->f_path.dentry->d_sb); + return err; +} + +static ssize_t unionfs_aio_read(struct kiocb *iocb, const struct iovec *iov, + unsigned long nr_segs, loff_t pos) +{ + int err = 0; + struct file *file = iocb->ki_filp; + + unionfs_read_lock(file->f_path.dentry->d_sb); + + if ((err = unionfs_file_revalidate(file, 0))) + goto out; + + err = generic_file_aio_read(iocb, iov, nr_segs, pos); + + if (err == -EIOCBQUEUED) + err = wait_on_sync_kiocb(iocb); + + if (err >= 0) + touch_atime(unionfs_lower_mnt(file->f_path.dentry), + unionfs_lower_dentry(file->f_path.dentry)); + +out: + unionfs_read_unlock(file->f_path.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 = 0; + + unionfs_read_lock(file->f_path.dentry->d_sb); + + if ((err = unionfs_file_revalidate(file, 1))) + goto out; + + err = do_sync_write(file, buf, count, ppos); + +out: + unionfs_read_unlock(file->f_path.dentry->d_sb); + return err; +} + +static int unionfs_file_readdir(struct file *file, void *dirent, + filldir_t filldir) +{ + return -ENOTDIR; +} + +static int unionfs_mmap(struct file *file, struct vm_area_struct *vma) +{ + int err = 0; + int willwrite; + struct file *lower_file; + + unionfs_read_lock(file->f_path.dentry->d_sb); + + if ((err = unionfs_file_revalidate(file, 1))) + goto out; + + /* This might 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; + + /* + * File systems which do not implement ->writepage may use + * generic_file_readonly_mmap as their ->mmap op. If you call + * generic_file_readonly_mmap with VM_WRITE, you'd get an -EINVAL. + * But we cannot call the lower ->mmap op, so we can't tell that + * writeable mappings won't work. Therefore, our only choice is to + * check if the lower file system supports the ->writepage, and if + * not, return EINVAL (the same error that + * generic_file_readonly_mmap returns in that case). + */ + lower_file = unionfs_lower_file(file); + if (willwrite && !lower_file->f_mapping->a_ops->writepage) { + err = -EINVAL; + printk("unionfs: branch %d file system does not support " + "writeable mmap\n", fbstart(file)); + } else { + err = generic_file_mmap(file, vma); + if (err) + printk("unionfs: generic_file_mmap failed %d\n", err); + } + +out: + unionfs_read_unlock(file->f_path.dentry->d_sb); + return err; +} + +struct file_operations unionfs_main_fops = { + .llseek = generic_file_llseek, + .read = unionfs_read, + .aio_read = unionfs_aio_read, + .write = unionfs_write, + .aio_write = generic_file_aio_write, + .readdir = unionfs_file_readdir, + .unlocked_ioctl = unionfs_ioctl, + .mmap = unionfs_mmap, + .open = unionfs_open, + .flush = unionfs_flush, + .release = unionfs_file_release, + .fsync = file_fsync, + .splice_read = generic_file_splice_read, +}; diff -puN /dev/null fs/unionfs/inode.c --- /dev/null +++ a/fs/unionfs/inode.c @@ -0,0 +1,1134 @@ +/* + * Copyright (c) 2003-2007 Erez Zadok + * Copyright (c) 2003-2006 Charles P. Wright + * Copyright (c) 2005-2007 Josef 'Jeff' Sipek + * Copyright (c) 2005-2006 Junjiro Okajima + * Copyright (c) 2005 Arun M. Krishnakumar + * Copyright (c) 2004-2006 David P. Quigley + * Copyright (c) 2003-2004 Mohammad Nayyer Zubair + * Copyright (c) 2003 Puja Gupta + * Copyright (c) 2003 Harikesavan Krishnan + * Copyright (c) 2003-2007 Stony Brook University + * Copyright (c) 2003-2007 The Research Foundation of SUNY + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include "union.h" + +static int unionfs_create(struct inode *parent, struct dentry *dentry, + int mode, struct nameidata *nd) +{ + int err = 0; + struct dentry *lower_dentry = NULL; + struct dentry *wh_dentry = NULL; + struct dentry *new_lower_dentry; + struct dentry *lower_parent_dentry = NULL; + int bindex = 0, bstart; + char *name = NULL; + int valid = 0; + + 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 = -ESTALE; /* 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); + lower_dentry = unionfs_lower_dentry(dentry); + + /* + * check if whiteout exists in this branch, i.e. lookup .wh.foo + * first. + */ + name = alloc_whname(dentry->d_name.name, dentry->d_name.len); + if (IS_ERR(name)) { + err = PTR_ERR(name); + goto out; + } + + wh_dentry = lookup_one_len(name, lower_dentry->d_parent, + dentry->d_name.len + UNIONFS_WHLEN); + if (IS_ERR(wh_dentry)) { + err = PTR_ERR(wh_dentry); + wh_dentry = NULL; + goto out; + } + + if (wh_dentry->d_inode) { + /* + * .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 *lower_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_lower_dentry = unionfs_lower_dentry(dentry); + dget(new_lower_dentry); + + lower_dir_dentry = dget_parent(wh_dentry); + lock_rename(lower_dir_dentry, lower_dir_dentry); + + if (!(err = is_robranch_super(dentry->d_sb, bstart))) { + err = vfs_rename(lower_dir_dentry->d_inode, + wh_dentry, + lower_dir_dentry->d_inode, + new_lower_dentry); + } + if (!err) { + fsstack_copy_attr_times(parent, + new_lower_dentry->d_parent-> + d_inode); + fsstack_copy_inode_size(parent, + new_lower_dentry->d_parent-> + d_inode); + parent->i_nlink = unionfs_get_nlinks(parent); + } + + unlock_rename(lower_dir_dentry, lower_dir_dentry); + dput(lower_dir_dentry); + + dput(new_lower_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--) { + lower_dentry = unionfs_lower_dentry_idx(dentry, bindex); + if (!lower_dentry) { + /* + * if lower_dentry is NULL, create the entire + * dentry directory structure in branch 'bindex'. + * lower_dentry will NOT be null when bindex == bstart + * because lookup passed as a negative unionfs dentry + * pointing to a lone negative underlying dentry. + */ + lower_dentry = create_parents(parent, dentry, bindex); + if (!lower_dentry || IS_ERR(lower_dentry)) { + if (IS_ERR(lower_dentry)) + err = PTR_ERR(lower_dentry); + continue; + } + } + + lower_parent_dentry = lock_parent(lower_dentry); + if (IS_ERR(lower_parent_dentry)) { + err = PTR_ERR(lower_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(lower_parent_dentry->d_inode, + lower_dentry, mode, nd); + + if (err || !lower_dentry->d_inode) { + unlock_dir(lower_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, + lower_parent_dentry-> + d_inode); + fsstack_copy_inode_size(parent, + lower_parent_dentry-> + d_inode); + /* update no. of links on parent directory */ + parent->i_nlink = unionfs_get_nlinks(parent); + } + unlock_dir(lower_parent_dentry); + break; + } + } + +out: + dput(wh_dentry); + kfree(name); + + unionfs_unlock_dentry(dentry); + unionfs_read_unlock(dentry->d_sb); + return err; +} + +/* + * unionfs_lookup is the only special function which takes a dentry, yet we + * do NOT want to call __unionfs_d_revalidate_chain because by definition, + * we don't have a valid dentry here yet. + */ +static struct dentry *unionfs_lookup(struct inode *parent, + struct dentry *dentry, + struct nameidata *nd) +{ + struct path path_save; + struct dentry *ret; + + unionfs_read_lock(dentry->d_sb); + + /* 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; + } + + unionfs_read_unlock(dentry->d_sb); + + return ret; +} + +static int unionfs_link(struct dentry *old_dentry, struct inode *dir, + struct dentry *new_dentry) +{ + int err = 0; + struct dentry *lower_old_dentry = NULL; + struct dentry *lower_new_dentry = NULL; + struct dentry *lower_dir_dentry = NULL; + struct dentry *whiteout_dentry; + char *name = NULL; + + unionfs_read_lock(old_dentry->d_sb); + unionfs_double_lock_dentry(new_dentry, old_dentry); + + if (!__unionfs_d_revalidate_chain(old_dentry, NULL)) { + err = -ESTALE; + goto out; + } + if (new_dentry->d_inode && + !__unionfs_d_revalidate_chain(new_dentry, NULL)) { + err = -ESTALE; + goto out; + } + + lower_new_dentry = unionfs_lower_dentry(new_dentry); + + /* + * check if whiteout exists in the branch of new dentry, i.e. lookup + * .wh.foo first. If present, delete it + */ + name = alloc_whname(new_dentry->d_name.name, new_dentry->d_name.len); + if (IS_ERR(name)) { + err = PTR_ERR(name); + goto out; + } + + whiteout_dentry = lookup_one_len(name, lower_new_dentry->d_parent, + new_dentry->d_name.len + + UNIONFS_WHLEN); + if (IS_ERR(whiteout_dentry)) { + err = PTR_ERR(whiteout_dentry); + goto out; + } + + if (!whiteout_dentry->d_inode) { + dput(whiteout_dentry); + whiteout_dentry = NULL; + } else { + /* found a .wh.foo entry, unlink it and then call vfs_link() */ + lower_dir_dentry = lock_parent(whiteout_dentry); + err = is_robranch_super(new_dentry->d_sb, dbstart(new_dentry)); + if (!err) + err = vfs_unlink(lower_dir_dentry->d_inode, + whiteout_dentry); + + fsstack_copy_attr_times(dir, lower_dir_dentry->d_inode); + dir->i_nlink = unionfs_get_nlinks(dir); + unlock_dir(lower_dir_dentry); + lower_dir_dentry = NULL; + dput(whiteout_dentry); + if (err) + goto out; + } + + if (dbstart(old_dentry) != dbstart(new_dentry)) { + lower_new_dentry = + create_parents(dir, new_dentry, dbstart(old_dentry)); + err = PTR_ERR(lower_new_dentry); + if (IS_COPYUP_ERR(err)) + goto docopyup; + if (!lower_new_dentry || IS_ERR(lower_new_dentry)) + goto out; + } + lower_new_dentry = unionfs_lower_dentry(new_dentry); + lower_old_dentry = unionfs_lower_dentry(old_dentry); + + BUG_ON(dbstart(old_dentry) != dbstart(new_dentry)); + lower_dir_dentry = lock_parent(lower_new_dentry); + if (!(err = is_robranch(old_dentry))) + err = vfs_link(lower_old_dentry, lower_dir_dentry->d_inode, + lower_new_dentry); + unlock_dir(lower_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) { + lower_new_dentry = + create_parents(dir, new_dentry, + bindex); + lower_old_dentry = + unionfs_lower_dentry(old_dentry); + lower_dir_dentry = + lock_parent(lower_new_dentry); + /* do vfs_link */ + err = vfs_link(lower_old_dentry, + lower_dir_dentry->d_inode, + lower_new_dentry); + unlock_dir(lower_dir_dentry); + goto check_link; + } + } + goto out; + } + +check_link: + if (err || !lower_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, lower_new_dentry->d_parent->d_inode, + unionfs_get_nlinks); + fsstack_copy_inode_size(dir, lower_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); + + unionfs_read_unlock(old_dentry->d_sb); + + return err; +} + +static int unionfs_symlink(struct inode *dir, struct dentry *dentry, + const char *symname) +{ + int err = 0; + struct dentry *lower_dentry = NULL; + struct dentry *whiteout_dentry = NULL; + struct dentry *lower_dir_dentry = NULL; + umode_t mode; + int bindex = 0, bstart; + char *name = NULL; + + unionfs_read_lock(dentry->d_sb); + unionfs_lock_dentry(dentry); + + if (dentry->d_inode && + !__unionfs_d_revalidate_chain(dentry, NULL)) { + err = -ESTALE; + goto out; + } + + /* We start out in the leftmost branch. */ + bstart = dbstart(dentry); + + lower_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, lower_dentry->d_parent, + dentry->d_name.len + UNIONFS_WHLEN); + if (IS_ERR(whiteout_dentry)) { + err = PTR_ERR(whiteout_dentry); + goto out; + } + + if (!whiteout_dentry->d_inode) { + dput(whiteout_dentry); + whiteout_dentry = NULL; + } else { + /* + * found a .wh.foo entry, unlink it and then call + * vfs_symlink(). + */ + lower_dir_dentry = lock_parent(whiteout_dentry); + + if (!(err = is_robranch_super(dentry->d_sb, bstart))) + err = vfs_unlink(lower_dir_dentry->d_inode, + whiteout_dentry); + dput(whiteout_dentry); + + fsstack_copy_attr_times(dir, lower_dir_dentry->d_inode); + /* propagate number of hard-links */ + dir->i_nlink = unionfs_get_nlinks(dir); + + unlock_dir(lower_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--) { + lower_dentry = unionfs_lower_dentry_idx(dentry, bindex); + if (!lower_dentry) { + /* + * if lower_dentry is NULL, create the entire + * dentry directory structure in branch 'bindex'. + * lower_dentry will NOT be null when bindex == + * bstart because lookup passed as a negative + * unionfs dentry pointing to a lone negative + * underlying dentry + */ + lower_dentry = create_parents(dir, dentry, bindex); + if (!lower_dentry || IS_ERR(lower_dentry)) { + if (IS_ERR(lower_dentry)) + err = PTR_ERR(lower_dentry); + + printk(KERN_DEBUG "unionfs: lower dentry " + "NULL (or error) for bindex = %d\n", + bindex); + continue; + } + } + + lower_dir_dentry = lock_parent(lower_dentry); + + if (!(err = is_robranch_super(dentry->d_sb, bindex))) { + mode = S_IALLUGO; + err = + vfs_symlink(lower_dir_dentry->d_inode, + lower_dentry, symname, mode); + } + unlock_dir(lower_dir_dentry); + + if (err || !lower_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, + lower_dir_dentry-> + d_inode); + fsstack_copy_inode_size(dir, + lower_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); + unionfs_read_unlock(dentry->d_sb); + return err; +} + +static int unionfs_mkdir(struct inode *parent, struct dentry *dentry, int mode) +{ + int err = 0; + struct dentry *lower_dentry = NULL, *whiteout_dentry = NULL; + struct dentry *lower_parent_dentry = NULL; + int bindex = 0, bstart; + char *name = NULL; + int whiteout_unlinked = 0; + struct sioq_args args; + + unionfs_read_lock(dentry->d_sb); + unionfs_lock_dentry(dentry); + + if (dentry->d_inode && + !__unionfs_d_revalidate_chain(dentry, NULL)) { + err = -ESTALE; + goto out; + } + + bstart = dbstart(dentry); + + lower_dentry = unionfs_lower_dentry(dentry); + + /* + * check if whiteout exists in this branch, i.e. lookup .wh.foo + * first. + */ + name = alloc_whname(dentry->d_name.name, dentry->d_name.len); + if (IS_ERR(name)) { + err = PTR_ERR(name); + goto out; + } + + whiteout_dentry = lookup_one_len(name, lower_dentry->d_parent, + dentry->d_name.len + UNIONFS_WHLEN); + if (IS_ERR(whiteout_dentry)) { + err = PTR_ERR(whiteout_dentry); + goto out; + } + + if (!whiteout_dentry->d_inode) { + dput(whiteout_dentry); + whiteout_dentry = NULL; + } else { + lower_parent_dentry = lock_parent(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 = lower_parent_dentry->d_inode; + args.unlink.dentry = whiteout_dentry; + run_sioq(__unionfs_unlink, &args); + err = args.err; + } + dput(whiteout_dentry); + + unlock_dir(lower_parent_dentry); + + if (err) { + /* exit if the error returned was NOT -EROFS */ + if (!IS_COPYUP_ERR(err)) + goto out; + bstart--; + } else + whiteout_unlinked = 1; + } + + for (bindex = bstart; bindex >= 0; bindex--) { + int i; + int bend = dbend(dentry); + + if (is_robranch_super(dentry->d_sb, bindex)) + continue; + + lower_dentry = unionfs_lower_dentry_idx(dentry, bindex); + if (!lower_dentry) { + lower_dentry = create_parents(parent, dentry, bindex); + if (!lower_dentry || IS_ERR(lower_dentry)) { + printk(KERN_DEBUG "unionfs: lower dentry " + " NULL for bindex = %d\n", bindex); + continue; + } + } + + lower_parent_dentry = lock_parent(lower_dentry); + + if (IS_ERR(lower_parent_dentry)) { + err = PTR_ERR(lower_parent_dentry); + goto out; + } + + err = vfs_mkdir(lower_parent_dentry->d_inode, lower_dentry, + mode); + + unlock_dir(lower_parent_dentry); + + /* did the mkdir succeed? */ + 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, + lower_parent_dentry->d_inode); + fsstack_copy_inode_size(parent, + lower_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 "unionfs: 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); + unionfs_read_unlock(dentry->d_sb); + return err; +} + +static int unionfs_mknod(struct inode *dir, struct dentry *dentry, int mode, + dev_t dev) +{ + int err = 0; + struct dentry *lower_dentry = NULL, *whiteout_dentry = NULL; + struct dentry *lower_parent_dentry = NULL; + int bindex = 0, bstart; + char *name = NULL; + int whiteout_unlinked = 0; + + unionfs_read_lock(dentry->d_sb); + unionfs_lock_dentry(dentry); + + if (dentry->d_inode && + !__unionfs_d_revalidate_chain(dentry, NULL)) { + err = -ESTALE; + goto out; + } + + bstart = dbstart(dentry); + + lower_dentry = unionfs_lower_dentry(dentry); + + /* + * check if whiteout exists in this branch, i.e. lookup .wh.foo + * first. + */ + name = alloc_whname(dentry->d_name.name, dentry->d_name.len); + if (IS_ERR(name)) { + err = PTR_ERR(name); + goto out; + } + + whiteout_dentry = lookup_one_len(name, lower_dentry->d_parent, + dentry->d_name.len + UNIONFS_WHLEN); + if (IS_ERR(whiteout_dentry)) { + err = PTR_ERR(whiteout_dentry); + goto out; + } + + if (!whiteout_dentry->d_inode) { + dput(whiteout_dentry); + whiteout_dentry = NULL; + } else { + /* found .wh.foo, unlink it */ + lower_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(lower_parent_dentry->d_inode, + whiteout_dentry); + dput(whiteout_dentry); + + unlock_dir(lower_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; + + lower_dentry = unionfs_lower_dentry_idx(dentry, bindex); + if (!lower_dentry) { + lower_dentry = create_parents(dir, dentry, bindex); + if (IS_ERR(lower_dentry)) { + printk(KERN_DEBUG "unionfs: failed to create " + "parents on %d, err = %ld\n", + bindex, PTR_ERR(lower_dentry)); + continue; + } + } + + lower_parent_dentry = lock_parent(lower_dentry); + if (IS_ERR(lower_parent_dentry)) { + err = PTR_ERR(lower_parent_dentry); + goto out; + } + + err = vfs_mknod(lower_parent_dentry->d_inode, + lower_dentry, mode, dev); + + if (err) { + unlock_dir(lower_parent_dentry); + break; + } + + err = unionfs_interpose(dentry, dir->i_sb, 0); + if (!err) { + fsstack_copy_attr_times(dir, + lower_parent_dentry->d_inode); + fsstack_copy_inode_size(dir, + lower_parent_dentry->d_inode); + /* update number of links on parent directory */ + dir->i_nlink = unionfs_get_nlinks(dir); + } + unlock_dir(lower_parent_dentry); + + break; + } + +out: + if (!dentry->d_inode) + d_drop(dentry); + + kfree(name); + + unionfs_unlock_dentry(dentry); + unionfs_read_unlock(dentry->d_sb); + return err; +} + +static int unionfs_readlink(struct dentry *dentry, char __user *buf, + int bufsiz) +{ + int err; + struct dentry *lower_dentry; + + unionfs_read_lock(dentry->d_sb); + unionfs_lock_dentry(dentry); + + if (!__unionfs_d_revalidate_chain(dentry, NULL)) { + err = -ESTALE; + goto out; + } + + lower_dentry = unionfs_lower_dentry(dentry); + + if (!lower_dentry->d_inode->i_op || + !lower_dentry->d_inode->i_op->readlink) { + err = -EINVAL; + goto out; + } + + err = lower_dentry->d_inode->i_op->readlink(lower_dentry, + buf, bufsiz); + if (err > 0) + fsstack_copy_attr_atime(dentry->d_inode, + lower_dentry->d_inode); + +out: + unionfs_unlock_dentry(dentry); + unionfs_read_unlock(dentry->d_sb); + return err; +} + +/* + * unionfs_follow_link takes a dentry, but it is simple. It only needs to + * allocate some memory and then call our ->readlink method. Our + * unionfs_readlink *does* lock our dentry and revalidate the dentry. + * Therefore, we do not have to lock our dentry here, to prevent a deadlock; + * nor do we need to revalidate it either. It is safe to not lock our + * dentry here, nor revalidate it, because unionfs_follow_link does not do + * anything (prior to calling ->readlink) which could become inconsistent + * due to branch management. + */ +static void *unionfs_follow_link(struct dentry *dentry, struct nameidata *nd) +{ + char *buf; + int len = PAGE_SIZE, err; + mm_segment_t old_fs; + + unionfs_read_lock(dentry->d_sb); + + /* 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: + unionfs_read_unlock(dentry->d_sb); + return ERR_PTR(err); +} + +/* FIXME: We may not have to lock here */ +static void unionfs_put_link(struct dentry *dentry, struct nameidata *nd, + void *cookie) +{ + unionfs_read_lock(dentry->d_sb); + kfree(nd_get_link(nd)); + unionfs_read_unlock(dentry->d_sb); +} + +/* + * 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; + perms = branchperms(nd->mnt->mnt_sb, bindex); + 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 */ +} + +/* + * Don't grab the superblock read-lock in unionfs_permission, which prevents + * a deadlock with the branch-management "add branch" code (which grabbed + * the write lock). It is safe to not grab the read lock here, because even + * with branch management taking place, there is no chance that + * unionfs_permission, or anything it calls, will use stale branch + * information. + */ +static int unionfs_permission(struct inode *inode, int mask, + struct nameidata *nd) +{ + struct inode *lower_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); + + 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++) { + lower_inode = unionfs_lower_inode_idx(inode, bindex); + if (!lower_inode) + continue; + + /* + * check the condition for D-F-D underlying files/directories, + * we don't have to check for files, if we are checking for + * directories. + */ + if (!is_file && !S_ISDIR(lower_inode->i_mode)) + continue; + + /* + * We use our own special version of permission, such that + * only the first branch returns -EROFS. + */ + err = inode_permission(lower_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(lower_inode); + if (!err) + put_write_access(lower_inode); + } + break; + } + } + +out: + return err; +} + +static int unionfs_setattr(struct dentry *dentry, struct iattr *ia) +{ + int err = 0; + struct dentry *lower_dentry; + struct inode *inode = NULL; + struct inode *lower_inode = NULL; + int bstart, bend, bindex; + int i; + int copyup = 0; + + unionfs_read_lock(dentry->d_sb); + unionfs_lock_dentry(dentry); + + if (!__unionfs_d_revalidate_chain(dentry, NULL)) { + err = -ESTALE; + goto out; + } + + bstart = dbstart(dentry); + bend = dbend(dentry); + inode = dentry->d_inode; + + for (bindex = bstart; (bindex <= bend) || (bindex == bstart); + bindex++) { + lower_dentry = unionfs_lower_dentry_idx(dentry, bindex); + if (!lower_dentry) + continue; + BUG_ON(lower_dentry->d_inode == NULL); + + /* If the file is on a read only branch */ + if (is_robranch_super(dentry->d_sb, bindex) + || IS_RDONLY(lower_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; + lower_dentry = + unionfs_lower_dentry(dentry); + break; + } + /* + * if error is in the leftmost branch, pass + * it up. + */ + if (i == 0) + goto out; + } + + } + err = notify_change(lower_dentry, ia); + if (err) + goto out; + break; + } + + /* for mmap */ + if (ia->ia_valid & ATTR_SIZE) { + if (ia->ia_size != i_size_read(inode)) { + err = vmtruncate(inode, ia->ia_size); + if (err) + printk("unionfs_setattr: vmtruncate failed\n"); + } + } + + /* get the size from the first lower inode */ + lower_inode = unionfs_lower_inode(dentry->d_inode); + fsstack_copy_attr_all(inode, lower_inode, unionfs_get_nlinks); + fsstack_copy_inode_size(inode, lower_inode); + +out: + unionfs_unlock_dentry(dentry); + unionfs_read_unlock(dentry->d_sb); + 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 -puN /dev/null fs/unionfs/lookup.c --- /dev/null +++ a/fs/unionfs/lookup.c @@ -0,0 +1,551 @@ +/* + * Copyright (c) 2003-2007 Erez Zadok + * Copyright (c) 2003-2006 Charles P. Wright + * Copyright (c) 2005-2007 Josef 'Jeff' Sipek + * Copyright (c) 2005-2006 Junjiro Okajima + * Copyright (c) 2005 Arun M. Krishnakumar + * Copyright (c) 2004-2006 David P. Quigley + * Copyright (c) 2003-2004 Mohammad Nayyer Zubair + * Copyright (c) 2003 Puja Gupta + * Copyright (c) 2003 Harikesavan Krishnan + * Copyright (c) 2003-2007 Stony Brook University + * Copyright (c) 2003-2007 The Research Foundation of SUNY + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include "union.h" + +static int realloc_dentry_private_data(struct dentry *dentry); + +/* is the filename valid == !(whiteout for a file or opaque dir marker) */ +static int is_validname(const char *name) +{ + if (!strncmp(name, UNIONFS_WHPFX, UNIONFS_WHLEN)) + return 0; + if (!strncmp(name, UNIONFS_DIR_OPAQUE_NAME, + sizeof(UNIONFS_DIR_OPAQUE_NAME) - 1)) + return 0; + return 1; +} + +/* The rest of these are utility functions for lookup. */ +static noinline int is_opaque_dir(struct dentry *dentry, int bindex) +{ + int err = 0; + struct dentry *lower_dentry; + struct dentry *wh_lower_dentry; + struct inode *lower_inode; + struct sioq_args args; + + lower_dentry = unionfs_lower_dentry_idx(dentry, bindex); + lower_inode = lower_dentry->d_inode; + + BUG_ON(!S_ISDIR(lower_inode->i_mode)); + + mutex_lock(&lower_inode->i_mutex); + + if (!permission(lower_inode, MAY_EXEC, NULL)) + wh_lower_dentry = + lookup_one_len(UNIONFS_DIR_OPAQUE, lower_dentry, + sizeof(UNIONFS_DIR_OPAQUE) - 1); + else { + args.is_opaque.dentry = lower_dentry; + run_sioq(__is_opaque_dir, &args); + wh_lower_dentry = args.ret; + } + + mutex_unlock(&lower_inode->i_mutex); + + if (IS_ERR(wh_lower_dentry)) { + err = PTR_ERR(wh_lower_dentry); + goto out; + } + + /* This is an opaque dir iff wh_lower_dentry is positive */ + err = !!wh_lower_dentry->d_inode; + + dput(wh_lower_dentry); +out: + return err; +} + +/* main (and complex) driver function for Unionfs's lookup */ +struct dentry *unionfs_lookup_backend(struct dentry *dentry, + struct nameidata *nd, int lookupmode) +{ + int err = 0; + struct dentry *lower_dentry = NULL; + struct dentry *wh_lower_dentry = NULL; + struct dentry *lower_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_lower_dentry = NULL; + struct vfsmount *first_lower_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; + } + + switch(lookupmode) { + case INTERPOSE_PARTIAL: + break; + case INTERPOSE_LOOKUP: + if ((err = new_dentry_private_data(dentry))) + goto out; + allocated_new_info = 1; + break; + default: + if ((err = realloc_dentry_private_data(dentry))) + goto out; + allocated_new_info = 1; + break; + } + + /* 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++) { + lower_dentry = unionfs_lower_dentry_idx(dentry, bindex); + if (lookupmode == INTERPOSE_PARTIAL && lower_dentry) + continue; + BUG_ON(lower_dentry != NULL); + + lower_dir_dentry = + unionfs_lower_dentry_idx(parent_dentry, bindex); + + /* if the parent lower dentry does not exist skip this */ + if (!(lower_dir_dentry && lower_dir_dentry->d_inode)) + continue; + + /* also skip it if the parent isn't a directory. */ + if (!S_ISDIR(lower_dir_dentry->d_inode->i_mode)) + continue; + + /* Reuse the whiteout name because its value doesn't change. */ + if (!whname) { + whname = alloc_whname(name, namelen); + if (IS_ERR(whname)) { + err = PTR_ERR(whname); + goto out_free; + } + } + + /* check if whiteout exists in this branch: lookup .wh.foo */ + wh_lower_dentry = lookup_one_len(whname, lower_dir_dentry, + namelen + UNIONFS_WHLEN); + if (IS_ERR(wh_lower_dentry)) { + dput(first_lower_dentry); + unionfs_mntput(first_dentry, first_dentry_offset); + err = PTR_ERR(wh_lower_dentry); + goto out_free; + } + + if (wh_lower_dentry->d_inode) { + /* We found a whiteout so lets give up. */ + if (S_ISREG(wh_lower_dentry->d_inode->i_mode)) { + set_dbend(dentry, bindex); + set_dbopaque(dentry, bindex); + dput(wh_lower_dentry); + break; + } + err = -EIO; + printk(KERN_NOTICE "unionfs: EIO: invalid whiteout " + "entry type %d.\n", + wh_lower_dentry->d_inode->i_mode); + dput(wh_lower_dentry); + dput(first_lower_dentry); + unionfs_mntput(first_dentry, first_dentry_offset); + goto out_free; + } + + dput(wh_lower_dentry); + wh_lower_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); + + lower_dentry = lookup_one_len_nd(name, lower_dir_dentry, + namelen, nd); + if (IS_ERR(lower_dentry)) { + dput(first_lower_dentry); + unionfs_mntput(first_dentry, first_dentry_offset); + err = PTR_ERR(lower_dentry); + goto out_free; + } + + /* + * Store the first negative dentry specially, because if they + * are all negative we need this for future creates. + */ + if (!lower_dentry->d_inode) { + if (!first_lower_dentry && (dbstart(dentry) == -1)) { + first_lower_dentry = lower_dentry; + /* + * FIXME: following line needs to be changed + * to allow mount-point crossing + */ + first_dentry = parent_dentry; + first_lower_mnt = + unionfs_mntget(parent_dentry, bindex); + first_dentry_offset = bindex; + } else + dput(lower_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, lower_dentry); + /* + * FIXME: the following line needs to get fixed to allow + * mount-point 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, + lower_dir_dentry->d_inode); + + /* We terminate file lookups here. */ + if (!S_ISDIR(lower_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_lower_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_lower_dentry = + lookup_one_len_nd(name, lower_dir_dentry, + namelen, nd); + first_dentry_offset = bindex; + if (IS_ERR(first_lower_dentry)) { + err = PTR_ERR(first_lower_dentry); + goto out; + } + + /* + * FIXME: the following line needs to be changed to allow + * mount-point crossing + */ + first_dentry = dentry; + first_lower_mnt = unionfs_mntget(dentry, bindex); + } + unionfs_set_lower_dentry_idx(dentry, first_dentry_offset, + first_lower_dentry); + unionfs_set_lower_mnt_idx(dentry, first_dentry_offset, + first_lower_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_lower_dentry); + unionfs_mntput(first_dentry, first_dentry_offset); + + /* Partial lookups need to re-interpose, 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); + + 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 dentry *dentry) +{ + if (!dentry || !dentry->d_fsdata) + return; + kmem_cache_free(unionfs_dentry_cachep, dentry->d_fsdata); + dentry->d_fsdata = NULL; +} + +static inline int __realloc_dentry_private_data(struct dentry *dentry) +{ + struct unionfs_dentry_info *info = UNIONFS_D(dentry); + void *p; + int size; + + BUG_ON(!info); + + size = sizeof(struct path) * sbmax(dentry->d_sb); + p = krealloc(info->lower_paths, size, GFP_ATOMIC); + if (!p) + return -ENOMEM; + + info->lower_paths = p; + + 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)); + + memset(info->lower_paths, 0, size); + + return 0; +} + +/* UNIONFS_D(dentry)->lock must be locked */ +static int realloc_dentry_private_data(struct dentry *dentry) +{ + if (!__realloc_dentry_private_data(dentry)) + return 0; + + kfree(UNIONFS_D(dentry)->lower_paths); + free_dentry_private_data(dentry); + return -ENOMEM; +} + +/* allocate new dentry private data */ +int new_dentry_private_data(struct dentry *dentry) +{ + struct unionfs_dentry_info *info = UNIONFS_D(dentry); + + BUG_ON(info); + + info = kmem_cache_alloc(unionfs_dentry_cachep, GFP_ATOMIC); + if (!info) + return -ENOMEM; + + mutex_init(&info->lock); + mutex_lock(&info->lock); + + info->lower_paths = NULL; + + dentry->d_fsdata = info; + + if (!__realloc_dentry_private_data(dentry)) + return 0; + + mutex_unlock(&info->lock); + free_dentry_private_data(dentry); + 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 *lower_dentry; + + for (bindex = bstart; bindex <= bend; bindex++) { + lower_dentry = unionfs_lower_dentry_idx(dentry, bindex); + if (!lower_dentry) + continue; + if (lower_dentry->d_inode) { + set_dbstart(dentry, bindex); + break; + } + dput(lower_dentry); + unionfs_set_lower_dentry_idx(dentry, bindex, NULL); + } +} diff -puN /dev/null fs/unionfs/main.c --- /dev/null +++ a/fs/unionfs/main.c @@ -0,0 +1,729 @@ +/* + * Copyright (c) 2003-2007 Erez Zadok + * Copyright (c) 2003-2006 Charles P. Wright + * Copyright (c) 2005-2007 Josef 'Jeff' Sipek + * Copyright (c) 2005-2006 Junjiro Okajima + * Copyright (c) 2005 Arun M. Krishnakumar + * Copyright (c) 2004-2006 David P. Quigley + * Copyright (c) 2003-2004 Mohammad Nayyer Zubair + * Copyright (c) 2003 Puja Gupta + * Copyright (c) 2003 Harikesavan Krishnan + * Copyright (c) 2003-2007 Stony Brook University + * Copyright (c) 2003-2007 The Research Foundation of SUNY + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include "union.h" +#include +#include + +/* + * Connect a unionfs inode dentry/inode with several lower ones. This is + * the classic stackable file system "vnode interposition" action. + * + * @sb: unionfs's super_block + */ +int unionfs_interpose(struct dentry *dentry, struct super_block *sb, int flag) +{ + struct inode *lower_inode; + struct dentry *lower_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++) { + lower_dentry = unionfs_lower_dentry_idx(dentry, bindex); + if (!lower_dentry) { + unionfs_set_lower_inode_idx(inode, bindex, NULL); + continue; + } + + /* Initialize the lower inode to the new lower inode. */ + if (!lower_dentry->d_inode) + continue; + + unionfs_set_lower_inode_idx(inode, bindex, + igrab(lower_dentry->d_inode)); + } + + ibstart(inode) = dbstart(dentry); + ibend(inode) = dbend(dentry); + + /* Use attributes from the first branch. */ + lower_inode = unionfs_lower_inode(inode); + + /* Use different set of inode ops for symlinks & directories */ + if (S_ISLNK(lower_inode->i_mode)) + inode->i_op = &unionfs_symlink_iops; + else if (S_ISDIR(lower_inode->i_mode)) + inode->i_op = &unionfs_dir_iops; + + /* Use different set of file ops for directories */ + if (S_ISDIR(lower_inode->i_mode)) + inode->i_fop = &unionfs_dir_fops; + + /* properly initialize special inodes */ + if (S_ISBLK(lower_inode->i_mode) || S_ISCHR(lower_inode->i_mode) || + S_ISFIFO(lower_inode->i_mode) || S_ISSOCK(lower_inode->i_mode)) + init_special_inode(inode, lower_inode->i_mode, + lower_inode->i_rdev); + + /* all well, copy inode attributes */ + fsstack_copy_attr_all(inode, lower_inode, unionfs_get_nlinks); + fsstack_copy_inode_size(inode, lower_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 "unionfs: invalid interpose flag passed!"); + BUG(); + } + +out: + return err; +} + +/* like interpose above, but for an already existing dentry */ +void unionfs_reinterpose(struct dentry *dentry) +{ + struct dentry *lower_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++) { + lower_dentry = unionfs_lower_dentry_idx(dentry, bindex); + if (!lower_dentry) + continue; + + if (!lower_dentry->d_inode) + continue; + if (unionfs_lower_inode_idx(inode, bindex)) + continue; + unionfs_set_lower_inode_idx(inode, bindex, + igrab(lower_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 lower_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 + * + * We don't need to lock the superblock private data's rwsem, as we get + * called only by unionfs_read_super - it is still a long time before anyone + * can even get a reference to us. + */ +static int parse_dirs_option(struct super_block *sb, struct unionfs_dentry_info + *lower_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 lower dentry */ + UNIONFS_SB(sb)->data = + kcalloc(branches, sizeof(struct unionfs_data), GFP_KERNEL); + if (!UNIONFS_SB(sb)->data) { + err = -ENOMEM; + goto out; + } + + lower_root_info->lower_paths = + kcalloc(branches, sizeof(struct path), GFP_KERNEL); + if (!lower_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 " + "lower directory '%s' (error %d)\n", + name, err); + goto out; + } + + if ((err = check_branch(&nd))) { + printk(KERN_WARNING "unionfs: lower directory " + "'%s' is not a valid branch\n", name); + path_release(&nd); + goto out; + } + + lower_root_info->lower_paths[bindex].dentry = nd.dentry; + lower_root_info->lower_paths[bindex].mnt = nd.mnt; + + set_branchperms(sb, bindex, perms); + set_branch_count(sb, bindex, 0); + new_branch_id(sb, bindex); + + if (lower_root_info->bstart < 0) + lower_root_info->bstart = bindex; + lower_root_info->bend = bindex; + bindex++; + } + + if (branches == 0) { + printk(KERN_WARNING "unionfs: no branches specified\n"); + err = -EINVAL; + goto out; + } + + BUG_ON(branches != (lower_root_info->bend + 1)); + + /* + * Ensure that no overlaps exist in the branches. + * + * This test is required because the Linux kernel has no support + * currently for ensuring coherency between stackable layers and + * branches. If we were to allow overlapping branches, it would be + * possible, for example, to delete a file via one branch, which + * would not be reflected in another branch. Such incoherency could + * lead to inconsistencies and even kernel oopses. Rather than + * implement hacks to work around some of these cache-coherency + * problems, we prevent branch overlapping, for now. A complete + * solution will involve proper kernel/VFS support for cache + * coherency, at which time we could safely remove this + * branch-overlapping test. + */ + for (i = 0; i < branches; i++) { + for (j = i + 1; j < branches; j++) { + dent1 = lower_root_info->lower_paths[i].dentry; + dent2 = lower_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 (lower_root_info->lower_paths[i].dentry) { + dput(lower_root_info->lower_paths[i].dentry); + /* initialize: can't use unionfs_mntput here */ + mntput(lower_root_info->lower_paths[i].mnt); + } + + kfree(lower_root_info->lower_paths); + kfree(UNIONFS_SB(sb)->data); + + /* + * MUST clear the pointers to prevent potential double free if + * the caller dies later on + */ + lower_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 (lower) directory; + * We want to mount our stackable file system on top of that lower directory. + */ +static struct unionfs_dentry_info *unionfs_parse_options( + struct super_block *sb, + char *options) +{ + struct unionfs_dentry_info *lower_root_info; + char *optname; + int err = 0; + int bindex; + int dirsfound = 0; + + /* allocate private data area */ + err = -ENOMEM; + lower_root_info = + kzalloc(sizeof(struct unionfs_dentry_info), GFP_KERNEL); + if (!lower_root_info) + goto out_error; + lower_root_info->bstart = -1; + lower_root_info->bend = -1; + lower_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, lower_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 (lower_root_info && lower_root_info->lower_paths) { + for (bindex = lower_root_info->bstart; + bindex >= 0 && bindex <= lower_root_info->bend; + bindex++) { + struct dentry *d; + struct vfsmount *m; + + d = lower_root_info->lower_paths[bindex].dentry; + m = lower_root_info->lower_paths[bindex].mnt; + + dput(d); + /* initializing: can't use unionfs_mntput here */ + mntput(m); + } + } + + kfree(lower_root_info->lower_paths); + kfree(lower_root_info); + + kfree(UNIONFS_SB(sb)->data); + UNIONFS_SB(sb)->data = NULL; + + lower_root_info = ERR_PTR(err); +out: + return lower_root_info; +} + +/* + * our custom d_alloc_root work-alike + * + * 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; +} + +/* + * There is no need to lock the unionfs_super_info's rwsem as there is no + * way anyone can have a reference to the superblock at this point in time. + */ +static int unionfs_read_super(struct super_block *sb, void *raw_data, + int silent) +{ + int err = 0; + + struct unionfs_dentry_info *lower_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 "unionfs: read_super: out of memory\n"); + 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 */ + + lower_root_info = unionfs_parse_options(sb, raw_data); + if (IS_ERR(lower_root_info)) { + printk(KERN_WARNING + "unionfs: read_super: error while parsing options " + "(err = %ld)\n", PTR_ERR(lower_root_info)); + err = PTR_ERR(lower_root_info); + lower_root_info = NULL; + goto out_free; + } + if (lower_root_info->bstart == -1) { + err = -ENOENT; + goto out_free; + } + + /* set the lower superblock field of upper superblock */ + bstart = lower_root_info->bstart; + BUG_ON(bstart != 0); + sbend(sb) = bend = lower_root_info->bend; + for (bindex = bstart; bindex <= bend; bindex++) { + struct dentry *d = lower_root_info->lower_paths[bindex].dentry; + unionfs_set_lower_super_idx(sb, bindex, d->d_sb); + } + + /* max Bytes is the maximum bytes from highest priority branch */ + sb->s_maxbytes = unionfs_lower_super_idx(sb, 0)->s_maxbytes; + + 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 lower dentries for s_root */ + for (bindex = bstart; bindex <= bend; bindex++) { + struct dentry *d; + struct vfsmount *m; + + d = lower_root_info->lower_paths[bindex].dentry; + m = lower_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(sb->s_root); + } + dput(sb->s_root); + +out_dput: + if (lower_root_info && !IS_ERR(lower_root_info)) { + for (bindex = lower_root_info->bstart; + bindex <= lower_root_info->bend; bindex++) { + struct dentry *d; + struct vfsmount *m; + + d = lower_root_info->lower_paths[bindex].dentry; + m = lower_root_info->lower_paths[bindex].mnt; + + dput(d); + /* initializing: can't use unionfs_mntput here */ + mntput(m); + } + kfree(lower_root_info->lower_paths); + kfree(lower_root_info); + lower_root_info = NULL; + } + +out_free: + kfree(UNIONFS_SB(sb)->data); + kfree(UNIONFS_SB(sb)); + sb->s_fs_info = NULL; + +out: + if (lower_root_info && !IS_ERR(lower_root_info)) { + kfree(lower_root_info->lower_paths); + kfree(lower_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 -puN /dev/null fs/unionfs/mmap.c --- /dev/null +++ a/fs/unionfs/mmap.c @@ -0,0 +1,316 @@ +/* + * 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) 2006 Shaya Potter + * 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" + +/* + * Unionfs doesn't implement ->writepages, which is OK with the VFS and + * nkeeps our code simpler and smaller. Nevertheless, somehow, our own + * ->writepage must be called so we can sync the upper pages with the lower + * pages: otherwise data changed at the upper layer won't get written to the + * lower layer. + * + * Some lower file systems (e.g., NFS) expect the VFS to call its writepages + * only, which in turn will call generic_writepages and invoke each of the + * lower file system's ->writepage. NFS in particular uses the + * wbc->fs_private field in its nfs_writepage, which is set in its + * nfs_writepages. So if we don't call the lower nfs_writepages first, then + * NFS's nfs_writepage will dereference a NULL wbc->fs_private and cause an + * OOPS. If, however, we implement a unionfs_writepages and then we do call + * the lower nfs_writepages, then we "lose control" over the pages we're + * trying to write to the lower file system: we won't be writing our own + * new/modified data from the upper pages to the lower pages, and any + * mmap-based changes are lost. + * + * This is a fundamental cache-coherency problem in Linux. The kernel isn't + * able to support such stacking abstractions cleanly. One possible clean + * way would be that a lower file system's ->writepage method have some sort + * of a callback to validate if any upper pages for the same file+offset + * exist and have newer content in them. + * + * This whole NULL ptr dereference is triggered at the lower file system + * (NFS) because the wbc->for_writepages is set to 1. Therefore, to avoid + * this NULL pointer dereference, we set this flag to 0 and restore it upon + * exit. This probably means that we're slightly less efficient in writing + * pages out, doing them one at a time, but at least we avoid the oops until + * such day as Linux can better support address_space_ops in a stackable + * fashion. + */ +static int unionfs_writepage(struct page *page, struct writeback_control *wbc) +{ + int err = -EIO; + struct inode *inode; + struct inode *lower_inode; + struct page *lower_page; + char *kaddr, *lower_kaddr; + int saved_for_writepages = wbc->for_writepages; + + inode = page->mapping->host; + lower_inode = unionfs_lower_inode(inode); + + /* find lower page (returns a locked page) */ + lower_page = grab_cache_page(lower_inode->i_mapping, page->index); + if (!lower_page) + goto out; + + /* get page address, and encode it */ + kaddr = kmap(page); + lower_kaddr = kmap(lower_page); + + memcpy(lower_kaddr, kaddr, PAGE_CACHE_SIZE); + + kunmap(page); + kunmap(lower_page); + + BUG_ON(!lower_inode->i_mapping->a_ops->writepage); + + /* workaround for some lower file systems: see big comment on top */ + if (wbc->for_writepages && !wbc->fs_private) + wbc->for_writepages = 0; + + /* call lower writepage (expects locked page) */ + err = lower_inode->i_mapping->a_ops->writepage(lower_page, wbc); + wbc->for_writepages = saved_for_writepages; /* restore value */ + + /* + * update mtime and ctime of lower level file system + * unionfs' mtime and ctime are updated by generic_file_write + */ + lower_inode->i_mtime = lower_inode->i_ctime = CURRENT_TIME; + + page_cache_release(lower_page); /* b/c grab_cache_page increased refcnt */ + + if (err) + ClearPageUptodate(page); + else + SetPageUptodate(page); + +out: + unlock_page(page); + return err; +} + +/* + * readpage is called from generic_page_read and the fault handler. + * If your file system uses generic_page_read for the read op, it + * must implement readpage. + * + * Readpage expects a locked page, and must unlock it. + */ +static int unionfs_do_readpage(struct file *file, struct page *page) +{ + int err = -EIO; + struct file *lower_file; + struct inode *inode; + mm_segment_t old_fs; + char *page_data = NULL; + loff_t offset; + + if (!UNIONFS_F(file)) { + err = -ENOENT; + goto out; + } + + lower_file = unionfs_lower_file(file); + /* FIXME: is this assertion right here? */ + BUG_ON(lower_file == NULL); + + inode = file->f_path.dentry->d_inode; + + page_data = (char *) kmap(page); + /* + * Use vfs_read because some lower file systems don't have a + * readpage method, and some file systems (esp. distributed ones) + * don't like their pages to be accessed directly. Using vfs_read + * may be a little slower, but a lot safer, as the VFS does a lot of + * the necessary magic for us. + */ + offset = lower_file->f_pos = (page->index << PAGE_CACHE_SHIFT); + old_fs = get_fs(); + set_fs(KERNEL_DS); + err = vfs_read(lower_file, page_data, PAGE_CACHE_SIZE, + &lower_file->f_pos); + set_fs(old_fs); + + kunmap(page); + + if (err < 0) + goto out; + err = 0; + + /* if vfs_read succeeded above, sync up our times */ + fsstack_copy_attr_times(inode, lower_file->f_path.dentry->d_inode); + +out: + if (err == 0) + SetPageUptodate(page); + else + ClearPageUptodate(page); + + return err; +} + +static int unionfs_readpage(struct file *file, struct page *page) +{ + int err; + + unionfs_read_lock(file->f_dentry->d_sb); + + if ((err = unionfs_file_revalidate(file, 0))) + goto out; + + err = unionfs_do_readpage(file, page); + + if (!err) + touch_atime(unionfs_lower_mnt(file->f_path.dentry), + unionfs_lower_dentry(file->f_path.dentry)); + + /* + * we have to unlock our page, b/c we _might_ have gotten a locked + * page. but we no longer have to wakeup on our page here, b/c + * UnlockPage does it + */ +out: + unlock_page(page); + unionfs_read_unlock(file->f_dentry->d_sb); + + return err; +} + +static int unionfs_prepare_write(struct file *file, struct page *page, + unsigned from, unsigned to) +{ + int err; + + unionfs_read_lock(file->f_dentry->d_sb); + + err = unionfs_file_revalidate(file, 1); + + unionfs_read_unlock(file->f_dentry->d_sb); + + return err; +} + +static int unionfs_commit_write(struct file *file, struct page *page, + unsigned from, unsigned to) +{ + int err = -ENOMEM; + struct inode *inode, *lower_inode; + struct file *lower_file = NULL; + loff_t pos; + unsigned bytes = to - from; + char *page_data = NULL; + mm_segment_t old_fs; + + BUG_ON(file == NULL); + + unionfs_read_lock(file->f_dentry->d_sb); + + if ((err = unionfs_file_revalidate(file, 1))) + goto out; + + inode = page->mapping->host; + lower_inode = unionfs_lower_inode(inode); + + if (UNIONFS_F(file) != NULL) + lower_file = unionfs_lower_file(file); + + /* FIXME: is this assertion right here? */ + BUG_ON(lower_file == NULL); + + page_data = (char *)kmap(page); + lower_file->f_pos = (page->index << PAGE_CACHE_SHIFT) + from; + + /* SP: I use vfs_write instead of copying page data and the + * prepare_write/commit_write combo because file system's like + * GFS/OCFS2 don't like things touching those directly, + * calling the underlying write op, while a little bit slower, will + * call all the FS specific code as well + */ + old_fs = get_fs(); + set_fs(KERNEL_DS); + err = vfs_write(lower_file, page_data + from, bytes, + &lower_file->f_pos); + set_fs(old_fs); + + kunmap(page); + + if (err < 0) + goto out; + + inode->i_blocks = lower_inode->i_blocks; + /* we may have to update i_size */ + pos = ((loff_t) page->index << PAGE_CACHE_SHIFT) + to; + if (pos > i_size_read(inode)) + i_size_write(inode, pos); + + /* + * update mtime and ctime of lower level file system + * unionfs' mtime and ctime are updated by generic_file_write + */ + lower_inode->i_mtime = lower_inode->i_ctime = CURRENT_TIME; + + mark_inode_dirty_sync(inode); + +out: + if (err < 0) + ClearPageUptodate(page); + + unionfs_read_unlock(file->f_dentry->d_sb); + return err; /* assume all is ok */ +} + +static void unionfs_sync_page(struct page *page) +{ + struct inode *inode; + struct inode *lower_inode; + struct page *lower_page; + struct address_space *mapping; + + inode = page->mapping->host; + lower_inode = unionfs_lower_inode(inode); + + /* find lower page (returns a locked page) */ + lower_page = grab_cache_page(lower_inode->i_mapping, page->index); + if (!lower_page) + goto out; + + /* do the actual sync */ + mapping = lower_page->mapping; + /* + * XXX: can we optimize ala RAIF and set the lower page to be + * discarded after a successful sync_page? + */ + if (mapping && mapping->a_ops && mapping->a_ops->sync_page) + mapping->a_ops->sync_page(lower_page); + + unlock_page(lower_page); /* b/c grab_cache_page locked it */ + page_cache_release(lower_page); /* b/c grab_cache_page increased refcnt */ + +out: + return; +} + +struct address_space_operations unionfs_aops = { + .writepage = unionfs_writepage, + .readpage = unionfs_readpage, + .prepare_write = unionfs_prepare_write, + .commit_write = unionfs_commit_write, + .sync_page = unionfs_sync_page, +}; diff -puN /dev/null fs/unionfs/rdstate.c --- /dev/null +++ a/fs/unionfs/rdstate.c @@ -0,0 +1,282 @@ +/* + * Copyright (c) 2003-2007 Erez Zadok + * Copyright (c) 2003-2006 Charles P. Wright + * Copyright (c) 2005-2007 Josef 'Jeff' Sipek + * Copyright (c) 2005-2006 Junjiro Okajima + * Copyright (c) 2005 Arun M. Krishnakumar + * Copyright (c) 2004-2006 David P. Quigley + * Copyright (c) 2003-2004 Mohammad Nayyer Zubair + * Copyright (c) 2003 Puja Gupta + * Copyright (c) 2003 Harikesavan Krishnan + * Copyright (c) 2003-2007 Stony Brook University + * Copyright (c) 2003-2007 The Research Foundation of SUNY + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include "union.h" + +/* 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); + + 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 *lower_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 (!(lower_inode = unionfs_lower_inode_idx(inode, bindex))) + continue; + + if (lower_inode->i_size == DENTPAGE) + hashsize += DENTPERONEPAGE; + else + hashsize += (lower_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 "unionfs: filldir: possible " + "I/O error: 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 -puN /dev/null fs/unionfs/rename.c --- /dev/null +++ a/fs/unionfs/rename.c @@ -0,0 +1,477 @@ +/* + * Copyright (c) 2003-2007 Erez Zadok + * Copyright (c) 2003-2006 Charles P. Wright + * Copyright (c) 2005-2007 Josef 'Jeff' Sipek + * Copyright (c) 2005-2006 Junjiro Okajima + * Copyright (c) 2005 Arun M. Krishnakumar + * Copyright (c) 2004-2006 David P. Quigley + * Copyright (c) 2003-2004 Mohammad Nayyer Zubair + * Copyright (c) 2003 Puja Gupta + * Copyright (c) 2003 Harikesavan Krishnan + * Copyright (c) 2003-2007 Stony Brook University + * Copyright (c) 2003-2007 The Research Foundation of SUNY + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include "union.h" + +static int __unionfs_rename(struct inode *old_dir, struct dentry *old_dentry, + struct inode *new_dir, struct dentry *new_dentry, + int bindex, struct dentry **wh_old) +{ + int err = 0; + struct dentry *lower_old_dentry; + struct dentry *lower_new_dentry; + struct dentry *lower_old_dir_dentry; + struct dentry *lower_new_dir_dentry; + struct dentry *lower_wh_dentry; + struct dentry *lower_wh_dir_dentry; + char *wh_name = NULL; + + lower_new_dentry = unionfs_lower_dentry_idx(new_dentry, bindex); + lower_old_dentry = unionfs_lower_dentry_idx(old_dentry, bindex); + + if (!lower_new_dentry) { + lower_new_dentry = + create_parents(new_dentry->d_parent->d_inode, + new_dentry, bindex); + if (IS_ERR(lower_new_dentry)) { + printk(KERN_DEBUG "unionfs: error creating directory " + "tree for rename, bindex = %d, err = %ld\n", + bindex, PTR_ERR(lower_new_dentry)); + err = PTR_ERR(lower_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; + } + + lower_wh_dentry = lookup_one_len(wh_name, lower_new_dentry->d_parent, + new_dentry->d_name.len + + UNIONFS_WHLEN); + if (IS_ERR(lower_wh_dentry)) { + err = PTR_ERR(lower_wh_dentry); + goto out; + } + + if (lower_wh_dentry->d_inode) { + /* get rid of the whiteout that is existing */ + if (lower_new_dentry->d_inode) { + printk(KERN_WARNING "unionfs: both a whiteout and a " + "dentry exist when doing a rename!\n"); + err = -EIO; + + dput(lower_wh_dentry); + goto out; + } + + lower_wh_dir_dentry = lock_parent(lower_wh_dentry); + if (!(err = is_robranch_super(old_dentry->d_sb, bindex))) + err = vfs_unlink(lower_wh_dir_dentry->d_inode, + lower_wh_dentry); + + dput(lower_wh_dentry); + unlock_dir(lower_wh_dir_dentry); + if (err) + goto out; + } else + dput(lower_wh_dentry); + + dget(lower_old_dentry); + lower_old_dir_dentry = dget_parent(lower_old_dentry); + lower_new_dir_dentry = dget_parent(lower_new_dentry); + + lock_rename(lower_old_dir_dentry, lower_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, lower_old_dir_dentry, + old_dentry->d_name.len + + UNIONFS_WHLEN); + kfree(whname); + err = PTR_ERR(*wh_old); + if (IS_ERR(*wh_old)) { + *wh_old = NULL; + goto out_unlock; + } + } + + err = vfs_rename(lower_old_dir_dentry->d_inode, lower_old_dentry, + lower_new_dir_dentry->d_inode, lower_new_dentry); + +out_unlock: + unlock_rename(lower_old_dir_dentry, lower_new_dir_dentry); + + dput(lower_old_dir_dentry); + dput(lower_new_dir_dentry); + dput(lower_old_dentry); + +out: + if (!err) { + /* Fixup the new_dentry. */ + 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; +} + +/* + * Main rename code. This is sufficienly complex, that it's documented in + * Docmentation/filesystems/unionfs/rename.txt. This routine calls + * __unionfs_rename() above to perform some of the work. + */ +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 = __unionfs_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 = __unionfs_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 *lower_parent; + BUG_ON(!wh_old || wh_old->d_inode || bwh_old < 0); + lower_parent = lock_parent(wh_old); + local_err = vfs_create(lower_parent->d_inode, wh_old, S_IRUGO, + NULL); + unlock_dir(lower_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 "unionfs: 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_lower_dentry(new_dentry, old_bstart); + if (local_err) { + printk(KERN_WARNING "unionfs: revert failed in rename: " + "the new refresh failed.\n"); + eio = -EIO; + } + + local_err = unionfs_refresh_lower_dentry(old_dentry, old_bstart); + if (local_err) { + printk(KERN_WARNING "unionfs: 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 "unionfs: 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 "unionfs: revert failed in rename: " + "the object was created underneath us!\n"); + eio = -EIO; + goto revert_out; + } + + local_err = __unionfs_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 "unionfs: revert failed in rename!\n"); + eio = -EIO; + } + + local_err = unionfs_refresh_lower_dentry(new_dentry, bindex); + if (local_err) + eio = -EIO; + local_err = unionfs_refresh_lower_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, *lower_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++) { + lower_parent = unionfs_lower_dentry_idx(parent, bindex); + if (!lower_parent) + continue; + wh_dentry = lookup_one_len(whname, lower_parent, + dentry->d_name.len + UNIONFS_WHLEN); + if (IS_ERR(wh_dentry)) + continue; + if (wh_dentry->d_inode) + break; + dput(wh_dentry); + wh_dentry = ERR_PTR(-ENOENT); + } + unionfs_unlock_dentry(parent); + dput(parent); + kfree(whname); + return wh_dentry; +} + +/* + * We can't copyup a directory, because it may involve huge numbers of + * children, etc. Doing that in the kernel would be bad, so instead we + * return EXDEV to the user-space utility that caused this, and let the + * user-space recurse and ask us to copy up each file separately. + */ +static int may_rename_dir(struct dentry *dentry) +{ + 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; + + unionfs_read_lock(old_dentry->d_sb); + unionfs_double_lock_dentry(old_dentry, new_dentry); + + if (!__unionfs_d_revalidate_chain(old_dentry, NULL)) { + err = -ESTALE; + goto out; + } + if (!d_deleted(new_dentry) && new_dentry->d_inode && + !__unionfs_d_revalidate_chain(new_dentry, NULL)) { + err = -ESTALE; + goto out; + } + + 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 lower because of whiteout, + * simply override it even if the whited-out 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 lower 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); + unionfs_read_unlock(old_dentry->d_sb); + return err; +} diff -puN /dev/null fs/unionfs/sioq.c --- /dev/null +++ a/fs/unionfs/sioq.c @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2003-2007 Erez Zadok + * Copyright (c) 2005-2007 Josef 'Jeff' Sipek + * Copyright (c) 2005-2006 Junjiro Okajima + * Copyright (c) 2004-2006 David P. Quigley + * Copyright (c) 2003-2007 Stony Brook University + * Copyright (c) 2003-2007 The Research Foundation of SUNY + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include "union.h" + +/* + * 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 "unionfs: 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 -puN /dev/null fs/unionfs/sioq.h --- /dev/null +++ a/fs/unionfs/sioq.h @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2003-2007 Erez Zadok + * Copyright (c) 2005-2007 Josef 'Jeff' Sipek + * Copyright (c) 2005-2006 Junjiro Okajima + * Copyright (c) 2004-2006 David P. Quigley + * Copyright (c) 2003-2007 Stony Brook University + * Copyright (c) 2003-2007 The Research Foundation of SUNY + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#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 definitions for SIOQ functions */ +extern int __init init_sioq(void); +extern void stop_sioq(void); +extern void run_sioq(work_func_t func, struct sioq_args *args); + +/* Extern definitions for our privilege 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 -puN /dev/null fs/unionfs/subr.c --- /dev/null +++ a/fs/unionfs/subr.c @@ -0,0 +1,238 @@ +/* + * Copyright (c) 2003-2007 Erez Zadok + * Copyright (c) 2003-2006 Charles P. Wright + * Copyright (c) 2005-2007 Josef 'Jeff' Sipek + * Copyright (c) 2005-2006 Junjiro Okajima + * Copyright (c) 2005 Arun M. Krishnakumar + * Copyright (c) 2004-2006 David P. Quigley + * Copyright (c) 2003-2004 Mohammad Nayyer Zubair + * Copyright (c) 2003 Puja Gupta + * Copyright (c) 2003 Harikesavan Krishnan + * Copyright (c) 2003-2007 Stony Brook University + * Copyright (c) 2003-2007 The Research Foundation of SUNY + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include "union.h" + +/* + * Pass an unionfs dentry and an index. It will try to create a whiteout + * for the filename in dentry, and will try in branch 'index'. On error, + * it will proceed to a branch to the left. + */ +int create_whiteout(struct dentry *dentry, int start) +{ + int bstart, bend, bindex; + struct dentry *lower_dir_dentry; + struct dentry *lower_dentry; + struct dentry *lower_wh_dentry; + 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--) { + lower_dentry = unionfs_lower_dentry_idx(dentry, bindex); + + if (!lower_dentry) { + /* + * if lower dentry is not present, create the + * entire lower dentry directory structure and go + * ahead. Since we want to just create whiteout, we + * only want the parent dentry, and hence get rid of + * this dentry. + */ + lower_dentry = create_parents(dentry->d_inode, + dentry, bindex); + if (!lower_dentry || IS_ERR(lower_dentry)) { + printk(KERN_DEBUG "unionfs: create_parents " + "failed for bindex = %d\n", bindex); + continue; + } + } + + lower_wh_dentry = + lookup_one_len(name, lower_dentry->d_parent, + dentry->d_name.len + UNIONFS_WHLEN); + if (IS_ERR(lower_wh_dentry)) + continue; + + /* + * The whiteout already exists. This used to be impossible, + * but now is possible because of opaqueness. + */ + if (lower_wh_dentry->d_inode) { + dput(lower_wh_dentry); + err = 0; + goto out; + } + + lower_dir_dentry = lock_parent(lower_wh_dentry); + if (!(err = is_robranch_super(dentry->d_sb, bindex))) + err = vfs_create(lower_dir_dentry->d_inode, + lower_wh_dentry, + ~current->fs->umask & S_IRWXUGO, + NULL); + unlock_dir(lower_dir_dentry); + dput(lower_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_lower_dentry(struct dentry *dentry, int bindex) +{ + struct dentry *lower_dentry; + struct dentry *lower_parent; + int err = 0; + + verify_locked(dentry); + + unionfs_lock_dentry(dentry->d_parent); + lower_parent = unionfs_lower_dentry_idx(dentry->d_parent, bindex); + unionfs_unlock_dentry(dentry->d_parent); + + BUG_ON(!S_ISDIR(lower_parent->d_inode->i_mode)); + + lower_dentry = lookup_one_len(dentry->d_name.name, lower_parent, + dentry->d_name.len); + if (IS_ERR(lower_dentry)) { + err = PTR_ERR(lower_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 (!lower_dentry->d_inode) { + dput(lower_dentry); + unionfs_set_lower_dentry_idx(dentry, bindex, NULL); + } else { + unionfs_set_lower_dentry_idx(dentry, bindex, lower_dentry); + unionfs_set_lower_inode_idx(dentry->d_inode, bindex, + igrab(lower_dentry->d_inode)); + } + +out: + return err; +} + +int make_dir_opaque(struct dentry *dentry, int bindex) +{ + int err = 0; + struct dentry *lower_dentry, *diropq; + struct inode *lower_dir; + + lower_dentry = unionfs_lower_dentry_idx(dentry, bindex); + lower_dir = lower_dentry->d_inode; + BUG_ON(!S_ISDIR(dentry->d_inode->i_mode) || + !S_ISDIR(lower_dir->i_mode)); + + mutex_lock(&lower_dir->i_mutex); + diropq = lookup_one_len(UNIONFS_DIR_OPAQUE, lower_dentry, + sizeof(UNIONFS_DIR_OPAQUE) - 1); + if (IS_ERR(diropq)) { + err = PTR_ERR(diropq); + goto out; + } + + if (!diropq->d_inode) + err = vfs_create(lower_dir, diropq, S_IRUGO, NULL); + if (!err) + set_dbopaque(dentry, bindex); + + dput(diropq); + +out: + mutex_unlock(&lower_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 *lower_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++) { + lower_inode = unionfs_lower_inode_idx(inode, bindex); + + /* ignore files */ + if (!lower_inode || !S_ISDIR(lower_inode->i_mode)) + continue; + + BUG_ON(lower_inode->i_nlink < 0); + + /* A deleted directory. */ + if (lower_inode->i_nlink == 0) + continue; + dirs++; + + /* + * A broken directory... + * + * Some filesystems don't properly set the number of links + * on empty directories + */ + if (lower_inode->i_nlink == 1) + sum_nlinks += 2; + else + sum_nlinks += (lower_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 -puN /dev/null fs/unionfs/super.c --- /dev/null +++ a/fs/unionfs/super.c @@ -0,0 +1,1002 @@ +/* + * Copyright (c) 2003-2007 Erez Zadok + * Copyright (c) 2003-2006 Charles P. Wright + * Copyright (c) 2005-2007 Josef 'Jeff' Sipek + * Copyright (c) 2005-2006 Junjiro Okajima + * Copyright (c) 2005 Arun M. Krishnakumar + * Copyright (c) 2004-2006 David P. Quigley + * Copyright (c) 2003-2004 Mohammad Nayyer Zubair + * Copyright (c) 2003 Puja Gupta + * Copyright (c) 2003 Harikesavan Krishnan + * Copyright (c) 2003-2007 Stony Brook University + * Copyright (c) 2003-2007 The Research Foundation of SUNY + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include "union.h" + +/* + * 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) +{ + extern struct address_space_operations unionfs_aops; + int size; + struct unionfs_inode_info *info = UNIONFS_I(inode); + + unionfs_read_lock(inode->i_sb); + + 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 "unionfs: 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; + + inode->i_mapping->a_ops = &unionfs_aops; + + unionfs_read_unlock(inode->i_sb); +} + +/* + * 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. + * + * No need to lock sb info's rwsem. + */ +static void unionfs_delete_inode(struct inode *inode) +{ + inode->i_size = 0; /* every f/s seems to do that */ + + if (inode->i_data.nrpages) + truncate_inode_pages(&inode->i_data, 0); + + clear_inode(inode); +} + +/* + * final actions when unmounting a file system + * + * No need to lock rwsem. + */ +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; + struct dentry *lower_dentry; + + sb = dentry->d_sb; + + unionfs_read_lock(sb); + unionfs_lock_dentry(dentry); + + if (!__unionfs_d_revalidate_chain(dentry, NULL)) { + err = -ESTALE; + goto out; + } + + lower_dentry = unionfs_lower_dentry(sb->s_root); + err = vfs_statfs(lower_dentry, buf); + + /* set return buf to our f/s to avoid confusing user-level utils */ + buf->f_type = UNIONFS_SUPER_MAGIC; + + /* + * Our maximum file name can is shorter by a few bytes because every + * file name could potentially be whited-out. + */ + buf->f_namelen -= UNIONFS_WHLEN; + + memset(&buf->f_fsid, 0, sizeof(__kernel_fsid_t)); + memset(&buf->f_spare, 0, sizeof(buf->f_spare)); + +out: + unionfs_unlock_dentry(dentry); + unionfs_read_unlock(sb); + 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 " + "lower 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 " + "lower 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 " + "lower directory \"%s\" (error %d)\n", + new_branch, err); + goto out; + } + /* + * It's probably safe to check_mode the new branch to insert. Note: + * we don't allow inserting branches which are unionfs's by + * themselves (check_branch returns EINVAL in that case). This is + * because this code base doesn't support stacking unionfs: the ODF + * code base supports that correctly. + */ + if ((err = check_branch(&nd))) { + printk(KERN_WARNING "unionfs: lower 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 = 0; /* no. of current branches */ + int new_branches = 0; /* 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; + struct inode **new_lower_inodes = NULL; + int new_high_branch_id; /* new high branch ID */ + int size; /* memory allocation size, temp var */ + int old_ibstart, old_ibend; + + 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 lower 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: check if leftmost branch might be read-only, + * 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 pages that will + * have to be re-read. + *******************************************************************/ + + if (!(tmp_data[0].branchperms & MAY_WRITE)) { + printk("unionfs: leftmost branch cannot be read-only " + "(use \"remount,ro\" to create a read-only union)\n"); + err = -EINVAL; + goto out_release; + } + + /* (re)allocate space for new pointers to lower dentry */ + size = new_branches * sizeof(struct unionfs_data); + new_data = krealloc(tmp_data, size, GFP_KERNEL); + if (!new_data) { + err = -ENOMEM; + goto out_release; + } + + /* allocate space for new pointers to lower paths */ + size = new_branches * sizeof(struct path); + new_lower_paths = krealloc(tmp_lower_paths, size, GFP_KERNEL); + if (!new_lower_paths) { + err = -ENOMEM; + goto out_release; + } + + /* allocate space for new pointers to lower inodes */ + new_lower_inodes = kcalloc(new_branches, + sizeof(struct inode *), GFP_KERNEL); + if (!new_lower_inodes) { + err = -ENOMEM; + goto out_release; + } + + /* + * 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). + */ + + /* + * Now 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 page tables. 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 we 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); + old_ibstart = ibstart(sb->s_root->d_inode); + old_ibend = ibend(sb->s_root->d_inode); + ibend(sb->s_root->d_inode) = new_branches - 1; + UNIONFS_D(sb->s_root)->bcount = new_branches; + new_branches = i; /* no. of branches to release below */ + + /* + * Update lower inodes: 3 steps + * 1. grab ref on all new lower inodes + */ + for (i=dbstart(sb->s_root); i<=dbend(sb->s_root); i++) { + struct dentry *lower_dentry = + unionfs_lower_dentry_idx(sb->s_root, i); + atomic_inc(&lower_dentry->d_inode->i_count); + new_lower_inodes[i] = lower_dentry->d_inode; + } + /* 2. release reference on all older lower inodes */ + for (i=old_ibstart; i<=old_ibend; i++) { + iput(unionfs_lower_inode_idx(sb->s_root->d_inode, i)); + unionfs_set_lower_inode_idx(sb->s_root->d_inode, i, NULL); + } + kfree(UNIONFS_I(sb->s_root->d_inode)->lower_inodes); + /* 3. update root dentry's inode to new lower_inodes array */ + UNIONFS_I(sb->s_root->d_inode)->lower_inodes = new_lower_inodes; + new_lower_inodes = NULL; + + /* 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); + + err = 0; /* reset to success */ + + if (!(*flags & MS_SILENT)) + printk("unionfs: new generation number %d\n", i); + + /* + * 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 lower_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++) { + lower_inode = unionfs_lower_inode_idx(inode, bindex); + if (!lower_inode) + continue; + iput(lower_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; + + 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); + if (!unionfs_inode_cachep) + err = -ENOMEM; + return err; +} + +/* unionfs inode cache destructor */ +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. + * + * No need to grab sb info's rwsem. + */ +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, *lower_sb; + struct vfsmount *lower_mnt; + int bindex, bstart, bend; + + if (!(flags & MNT_FORCE)) + /* + * we are not being MNT_FORCE'd, therefore we should emulate + * old behavior + */ + return; + + sb = mnt->mnt_sb; + + unionfs_read_lock(sb); + + bstart = sbstart(sb); + bend = sbend(sb); + for (bindex = bstart; bindex <= bend; bindex++) { + lower_mnt = unionfs_lower_mnt_idx(sb->s_root, bindex); + lower_sb = unionfs_lower_super_idx(sb, bindex); + + if (lower_mnt && lower_sb && lower_sb->s_op && + lower_sb->s_op->umount_begin) + lower_sb->s_op->umount_begin(lower_mnt, flags); + } + + unionfs_read_unlock(sb); +} + +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_read_lock(sb); + + 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; + } + + perms = branchperms(sb, bindex); + + 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); + + unionfs_read_unlock(sb); + + return ret; +} + +struct super_operations unionfs_sops = { + .read_inode = unionfs_read_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 -puN /dev/null fs/unionfs/union.h --- /dev/null +++ a/fs/unionfs/union.h @@ -0,0 +1,466 @@ +/* + * 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 SUNY + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#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 + +#include + +/* the file system name */ +#define UNIONFS_NAME "unionfs" + +/* unionfs root inode number */ +#define UNIONFS_ROOT_INO 1 + +/* 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 lower 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; + + /* + * This rwsem is used to make sure that a branch management + * operation... + * 1) will not begin before all currently in-flight operations + * complete + * 2) any new operations do not execute until the currently + * running branch management operation completes + */ + struct rw_semaphore rwsem; + 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, based off of rdversion */ + unsigned int offset; /* The entry we have returned. */ + int bindex; + loff_t dirpos; /* 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" + +/* externs for cache creation/deletion routines */ +extern void unionfs_destroy_filldir_cache(void); +extern int unionfs_init_filldir_cache(void); +extern int unionfs_init_inode_cache(void); +extern void unionfs_destroy_inode_cache(void); +extern int unionfs_init_dentry_cache(void); +extern void unionfs_destroy_dentry_cache(void); + +/* Initialize and free readdir-specific state. */ +extern int init_rdstate(struct file *file); +extern struct unionfs_dir_state *alloc_rdstate(struct inode *inode, int bindex); +extern struct unionfs_dir_state *find_rdstate(struct inode *inode, loff_t fpos); +extern void free_rdstate(struct unionfs_dir_state *state); +extern int add_filldir_node(struct unionfs_dir_state *rdstate, const char *name, + int namelen, int bindex, int whiteout); +extern struct filldir_node *find_filldir_node(struct unionfs_dir_state *rdstate, + const char *name, int namelen); + +extern struct dentry **alloc_new_dentries(int objs); +extern 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 unionfs_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); +extern void free_dentry_private_data(struct dentry *dentry); +extern void update_bstart(struct dentry *dentry); + +/* + * EXTERNALS: + */ + +/* replicates the directory structure up to given dentry in given branch */ +extern struct dentry *create_parents(struct inode *dir, struct dentry *dentry, + int bindex); +extern int make_dir_opaque(struct dentry *dir, int bindex); + +/* partial lookup */ +extern int unionfs_partial_lookup(struct dentry *dentry); + +/* + * Pass an unionfs dentry and an index and it will try to create a whiteout + * in branch 'index'. + * + * On error, it will proceed to a branch to the left + */ +extern int create_whiteout(struct dentry *dentry, int start); +/* copies a file from dbstart to newbindex branch */ +extern int copyup_file(struct inode *dir, struct file *file, int bstart, + int newbindex, loff_t size); +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 *lower_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 lower dentry. */ +extern int unionfs_refresh_lower_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); +extern int unionfs_unlink(struct inode *dir, struct dentry *dentry); +extern int unionfs_rmdir(struct inode *dir, struct dentry *dentry); + +extern 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); + +#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; + + ret = (!(branchperms(sb, index) & MAY_WRITE)) ? -EROFS : 0; + 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); + + if ((!(branchperms(dentry->d_sb, index) & MAY_WRITE)) || + IS_RDONLY(unionfs_lower_dentry_idx(dentry, index)->d_inode)) + err = -EROFS; + 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); +} + +/* 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); +} + +static inline struct vfsmount *unionfs_mntget(struct dentry *dentry, + int bindex) +{ + BUG_ON(!dentry || bindex < 0); + + return mntget(unionfs_lower_mnt_idx(dentry, bindex)); +} + +static inline void unionfs_mntput(struct dentry *dentry, int bindex) +{ + if (!dentry) + return; + + BUG_ON(bindex < 0); + + mntput(unionfs_lower_mnt_idx(dentry, bindex)); +} +#endif /* not _UNION_H_ */ diff -puN /dev/null fs/unionfs/unlink.c --- /dev/null +++ a/fs/unionfs/unlink.c @@ -0,0 +1,176 @@ +/* + * Copyright (c) 2003-2007 Erez Zadok + * Copyright (c) 2003-2006 Charles P. Wright + * Copyright (c) 2005-2007 Josef 'Jeff' Sipek + * Copyright (c) 2005-2006 Junjiro Okajima + * Copyright (c) 2005 Arun M. Krishnakumar + * Copyright (c) 2004-2006 David P. Quigley + * Copyright (c) 2003-2004 Mohammad Nayyer Zubair + * Copyright (c) 2003 Puja Gupta + * Copyright (c) 2003 Harikesavan Krishnan + * Copyright (c) 2003-2007 Stony Brook University + * Copyright (c) 2003-2007 The Research Foundation of SUNY + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include "union.h" + +/* unlink a file by creating a whiteout */ +static int unionfs_unlink_whiteout(struct inode *dir, struct dentry *dentry) +{ + struct dentry *lower_dentry; + struct dentry *lower_dir_dentry; + int bindex; + int err = 0; + + if ((err = unionfs_partial_lookup(dentry))) + goto out; + + bindex = dbstart(dentry); + + lower_dentry = unionfs_lower_dentry_idx(dentry, bindex); + if (!lower_dentry) + goto out; + + lower_dir_dentry = lock_parent(lower_dentry); + + /* avoid destroying the lower inode if the file is in use */ + dget(lower_dentry); + if (!(err = is_robranch_super(dentry->d_sb, bindex))) + err = vfs_unlink(lower_dir_dentry->d_inode, lower_dentry); + dput(lower_dentry); + fsstack_copy_attr_times(dir, lower_dir_dentry->d_inode); + unlock_dir(lower_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 lower 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; + + unionfs_read_lock(dentry->d_sb); + unionfs_lock_dentry(dentry); + + if (!__unionfs_d_revalidate_chain(dentry, NULL)) { + err = -ESTALE; + goto out; + } + + err = unionfs_unlink_whiteout(dir, dentry); + /* call d_drop so the system "forgets" about us */ + if (!err) + d_drop(dentry); + +out: + unionfs_unlock_dentry(dentry); + unionfs_read_unlock(dentry->d_sb); + return err; +} + +static int unionfs_rmdir_first(struct inode *dir, struct dentry *dentry, + struct unionfs_dir_state *namelist) +{ + int err; + struct dentry *lower_dentry; + struct dentry *lower_dir_dentry = NULL; + + /* Here we need to remove whiteout entries. */ + err = delete_whiteouts(dentry, dbstart(dentry), namelist); + if (err) + goto out; + + lower_dentry = unionfs_lower_dentry(dentry); + + lower_dir_dentry = lock_parent(lower_dentry); + + /* avoid destroying the lower inode if the file is in use */ + dget(lower_dentry); + if (!(err = is_robranch(dentry))) + err = vfs_rmdir(lower_dir_dentry->d_inode, lower_dentry); + dput(lower_dentry); + + fsstack_copy_attr_times(dir, lower_dir_dentry->d_inode); + /* propagate number of hard-links */ + dentry->d_inode->i_nlink = unionfs_get_nlinks(dentry->d_inode); + +out: + if (lower_dir_dentry) + unlock_dir(lower_dir_dentry); + return err; +} + +int unionfs_rmdir(struct inode *dir, struct dentry *dentry) +{ + int err = 0; + struct unionfs_dir_state *namelist = NULL; + + unionfs_read_lock(dentry->d_sb); + unionfs_lock_dentry(dentry); + + if (!__unionfs_d_revalidate_chain(dentry, NULL)) { + err = -ESTALE; + goto out; + } + + /* 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); + unionfs_read_unlock(dentry->d_sb); + return err; +} diff -puN /dev/null fs/unionfs/xattr.c --- /dev/null +++ a/fs/unionfs/xattr.c @@ -0,0 +1,161 @@ +/* + * Copyright (c) 2003-2007 Erez Zadok + * Copyright (c) 2003-2006 Charles P. Wright + * Copyright (c) 2005-2007 Josef 'Jeff' Sipek + * Copyright (c) 2005-2006 Junjiro Okajima + * Copyright (c) 2005 Arun M. Krishnakumar + * Copyright (c) 2004-2006 David P. Quigley + * Copyright (c) 2003-2004 Mohammad Nayyer Zubair + * Copyright (c) 2003 Puja Gupta + * Copyright (c) 2003 Harikesavan Krishnan + * Copyright (c) 2003-2007 Stony Brook University + * Copyright (c) 2003-2007 The Research Foundation of SUNY + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include "union.h" + +/* 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 *lower_dentry = NULL; + int err = -EOPNOTSUPP; + + unionfs_read_lock(dentry->d_sb); + unionfs_lock_dentry(dentry); + + if (!__unionfs_d_revalidate_chain(dentry, NULL)) { + err = -ESTALE; + goto out; + } + + lower_dentry = unionfs_lower_dentry(dentry); + + err = vfs_getxattr(lower_dentry, (char*) name, value, size); + +out: + unionfs_unlock_dentry(dentry); + unionfs_read_unlock(dentry->d_sb); + 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 *lower_dentry = NULL; + int err = -EOPNOTSUPP; + + unionfs_read_lock(dentry->d_sb); + unionfs_lock_dentry(dentry); + + if (!__unionfs_d_revalidate_chain(dentry, NULL)) { + err = -ESTALE; + goto out; + } + + lower_dentry = unionfs_lower_dentry(dentry); + + err = vfs_setxattr(lower_dentry, (char*) name, (void*) value, + size, flags); + +out: + unionfs_unlock_dentry(dentry); + unionfs_read_unlock(dentry->d_sb); + return err; +} + +/* + * BKL held by caller. + * dentry->d_inode->i_mutex locked + */ +int unionfs_removexattr(struct dentry *dentry, const char *name) +{ + struct dentry *lower_dentry = NULL; + int err = -EOPNOTSUPP; + + unionfs_read_lock(dentry->d_sb); + unionfs_lock_dentry(dentry); + + if (!__unionfs_d_revalidate_chain(dentry, NULL)) { + err = -ESTALE; + goto out; + } + + lower_dentry = unionfs_lower_dentry(dentry); + + err = vfs_removexattr(lower_dentry, (char*) name); + +out: + unionfs_unlock_dentry(dentry); + unionfs_read_unlock(dentry->d_sb); + 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 *lower_dentry = NULL; + int err = -EOPNOTSUPP; + char *encoded_list = NULL; + + unionfs_read_lock(dentry->d_sb); + unionfs_lock_dentry(dentry); + + if (!__unionfs_d_revalidate_chain(dentry, NULL)) { + err = -ESTALE; + goto out; + } + + lower_dentry = unionfs_lower_dentry(dentry); + + encoded_list = list; + err = vfs_listxattr(lower_dentry, encoded_list, size); + +out: + unionfs_unlock_dentry(dentry); + unionfs_read_unlock(dentry->d_sb); + return err; +} diff -puN include/linux/fs_stack.h~git-unionfs include/linux/fs_stack.h --- a/include/linux/fs_stack.h~git-unionfs +++ a/include/linux/fs_stack.h @@ -1,7 +1,19 @@ +/* + * Copyright (c) 2006-2007 Erez Zadok + * Copyright (c) 2006-2007 Josef 'Jeff' Sipek + * Copyright (c) 2006-2007 Stony Brook University + * Copyright (c) 2006-2007 The Research Foundation of SUNY + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + #ifndef _LINUX_FS_STACK_H #define _LINUX_FS_STACK_H -/* This file defines generic functions used primarily by stackable +/* + * This file defines generic functions used primarily by stackable * filesystems; none of these functions require i_mutex to be held. */ @@ -9,9 +21,10 @@ /* externs for fs/stack.c */ extern void fsstack_copy_attr_all(struct inode *dest, const struct inode *src, - int (*get_nlinks)(struct inode *)); + int (*get_nlinks)(struct inode *)); -extern void fsstack_copy_inode_size(struct inode *dst, const struct inode *src); +extern void fsstack_copy_inode_size(struct inode *dst, + const struct inode *src); /* inlines */ static inline void fsstack_copy_attr_atime(struct inode *dest, diff -puN include/linux/magic.h~git-unionfs include/linux/magic.h --- a/include/linux/magic.h~git-unionfs +++ a/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 -puN include/linux/mm.h~git-unionfs include/linux/mm.h --- a/include/linux/mm.h~git-unionfs +++ a/include/linux/mm.h @@ -1203,6 +1203,7 @@ int drop_caches_sysctl_handler(struct ct 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 -puN include/linux/namei.h~git-unionfs include/linux/namei.h --- a/include/linux/namei.h~git-unionfs +++ a/include/linux/namei.h @@ -3,6 +3,7 @@ #include #include +#include struct vfsmount; @@ -100,4 +101,16 @@ static inline char *nd_get_link(struct n 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 -puN /dev/null include/linux/union_fs.h --- /dev/null +++ a/include/linux/union_fs.h @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2003-2007 Erez Zadok + * Copyright (c) 2005-2007 Josef 'Jeff' Sipek + * Copyright (c) 2003-2007 Stony Brook University + * Copyright (c) 2003-2007 The Research Foundation of SUNY + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#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 */ + _