All of lore.kernel.org
 help / color / mirror / Atom feed
From: "J. R. Okajima" <hooanon05@yahoo.co.jp>
To: linux-kernel@vger.kernel.org
Cc: linux-fsdevel@vger.kernel.org, "J. R. Okajima" <hooanon05@yahoo.co.jp>
Subject: [Aufs 13/25] aufs whiteout
Date: Mon,  9 Mar 2009 12:25:06 +0900	[thread overview]
Message-ID: <1236569118-3750-14-git-send-email-hooanon05@yahoo.co.jp> (raw)
In-Reply-To: <1236569118-3750-1-git-send-email-hooanon05@yahoo.co.jp>

initial commit
whiteout for logical deletion and opaque directory

Signed-off-by: J. R. Okajima <hooanon05@yahoo.co.jp>
---
 fs/aufs/whout.c | 1010 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 fs/aufs/whout.h |   78 +++++
 2 files changed, 1088 insertions(+), 0 deletions(-)
 create mode 100644 fs/aufs/whout.c
 create mode 100644 fs/aufs/whout.h

diff --git a/fs/aufs/whout.c b/fs/aufs/whout.c
new file mode 100644
index 0000000..f2343d2
--- /dev/null
+++ b/fs/aufs/whout.c
@@ -0,0 +1,1010 @@
+/*
+ * Copyright (C) 2005-2009 Junjiro R. Okajima
+ *
+ * This program, aufs is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+/*
+ * whiteout for logical deletion and opaque directory
+ */
+
+#include <linux/fs.h>
+#include "aufs.h"
+
+#define WH_MASK			S_IRUGO
+
+/*
+ * If a directory contains this file, then it is opaque.  We start with the
+ * .wh. flag so that it is blocked by lookup.
+ */
+static struct qstr diropq_name = {
+	.name = AUFS_WH_DIROPQ,
+	.len = sizeof(AUFS_WH_DIROPQ) - 1
+};
+
+/*
+ * generate whiteout name, which is NOT terminated by NULL.
+ * @name: original d_name.name
+ * @len: original d_name.len
+ * @wh: whiteout qstr
+ * returns zero when succeeds, otherwise error.
+ * succeeded value as wh->name should be freed by kfree().
+ */
+int au_wh_name_alloc(struct qstr *wh, const struct qstr *name)
+{
+	char *p;
+
+	if (unlikely(name->len > PATH_MAX - AUFS_WH_PFX_LEN))
+		return -ENAMETOOLONG;
+
+	wh->len = name->len + AUFS_WH_PFX_LEN;
+	p = kmalloc(wh->len, GFP_NOFS);
+	wh->name = p;
+	if (p) {
+		memcpy(p, AUFS_WH_PFX, AUFS_WH_PFX_LEN);
+		memcpy(p + AUFS_WH_PFX_LEN, name->name, name->len);
+		/* smp_mb(); */
+		return 0;
+	}
+	return -ENOMEM;
+}
+
+/* ---------------------------------------------------------------------- */
+
+/*
+ * test if the @wh_name exists under @h_parent.
+ * @try_sio specifies the necessary of super-io.
+ */
+int au_wh_test(struct dentry *h_parent, struct qstr *wh_name,
+	       struct au_branch *br, int try_sio)
+{
+	int err;
+	struct dentry *wh_dentry;
+	struct inode *h_dir;
+
+	h_dir = h_parent->d_inode;
+	if (!try_sio)
+		wh_dentry = au_lkup_one(wh_name, h_parent, br, /*nd*/NULL);
+	else
+		wh_dentry = au_sio_lkup_one(wh_name, h_parent, br);
+	err = PTR_ERR(wh_dentry);
+	if (IS_ERR(wh_dentry))
+		goto out;
+
+	err = 0;
+	if (!wh_dentry->d_inode)
+		goto out_wh; /* success */
+
+	err = 1;
+	if (S_ISREG(wh_dentry->d_inode->i_mode))
+		goto out_wh; /* success */
+
+	err = -EIO;
+	AuIOErr("%.*s Invalid whiteout entry type 0%o.\n",
+		AuDLNPair(wh_dentry), wh_dentry->d_inode->i_mode);
+
+ out_wh:
+	dput(wh_dentry);
+ out:
+	return err;
+}
+
+/*
+ * test if the @h_dentry sets opaque or not.
+ */
+int au_diropq_test(struct dentry *h_dentry, struct au_branch *br)
+{
+	int err;
+	struct inode *h_dir;
+
+	h_dir = h_dentry->d_inode;
+	err = au_wh_test(h_dentry, &diropq_name, br,
+			 au_test_h_perm_sio(h_dir, MAY_EXEC));
+	return err;
+}
+
+/*
+ * returns a negative dentry whose name is unique and temporary.
+ */
+struct dentry *au_whtmp_lkup(struct dentry *h_parent, struct au_branch *br,
+			     struct qstr *prefix)
+{
+#define HEX_LEN	4
+	struct dentry *dentry;
+	int i;
+	char defname[AUFS_WH_PFX_LEN * 2 + DNAME_INLINE_LEN_MIN + 1
+		     + HEX_LEN + 1], *name, *p;
+	static unsigned short cnt;
+	struct qstr qs;
+
+	name = defname;
+	qs.len = sizeof(defname) - DNAME_INLINE_LEN_MIN + prefix->len - 1;
+	if (unlikely(prefix->len > DNAME_INLINE_LEN_MIN)) {
+		dentry = ERR_PTR(-ENAMETOOLONG);
+		if (unlikely(qs.len >= PATH_MAX))
+			goto out;
+		dentry = ERR_PTR(-ENOMEM);
+		name = kmalloc(qs.len + 1, GFP_NOFS);
+		if (unlikely(!name))
+			goto out;
+	}
+
+	/* doubly whiteout-ed */
+	memcpy(name, AUFS_WH_PFX AUFS_WH_PFX, AUFS_WH_PFX_LEN * 2);
+	p = name + AUFS_WH_PFX_LEN * 2;
+	memcpy(p, prefix->name, prefix->len);
+	p += prefix->len;
+	*p++ = '.';
+	AuDebugOn(name + qs.len + 1 - p <= HEX_LEN);
+
+	qs.name = name;
+	for (i = 0; i < 3; i++) {
+		sprintf(p, "%.*d", HEX_LEN, cnt++);
+		dentry = au_sio_lkup_one(&qs, h_parent, br);
+		if (IS_ERR(dentry) || !dentry->d_inode)
+			goto out_name;
+		dput(dentry);
+	}
+	/* AuWarn("could not get random name\n"); */
+	dentry = ERR_PTR(-EEXIST);
+	AuDbg("%.*s\n", AuLNPair(&qs));
+	BUG();
+
+ out_name:
+	if (name != defname)
+		kfree(name);
+ out:
+	return dentry;
+#undef HEX_LEN
+}
+
+/*
+ * rename the @h_dentry on @br to the whiteouted temporary name.
+ */
+int au_whtmp_ren(struct dentry *h_dentry, struct au_branch *br)
+{
+	int err;
+	struct path h_path = {
+		.mnt = br->br_mnt
+	};
+	struct inode *h_dir;
+	struct dentry *h_parent;
+
+	h_parent = h_dentry->d_parent; /* dir inode is locked */
+	h_dir = h_parent->d_inode;
+	IMustLock(h_dir);
+
+	h_path.dentry = au_whtmp_lkup(h_parent, br, &h_dentry->d_name);
+	err = PTR_ERR(h_path.dentry);
+	if (IS_ERR(h_path.dentry))
+		goto out;
+
+	/* under the same dir, no need to lock_rename() */
+	err = vfsub_rename(h_dir, h_dentry, h_dir, &h_path);
+	AuTraceErr(err);
+	dput(h_path.dentry);
+
+ out:
+	return err;
+}
+
+/* ---------------------------------------------------------------------- */
+/*
+ * functions for removing a whiteout
+ */
+
+static int do_unlink_wh(struct inode *h_dir, struct path *h_path)
+{
+	int force;
+
+	/*
+	 * forces superio when the dir has a sticky bit.
+	 * this may be a violation of unix fs semantics.
+	 */
+	force = (h_dir->i_mode & S_ISVTX)
+		&& h_path->dentry->d_inode->i_uid != current_fsuid();
+	return vfsub_unlink(h_dir, h_path, force);
+}
+
+int au_wh_unlink_dentry(struct inode *h_dir, struct path *h_path,
+			struct dentry *dentry)
+{
+	int err;
+
+	err = do_unlink_wh(h_dir, h_path);
+	if (!err && dentry)
+		au_set_dbwh(dentry, -1);
+
+	return err;
+}
+
+static int unlink_wh_name(struct dentry *h_parent, struct qstr *wh,
+			  struct au_branch *br)
+{
+	int err;
+	struct path h_path = {
+		.mnt = br->br_mnt
+	};
+
+	err = 0;
+	h_path.dentry = au_lkup_one(wh, h_parent, br, /*nd*/NULL);
+	if (IS_ERR(h_path.dentry))
+		err = PTR_ERR(h_path.dentry);
+	else {
+		if (h_path.dentry->d_inode
+		    && S_ISREG(h_path.dentry->d_inode->i_mode))
+			err = do_unlink_wh(h_parent->d_inode, &h_path);
+		dput(h_path.dentry);
+	}
+
+	return err;
+}
+
+/* ---------------------------------------------------------------------- */
+/*
+ * initialize/clean whiteout for a branch
+ */
+
+static void au_wh_clean(struct inode *h_dir, struct path *whpath,
+			const int isdir)
+{
+	int err;
+
+	if (!whpath->dentry->d_inode)
+		return;
+
+	err = mnt_want_write(whpath->mnt);
+	if (!err) {
+		if (isdir)
+			err = vfsub_rmdir(h_dir, whpath);
+		else
+			err = vfsub_unlink(h_dir, whpath, /*force*/0);
+		mnt_drop_write(whpath->mnt);
+	}
+	if (unlikely(err))
+		AuWarn("failed removing %.*s (%d), ignored.\n",
+		       AuDLNPair(whpath->dentry), err);
+}
+
+static int test_linkable(struct dentry *h_root)
+{
+	struct inode *h_dir = h_root->d_inode;
+
+	if (h_dir->i_op->link)
+		return 0;
+
+	AuErr("%.*s (%s) doesn't support link(2), use noplink and rw+nolwh\n",
+	      AuDLNPair(h_root), au_sbtype(h_root->d_sb));
+	return -ENOSYS;
+}
+
+/* todo: should this mkdir be done in /sbin/mount.aufs helper? */
+static int au_whdir(struct inode *h_dir, struct path *path)
+{
+	int err;
+
+	err = -EEXIST;
+	if (!path->dentry->d_inode) {
+		int mode = S_IRWXU;
+
+		if (au_test_nfs(path->dentry->d_sb))
+			mode |= S_IXUGO;
+		err = mnt_want_write(path->mnt);
+		if (!err) {
+			err = vfsub_mkdir(h_dir, path, mode);
+			mnt_drop_write(path->mnt);
+		}
+	} else if (S_ISDIR(path->dentry->d_inode->i_mode))
+		err = 0;
+	else
+		AuErr("unknown %.*s exists\n", AuDLNPair(path->dentry));
+
+	return err;
+}
+
+struct au_wh_base {
+	const struct qstr *name;
+	struct dentry *dentry;
+};
+
+static void au_wh_init_ro(struct inode *h_dir, struct au_wh_base base[],
+			  struct path *h_path)
+{
+	h_path->dentry = base[AuBrWh_BASE].dentry;
+	au_wh_clean(h_dir, h_path, /*isdir*/0);
+	h_path->dentry = base[AuBrWh_PLINK].dentry;
+	au_wh_clean(h_dir, h_path, /*isdir*/1);
+	h_path->dentry = base[AuBrWh_ORPH].dentry;
+	au_wh_clean(h_dir, h_path, /*isdir*/1);
+}
+
+/*
+ * returns tri-state,
+ * minus: error, caller should print the mesage
+ * zero: succuess
+ * plus: error, caller should NOT print the mesage
+ */
+static int au_wh_init_rw_nolink(struct dentry *h_root, struct au_wbr *wbr,
+				int do_plink, struct au_wh_base base[],
+				struct path *h_path)
+{
+	int err;
+	struct inode *h_dir;
+
+	h_dir = h_root->d_inode;
+	h_path->dentry = base[AuBrWh_BASE].dentry;
+	au_wh_clean(h_dir, h_path, /*isdir*/0);
+	h_path->dentry = base[AuBrWh_PLINK].dentry;
+	if (do_plink) {
+		err = test_linkable(h_root);
+		if (unlikely(err)) {
+			err = 1;
+			goto out;
+		}
+
+		err = au_whdir(h_dir, h_path);
+		if (unlikely(err))
+			goto out;
+		wbr->wbr_plink = dget(base[AuBrWh_PLINK].dentry);
+	} else
+		au_wh_clean(h_dir, h_path, /*isdir*/1);
+	h_path->dentry = base[AuBrWh_ORPH].dentry;
+	err = au_whdir(h_dir, h_path);
+	if (unlikely(err))
+		goto out;
+	wbr->wbr_orph = dget(base[AuBrWh_ORPH].dentry);
+
+ out:
+	return err;
+}
+
+/*
+ * for the moment, aufs supports the branch filesystem which does not support
+ * link(2). testing on FAT which does not support i_op->setattr() fully either,
+ * copyup failed. finally, such filesystem will not be used as the writable
+ * branch.
+ *
+ * returns tri-state, see above.
+ */
+static int au_wh_init_rw(struct dentry *h_root, struct au_wbr *wbr,
+			 int do_plink, struct au_wh_base base[],
+			 struct path *h_path)
+{
+	int err;
+	struct inode *h_dir;
+
+	err = test_linkable(h_root);
+	if (unlikely(err)) {
+		err = 1;
+		goto out;
+	}
+
+	/*
+	 * todo: should this create be done in /sbin/mount.aufs helper?
+	 */
+	err = -EEXIST;
+	h_dir = h_root->d_inode;
+	if (!base[AuBrWh_BASE].dentry->d_inode) {
+		err = mnt_want_write(h_path->mnt);
+		if (!err) {
+			h_path->dentry = base[AuBrWh_BASE].dentry;
+			err = vfsub_create(h_dir, h_path, WH_MASK);
+			mnt_drop_write(h_path->mnt);
+		}
+	} else if (S_ISREG(base[AuBrWh_BASE].dentry->d_inode->i_mode))
+		err = 0;
+	else
+		AuErr("unknown %.*s/%.*s exists\n",
+		      AuDLNPair(h_root), AuDLNPair(base[AuBrWh_BASE].dentry));
+	if (unlikely(err))
+		goto out;
+
+	h_path->dentry = base[AuBrWh_PLINK].dentry;
+	if (do_plink) {
+		err = au_whdir(h_dir, h_path);
+		if (unlikely(err))
+			goto out;
+		wbr->wbr_plink = dget(base[AuBrWh_PLINK].dentry);
+	} else
+		au_wh_clean(h_dir, h_path, /*isdir*/1);
+	wbr->wbr_whbase = dget(base[AuBrWh_BASE].dentry);
+
+	h_path->dentry = base[AuBrWh_ORPH].dentry;
+	err = au_whdir(h_dir, h_path);
+	if (unlikely(err))
+		goto out;
+	wbr->wbr_orph = dget(base[AuBrWh_ORPH].dentry);
+
+ out:
+	return err;
+}
+
+/*
+ * initialize the whiteout base file/dir for @br.
+ */
+int au_wh_init(struct dentry *h_root, struct au_branch *br,
+	       struct super_block *sb)
+{
+	int err, i;
+	const unsigned char do_plink
+		= !!au_opt_test(au_mntflags(sb), PLINK);
+	struct path path = {
+		.mnt = br->br_mnt
+	};
+	struct inode *h_dir;
+	struct au_wbr *wbr = br->br_wbr;
+	static const struct qstr base_name[] = {
+		[AuBrWh_BASE] = {
+			.name	= AUFS_BASE_NAME,
+			.len	= sizeof(AUFS_BASE_NAME) - 1
+		},
+		[AuBrWh_PLINK] = {
+			.name	= AUFS_PLINKDIR_NAME,
+			.len	= sizeof(AUFS_PLINKDIR_NAME) - 1
+		},
+		[AuBrWh_ORPH] = {
+			.name	= AUFS_ORPHDIR_NAME,
+			.len	= sizeof(AUFS_ORPHDIR_NAME) - 1
+		}
+	};
+	struct au_wh_base base[] = {
+		[AuBrWh_BASE] = {
+			.name	= base_name + AuBrWh_BASE,
+			.dentry	= NULL
+		},
+		[AuBrWh_PLINK] = {
+			.name	= base_name + AuBrWh_PLINK,
+			.dentry	= NULL
+		},
+		[AuBrWh_ORPH] = {
+			.name	= base_name + AuBrWh_ORPH,
+			.dentry	= NULL
+		}
+	};
+
+
+	h_dir = h_root->d_inode;
+	for (i = 0; i < AuBrWh_Last; i++) {
+		/* doubly whiteouted */
+		struct dentry *d;
+
+		d = au_wh_lkup(h_root, (void *)base[i].name, br);
+		err = PTR_ERR(d);
+		if (IS_ERR(d))
+			goto out;
+
+		base[i].dentry = d;
+		AuDebugOn(wbr
+			  && wbr->wbr_wh[i]
+			  && wbr->wbr_wh[i] != base[i].dentry);
+	}
+
+	if (wbr)
+		for (i = 0; i < AuBrWh_Last; i++) {
+			dput(wbr->wbr_wh[i]);
+			wbr->wbr_wh[i] = NULL;
+		}
+
+	err = 0;
+
+	switch (br->br_perm) {
+	case AuBrPerm_RO:
+	case AuBrPerm_ROWH:
+	case AuBrPerm_RR:
+	case AuBrPerm_RRWH:
+		au_wh_init_ro(h_dir, base, &path);
+		break;
+
+	case AuBrPerm_RWNoLinkWH:
+		err = au_wh_init_rw_nolink(h_root, wbr, do_plink, base, &path);
+		if (err > 0)
+			goto out;
+		else if (err)
+			goto out_err;
+		break;
+
+	case AuBrPerm_RW:
+		err = au_wh_init_rw(h_root, wbr, do_plink, base, &path);
+		if (err > 0)
+			goto out;
+		else if (err)
+			goto out_err;
+		break;
+
+	default:
+		BUG();
+	}
+	goto out; /* success */
+
+ out_err:
+	AuErr("an error(%d) on the writable branch %.*s(%s)\n",
+	      err, AuDLNPair(h_root), au_sbtype(h_root->d_sb));
+ out:
+	for (i = 0; i < AuBrWh_Last; i++)
+		dput(base[i].dentry);
+	return err;
+}
+
+/* ---------------------------------------------------------------------- */
+/*
+ * whiteouts are all hard-linked usually.
+ * when its link count reaches a ceiling, we create a new whiteout base
+ * asynchronously.
+ */
+
+struct reinit_br_wh {
+	struct super_block *sb;
+	struct au_branch *br;
+};
+
+static void reinit_br_wh(void *arg)
+{
+	int err;
+	aufs_bindex_t bindex;
+	struct path h_path;
+	struct reinit_br_wh *a = arg;
+	struct au_wbr *wbr;
+	struct inode *dir;
+	struct dentry *h_root;
+	struct au_hinode *hdir;
+
+	err = 0;
+	wbr = a->br->br_wbr;
+	/* big aufs lock */
+	si_noflush_write_lock(a->sb);
+	if (!au_br_writable(a->br->br_perm))
+		goto out;
+	bindex = au_br_index(a->sb, a->br->br_id);
+	if (unlikely(bindex < 0))
+		goto out;
+
+	dir = a->sb->s_root->d_inode;
+	/* ii_read_lock_parent(dir); */
+	hdir = au_hi(dir, bindex);
+	h_root = au_h_dptr(a->sb->s_root, bindex);
+
+	au_hin_imtx_lock_nested(hdir, AuLsc_I_PARENT);
+	wbr_wh_write_lock(wbr);
+	err = au_h_verify(wbr->wbr_whbase, au_opt_udba(a->sb), hdir->hi_inode,
+			  h_root, a->br);
+	if (!err) {
+		err = mnt_want_write(a->br->br_mnt);
+		if (!err) {
+			h_path.dentry = wbr->wbr_whbase;
+			h_path.mnt = a->br->br_mnt;
+			err = vfsub_unlink(hdir->hi_inode, &h_path, /*force*/0);
+			mnt_drop_write(a->br->br_mnt);
+		}
+	} else {
+		AuWarn("%.*s is moved, ignored\n", AuDLNPair(wbr->wbr_whbase));
+		err = 0;
+	}
+	dput(wbr->wbr_whbase);
+	wbr->wbr_whbase = NULL;
+	if (!err)
+		err = au_wh_init(h_root, a->br, a->sb);
+	wbr_wh_write_unlock(wbr);
+	au_hin_imtx_unlock(hdir);
+	/* ii_read_unlock(dir); */
+
+ out:
+	if (wbr)
+		atomic_dec(&wbr->wbr_wh_running);
+	atomic_dec(&a->br->br_count);
+	au_nwt_done(&au_sbi(a->sb)->si_nowait);
+	si_write_unlock(a->sb);
+	kfree(arg);
+	if (unlikely(err))
+		AuIOErr("err %d\n", err);
+}
+
+static void kick_reinit_br_wh(struct super_block *sb, struct au_branch *br)
+{
+	int do_dec, wkq_err;
+	struct reinit_br_wh *arg;
+
+	do_dec = 1;
+	if (atomic_inc_return(&br->br_wbr->wbr_wh_running) != 1)
+		goto out;
+
+	/* ignore ENOMEM */
+	arg = kmalloc(sizeof(*arg), GFP_NOFS);
+	if (arg) {
+		/*
+		 * dec(wh_running), kfree(arg) and dec(br_count)
+		 * in reinit function
+		 */
+		arg->sb = sb;
+		arg->br = br;
+		atomic_inc(&br->br_count);
+		wkq_err = au_wkq_nowait(reinit_br_wh, arg, sb);
+		if (unlikely(wkq_err)) {
+			atomic_dec(&br->br_wbr->wbr_wh_running);
+			atomic_dec(&br->br_count);
+			kfree(arg);
+		}
+		do_dec = 0;
+	}
+
+ out:
+	if (do_dec)
+		atomic_dec(&br->br_wbr->wbr_wh_running);
+}
+
+/* ---------------------------------------------------------------------- */
+
+/*
+ * create the whiteout @wh.
+ */
+static int link_or_create_wh(struct super_block *sb, aufs_bindex_t bindex,
+			     struct dentry *wh)
+{
+	int err;
+	struct path h_path = {
+		.dentry = wh
+	};
+	struct au_branch *br;
+	struct au_wbr *wbr;
+	struct dentry *h_parent;
+	struct inode *h_dir;
+
+	h_parent = wh->d_parent; /* dir inode is locked */
+	h_dir = h_parent->d_inode;
+	IMustLock(h_dir);
+
+	br = au_sbr(sb, bindex);
+	h_path.mnt = br->br_mnt;
+	wbr = br->br_wbr;
+	wbr_wh_read_lock(wbr);
+	if (wbr->wbr_whbase) {
+		err = vfsub_link(wbr->wbr_whbase, h_dir, &h_path);
+		if (!err || err != -EMLINK)
+			goto out;
+
+		/* link count full. re-initialize br_whbase. */
+		kick_reinit_br_wh(sb, br);
+	}
+
+	/* return this error in this context */
+	err = vfsub_create(h_dir, &h_path, WH_MASK);
+
+ out:
+	wbr_wh_read_unlock(wbr);
+	return err;
+}
+
+/* ---------------------------------------------------------------------- */
+
+/*
+ * create or remove the diropq.
+ */
+static struct dentry *do_diropq(struct dentry *dentry, aufs_bindex_t bindex,
+				unsigned int flags)
+{
+	struct dentry *opq_dentry, *h_dentry;
+	struct super_block *sb;
+	struct au_branch *br;
+	int err;
+
+	sb = dentry->d_sb;
+	br = au_sbr(sb, bindex);
+	h_dentry = au_h_dptr(dentry, bindex);
+	opq_dentry = au_lkup_one(&diropq_name, h_dentry, br, /*nd*/NULL);
+	if (IS_ERR(opq_dentry))
+		goto out;
+
+	if (au_ftest_diropq(flags, CREATE)) {
+		err = link_or_create_wh(sb, bindex, opq_dentry);
+		if (!err) {
+			au_set_dbdiropq(dentry, bindex);
+			goto out; /* success */
+		}
+	} else {
+		struct path tmp = {
+			.dentry = opq_dentry,
+			.mnt	= br->br_mnt
+		};
+		err = do_unlink_wh(au_h_iptr(dentry->d_inode, bindex), &tmp);
+		if (!err)
+			au_set_dbdiropq(dentry, -1);
+	}
+	dput(opq_dentry);
+	opq_dentry = ERR_PTR(err);
+
+ out:
+	return opq_dentry;
+}
+
+struct do_diropq_args {
+	struct dentry **errp;
+	struct dentry *dentry;
+	aufs_bindex_t bindex;
+	unsigned int flags;
+};
+
+static void call_do_diropq(void *args)
+{
+	struct do_diropq_args *a = args;
+	*a->errp = do_diropq(a->dentry, a->bindex, a->flags);
+}
+
+struct dentry *au_diropq_sio(struct dentry *dentry, aufs_bindex_t bindex,
+			     unsigned int flags)
+{
+	struct dentry *diropq, *h_dentry;
+
+	h_dentry = au_h_dptr(dentry, bindex);
+	if (!au_test_h_perm_sio(h_dentry->d_inode, MAY_EXEC | MAY_WRITE))
+		diropq = do_diropq(dentry, bindex, flags);
+	else {
+		int wkq_err;
+		struct do_diropq_args args = {
+			.errp		= &diropq,
+			.dentry		= dentry,
+			.bindex		= bindex,
+			.flags		= flags
+		};
+
+		wkq_err = au_wkq_wait(call_do_diropq, &args);
+		if (unlikely(wkq_err))
+			diropq = ERR_PTR(wkq_err);
+	}
+
+	return diropq;
+}
+
+/* ---------------------------------------------------------------------- */
+
+/*
+ * lookup whiteout dentry.
+ * @h_parent: lower parent dentry which must exist and be locked
+ * @base_name: name of dentry which will be whiteouted
+ * returns dentry for whiteout.
+ */
+struct dentry *au_wh_lkup(struct dentry *h_parent, struct qstr *base_name,
+			  struct au_branch *br)
+{
+	int err;
+	struct qstr wh_name;
+	struct dentry *wh_dentry;
+
+	err = au_wh_name_alloc(&wh_name, base_name);
+	wh_dentry = ERR_PTR(err);
+	if (!err) {
+		wh_dentry = au_lkup_one(&wh_name, h_parent, br, /*nd*/NULL);
+		kfree(wh_name.name);
+	}
+	return wh_dentry;
+}
+
+/*
+ * link/create a whiteout for @dentry on @bindex.
+ */
+struct dentry *au_wh_create(struct dentry *dentry, aufs_bindex_t bindex,
+			    struct dentry *h_parent)
+{
+	struct dentry *wh_dentry;
+	struct super_block *sb;
+	int err;
+
+	sb = dentry->d_sb;
+	wh_dentry = au_wh_lkup(h_parent, &dentry->d_name, au_sbr(sb, bindex));
+	if (!IS_ERR(wh_dentry) && !wh_dentry->d_inode) {
+		err = link_or_create_wh(sb, bindex, wh_dentry);
+		if (!err)
+			au_set_dbwh(dentry, bindex);
+		else {
+			dput(wh_dentry);
+			wh_dentry = ERR_PTR(err);
+		}
+	}
+
+	return wh_dentry;
+}
+
+/* ---------------------------------------------------------------------- */
+
+/* Delete all whiteouts in this directory on branch bindex. */
+static int del_wh_children(struct dentry *h_dentry, struct au_nhash *whlist,
+			   aufs_bindex_t bindex, struct au_branch *br)
+{
+	int err, i;
+	struct qstr wh_name;
+	char *p;
+	struct hlist_head *head;
+	struct au_vdir_wh *tpos;
+	struct hlist_node *pos;
+	struct au_vdir_destr *str;
+
+	err = -ENOMEM;
+	p = __getname();
+	wh_name.name = p;
+	if (unlikely(!wh_name.name))
+		goto out;
+
+	err = 0;
+	memcpy(p, AUFS_WH_PFX, AUFS_WH_PFX_LEN);
+	p += AUFS_WH_PFX_LEN;
+	head = whlist->heads;
+	for (i = 0; !err && i < AuSize_NHASH; i++, head++) {
+		hlist_for_each_entry(tpos, pos, head, wh_hash) {
+			if (tpos->wh_bindex != bindex)
+				continue;
+
+			str = &tpos->wh_str;
+			if (str->len + AUFS_WH_PFX_LEN <= PATH_MAX) {
+				memcpy(p, str->name, str->len);
+				wh_name.len = AUFS_WH_PFX_LEN + str->len;
+				err = unlink_wh_name(h_dentry, &wh_name, br);
+				if (!err)
+					continue;
+				break;
+			}
+			AuIOErr("whiteout name too long %.*s\n",
+				str->len, str->name);
+			err = -EIO;
+			break;
+		}
+	}
+	__putname(wh_name.name);
+
+ out:
+	return err;
+}
+
+struct del_wh_children_args {
+	int *errp;
+	struct dentry *h_dentry;
+	struct au_nhash *whlist;
+	aufs_bindex_t bindex;
+	struct au_branch *br;
+};
+
+static void call_del_wh_children(void *args)
+{
+	struct del_wh_children_args *a = args;
+	*a->errp = del_wh_children(a->h_dentry, a->whlist, a->bindex, a->br);
+}
+
+/* ---------------------------------------------------------------------- */
+
+/*
+ * rmdir the whiteouted temporary named dir @h_dentry.
+ * @whlist: whiteouted children.
+ */
+int au_whtmp_rmdir(struct inode *dir, aufs_bindex_t bindex,
+		   struct dentry *wh_dentry, struct au_nhash *whlist)
+{
+	int err;
+	struct path h_tmp;
+	struct inode *wh_inode, *h_dir;
+	struct au_branch *br;
+
+	h_dir = wh_dentry->d_parent->d_inode; /* dir inode is locked */
+	IMustLock(h_dir);
+
+	br = au_sbr(dir->i_sb, bindex);
+	wh_inode = wh_dentry->d_inode;
+	mutex_lock_nested(&wh_inode->i_mutex, AuLsc_I_CHILD);
+
+	/*
+	 * someone else might change some whiteouts while we were sleeping.
+	 * it means this whlist may have an obsoleted entry.
+	 */
+	if (!au_test_h_perm_sio(wh_inode, MAY_EXEC | MAY_WRITE))
+		err = del_wh_children(wh_dentry, whlist, bindex, br);
+	else {
+		int wkq_err;
+		struct del_wh_children_args args = {
+			.errp		= &err,
+			.h_dentry	= wh_dentry,
+			.whlist		= whlist,
+			.bindex		= bindex,
+			.br		= br
+		};
+
+		wkq_err = au_wkq_wait(call_del_wh_children, &args);
+		if (unlikely(wkq_err))
+			err = wkq_err;
+	}
+	mutex_unlock(&wh_inode->i_mutex);
+
+	if (!err) {
+		h_tmp.dentry = wh_dentry;
+		h_tmp.mnt = br->br_mnt;
+		err = vfsub_rmdir(h_dir, &h_tmp);
+		/* d_drop(h_dentry); */
+	}
+
+	if (!err) {
+		if (au_ibstart(dir) == bindex) {
+			au_cpup_attr_timesizes(dir);
+			drop_nlink(dir);
+		}
+		return 0; /* success */
+	}
+
+	AuWarn("failed removing %.*s(%d), ignored\n",
+	       AuDLNPair(wh_dentry), err);
+	return err;
+}
+
+static void au_whtmp_rmdir_free_args(struct au_whtmp_rmdir_args *args)
+{
+	au_nhash_fin(&args->whlist);
+	dput(args->wh_dentry);
+	iput(args->dir);
+	kfree(args);
+}
+
+static void call_rmdir_whtmp(void *args)
+{
+	int err;
+	struct au_whtmp_rmdir_args *a = args;
+	struct super_block *sb;
+	struct dentry *h_parent;
+	struct inode *h_dir;
+	struct au_branch *br;
+	struct au_hinode *hdir;
+
+	/* rmdir by nfsd may cause deadlock with this i_mutex */
+	/* mutex_lock(&a->dir->i_mutex); */
+	sb = a->dir->i_sb;
+	si_noflush_read_lock(sb);
+	err = au_test_ro(sb, a->bindex, NULL);
+	if (unlikely(err))
+		goto out;
+
+	err = -EIO;
+	br = au_sbr(sb, a->bindex);
+	ii_write_lock_parent(a->dir);
+	h_parent = dget_parent(a->wh_dentry);
+	h_dir = h_parent->d_inode;
+	hdir = au_hi(a->dir, a->bindex);
+	au_hin_imtx_lock_nested(hdir, AuLsc_I_PARENT);
+	err = au_h_verify(a->wh_dentry, au_opt_udba(sb), h_dir, h_parent, br);
+	if (!err) {
+		err = mnt_want_write(br->br_mnt);
+		if (!err) {
+			err = au_whtmp_rmdir(a->dir, a->bindex, a->wh_dentry,
+					     &a->whlist);
+			mnt_drop_write(br->br_mnt);
+		}
+	}
+	au_hin_imtx_unlock(hdir);
+	dput(h_parent);
+	ii_write_unlock(a->dir);
+
+ out:
+	/* mutex_unlock(&a->dir->i_mutex); */
+	au_nwt_done(&au_sbi(sb)->si_nowait);
+	si_read_unlock(sb);
+	au_whtmp_rmdir_free_args(a);
+	if (unlikely(err))
+		AuIOErr("err %d\n", err);
+}
+
+void au_whtmp_kick_rmdir(struct inode *dir, aufs_bindex_t bindex,
+			 struct dentry *wh_dentry, struct au_nhash *whlist,
+			 struct au_whtmp_rmdir_args *args)
+{
+	int wkq_err;
+
+	IMustLock(dir);
+
+	/* all post-process will be done in do_rmdir_whtmp(). */
+	args->dir = au_igrab(dir);
+	args->bindex = bindex;
+	args->wh_dentry = dget(wh_dentry);
+	au_nhash_init(&args->whlist);
+	au_nhash_move(&args->whlist, whlist);
+	wkq_err = au_wkq_nowait(call_rmdir_whtmp, args, dir->i_sb);
+	if (unlikely(wkq_err)) {
+		AuWarn("rmdir error %.*s (%d), ignored\n",
+		       AuDLNPair(wh_dentry), wkq_err);
+		au_whtmp_rmdir_free_args(args);
+	}
+}
diff --git a/fs/aufs/whout.h b/fs/aufs/whout.h
new file mode 100644
index 0000000..084e34c
--- /dev/null
+++ b/fs/aufs/whout.h
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2005-2009 Junjiro R. Okajima
+ *
+ * This program, aufs is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+/*
+ * whiteout for logical deletion and opaque directory
+ */
+
+#ifndef __AUFS_WHOUT_H__
+#define __AUFS_WHOUT_H__
+
+#ifdef __KERNEL__
+
+#include <linux/fs.h>
+#include <linux/aufs_type.h>
+#include "dir.h"
+
+/* whout.c */
+int au_wh_name_alloc(struct qstr *wh, const struct qstr *name);
+struct au_branch;
+int au_wh_test(struct dentry *h_parent, struct qstr *wh_name,
+	       struct au_branch *br, int try_sio);
+int au_diropq_test(struct dentry *h_dentry, struct au_branch *br);
+struct dentry *au_whtmp_lkup(struct dentry *h_parent, struct au_branch *br,
+			     struct qstr *prefix);
+int au_whtmp_ren(struct dentry *h_dentry, struct au_branch *br);
+int au_wh_unlink_dentry(struct inode *h_dir, struct path *h_path,
+			struct dentry *dentry);
+int au_wh_init(struct dentry *h_parent, struct au_branch *br,
+	       struct super_block *sb);
+
+/* diropq flags */
+#define AuDiropq_CREATE	1
+#define au_ftest_diropq(flags, name)	((flags) & AuDiropq_##name)
+#define au_fset_diropq(flags, name)	{ (flags) |= AuDiropq_##name; }
+#define au_fclr_diropq(flags, name)	{ (flags) &= ~AuDiropq_##name; }
+
+struct dentry *au_diropq_sio(struct dentry *dentry, aufs_bindex_t bindex,
+			     unsigned int flags);
+struct dentry *au_wh_lkup(struct dentry *h_parent, struct qstr *base_name,
+			  struct au_branch *br);
+struct dentry *au_wh_create(struct dentry *dentry, aufs_bindex_t bindex,
+			    struct dentry *h_parent);
+
+/* real rmdir for the whiteout-ed dir */
+struct au_whtmp_rmdir_args {
+	struct inode *dir;
+	aufs_bindex_t bindex;
+	struct dentry *wh_dentry;
+	struct au_nhash whlist;
+};
+
+int au_whtmp_rmdir(struct inode *dir, aufs_bindex_t bindex,
+		   struct dentry *wh_dentry, struct au_nhash *whlist);
+void au_whtmp_kick_rmdir(struct inode *dir, aufs_bindex_t bindex,
+			 struct dentry *wh_dentry, struct au_nhash *whlist,
+			 struct au_whtmp_rmdir_args *args);
+
+/* ---------------------------------------------------------------------- */
+
+static inline struct dentry *au_diropq_create(struct dentry *dentry,
+					      aufs_bindex_t bindex)
+{
+	return au_diropq_sio(dentry, bindex, AuDiropq_CREATE);
+}
+
+static inline int au_diropq_remove(struct dentry *dentry, aufs_bindex_t bindex)
+{
+	return PTR_ERR(au_diropq_sio(dentry, bindex, !AuDiropq_CREATE));
+}
+
+#endif /* __KERNEL__ */
+#endif /* __AUFS_WHOUT_H__ */
-- 
1.6.1.284.g5dc13


  parent reply	other threads:[~2009-03-09  3:33 UTC|newest]

Thread overview: 31+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2009-03-09  3:24 [Aufs 00/25] source files J. R. Okajima
2009-03-09  3:24 ` [Aufs 01/25] aufs documents J. R. Okajima
2009-03-09  5:48   ` Sam Ravnborg
2009-03-09  8:28     ` hooanon05
2009-03-09  3:24 ` [Aufs 02/25] aufs public header file J. R. Okajima
2009-03-09  5:50   ` Sam Ravnborg
2009-03-09  8:30     ` hooanon05
2009-03-09  3:24 ` [Aufs 03/25] aufs module global J. R. Okajima
2009-03-09  5:51   ` Sam Ravnborg
2009-03-09  3:24 ` [Aufs 04/25] aufs super_block J. R. Okajima
2009-03-09  3:24 ` [Aufs 05/25] aufs branch directory/filesystem J. R. Okajima
2009-03-09  3:24 ` [Aufs 06/25] aufs xino J. R. Okajima
2009-03-09  3:25 ` [Aufs 07/25] aufs object lifetime management via sysfs J. R. Okajima
2009-03-09  3:25 ` [Aufs 08/25] aufs mount options/flags J. R. Okajima
2009-03-09  3:25 ` [Aufs 09/25] aufs workqueue J. R. Okajima
2009-03-09  3:25 ` [Aufs 10/25] aufs sub-VFS J. R. Okajima
2009-03-09  3:25 ` [Aufs 11/25] aufs sub-dcache J. R. Okajima
2009-03-09  3:25 ` [Aufs 12/25] aufs copy-up J. R. Okajima
2009-03-09  3:25 ` J. R. Okajima [this message]
2009-03-09  3:25 ` [Aufs 14/25] aufs pseudo-link J. R. Okajima
2009-03-09  3:25 ` [Aufs 15/25] aufs policies to select one among multiple writable branches J. R. Okajima
2009-03-09  3:25 ` [Aufs 16/25] aufs dentry and lookup J. R. Okajima
2009-03-09  3:25 ` [Aufs 17/25] aufs file J. R. Okajima
2009-03-09  3:25 ` [Aufs 18/25] aufs direcotry J. R. Okajima
2009-03-09  3:25 ` [Aufs 19/25] aufs inode J. R. Okajima
2009-03-09  3:25 ` [Aufs 20/25] aufs ioctl J. R. Okajima
2009-03-09  3:25 ` [Aufs 21/25] aufs sysfs entries J. R. Okajima
2009-03-09  3:25 ` [Aufs 22/25] aufs branch for loopback block device J. R. Okajima
2009-03-09  3:25 ` [Aufs 23/25] aufs internal inotify J. R. Okajima
2009-03-09  3:25 ` [Aufs 24/25] aufs test for fstype J. R. Okajima
2009-03-09  3:25 ` [Aufs 25/25] aufs debug J. R. Okajima

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=1236569118-3750-14-git-send-email-hooanon05@yahoo.co.jp \
    --to=hooanon05@yahoo.co.jp \
    --cc=linux-fsdevel@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.