[AUFS PATCH v2.6.26-rc2-mm1 14/39] aufs copy-up

From: hooanon05
Date: Tue May 20 2008 - 23:46:40 EST


From: Junjiro Okajima <hooanon05@xxxxxxxxxxx>

initial commit
internal copy-up functions
see wbr_policy.c for copy-down

Signed-off-by: Junjiro Okajima <hooanon05@xxxxxxxxxxx>
---
fs/aufs/cpup.c | 1021 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
fs/aufs/cpup.h | 80 +++++
2 files changed, 1101 insertions(+), 0 deletions(-)
create mode 100644 fs/aufs/cpup.c
create mode 100644 fs/aufs/cpup.h

diff --git a/fs/aufs/cpup.c b/fs/aufs/cpup.c
new file mode 100644
index 0000000..2fbfe9b
--- /dev/null
+++ b/fs/aufs/cpup.c
@@ -0,0 +1,1021 @@
+/*
+ * Copyright (C) 2005-2008 Junjiro 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/*
+ * copy-up functions, see wbr_policy.c for copy-down
+ */
+
+#include <linux/fs_stack.h>
+#include <linux/uaccess.h>
+#include "aufs.h"
+
+/* todo? violent cpup_attr_*() functions don't care inode lock */
+void au_cpup_attr_timesizes(struct inode *inode)
+{
+ struct inode *h_inode;
+
+ LKTRTrace("i%lu\n", inode->i_ino);
+ /* todo? IMustLock(inode); */
+ h_inode = au_h_iptr(inode, au_ibstart(inode));
+ AuDebugOn(!h_inode);
+ /* todo? IMustLock(!h_inode); */
+
+ fsstack_copy_attr_times(inode, h_inode);
+ /* todo: this spin_lock conflicts the new unionfs patch in -mm tree */
+ /* spin_lock(&inode->i_lock); */
+ fsstack_copy_inode_size(inode, h_inode);
+ /* spin_unlock(&inode->i_lock); */
+}
+
+void au_cpup_attr_nlink(struct inode *inode)
+{
+ struct inode *h_inode;
+
+ LKTRTrace("i%lu\n", inode->i_ino);
+ /* todo? IMustLock(inode); */
+ AuDebugOn(!inode->i_mode);
+
+ h_inode = au_h_iptr(inode, au_ibstart(inode));
+ inode->i_nlink = h_inode->i_nlink;
+
+ /*
+ * fewer nlink makes find(1) noisy, but larger nlink doesn't.
+ * it may includes whplink directory.
+ */
+ if (unlikely(S_ISDIR(h_inode->i_mode))) {
+ aufs_bindex_t bindex, bend;
+ bend = au_ibend(inode);
+ for (bindex = au_ibstart(inode) + 1; bindex <= bend; bindex++) {
+ h_inode = au_h_iptr(inode, bindex);
+ if (h_inode)
+ au_add_nlink(inode, h_inode);
+ }
+ }
+}
+
+void au_cpup_attr_changeable(struct inode *inode)
+{
+ struct inode *h_inode;
+
+ LKTRTrace("i%lu\n", inode->i_ino);
+ /* todo? IMustLock(inode); */
+ h_inode = au_h_iptr(inode, au_ibstart(inode));
+ AuDebugOn(!h_inode);
+
+ inode->i_mode = h_inode->i_mode;
+ inode->i_uid = h_inode->i_uid;
+ inode->i_gid = h_inode->i_gid;
+ au_cpup_attr_timesizes(inode);
+
+ /* todo: remove this? */
+ inode->i_flags = h_inode->i_flags;
+}
+
+void au_cpup_igen(struct inode *inode, struct inode *h_inode)
+{
+ inode->i_generation = h_inode->i_generation;
+ au_ii(inode)->ii_hsb1 = h_inode->i_sb;
+}
+
+void au_cpup_attr_all(struct inode *inode)
+{
+ struct inode *h_inode;
+
+ LKTRTrace("i%lu\n", inode->i_ino);
+ /* todo? IMustLock(inode); */
+ h_inode = au_h_iptr(inode, au_ibstart(inode));
+ AuDebugOn(!h_inode);
+
+ au_cpup_attr_changeable(inode);
+ if (inode->i_nlink > 0)
+ au_cpup_attr_nlink(inode);
+
+ switch (inode->i_mode & S_IFMT) {
+ case S_IFBLK:
+ case S_IFCHR:
+ inode->i_rdev = au_h_rdev(h_inode, /*h_mnt*/NULL,
+ /*h_dentry*/NULL);
+ }
+ inode->i_blkbits = h_inode->i_blkbits;
+ au_cpup_igen(inode, h_inode);
+}
+
+/* ---------------------------------------------------------------------- */
+
+/* Note: dt_dentry and dt_hidden_dentry are not dget/dput-ed */
+
+/* keep the timestamps of the parent dir when cpup */
+void au_dtime_store(struct au_dtime *dt, struct dentry *dentry,
+ struct dentry *h_dentry, struct au_hinode *hdir)
+{
+ struct inode *inode;
+
+ LKTRTrace("%.*s, hdir %d\n", AuDLNPair(dentry), !!hdir);
+ AuDebugOn(!dentry || !h_dentry || !h_dentry->d_inode);
+
+ dt->dt_dentry = dentry;
+ dt->dt_h_dentry = h_dentry;
+ dt->dt_hdir = hdir;
+ inode = h_dentry->d_inode;
+ dt->dt_atime = inode->i_atime;
+ dt->dt_mtime = inode->i_mtime;
+ /* smp_mb(); */
+}
+
+void au_dtime_revert(struct au_dtime *dt)
+{
+ struct iattr attr;
+ int err;
+ struct au_hin_ignore ign;
+ struct vfsub_args vargs;
+
+ LKTRTrace("%.*s\n", AuDLNPair(dt->dt_dentry));
+
+ attr.ia_atime = dt->dt_atime;
+ attr.ia_mtime = dt->dt_mtime;
+ attr.ia_valid = ATTR_FORCE | ATTR_MTIME | ATTR_MTIME_SET
+ | ATTR_ATIME | ATTR_ATIME_SET;
+
+ vfsub_args_init(&vargs, &ign,
+ au_opt_test_dlgt(au_mntflags(dt->dt_dentry->d_sb)), 0);
+ if (unlikely(dt->dt_hdir))
+ vfsub_ign_hinode(&vargs, IN_ATTRIB, dt->dt_hdir);
+ err = vfsub_notify_change(dt->dt_h_dentry, &attr, &vargs);
+ if (unlikely(err))
+ AuWarn("restoring timestamps failed(%d). ignored\n", err);
+}
+
+/* ---------------------------------------------------------------------- */
+
+static noinline_for_stack int
+cpup_iattr(struct dentry *h_dst, struct dentry *h_src, int dlgt)
+{
+ int err, sbits;
+ struct iattr ia;
+ struct inode *h_isrc, *h_idst;
+ struct vfsub_args vargs;
+
+ LKTRTrace("%.*s\n", AuDLNPair(h_dst));
+ h_idst = h_dst->d_inode;
+ /* todo? IMustLock(h_idst); */
+ h_isrc = h_src->d_inode;
+ /* todo? IMustLock(h_isrc); */
+
+ ia.ia_valid = ATTR_FORCE | ATTR_MODE | ATTR_UID | ATTR_GID
+ | ATTR_ATIME | ATTR_MTIME
+ | ATTR_ATIME_SET | ATTR_MTIME_SET;
+ ia.ia_mode = h_isrc->i_mode;
+ ia.ia_uid = h_isrc->i_uid;
+ ia.ia_gid = h_isrc->i_gid;
+ ia.ia_atime = h_isrc->i_atime;
+ ia.ia_mtime = h_isrc->i_mtime;
+ sbits = !!(ia.ia_mode & (S_ISUID | S_ISGID));
+
+ vfsub_args_init(&vargs, NULL, dlgt, /*force_unlink*/0);
+ err = vfsub_notify_change(h_dst, &ia, &vargs);
+
+ /* is this nfs only? */
+ if (!err && sbits && au_test_nfs(h_dst->d_sb)) {
+ ia.ia_valid = ATTR_FORCE | ATTR_MODE;
+ ia.ia_mode = h_isrc->i_mode;
+ err = vfsub_notify_change(h_dst, &ia, &vargs);
+ }
+
+ /* todo? remove this? */
+ if (!err)
+ h_idst->i_flags = h_isrc->i_flags;
+
+ AuTraceErr(err);
+ return err;
+}
+
+/*
+ * to support a sparse file which is opened with O_APPEND,
+ * we need to close the file.
+ */
+static noinline_for_stack int
+cpup_regular(struct dentry *dentry, aufs_bindex_t bdst, aufs_bindex_t bsrc,
+ loff_t len)
+{
+ int err, i;
+ struct super_block *sb;
+ struct inode *h_inode;
+ enum { SRC, DST };
+ struct {
+ aufs_bindex_t bindex;
+ unsigned int flags;
+ struct dentry *dentry;
+ struct file *file;
+ void *label, *label_file;
+ } *h, hidden[] = {
+ {
+ .bindex = bsrc,
+ .flags = O_RDONLY | O_NOATIME | O_LARGEFILE,
+ .file = NULL,
+ .label = &&out,
+ .label_file = &&out_src_file
+ },
+ {
+ .bindex = bdst,
+ .flags = O_WRONLY | O_NOATIME | O_LARGEFILE,
+ .file = NULL,
+ .label = &&out_src_file,
+ .label_file = &&out_dst_file
+ }
+ };
+
+ LKTRTrace("dentry %.*s, bdst %d, bsrc %d, len %lld\n",
+ AuDLNPair(dentry), bdst, bsrc, len);
+ AuDebugOn(bsrc <= bdst);
+ AuDebugOn(!len);
+ sb = dentry->d_sb;
+ AuDebugOn(au_test_ro(sb, bdst, dentry->d_inode));
+ /* bsrc branch can be ro/rw. */
+
+ h = hidden;
+ for (i = 0; i < 2; i++, h++) {
+ h->dentry = au_h_dptr(dentry, h->bindex);
+ AuDebugOn(!h->dentry);
+ h_inode = h->dentry->d_inode;
+ AuDebugOn(!h_inode || !S_ISREG(h_inode->i_mode));
+ h->file = au_h_open(dentry, h->bindex, h->flags, /*file*/NULL);
+ err = PTR_ERR(h->file);
+ if (IS_ERR(h->file))
+ goto *h->label;
+ err = -EINVAL;
+ if (unlikely(!h->file->f_op))
+ goto *h->label_file;
+ }
+
+ /* stop updating while we copyup */
+ IMustLock(hidden[SRC].dentry->d_inode);
+ err = au_copy_file(hidden[DST].file, hidden[SRC].file, len, sb);
+
+ out_dst_file:
+ fput(hidden[DST].file);
+ au_sbr_put(sb, hidden[DST].bindex);
+ out_src_file:
+ fput(hidden[SRC].file);
+ au_sbr_put(sb, hidden[SRC].bindex);
+ out:
+ AuTraceErr(err);
+ return err;
+}
+
+static noinline_for_stack int
+au_do_cpup_regular(struct dentry *dentry, aufs_bindex_t bdst,
+ aufs_bindex_t bsrc, loff_t len, struct inode *h_inode,
+ struct inode *h_dir, struct dentry *h_dst, int dlgt)
+{
+ int err, rerr;
+ loff_t l;
+ struct vfsub_args vargs;
+
+ AuTraceEnter();
+
+ err = 0;
+ l = i_size_read(h_inode);
+ if (len == -1 || l < len)
+ len = l;
+ if (len)
+ err = cpup_regular(dentry, bdst, bsrc, len);
+ if (!err)
+ goto out; /* success */
+
+ vfsub_args_init(&vargs, NULL, dlgt, 0);
+ rerr = vfsub_unlink(h_dir, h_dst, &vargs);
+ if (rerr) {
+ AuIOErr("failed unlinking cpup-ed %.*s(%d, %d)\n",
+ AuDLNPair(h_dst), err, rerr);
+ err = -EIO;
+ }
+
+ out:
+ AuTraceErr(err);
+ return err;
+}
+
+static noinline_for_stack int
+au_do_cpup_symlink(struct dentry *h_dst, struct dentry *h_src,
+ struct inode *h_dir, umode_t mode, int dlgt)
+{
+ int err, symlen;
+ char *sym;
+ mm_segment_t old_fs;
+
+ AuTraceEnter();
+
+ err = -ENOMEM;
+ sym = __getname();
+ if (unlikely(!sym))
+ goto out;
+
+ old_fs = get_fs();
+ set_fs(KERNEL_DS);
+ symlen = h_src->d_inode->i_op->readlink(h_src, (char __user *)sym,
+ PATH_MAX);
+ err = symlen;
+ set_fs(old_fs);
+
+ if (symlen > 0) {
+ sym[symlen] = 0;
+ err = vfsub_symlink(h_dir, h_dst, sym, mode, dlgt);
+ }
+ __putname(sym);
+
+ out:
+ AuTraceErr(err);
+ return err;
+}
+
+/* return with hidden dst inode is locked */
+static noinline_for_stack int
+cpup_entry(struct dentry *dentry, aufs_bindex_t bdst, aufs_bindex_t bsrc,
+ loff_t len, unsigned int flags, int dlgt)
+{
+ int err, isdir, hinotify;
+ struct dentry *h_src, *h_dst, *h_parent, *parent;
+ struct inode *h_inode, *h_dir;
+ struct au_dtime dt;
+ umode_t mode;
+ struct super_block *sb;
+ struct au_hinode *hgdir;
+ const int do_dt = au_ftest_cpup(flags, DTIME);
+ unsigned int mnt_flags;
+
+ LKTRTrace("%.*s, i%lu, bdst %d, bsrc %d, len %Ld, dtime %u\n",
+ AuDLNPair(dentry), dentry->d_inode->i_ino, bdst, bsrc, len,
+ do_dt);
+ sb = dentry->d_sb;
+ AuDebugOn(bdst >= bsrc || au_test_ro(sb, bdst, NULL));
+ /* bsrc branch can be ro/rw. */
+
+ h_src = au_h_dptr(dentry, bsrc);
+ AuDebugOn(!h_src);
+ h_inode = h_src->d_inode;
+ AuDebugOn(!h_inode);
+
+ /* stop referencing while we are creating */
+ parent = dget_parent(dentry);
+ h_dst = au_h_dptr(dentry, bdst);
+ AuDebugOn(h_dst && h_dst->d_inode);
+ h_parent = h_dst->d_parent; /* dir inode is locked */
+ h_dir = h_parent->d_inode;
+ IMustLock(h_dir);
+
+ mnt_flags = au_mntflags(sb);
+ hinotify = !!au_opt_test(mnt_flags, UDBA_INOTIFY);
+ if (do_dt) {
+ hgdir = NULL;
+ if (unlikely(hinotify && !IS_ROOT(parent))) {
+ struct dentry *gparent;
+ gparent = dget_parent(parent);
+ hgdir = au_hi(gparent->d_inode, bdst);
+ dput(gparent);
+ }
+ au_dtime_store(&dt, parent, h_parent, hgdir);
+ }
+
+ isdir = 0;
+ mode = h_inode->i_mode;
+ switch (mode & S_IFMT) {
+ case S_IFREG:
+ /* stop updating while we are referencing */
+ IMustLock(h_inode);
+ err = au_h_create(h_dir, h_dst, mode | S_IWUSR, dlgt, NULL,
+ au_nfsmnt(sb, bdst));
+ if (!err)
+ err = au_do_cpup_regular(dentry, bdst, bsrc, len,
+ h_inode, h_dir, h_dst, dlgt);
+ break;
+ case S_IFDIR:
+ isdir = 1;
+ err = vfsub_mkdir(h_dir, h_dst, mode, dlgt);
+ if (!err) {
+ /* setattr case: dir is not locked */
+ if (0 && au_ibstart(parent->d_inode) == bdst)
+ au_cpup_attr_nlink(parent->d_inode);
+ au_cpup_attr_nlink(dentry->d_inode);
+ }
+ break;
+ case S_IFLNK:
+ err = au_do_cpup_symlink(h_dst, h_src, h_dir, mode, dlgt);
+ break;
+ case S_IFCHR:
+ case S_IFBLK:
+ AuDebugOn(!capable(CAP_MKNOD));
+ /*FALLTHROUGH*/
+ case S_IFIFO:
+ case S_IFSOCK:
+ err = vfsub_mknod(h_dir, h_dst, mode,
+ au_h_rdev(h_inode, /*h_mnt*/NULL, h_src),
+ dlgt);
+ break;
+ default:
+ AuIOErr("Unknown inode type 0%o\n", mode);
+ err = -EIO;
+ }
+
+ /* todo: should it be always? */
+ if (unlikely(hinotify
+ && !isdir
+ && au_opt_test(mnt_flags, XINO)
+ && h_inode->i_nlink == 1
+ && bdst < bsrc))
+ au_xino_write0(sb, bsrc, h_inode->i_ino, /*ino*/0);
+ /* ignore this error */
+
+ if (do_dt)
+ au_dtime_revert(&dt);
+ dput(parent);
+ AuTraceErr(err);
+ return err;
+}
+
+/*
+ * copyup the @dentry from @bsrc to @bdst.
+ * the caller must set the both of hidden dentries.
+ * @len is for truncating when it is -1 copyup the entire file.
+ */
+int au_cpup_single(struct dentry *dentry, aufs_bindex_t bdst,
+ aufs_bindex_t bsrc, loff_t len, unsigned int flags)
+{
+ int err, rerr, isdir, dlgt, plink;
+ struct dentry *h_src, *h_dst, *parent, *h_parent;
+ struct inode *dst_inode, *h_dir, *inode;
+ struct super_block *sb;
+ aufs_bindex_t old_ibstart;
+ struct au_dtime dt;
+ struct vfsub_args vargs;
+ struct au_hinode *hgdir;
+ unsigned int mnt_flags;
+
+ LKTRTrace("%.*s, i%lu, bdst %d, bsrc %d, len %Ld, flags 0x%x\n",
+ AuDLNPair(dentry), dentry->d_inode->i_ino, bdst, bsrc, len,
+ flags);
+ sb = dentry->d_sb;
+ AuDebugOn(bsrc <= bdst);
+ h_dst = au_h_dptr(dentry, bdst);
+ AuDebugOn(!h_dst || h_dst->d_inode);
+ h_parent = h_dst->d_parent; /* dir inode is locked */
+ h_dir = h_parent->d_inode;
+ IMustLock(h_dir);
+ h_src = au_h_dptr(dentry, bsrc);
+ AuDebugOn(!h_src || !h_src->d_inode);
+ inode = dentry->d_inode;
+ IiMustWriteLock(inode);
+ parent = dget_parent(dentry);
+
+ mnt_flags = au_mntflags(sb);
+ dlgt = !!au_opt_test_dlgt(mnt_flags);
+ plink = !!au_opt_test(mnt_flags, PLINK);
+ dst_inode = au_h_iptr(inode, bdst);
+ if (unlikely(dst_inode)) {
+ if (unlikely(!plink)) {
+ err = -EIO;
+ AuIOErr("i%lu exists on a upper branch "
+ "but plink is disabled\n", inode->i_ino);
+ goto out;
+ }
+
+ if (dst_inode->i_nlink) {
+ h_src = au_plink_lkup(sb, bdst, inode);
+ err = PTR_ERR(h_src);
+ if (IS_ERR(h_src))
+ goto out;
+ AuDebugOn(!h_src->d_inode);
+ err = vfsub_link(h_src, h_dir, h_dst, dlgt);
+ dput(h_src);
+ goto out;
+ } else
+ /* todo: cpup_wh_file? */
+ /* udba work */
+ au_update_brange(inode, 1);
+ }
+
+ old_ibstart = au_ibstart(inode);
+ err = cpup_entry(dentry, bdst, bsrc, len, flags, dlgt);
+ if (unlikely(err))
+ goto out;
+ dst_inode = h_dst->d_inode;
+ mutex_lock_nested(&dst_inode->i_mutex, AuLsc_I_CHILD2);
+
+ /* todo: test dlgt? */
+ err = cpup_iattr(h_dst, h_src, dlgt);
+#if 0 /* reserved for future use */
+ if (0 && !err)
+ err = cpup_xattrs(h_src, h_dst);
+#endif
+ isdir = S_ISDIR(dst_inode->i_mode);
+ if (!err) {
+ if (bdst < old_ibstart)
+ au_set_ibstart(inode, bdst);
+ au_set_h_iptr(inode, bdst, igrab(dst_inode),
+ au_hi_flags(inode, isdir));
+ mutex_unlock(&dst_inode->i_mutex);
+ if (!isdir
+ && h_src->d_inode->i_nlink > 1
+ && plink)
+ au_plink_append(sb, inode, h_dst, bdst);
+ goto out; /* success */
+ }
+
+ /* revert */
+ mutex_unlock(&dst_inode->i_mutex);
+ hgdir = NULL;
+ if (unlikely(au_opt_test(mnt_flags, UDBA_INOTIFY)
+ && !IS_ROOT(parent))) {
+ struct dentry *gparent;
+ gparent = dget_parent(parent);
+ hgdir = au_hi(gparent->d_inode, bdst);
+ dput(gparent);
+ }
+ au_dtime_store(&dt, parent, h_parent, hgdir);
+ vfsub_args_init(&vargs, NULL, dlgt, 0);
+ if (!isdir)
+ rerr = vfsub_unlink(h_dir, h_dst, &vargs);
+ else
+ rerr = vfsub_rmdir(h_dir, h_dst, &vargs);
+ au_dtime_revert(&dt);
+ if (rerr) {
+ AuIOErr("failed removing broken entry(%d, %d)\n", err, rerr);
+ err = -EIO;
+ }
+
+ out:
+ dput(parent);
+ AuTraceErr(err);
+ return err;
+}
+
+struct au_cpup_single_args {
+ int *errp;
+ struct dentry *dentry;
+ aufs_bindex_t bdst, bsrc;
+ loff_t len;
+ unsigned int flags;
+};
+
+static void au_call_cpup_single(void *args)
+{
+ struct au_cpup_single_args *a = args;
+ *a->errp = au_cpup_single(a->dentry, a->bdst, a->bsrc, a->len,
+ a->flags);
+}
+
+int au_sio_cpup_single(struct dentry *dentry, aufs_bindex_t bdst,
+ aufs_bindex_t bsrc, loff_t len, unsigned int flags)
+{
+ int err, wkq_err;
+ struct dentry *h_dentry;
+ umode_t mode;
+
+ LKTRTrace("%.*s, i%lu, bdst %d, bsrc %d, len %Ld, flags 0x%x\n",
+ AuDLNPair(dentry), dentry->d_inode->i_ino, bdst, bsrc, len,
+ flags);
+
+ h_dentry = au_h_dptr(dentry, bsrc);
+ mode = h_dentry->d_inode->i_mode & S_IFMT;
+ if ((mode != S_IFCHR && mode != S_IFBLK)
+ || capable(CAP_MKNOD))
+ err = au_cpup_single(dentry, bdst, bsrc, len, flags);
+ else {
+ struct au_cpup_single_args args = {
+ .errp = &err,
+ .dentry = dentry,
+ .bdst = bdst,
+ .bsrc = bsrc,
+ .len = len,
+ .flags = flags
+ };
+ wkq_err = au_wkq_wait(au_call_cpup_single, &args, /*dlgt*/0);
+ if (unlikely(wkq_err))
+ err = wkq_err;
+ }
+
+ AuTraceErr(err);
+ return err;
+}
+
+/*
+ * copyup the @dentry from the first active hidden branch to @bdst,
+ * using au_cpup_single().
+ */
+int au_cpup_simple(struct dentry *dentry, aufs_bindex_t bdst, loff_t len,
+ unsigned int flags)
+{
+ int err;
+ struct inode *inode;
+ aufs_bindex_t bsrc, bend;
+
+ LKTRTrace("%.*s, bdst %d, len %Ld, flags 0x%x\n",
+ AuDLNPair(dentry), bdst, len, flags);
+ inode = dentry->d_inode;
+ AuDebugOn(!S_ISDIR(inode->i_mode) && au_dbstart(dentry) < bdst);
+
+ bend = au_dbend(dentry);
+ for (bsrc = bdst + 1; bsrc <= bend; bsrc++)
+ if (au_h_dptr(dentry, bsrc))
+ break;
+ AuDebugOn(!au_h_dptr(dentry, bsrc));
+
+ err = au_lkup_neg(dentry, bdst);
+ if (!err) {
+ err = au_cpup_single(dentry, bdst, bsrc, len, flags);
+ if (!err)
+ return 0; /* success */
+
+ /* revert */
+ au_set_h_dptr(dentry, bdst, NULL);
+ au_set_dbstart(dentry, bsrc);
+ }
+
+ AuTraceErr(err);
+ return err;
+}
+
+struct au_cpup_simple_args {
+ int *errp;
+ struct dentry *dentry;
+ aufs_bindex_t bdst;
+ loff_t len;
+ unsigned int flags;
+};
+
+static void au_call_cpup_simple(void *args)
+{
+ struct au_cpup_simple_args *a = args;
+ *a->errp = au_cpup_simple(a->dentry, a->bdst, a->len, a->flags);
+}
+
+int au_sio_cpup_simple(struct dentry *dentry, aufs_bindex_t bdst, loff_t len,
+ unsigned int flags)
+{
+ int err, do_sio, dlgt, wkq_err;
+ struct dentry *parent;
+ struct inode *h_dir, *dir;
+
+ LKTRTrace("%.*s, b%d, len %Ld, flags 0x%x\n",
+ AuDLNPair(dentry), bdst, len, flags);
+
+ parent = dget_parent(dentry);
+ dir = parent->d_inode;
+ h_dir = au_h_iptr(dir, bdst);
+ dlgt = !!au_opt_test_dlgt(au_mntflags(dir->i_sb));
+ do_sio = au_test_h_perm_sio(h_dir, MAY_EXEC | MAY_WRITE, dlgt);
+ if (!do_sio) {
+ /*
+ * testing CAP_MKNOD is for generic fs,
+ * but CAP_FSETID is for xfs only, currently.
+ */
+ umode_t mode = dentry->d_inode->i_mode;
+ do_sio = (((mode & (S_IFCHR | S_IFBLK))
+ && !capable(CAP_MKNOD))
+ || ((mode & (S_ISUID | S_ISGID))
+ && !capable(CAP_FSETID)));
+ }
+ if (!do_sio)
+ err = au_cpup_simple(dentry, bdst, len, flags);
+ else {
+ struct au_cpup_simple_args args = {
+ .errp = &err,
+ .dentry = dentry,
+ .bdst = bdst,
+ .len = len,
+ .flags = flags
+ };
+ wkq_err = au_wkq_wait(au_call_cpup_simple, &args, /*dlgt*/0);
+ if (unlikely(wkq_err))
+ err = wkq_err;
+ }
+
+ dput(parent);
+ AuTraceErr(err);
+ return err;
+}
+
+/* ---------------------------------------------------------------------- */
+
+static noinline_for_stack int
+au_do_cpup_wh(struct dentry *dentry, aufs_bindex_t bdst,
+ struct dentry *wh_dentry, struct file *file, loff_t len)
+{
+ int err;
+ struct au_dinfo *dinfo;
+ aufs_bindex_t bstart;
+ struct dentry *h_d_bdst, *h_d_bstart;
+
+ AuTraceEnter();
+
+ dinfo = au_di(dentry);
+ bstart = dinfo->di_bstart;
+ h_d_bdst = dinfo->di_hdentry[0 + bdst].hd_dentry;
+ dinfo->di_bstart = bdst;
+ dinfo->di_hdentry[0 + bdst].hd_dentry = wh_dentry;
+ h_d_bstart = dinfo->di_hdentry[0 + bstart].hd_dentry;
+ if (file)
+ dinfo->di_hdentry[0 + bstart].hd_dentry
+ = au_h_fptr(file, au_fbstart(file))->f_dentry;
+ err = au_cpup_single(dentry, bdst, bstart, len, !AuCpup_DTIME);
+ if (!err && file) {
+ err = au_reopen_nondir(file);
+ dinfo->di_hdentry[0 + bstart].hd_dentry = h_d_bstart;
+ }
+ dinfo->di_hdentry[0 + bdst].hd_dentry = h_d_bdst;
+ dinfo->di_bstart = bstart;
+
+ AuTraceErr(err);
+ return err;
+}
+
+/*
+ * copyup the deleted file for writing.
+ */
+int au_cpup_wh(struct dentry *dentry, aufs_bindex_t bdst, loff_t len,
+ struct file *file)
+{
+ int err, dlgt;
+ struct dentry *parent, *h_parent, *wh_dentry;
+ struct super_block *sb;
+ unsigned int mnt_flags;
+ struct au_dtime dt;
+ struct vfsub_args vargs;
+ struct au_hinode *hgdir;
+ struct au_ndx ndx = {
+ .nd = NULL,
+ .flags = 0,
+ /* .br = NULL */
+ };
+
+ LKTRTrace("%.*s, bdst %d, len %Lu\n", AuDLNPair(dentry), bdst, len);
+ AuDebugOn(S_ISDIR(dentry->d_inode->i_mode)
+ || (file && !(file->f_mode & FMODE_WRITE)));
+ DiMustWriteLock(dentry);
+
+ parent = dget_parent(dentry);
+ IiMustAnyLock(parent->d_inode);
+ h_parent = au_h_dptr(parent, bdst);
+ AuDebugOn(!h_parent);
+
+ sb = parent->d_sb;
+ mnt_flags = au_mntflags(sb);
+ dlgt = 0;
+ ndx.nfsmnt = au_nfsmnt(sb, bdst);
+ if (unlikely(au_opt_test_dlgt(mnt_flags))) {
+ dlgt = 1;
+ au_fset_ndx(ndx.flags, DLGT);
+ }
+ wh_dentry = au_whtmp_lkup(h_parent, &dentry->d_name, &ndx);
+ err = PTR_ERR(wh_dentry);
+ if (IS_ERR(wh_dentry))
+ goto out;
+
+ hgdir = NULL;
+ if (unlikely(au_opt_test(mnt_flags, UDBA_INOTIFY)
+ && !IS_ROOT(parent))) {
+ struct dentry *gparent;
+ gparent = dget_parent(parent);
+ hgdir = au_hi(gparent->d_inode, bdst);
+ dput(gparent);
+ }
+ au_dtime_store(&dt, parent, h_parent, hgdir);
+ err = au_do_cpup_wh(dentry, bdst, wh_dentry, file, len);
+ if (unlikely(err))
+ goto out_wh;
+
+ AuDebugOn(!d_unhashed(dentry));
+ /* dget first to force sillyrename on nfs */
+ dget(wh_dentry);
+ vfsub_args_init(&vargs, NULL, dlgt, 0);
+ err = vfsub_unlink(h_parent->d_inode, wh_dentry, &vargs);
+ if (unlikely(err)) {
+ AuIOErr("failed remove copied-up tmp file %.*s(%d)\n",
+ AuDLNPair(wh_dentry), err);
+ err = -EIO;
+ }
+ au_dtime_revert(&dt);
+ au_set_hi_wh(dentry->d_inode, bdst, wh_dentry);
+
+ out_wh:
+ dput(wh_dentry);
+ out:
+ dput(parent);
+ AuTraceErr(err);
+ return err;
+}
+
+struct au_cpup_wh_args {
+ int *errp;
+ struct dentry *dentry;
+ aufs_bindex_t bdst;
+ loff_t len;
+ struct file *file;
+};
+
+static void au_call_cpup_wh(void *args)
+{
+ struct au_cpup_wh_args *a = args;
+ *a->errp = au_cpup_wh(a->dentry, a->bdst, a->len, a->file);
+}
+
+int au_sio_cpup_wh(struct dentry *dentry, aufs_bindex_t bdst, loff_t len,
+ struct file *file)
+{
+ int err, wkq_err;
+ struct dentry *parent;
+ struct inode *dir, *h_dir;
+
+ AuTraceEnter();
+ parent = dget_parent(dentry);
+ dir = parent->d_inode;
+ IiMustAnyLock(dir);
+ h_dir = au_h_iptr(dir, bdst);
+
+ if (!au_test_h_perm_sio
+ (h_dir, MAY_EXEC | MAY_WRITE,
+ au_opt_test_dlgt(au_mntflags(dentry->d_sb))))
+ err = au_cpup_wh(dentry, bdst, len, file);
+ else {
+ struct au_cpup_wh_args args = {
+ .errp = &err,
+ .dentry = dentry,
+ .bdst = bdst,
+ .len = len,
+ .file = file
+ };
+ wkq_err = au_wkq_wait(au_call_cpup_wh, &args, /*dlgt*/0);
+ if (unlikely(wkq_err))
+ err = wkq_err;
+ }
+ dput(parent);
+
+ AuTraceErr(err);
+ return err;
+}
+
+/* ---------------------------------------------------------------------- */
+
+/*
+ * generic routine for both of copy-up and copy-down.
+ * Although I've tried building a path by dcsub, I gave up this approach.
+ * Since the ancestor directory may be moved/renamed during copy.
+ */
+/* cf. revalidate function in file.c */
+int au_cp_dirs(struct dentry *dentry, aufs_bindex_t bdst, struct dentry *locked,
+ int (*cp)(struct dentry *dentry, aufs_bindex_t bdst,
+ struct dentry *h_parent, void *arg),
+ void *arg)
+{
+ int err, hinotify;
+ struct super_block *sb;
+ struct dentry *d, *parent, *h_parent, *gparent, *real_parent;
+
+ LKTRTrace("%.*s, b%d, parent i%lu, locked %p\n",
+ AuDLNPair(dentry), bdst, parent_ino(dentry), locked);
+ sb = dentry->d_sb;
+ AuDebugOn(au_test_ro(sb, bdst, NULL));
+ err = 0;
+ parent = dget_parent(dentry);
+ IiMustWriteLock(parent->d_inode);
+ if (unlikely(IS_ROOT(parent)))
+ goto out;
+ if (locked) {
+ DiMustAnyLock(locked);
+ IiMustAnyLock(locked->d_inode);
+ }
+
+ /* slow loop, keep it simple and stupid */
+ real_parent = parent;
+ hinotify = !!au_opt_test(au_mntflags(sb), UDBA_INOTIFY);
+ while (1) {
+ dput(parent);
+ parent = dget_parent(dentry);
+ h_parent = au_h_dptr(parent, bdst);
+ if (h_parent)
+ goto out; /* success */
+
+ /* find top dir which is needed to cpup */
+ do {
+ d = parent;
+ dput(parent);
+ parent = dget_parent(d);
+ if (parent != locked) {
+ di_read_lock_parent3(parent, !AuLock_IR);
+ h_parent = au_h_dptr(parent, bdst);
+ di_read_unlock(parent, !AuLock_IR);
+ } else
+ h_parent = au_h_dptr(parent, bdst);
+ } while (!h_parent);
+
+ if (d != real_parent)
+ di_write_lock_child3(d);
+
+ /* somebody else might create while we were sleeping */
+ if (!au_h_dptr(d, bdst) || !au_h_dptr(d, bdst)->d_inode) {
+ struct inode *h_dir = h_parent->d_inode,
+ *dir = parent->d_inode;
+
+ if (au_h_dptr(d, bdst))
+ au_update_dbstart(d);
+ if (parent != locked)
+ di_read_lock_parent3(parent, AuLock_IR);
+ gparent = NULL;
+ if (unlikely(hinotify && !IS_ROOT(parent))) {
+ gparent = dget_parent(parent);
+ if (gparent != locked)
+ ii_read_lock_parent4(gparent->d_inode);
+ else {
+ dput(gparent);
+ gparent = NULL;
+ }
+ }
+ au_hdir_lock(h_dir, dir, bdst);
+ err = cp(d, bdst, h_parent, arg);
+ au_hdir_unlock(h_dir, dir, bdst);
+ if (unlikely(gparent)) {
+ ii_read_unlock(gparent->d_inode);
+ dput(gparent);
+ }
+ if (parent != locked)
+ di_read_unlock(parent, AuLock_IR);
+ }
+
+ if (d != real_parent)
+ di_write_unlock(d);
+ if (unlikely(err))
+ break;
+ }
+
+ out:
+ dput(parent);
+ AuTraceErr(err);
+ return err;
+}
+
+static int au_cpup_dir(struct dentry *dentry, aufs_bindex_t bdst,
+ struct dentry *h_parent, void *arg)
+{
+ int err;
+
+ err = au_sio_cpup_simple(dentry, bdst, -1, AuCpup_DTIME);
+
+ AuTraceErr(err);
+ return err;
+}
+
+int au_cpup_dirs(struct dentry *dentry, aufs_bindex_t bdst,
+ struct dentry *locked)
+{
+ int err;
+
+ err = au_cp_dirs(dentry, bdst, locked, au_cpup_dir, NULL);
+
+ AuTraceErr(err);
+ return err;
+}
+
+int au_test_and_cpup_dirs(struct dentry *dentry, aufs_bindex_t bdst,
+ struct dentry *locked)
+{
+ int err;
+ struct dentry *parent;
+ struct inode *dir;
+
+ parent = dget_parent(dentry);
+ dir = parent->d_inode;
+ LKTRTrace("%.*s, b%d, parent i%lu, locked %p\n",
+ AuDLNPair(dentry), bdst, dir->i_ino, locked);
+ DiMustReadLock(parent);
+ IiMustReadLock(dir);
+
+ err = 0;
+ if (au_h_iptr(dir, bdst))
+ goto out;
+
+ di_read_unlock(parent, AuLock_IR);
+ di_write_lock_parent2(parent);
+ /* someone else might change our inode while we were sleeping */
+ if (unlikely(!au_h_iptr(dir, bdst)))
+ err = au_cpup_dirs(dentry, bdst, locked);
+ di_downgrade_lock(parent, AuLock_IR);
+
+ out:
+ dput(parent);
+ AuTraceErr(err);
+ return err;
+}
diff --git a/fs/aufs/cpup.h b/fs/aufs/cpup.h
new file mode 100644
index 0000000..94816e6
--- /dev/null
+++ b/fs/aufs/cpup.h
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2005-2008 Junjiro 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/*
+ * copy-up/down functions
+ */
+
+#ifndef __AUFS_CPUP_H__
+#define __AUFS_CPUP_H__
+
+#ifdef __KERNEL__
+
+#include <linux/fs.h>
+#include <linux/aufs_type.h>
+
+void au_cpup_attr_timesizes(struct inode *inode);
+void au_cpup_attr_nlink(struct inode *inode);
+void au_cpup_attr_changeable(struct inode *inode);
+void au_cpup_igen(struct inode *inode, struct inode *h_inode);
+void au_cpup_attr_all(struct inode *inode);
+
+/* ---------------------------------------------------------------------- */
+
+/* cpup flags */
+#define AuCpup_DTIME 1 /* do dtime_store/revert */
+#define au_ftest_cpup(flags, name) ((flags) & AuCpup_##name)
+#define au_fset_cpup(flags, name) { (flags) |= AuCpup_##name; }
+#define au_fclr_cpup(flags, name) { (flags) &= ~AuCpup_##name; }
+
+int au_cpup_single(struct dentry *dentry, aufs_bindex_t bdst,
+ aufs_bindex_t bsrc, loff_t len, unsigned int flags);
+int au_sio_cpup_single(struct dentry *dentry, aufs_bindex_t bdst,
+ aufs_bindex_t bsrc, loff_t len, unsigned int flags);
+int au_cpup_simple(struct dentry *dentry, aufs_bindex_t bdst, loff_t len,
+ unsigned int flags);
+int au_sio_cpup_simple(struct dentry *dentry, aufs_bindex_t bdst, loff_t len,
+ unsigned int flags);
+int au_cpup_wh(struct dentry *dentry, aufs_bindex_t bdst, loff_t len,
+ struct file *file);
+int au_sio_cpup_wh(struct dentry *dentry, aufs_bindex_t bdst, loff_t len,
+ struct file *file);
+
+int au_cp_dirs(struct dentry *dentry, aufs_bindex_t bdst, struct dentry *locked,
+ int (*cp)(struct dentry *dentry, aufs_bindex_t bdst,
+ struct dentry *h_parent, void *arg),
+ void *arg);
+int au_cpup_dirs(struct dentry *dentry, aufs_bindex_t bdst,
+ struct dentry *locked);
+int au_test_and_cpup_dirs(struct dentry *dentry, aufs_bindex_t bdst,
+ struct dentry *locked);
+
+/* ---------------------------------------------------------------------- */
+
+/* keep timestamps when copyup */
+struct au_dtime {
+ struct dentry *dt_dentry, *dt_h_dentry;
+ struct au_hinode *dt_hdir;
+ struct timespec dt_atime, dt_mtime;
+};
+void au_dtime_store(struct au_dtime *dt, struct dentry *dentry,
+ struct dentry *h_dentry, struct au_hinode *hdir);
+void au_dtime_revert(struct au_dtime *dt);
+
+#endif /* __KERNEL__ */
+#endif /* __AUFS_CPUP_H__ */
--
1.5.5.1.308.g1fbb5.dirty

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/