Commit 4dc51987 authored by J. R. Okajima's avatar J. R. Okajima
Browse files

aufs: inode op, del, unlink



Implement i_op->unlink() with supporting logical deletion by whiteout.
Signed-off-by: default avatarJ. R. Okajima <hooanon05g@gmail.com>
parent 78db268b
......@@ -17,7 +17,7 @@ aufs-y := module.o sbinfo.o super.o branch.o xino.o sysaufs.o opts.o \
dynop.o \
finfo.o file.o \
dir.o vdir.o \
iinfo.o inode.o i_op.o i_op_add.o
iinfo.o inode.o i_op.o i_op_add.o i_op_del.o
# all are boolean
aufs-$(CONFIG_PROC_FS) += procfs.o plink.o
......
......@@ -647,6 +647,7 @@ struct inode_operations aufs_iop[] = {
[AuIop_DIR] = {
.create = aufs_create,
.lookup = aufs_lookup,
.unlink = aufs_unlink,
.symlink = aufs_symlink,
.mkdir = aufs_mkdir,
.mknod = aufs_mknod,
......
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2005-2019 Junjiro R. Okajima
*/
/*
* inode operations (del entry)
*/
#include <linux/iversion.h>
#include "aufs.h"
/*
* decide if a new whiteout for @dentry is necessary or not.
* when it is necessary, prepare the parent dir for the upper branch whose
* branch index is @bcpup for creation. the actual creation of the whiteout will
* be done by caller.
* return value:
* 0: wh is unnecessary
* plus: wh is necessary
* minus: error
*/
int au_wr_dir_need_wh(struct dentry *dentry, int isdir, aufs_bindex_t *bcpup)
{
int need_wh, err;
aufs_bindex_t btop;
struct super_block *sb;
sb = dentry->d_sb;
btop = au_dbtop(dentry);
if (*bcpup < 0) {
*bcpup = btop;
if (au_test_ro(sb, btop, d_inode(dentry))) {
err = AuWbrCopyup(au_sbi(sb), dentry);
*bcpup = err;
if (unlikely(err < 0))
goto out;
}
} else
AuDebugOn(btop < *bcpup
|| au_test_ro(sb, *bcpup, d_inode(dentry)));
AuDbg("bcpup %d, btop %d\n", *bcpup, btop);
if (*bcpup != btop) {
err = au_cpup_dirs(dentry, *bcpup);
if (unlikely(err))
goto out;
need_wh = 1;
} else {
struct au_dinfo *dinfo, *tmp;
need_wh = -ENOMEM;
dinfo = au_di(dentry);
tmp = au_di_alloc(sb, AuLsc_DI_TMP);
if (tmp) {
au_di_cp(tmp, dinfo);
au_di_swap(tmp, dinfo);
/* returns the number of positive dentries */
need_wh = au_lkup_dentry(dentry, btop + 1,
/* AuLkup_IGNORE_PERM */ 0);
au_di_swap(tmp, dinfo);
au_rw_write_unlock(&tmp->di_rwsem);
au_di_free(tmp);
}
}
AuDbg("need_wh %d\n", need_wh);
err = need_wh;
out:
return err;
}
/*
* simple tests for the del-entry operations.
* following the checks in vfs, plus the parent-child relationship.
*/
int au_may_del(struct dentry *dentry, aufs_bindex_t bindex,
struct dentry *h_parent, int isdir)
{
int err;
umode_t h_mode;
struct dentry *h_dentry, *h_latest;
struct inode *h_inode;
h_dentry = au_h_dptr(dentry, bindex);
if (d_really_is_positive(dentry)) {
err = -ENOENT;
if (unlikely(d_is_negative(h_dentry)))
goto out;
h_inode = d_inode(h_dentry);
if (unlikely(!h_inode->i_nlink))
goto out;
h_mode = h_inode->i_mode;
if (!isdir) {
err = -EISDIR;
if (unlikely(S_ISDIR(h_mode)))
goto out;
} else if (unlikely(!S_ISDIR(h_mode))) {
err = -ENOTDIR;
goto out;
}
} else {
/* rename(2) case */
err = -EIO;
if (unlikely(d_is_positive(h_dentry)))
goto out;
}
err = -ENOENT;
/* expected parent dir is locked */
if (unlikely(h_parent != h_dentry->d_parent))
goto out;
err = 0;
/*
* rmdir a dir may break the consistency on some filesystem.
* let's try heavy test.
*/
err = -EACCES;
if (unlikely(au_test_h_perm(d_inode(h_parent), MAY_EXEC | MAY_WRITE)))
goto out;
h_latest = au_sio_lkup_one(&dentry->d_name, h_parent);
err = -EIO;
if (IS_ERR(h_latest))
goto out;
if (h_latest == h_dentry)
err = 0;
dput(h_latest);
out:
return err;
}
/*
* decide the branch where we operate for @dentry. the branch index will be set
* @rbcpup. after deciding it, 'pin' it and store the timestamps of the parent
* dir for reverting.
* when a new whiteout is necessary, create it.
*/
static struct dentry*
lock_hdir_create_wh(struct dentry *dentry, int isdir, aufs_bindex_t *rbcpup,
struct au_dtime *dt, struct au_pin *pin)
{
struct dentry *wh_dentry;
struct super_block *sb;
struct path h_path;
int err, need_wh;
unsigned int udba;
aufs_bindex_t bcpup;
need_wh = au_wr_dir_need_wh(dentry, isdir, rbcpup);
wh_dentry = ERR_PTR(need_wh);
if (unlikely(need_wh < 0))
goto out;
sb = dentry->d_sb;
udba = au_opt_udba(sb);
bcpup = *rbcpup;
err = au_pin(pin, dentry, bcpup, udba,
AuPin_DI_LOCKED | AuPin_MNT_WRITE);
wh_dentry = ERR_PTR(err);
if (unlikely(err))
goto out;
h_path.dentry = au_pinned_h_parent(pin);
if (udba != AuOpt_UDBA_NONE
&& au_dbtop(dentry) == bcpup) {
err = au_may_del(dentry, bcpup, h_path.dentry, isdir);
wh_dentry = ERR_PTR(err);
if (unlikely(err))
goto out_unpin;
}
h_path.mnt = au_sbr_mnt(sb, bcpup);
au_dtime_store(dt, au_pinned_parent(pin), &h_path);
wh_dentry = NULL;
if (!need_wh)
goto out; /* success, no need to create whiteout */
wh_dentry = au_wh_create(dentry, bcpup, h_path.dentry);
if (IS_ERR(wh_dentry))
goto out_unpin;
/* returns with the parent is locked and wh_dentry is dget-ed */
goto out; /* success */
out_unpin:
au_unpin(pin);
out:
return wh_dentry;
}
/*
* final procedure for deleting a entry.
* maintain dentry and iattr.
*/
static void epilog(struct inode *dir, struct dentry *dentry,
aufs_bindex_t bindex)
{
struct inode *inode;
inode = d_inode(dentry);
d_drop(dentry);
inode->i_ctime = dir->i_ctime;
au_dir_ts(dir, bindex);
inode_inc_iversion(dir);
}
/*
* when an error happened, remove the created whiteout and revert everything.
*/
static int do_revert(int err, struct inode *dir, aufs_bindex_t bindex,
aufs_bindex_t bwh, struct dentry *wh_dentry,
struct dentry *dentry, struct au_dtime *dt)
{
int rerr;
struct path h_path = {
.dentry = wh_dentry,
.mnt = au_sbr_mnt(dir->i_sb, bindex)
};
rerr = au_wh_unlink_dentry(au_h_iptr(dir, bindex), &h_path, dentry);
if (!rerr) {
au_set_dbwh(dentry, bwh);
au_dtime_revert(dt);
return 0;
}
AuIOErr("%pd reverting whiteout failed(%d, %d)\n", dentry, err, rerr);
return -EIO;
}
/* ---------------------------------------------------------------------- */
int aufs_unlink(struct inode *dir, struct dentry *dentry)
{
int err;
aufs_bindex_t bwh, bindex, btop;
struct inode *inode, *h_dir, *delegated;
struct dentry *parent, *wh_dentry;
/* to reduce stack size */
struct {
struct au_dtime dt;
struct au_pin pin;
struct path h_path;
} *a;
IMustLock(dir);
err = -ENOMEM;
a = kmalloc(sizeof(*a), GFP_NOFS);
if (unlikely(!a))
goto out;
err = aufs_read_lock(dentry, AuLock_DW | AuLock_GEN);
if (unlikely(err))
goto out_free;
err = au_d_hashed_positive(dentry);
if (unlikely(err))
goto out_unlock;
inode = d_inode(dentry);
IMustLock(inode);
err = -EISDIR;
if (unlikely(d_is_dir(dentry)))
goto out_unlock; /* possible? */
btop = au_dbtop(dentry);
bwh = au_dbwh(dentry);
bindex = -1;
parent = dentry->d_parent; /* dir inode is locked */
di_write_lock_parent(parent);
wh_dentry = lock_hdir_create_wh(dentry, /*isdir*/0, &bindex, &a->dt,
&a->pin);
err = PTR_ERR(wh_dentry);
if (IS_ERR(wh_dentry))
goto out_parent;
a->h_path.mnt = au_sbr_mnt(dentry->d_sb, btop);
a->h_path.dentry = au_h_dptr(dentry, btop);
dget(a->h_path.dentry);
if (bindex == btop) {
h_dir = au_pinned_h_dir(&a->pin);
delegated = NULL;
err = vfsub_unlink(h_dir, &a->h_path, &delegated, /*force*/0);
if (unlikely(err == -EWOULDBLOCK)) {
pr_warn("cannot retry for NFSv4 delegation"
" for an internal unlink\n");
iput(delegated);
}
} else {
/* dir inode is locked */
h_dir = d_inode(wh_dentry->d_parent);
IMustLock(h_dir);
err = 0;
}
if (!err) {
vfsub_drop_nlink(inode);
epilog(dir, dentry, bindex);
/* update target timestamps */
if (bindex == btop)
inode->i_ctime = d_inode(a->h_path.dentry)->i_ctime;
else
/* todo: this timestamp may be reverted later */
inode->i_ctime = h_dir->i_ctime;
goto out_unpin; /* success */
}
/* revert */
if (wh_dentry) {
int rerr;
rerr = do_revert(err, dir, bindex, bwh, wh_dentry, dentry,
&a->dt);
if (rerr)
err = rerr;
}
out_unpin:
au_unpin(&a->pin);
dput(wh_dentry);
dput(a->h_path.dentry);
out_parent:
di_write_unlock(parent);
out_unlock:
aufs_read_unlock(dentry, AuLock_DW);
out_free:
au_kfree_rcu(a);
out:
return err;
}
......@@ -172,6 +172,12 @@ int aufs_create(struct inode *dir, struct dentry *dentry, umode_t mode,
int aufs_tmpfile(struct inode *dir, struct dentry *dentry, umode_t mode);
int aufs_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode);
/* i_op_del.c */
int au_wr_dir_need_wh(struct dentry *dentry, int isdir, aufs_bindex_t *bcpup);
int au_may_del(struct dentry *dentry, aufs_bindex_t bindex,
struct dentry *h_parent, int isdir);
int aufs_unlink(struct inode *dir, struct dentry *dentry);
/* iinfo.c */
struct inode *au_h_iptr(struct inode *inode, aufs_bindex_t bindex);
void au_hiput(struct au_hinode *hinode);
......
......@@ -37,6 +37,12 @@ enum {
/* ---------------------------------------------------------------------- */
static inline void vfsub_drop_nlink(struct inode *inode)
{
AuDebugOn(!inode->i_nlink);
drop_nlink(inode);
}
int vfsub_sync_filesystem(struct super_block *h_sb, int wait);
/* ---------------------------------------------------------------------- */
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment