279 lines
7.9 KiB
C
279 lines
7.9 KiB
C
/*
|
|
* Copyright (c) 2003-2014 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-2014 Stony Brook University
|
|
* Copyright (c) 2003-2014 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"
|
|
|
|
/*
|
|
* Helper function for Unionfs's unlink operation.
|
|
*
|
|
* The main goal of this function is to optimize the unlinking of non-dir
|
|
* objects in unionfs by deleting all possible lower inode objects from the
|
|
* underlying branches having same dentry name as the non-dir dentry on
|
|
* which this unlink operation is called. This way we delete as many lower
|
|
* inodes as possible, and save space. Whiteouts need to be created in
|
|
* branch0 only if unlinking fails on any of the lower branch other than
|
|
* branch0, or if a lower branch is marked read-only.
|
|
*
|
|
* Also, while unlinking a file, if we encounter any dir type entry in any
|
|
* intermediate branch, then we remove the directory by calling vfs_rmdir.
|
|
* The following special cases are also handled:
|
|
|
|
* (1) If an error occurs in branch0 during vfs_unlink, then we return
|
|
* appropriate error.
|
|
*
|
|
* (2) If we get an error during unlink in any of other lower branch other
|
|
* than branch0, then we create a whiteout in branch0.
|
|
*
|
|
* (3) If a whiteout already exists in any intermediate branch, we delete
|
|
* all possible inodes only up to that branch (this is an "opaqueness"
|
|
* as as per Documentation/filesystems/unionfs/concepts.txt).
|
|
*
|
|
*/
|
|
static int unionfs_unlink_whiteout(struct inode *dir, struct dentry *dentry,
|
|
struct dentry *parent)
|
|
{
|
|
struct dentry *lower_dentry;
|
|
struct dentry *lower_dir_dentry;
|
|
int bindex;
|
|
int err = 0;
|
|
|
|
err = unionfs_partial_lookup(dentry, parent);
|
|
if (err)
|
|
goto out;
|
|
|
|
/* trying to unlink all possible valid instances */
|
|
for (bindex = dbstart(dentry); bindex <= dbend(dentry); bindex++) {
|
|
lower_dentry = unionfs_lower_dentry_idx(dentry, bindex);
|
|
if (!lower_dentry || !lower_dentry->d_inode)
|
|
continue;
|
|
|
|
lower_dir_dentry = lock_parent(lower_dentry);
|
|
|
|
/* avoid destroying the lower inode if the object is in use */
|
|
dget(lower_dentry);
|
|
err = is_robranch_super(dentry->d_sb, bindex);
|
|
if (!err) {
|
|
/* see Documentation/filesystems/unionfs/issues.txt */
|
|
lockdep_off();
|
|
if (!S_ISDIR(lower_dentry->d_inode->i_mode))
|
|
err = vfs_unlink(lower_dir_dentry->d_inode,
|
|
lower_dentry, NULL);
|
|
else
|
|
err = vfs_rmdir(lower_dir_dentry->d_inode,
|
|
lower_dentry);
|
|
lockdep_on();
|
|
}
|
|
|
|
/* if lower object deletion succeeds, update inode's times */
|
|
if (!err)
|
|
unionfs_copy_attr_times(dentry->d_inode);
|
|
dput(lower_dentry);
|
|
fsstack_copy_attr_times(dir, lower_dir_dentry->d_inode);
|
|
unlock_dir(lower_dir_dentry);
|
|
|
|
if (err)
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* Create the whiteout in branch 0 (highest priority) only if (a)
|
|
* there was an error in any intermediate branch other than branch 0
|
|
* due to failure of vfs_unlink/vfs_rmdir or (b) a branch marked or
|
|
* mounted read-only.
|
|
*/
|
|
if (err) {
|
|
if ((bindex == 0) ||
|
|
((bindex == dbstart(dentry)) &&
|
|
(!IS_COPYUP_ERR(err))))
|
|
goto out;
|
|
else {
|
|
if (!IS_COPYUP_ERR(err))
|
|
pr_debug("unionfs: lower object deletion "
|
|
"failed in branch:%d\n", bindex);
|
|
err = create_whiteout(dentry, sbstart(dentry->d_sb));
|
|
}
|
|
}
|
|
|
|
out:
|
|
if (!err)
|
|
inode_dec_link_count(dentry->d_inode);
|
|
|
|
/* 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;
|
|
struct inode *inode = dentry->d_inode;
|
|
struct dentry *parent;
|
|
int valid;
|
|
|
|
BUG_ON(S_ISDIR(inode->i_mode));
|
|
unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_CHILD);
|
|
parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT);
|
|
unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD);
|
|
|
|
valid = __unionfs_d_revalidate(dentry, parent, false, 0);
|
|
if (unlikely(!valid)) {
|
|
err = -ESTALE;
|
|
goto out;
|
|
}
|
|
unionfs_check_dentry(dentry);
|
|
|
|
err = unionfs_unlink_whiteout(dir, dentry, parent);
|
|
/* call d_drop so the system "forgets" about us */
|
|
if (!err) {
|
|
unionfs_postcopyup_release(dentry);
|
|
unionfs_postcopyup_setmnt(parent);
|
|
if (inode->i_nlink == 0) /* drop lower inodes */
|
|
iput_lowers_all(inode, false);
|
|
d_drop(dentry);
|
|
/*
|
|
* if unlink/whiteout succeeded, parent dir mtime has
|
|
* changed
|
|
*/
|
|
unionfs_copy_attr_times(dir);
|
|
}
|
|
|
|
out:
|
|
if (!err) {
|
|
unionfs_check_dentry(dentry);
|
|
unionfs_check_inode(dir);
|
|
}
|
|
unionfs_unlock_dentry(dentry);
|
|
unionfs_unlock_parent(dentry, parent);
|
|
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);
|
|
err = is_robranch(dentry);
|
|
if (!err)
|
|
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 */
|
|
set_nlink(dentry->d_inode, 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;
|
|
struct dentry *parent;
|
|
int dstart, dend;
|
|
bool valid;
|
|
|
|
unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_CHILD);
|
|
parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT);
|
|
unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD);
|
|
|
|
valid = __unionfs_d_revalidate(dentry, parent, false, 0);
|
|
if (unlikely(!valid)) {
|
|
err = -ESTALE;
|
|
goto out;
|
|
}
|
|
unionfs_check_dentry(dentry);
|
|
|
|
/* check if this unionfs directory is empty or not */
|
|
err = check_empty(dentry, parent, &namelist);
|
|
if (err)
|
|
goto out;
|
|
|
|
err = unionfs_rmdir_first(dir, dentry, namelist);
|
|
dstart = dbstart(dentry);
|
|
dend = dbend(dentry);
|
|
/*
|
|
* We create a whiteout for the directory if there was an error to
|
|
* rmdir the first directory entry in the union. Otherwise, we
|
|
* create a whiteout only if there is no chance that a lower
|
|
* priority branch might also have the same named directory. IOW,
|
|
* if there is not another same-named directory at a lower priority
|
|
* branch, then we don't need to create a whiteout for it.
|
|
*/
|
|
if (!err) {
|
|
if (dstart < dend)
|
|
err = create_whiteout(dentry, dstart);
|
|
} else {
|
|
int new_err;
|
|
|
|
if (dstart == 0)
|
|
goto out;
|
|
|
|
/* exit if the error returned was NOT -EROFS */
|
|
if (!IS_COPYUP_ERR(err))
|
|
goto out;
|
|
|
|
new_err = create_whiteout(dentry, dstart - 1);
|
|
if (new_err != -EEXIST)
|
|
err = new_err;
|
|
}
|
|
|
|
out:
|
|
/*
|
|
* Drop references to lower dentry/inode so storage space for them
|
|
* can be reclaimed. Then, call d_drop so the system "forgets"
|
|
* about us.
|
|
*/
|
|
if (!err) {
|
|
iput_lowers_all(dentry->d_inode, false);
|
|
dput(unionfs_lower_dentry_idx(dentry, dstart));
|
|
unionfs_set_lower_dentry_idx(dentry, dstart, NULL);
|
|
d_drop(dentry);
|
|
/* update our lower vfsmnts, in case a copyup took place */
|
|
unionfs_postcopyup_setmnt(dentry);
|
|
unionfs_check_dentry(dentry);
|
|
unionfs_check_inode(dir);
|
|
}
|
|
|
|
if (namelist)
|
|
free_rdstate(namelist);
|
|
|
|
unionfs_unlock_dentry(dentry);
|
|
unionfs_unlock_parent(dentry, parent);
|
|
unionfs_read_unlock(dentry->d_sb);
|
|
return err;
|
|
}
|