int au_xigen_inc(struct inode *inode) { int err; loff_t pos; ssize_t sz; __u32 igen; struct super_block *sb; struct au_sbinfo *sbinfo; LKTRTrace("i%lu\n", (unsigned long)inode->i_ino); err = 0; sb = inode->i_sb; if (unlikely(!au_opt_test_xino(au_mntflags(sb)))) goto out; pos = inode->i_ino; pos *= sizeof(igen); igen = inode->i_generation + 1; sbinfo = au_sbi(sb); sz = xino_fwrite(sbinfo->si_xwrite, sbinfo->si_xigen, &igen, sizeof(igen), &pos); if (sz == sizeof(igen)) goto out; /* success */ err = sz; if (unlikely(sz >= 0)) { err = -EIO; AuIOErr("xigen error (%zd)\n", sz); } out: AuTraceErr(err); return err; }
/* * 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; if (!try_sio) wh_dentry = vfsub_lkup_one(wh_name, h_parent); 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; }
int au_xigen_new(struct inode *inode) { int err; loff_t pos; ssize_t sz; struct super_block *sb; struct au_sbinfo *sbinfo; struct file *file; LKTRTrace("i%lu\n", inode->i_ino); err = 0; /* todo: dirty, at mount time */ if (inode->i_ino == AUFS_ROOT_INO) goto out; sb = inode->i_sb; if (unlikely(!au_opt_test_xino(au_mntflags(sb)))) goto out; err = -EFBIG; pos = inode->i_ino; if (unlikely(Au_LOFF_MAX / sizeof(inode->i_generation) - 1 < pos)) { AuIOErr1("too large i%lld\n", pos); goto out; } pos *= sizeof(inode->i_generation); err = 0; sbinfo = au_sbi(sb); file = sbinfo->si_xigen; BUG_ON(!file); if (i_size_read(file->f_dentry->d_inode) < pos + sizeof(inode->i_generation)) { inode->i_generation = atomic_inc_return(&sbinfo->si_xigen_next); sz = xino_fwrite(sbinfo->si_xwrite, file, &inode->i_generation, sizeof(inode->i_generation), &pos); } else sz = xino_fread(sbinfo->si_xread, file, &inode->i_generation, sizeof(inode->i_generation), &pos); if (sz == sizeof(inode->i_generation)) goto out; /* success */ err = sz; if (unlikely(sz >= 0)) { err = -EIO; AuIOErr("xigen error (%zd)\n", sz); } out: AuTraceErr(err); return err; }
/* * final procedure of adding a new entry, except link(2). * remove whiteout, instantiate, copyup the parent dir's times and size * and update version. * if it failed, re-create the removed whiteout. */ static int epilog(struct inode *dir, aufs_bindex_t bindex, struct dentry *wh_dentry, struct dentry *dentry) { int err, rerr; aufs_bindex_t bwh; struct path h_path; struct inode *inode, *h_dir; struct dentry *wh; bwh = -1; if (wh_dentry) { h_dir = wh_dentry->d_parent->d_inode; /* dir inode is locked */ IMustLock(h_dir); AuDebugOn(au_h_iptr(dir, bindex) != h_dir); bwh = au_dbwh(dentry); h_path.dentry = wh_dentry; h_path.mnt = au_sbr_mnt(dir->i_sb, bindex); err = au_wh_unlink_dentry(au_h_iptr(dir, bindex), &h_path, dentry); if (unlikely(err)) goto out; } inode = au_new_inode(dentry, /*must_new*/1); if (!IS_ERR(inode)) { d_instantiate(dentry, inode); dir = dentry->d_parent->d_inode; /* dir inode is locked */ IMustLock(dir); if (au_ibstart(dir) == au_dbstart(dentry)) au_cpup_attr_timesizes(dir); dir->i_version++; return 0; /* success */ } err = PTR_ERR(inode); if (!wh_dentry) goto out; /* revert */ /* dir inode is locked */ wh = au_wh_create(dentry, bwh, wh_dentry->d_parent); rerr = PTR_ERR(wh); if (IS_ERR(wh)) { AuIOErr("%.*s reverting whiteout failed(%d, %d)\n", AuDLNPair(dentry), err, rerr); err = -EIO; } else dput(wh); out: return err; }
static void au_hide(struct dentry *dentry) { int err; AuDbgDentry(dentry); if (d_is_dir(dentry)) { /* shrink_dcache_parent(dentry); */ err = au_hide_children(dentry); if (unlikely(err)) AuIOErr("%pd, failed hiding children, ignored %d\n", dentry, err); } au_do_hide(dentry); }
static void au_hide(struct dentry *dentry) { int err; struct inode *inode; AuDbgDentry(dentry); inode = dentry->d_inode; if (inode && S_ISDIR(inode->i_mode)) { /* shrink_dcache_parent(dentry); */ err = au_hide_children(dentry); if (unlikely(err)) AuIOErr("%.*s, failed hiding children, ignored %d\n", AuDLNPair(dentry), err); } au_do_hide(dentry); }
/* * when removing a dir, rename it to a unique temporary whiteout-ed name first * in order to be revertible and save time for removing many child whiteouts * under the dir. * returns 1 when there are too many child whiteout and caller should remove * them asynchronously. returns 0 when the number of children is enough small to * remove now or the branch fs is a remote fs. * otherwise return an error. */ static int renwh_and_rmdir(struct dentry *dentry, aufs_bindex_t bindex, struct au_nhash *whlist, struct inode *dir) { int rmdir_later, err, dirwh; struct dentry *h_dentry; struct super_block *sb; struct inode *inode; sb = dentry->d_sb; SiMustAnyLock(sb); h_dentry = au_h_dptr(dentry, bindex); err = au_whtmp_ren(h_dentry, au_sbr(sb, bindex)); if (unlikely(err)) goto out; /* stop monitoring */ inode = d_inode(dentry); au_hn_free(au_hi(inode, bindex)); if (!au_test_fs_remote(h_dentry->d_sb)) { dirwh = au_sbi(sb)->si_dirwh; rmdir_later = (dirwh <= 1); if (!rmdir_later) rmdir_later = au_nhash_test_longer_wh(whlist, bindex, dirwh); if (rmdir_later) return rmdir_later; } err = au_whtmp_rmdir(dir, bindex, h_dentry, whlist); if (unlikely(err)) { AuIOErr("rmdir %pd, b%d failed, %d. ignored\n", h_dentry, bindex, err); err = 0; } out: AuTraceErr(err); return err; }
void au_xigen_inc(struct inode *inode) { loff_t pos; ssize_t sz; __u32 igen; struct super_block *sb; struct au_sbinfo *sbinfo; sb = inode->i_sb; AuDebugOn(!au_opt_test(au_mntflags(sb), XINO)); sbinfo = au_sbi(sb); pos = inode->i_ino; pos *= sizeof(igen); igen = inode->i_generation + 1; sz = xino_fwrite(sbinfo->si_xwrite, sbinfo->si_xigen, &igen, sizeof(igen), &pos); if (sz == sizeof(igen)) return; /* success */ if (unlikely(sz >= 0)) AuIOErr("xigen error (%zd)\n", sz); }
/* * final procedure of adding a new entry, except link(2). * remove whiteout, instantiate, copyup the parent dir's times and size * and update version. * if it failed, re-create the removed whiteout. */ static int epilog(struct inode *dir, aufs_bindex_t bindex, struct dentry *wh_dentry, struct dentry *dentry) { int err, rerr; aufs_bindex_t bwh; struct inode *inode, *h_dir; struct dentry *wh; struct au_ndx ndx; struct super_block *sb; LKTRTrace("wh %p, %.*s\n", wh_dentry, AuDLNPair(dentry)); sb = dentry->d_sb; bwh = -1; if (wh_dentry) { h_dir = wh_dentry->d_parent->d_inode; /* dir inode is locked */ IMustLock(h_dir); AuDebugOn(au_h_iptr(dir, bindex) != h_dir); bwh = au_dbwh(dentry); err = au_wh_unlink_dentry(au_hi(dir, bindex), wh_dentry, dentry, /*dlgt*/0); if (unlikely(err)) goto out; } inode = au_new_inode(dentry, /*must_new*/1); if (!IS_ERR(inode)) { d_instantiate(dentry, inode); dir = dentry->d_parent->d_inode; /* dir inode is locked */ IMustLock(dir); /* or always cpup dir mtime? */ if (au_ibstart(dir) == au_dbstart(dentry)) au_cpup_attr_timesizes(dir); dir->i_version++; return 0; /* success */ } err = PTR_ERR(inode); if (!wh_dentry) goto out; /* revert */ ndx.flags = 0; if (au_test_dlgt(au_mntflags(sb))) au_fset_ndx(ndx.flags, DLGT); ndx.nfsmnt = au_nfsmnt(sb, bwh); ndx.nd = NULL; /* ndx.br = NULL; */ /* dir inode is locked */ wh = au_wh_create(dentry, bwh, wh_dentry->d_parent, &ndx); rerr = PTR_ERR(wh); if (IS_ERR(wh)) { AuIOErr("%.*s reverting whiteout failed(%d, %d)\n", AuDLNPair(dentry), err, rerr); err = -EIO; } else dput(wh); out: AuTraceErr(err); return err; }
int au_opts_mount(struct super_block *sb, struct au_opts *opts) { int err; unsigned int tmp; aufs_bindex_t bindex, bend; struct au_opt *opt; struct au_opt_xino *opt_xino, xino; struct au_sbinfo *sbinfo; struct au_branch *br; SiMustWriteLock(sb); err = 0; opt_xino = NULL; opt = opts->opt; while (err >= 0 && opt->type != Opt_tail) err = au_opt_simple(sb, opt++, opts); if (err > 0) err = 0; else if (unlikely(err < 0)) goto out; /* disable xino and udba temporary */ sbinfo = au_sbi(sb); tmp = sbinfo->si_mntflags; au_opt_clr(sbinfo->si_mntflags, XINO); au_opt_set_udba(sbinfo->si_mntflags, UDBA_REVAL); opt = opts->opt; while (err >= 0 && opt->type != Opt_tail) err = au_opt_br(sb, opt++, opts); if (err > 0) err = 0; else if (unlikely(err < 0)) goto out; bend = au_sbend(sb); if (unlikely(bend < 0)) { err = -EINVAL; pr_err("no branches\n"); goto out; } if (au_opt_test(tmp, XINO)) au_opt_set(sbinfo->si_mntflags, XINO); opt = opts->opt; while (!err && opt->type != Opt_tail) err = au_opt_xino(sb, opt++, &opt_xino, opts); if (unlikely(err)) goto out; err = au_opts_verify(sb, sb->s_flags, tmp); if (unlikely(err)) goto out; /* restore xino */ if (au_opt_test(tmp, XINO) && !opt_xino) { xino.file = au_xino_def(sb); err = PTR_ERR(xino.file); if (IS_ERR(xino.file)) goto out; err = au_xino_set(sb, &xino, /*remount*/0); fput(xino.file); if (unlikely(err)) goto out; } /* restore udba */ tmp &= AuOptMask_UDBA; sbinfo->si_mntflags &= ~AuOptMask_UDBA; sbinfo->si_mntflags |= tmp; bend = au_sbend(sb); for (bindex = 0; bindex <= bend; bindex++) { br = au_sbr(sb, bindex); err = au_hnotify_reset_br(tmp, br, br->br_perm); if (unlikely(err)) AuIOErr("hnotify failed on br %d, %d, ignored\n", bindex, err); /* go on even if err */ } if (au_opt_test(tmp, UDBA_HNOTIFY)) { struct inode *dir = sb->s_root->d_inode; au_hn_reset(dir, au_hi_flags(dir, /*isdir*/1) & ~AuHi_XINO); } out: return err; }
static int au_cpdown_dir(struct dentry *dentry, aufs_bindex_t bdst, struct dentry *h_parent, void *arg) { int err, rerr; aufs_bindex_t bopq, bstart; struct path h_path; struct dentry *parent; struct inode *h_dir, *h_inode, *inode, *dir; struct au_cpdown_dir_args *args = arg; bstart = au_dbstart(dentry); /* dentry is di-locked */ parent = dget_parent(dentry); dir = parent->d_inode; h_dir = h_parent->d_inode; AuDebugOn(h_dir != au_h_iptr(dir, bdst)); IMustLock(h_dir); err = au_lkup_neg(dentry, bdst); if (unlikely(err < 0)) goto out; h_path.dentry = au_h_dptr(dentry, bdst); h_path.mnt = au_sbr_mnt(dentry->d_sb, bdst); err = vfsub_sio_mkdir(au_h_iptr(dir, bdst), &h_path, S_IRWXU | S_IRUGO | S_IXUGO); if (unlikely(err)) goto out_put; au_fset_cpdown(args->flags, MADE_DIR); bopq = au_dbdiropq(dentry); au_fclr_cpdown(args->flags, WHED); au_fclr_cpdown(args->flags, DIROPQ); if (au_dbwh(dentry) == bdst) au_fset_cpdown(args->flags, WHED); if (!au_ftest_cpdown(args->flags, PARENT_OPQ) && bopq <= bdst) au_fset_cpdown(args->flags, PARENT_OPQ); h_inode = h_path.dentry->d_inode; mutex_lock_nested(&h_inode->i_mutex, AuLsc_I_CHILD); if (au_ftest_cpdown(args->flags, WHED)) { err = au_cpdown_dir_opq(dentry, bdst, args); if (unlikely(err)) { mutex_unlock(&h_inode->i_mutex); goto out_dir; } } err = au_cpdown_attr(&h_path, au_h_dptr(dentry, bstart)); mutex_unlock(&h_inode->i_mutex); if (unlikely(err)) goto out_opq; if (au_ftest_cpdown(args->flags, WHED)) { err = au_cpdown_dir_wh(dentry, h_parent, dir, bdst); if (unlikely(err)) goto out_opq; } inode = dentry->d_inode; if (au_ibend(inode) < bdst) au_set_ibend(inode, bdst); au_set_h_iptr(inode, bdst, au_igrab(h_inode), au_hi_flags(inode, /*isdir*/1)); goto out; /* success */ /* revert */ out_opq: if (au_ftest_cpdown(args->flags, DIROPQ)) { mutex_lock_nested(&h_inode->i_mutex, AuLsc_I_CHILD); rerr = au_diropq_remove(dentry, bdst); mutex_unlock(&h_inode->i_mutex); if (unlikely(rerr)) { AuIOErr("failed removing diropq for %.*s b%d (%d)\n", AuDLNPair(dentry), bdst, rerr); err = -EIO; goto out; } } out_dir: if (au_ftest_cpdown(args->flags, MADE_DIR)) { rerr = vfsub_sio_rmdir(au_h_iptr(dir, bdst), &h_path); if (unlikely(rerr)) { AuIOErr("failed removing %.*s b%d (%d)\n", AuDLNPair(dentry), bdst, rerr); err = -EIO; } } out_put: au_set_h_dptr(dentry, bdst, NULL); if (au_dbend(dentry) == bdst) au_update_dbend(dentry); out: dput(parent); return err; }
int au_refresh_dentry(struct dentry *dentry, struct dentry *parent) { int err, ebrange; unsigned int sigen; struct au_dinfo *dinfo, *tmp; struct super_block *sb; struct inode *inode; DiMustWriteLock(dentry); AuDebugOn(IS_ROOT(dentry)); AuDebugOn(!parent->d_inode); sb = dentry->d_sb; inode = dentry->d_inode; sigen = au_sigen(sb); err = au_digen_test(parent, sigen); if (unlikely(err)) goto out; dinfo = au_di(dentry); err = au_di_realloc(dinfo, au_sbend(sb) + 1); if (unlikely(err)) goto out; ebrange = au_dbrange_test(dentry); if (!ebrange) ebrange = au_do_refresh_hdentry(dentry, parent); if (d_unhashed(dentry) || ebrange) { AuDebugOn(au_dbstart(dentry) < 0 && au_dbend(dentry) >= 0); if (inode) err = au_refresh_hinode_self(inode); au_dbg_verify_dinode(dentry); if (!err) goto out_dgen; /* success */ goto out; } /* temporary dinfo */ AuDbgDentry(dentry); err = -ENOMEM; tmp = au_di_alloc(sb, AuLsc_DI_TMP); if (unlikely(!tmp)) goto out; au_di_swap(tmp, dinfo); /* returns the number of positive dentries */ /* * if current working dir is removed, it returns an error. * but the dentry is legal. */ err = au_lkup_dentry(dentry, /*bstart*/0, /*type*/0, /*nd*/NULL); AuDbgDentry(dentry); au_di_swap(tmp, dinfo); if (err == -ENOENT) err = 0; if (err >= 0) { /* compare/refresh by dinfo */ AuDbgDentry(dentry); err = au_refresh_by_dinfo(dentry, dinfo, tmp); au_dbg_verify_dinode(dentry); AuTraceErr(err); } au_rw_write_unlock(&tmp->di_rwsem); au_di_free(tmp); if (unlikely(err)) goto out; out_dgen: au_update_digen(dentry); out: if (unlikely(err && !(dentry->d_flags & DCACHE_NFSFS_RENAMED))) { AuIOErr("failed refreshing %.*s, %d\n", AuDLNPair(dentry), err); AuDbgDentry(dentry); } AuTraceErr(err); return err; }
/* * returns the number of lower positive dentries, * otherwise an error. * can be called at unlinking with @type is zero. */ int au_lkup_dentry(struct dentry *dentry, aufs_bindex_t bstart, mode_t type, struct nameidata *nd) { int npositive, err; aufs_bindex_t bindex, btail, bdiropq; unsigned char isdir; struct qstr whname; struct au_do_lookup_args args = { .flags = 0, .type = type, .nd = nd }; const struct qstr *name = &dentry->d_name; struct dentry *parent; struct inode *inode; err = au_test_shwh(dentry->d_sb, name); if (unlikely(err)) goto out; err = au_wh_name_alloc(&whname, name); if (unlikely(err)) goto out; inode = dentry->d_inode; isdir = !!(inode && S_ISDIR(inode->i_mode)); if (!type) au_fset_lkup(args.flags, ALLOW_NEG); npositive = 0; parent = dget_parent(dentry); btail = au_dbtaildir(parent); for (bindex = bstart; bindex <= btail; bindex++) { struct dentry *h_parent, *h_dentry; struct inode *h_inode, *h_dir; h_dentry = au_h_dptr(dentry, bindex); if (h_dentry) { if (h_dentry->d_inode) npositive++; if (type != S_IFDIR) break; continue; } h_parent = au_h_dptr(parent, bindex); if (!h_parent) continue; h_dir = h_parent->d_inode; if (!h_dir || !S_ISDIR(h_dir->i_mode)) continue; mutex_lock_nested(&h_dir->i_mutex, AuLsc_I_PARENT); h_dentry = au_do_lookup(h_parent, dentry, bindex, &whname, &args); mutex_unlock(&h_dir->i_mutex); err = PTR_ERR(h_dentry); if (IS_ERR(h_dentry)) goto out_parent; au_fclr_lkup(args.flags, ALLOW_NEG); if (au_dbwh(dentry) >= 0) break; if (!h_dentry) continue; h_inode = h_dentry->d_inode; if (!h_inode) continue; npositive++; if (!args.type) args.type = h_inode->i_mode & S_IFMT; if (args.type != S_IFDIR) break; else if (isdir) { /* the type of lower may be different */ bdiropq = au_dbdiropq(dentry); if (bdiropq >= 0 && bdiropq <= bindex) break; } } if (npositive) { AuLabel(positive); au_update_dbstart(dentry); } err = npositive; if (unlikely(!au_opt_test(au_mntflags(dentry->d_sb), UDBA_NONE) && au_dbstart(dentry) < 0)) { err = -EIO; AuIOErr("both of real entry and whiteout found, %.*s, err %d\n", AuDLNPair(dentry), err); } out_parent: dput(parent); kfree(whname.name); out: return err; } struct dentry *au_sio_lkup_one(struct qstr *name, struct dentry *parent, struct au_branch *br) { struct dentry *dentry; int wkq_err; if (!au_test_h_perm_sio(parent->d_inode, MAY_EXEC)) dentry = au_lkup_one(name, parent, br, /*nd*/NULL); else { struct au_lkup_one_args args = { .errp = &dentry, .name = name, .h_parent = parent, .br = br, .nd = NULL }; wkq_err = au_wkq_wait(au_call_lkup_one, &args); if (unlikely(wkq_err)) dentry = ERR_PTR(wkq_err); } return dentry; } /* * lookup @dentry on @bindex which should be negative. */ int au_lkup_neg(struct dentry *dentry, aufs_bindex_t bindex) { int err; struct dentry *parent, *h_parent, *h_dentry; parent = dget_parent(dentry); h_parent = au_h_dptr(parent, bindex); h_dentry = au_sio_lkup_one(&dentry->d_name, h_parent, au_sbr(dentry->d_sb, bindex)); err = PTR_ERR(h_dentry); if (IS_ERR(h_dentry)) goto out; if (unlikely(h_dentry->d_inode)) { err = -EIO; AuIOErr("%.*s should be negative on b%d.\n", AuDLNPair(h_dentry), bindex); dput(h_dentry); goto out; } err = 0; if (bindex < au_dbstart(dentry)) au_set_dbstart(dentry, bindex); if (au_dbend(dentry) < bindex) au_set_dbend(dentry, bindex); au_set_h_dptr(dentry, bindex, h_dentry); out: dput(parent); return err; }
int aufs_mkdir(struct inode *dir, struct dentry *dentry, int mode) { int err, rerr; aufs_bindex_t bindex; unsigned char diropq; struct path h_path; struct dentry *wh_dentry, *parent, *opq_dentry; struct mutex *h_mtx; struct super_block *sb; struct { struct au_pin pin; struct au_dtime dt; } *a; /* reduce the stack usage */ struct au_wr_dir_args wr_dir_args = { .force_btgt = -1, .flags = AuWrDir_ADD_ENTRY | AuWrDir_ISDIR }; 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_may_add(dentry); if (unlikely(err)) goto out_unlock; parent = dentry->d_parent; /* dir inode is locked */ di_write_lock_parent(parent); wh_dentry = lock_hdir_lkup_wh(dentry, &a->dt, /*src_dentry*/NULL, &a->pin, &wr_dir_args); err = PTR_ERR(wh_dentry); if (IS_ERR(wh_dentry)) goto out_parent; sb = dentry->d_sb; bindex = au_dbstart(dentry); h_path.dentry = au_h_dptr(dentry, bindex); h_path.mnt = au_sbr_mnt(sb, bindex); err = vfsub_mkdir(au_pinned_h_dir(&a->pin), &h_path, mode); if (unlikely(err)) goto out_unpin; /* make the dir opaque */ diropq = 0; h_mtx = &h_path.dentry->d_inode->i_mutex; if (wh_dentry || au_opt_test(au_mntflags(sb), ALWAYS_DIROPQ)) { mutex_lock_nested(h_mtx, AuLsc_I_CHILD); opq_dentry = au_diropq_create(dentry, bindex); mutex_unlock(h_mtx); err = PTR_ERR(opq_dentry); if (IS_ERR(opq_dentry)) goto out_dir; dput(opq_dentry); diropq = 1; } err = epilog(dir, bindex, wh_dentry, dentry); if (!err) { inc_nlink(dir); goto out_unpin; /* success */ } /* revert */ if (diropq) { AuLabel(revert opq); mutex_lock_nested(h_mtx, AuLsc_I_CHILD); rerr = au_diropq_remove(dentry, bindex); mutex_unlock(h_mtx); if (rerr) { AuIOErr("%.*s reverting diropq failed(%d, %d)\n", AuDLNPair(dentry), err, rerr); err = -EIO; } } out_dir: AuLabel(revert dir); rerr = vfsub_rmdir(au_pinned_h_dir(&a->pin), &h_path); if (rerr) { AuIOErr("%.*s reverting dir failed(%d, %d)\n", AuDLNPair(dentry), err, rerr); err = -EIO; } au_dtime_revert(&a->dt); out_unpin: au_unpin(&a->pin); dput(wh_dentry); out_parent: di_write_unlock(parent); out_unlock: if (unlikely(err)) { au_update_dbstart(dentry); d_drop(dentry); } aufs_read_unlock(dentry, AuLock_DW); out_free: kfree(a); out: return err; }
/* * 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("%.*s reverting whiteout failed(%d, %d)\n", AuDLNPair(dentry), err, rerr); return -EIO; } /* ---------------------------------------------------------------------- */ int aufs_unlink(struct inode *dir, struct dentry *dentry) { int err; aufs_bindex_t bwh, bindex, bstart; struct au_dtime dt; struct au_pin pin; struct path h_path; struct inode *inode, *h_dir; struct dentry *parent, *wh_dentry; IMustLock(dir); err = aufs_read_lock(dentry, AuLock_DW | AuLock_GEN); if (unlikely(err)) goto out; err = au_d_hashed_positive(dentry); if (unlikely(err)) goto out_unlock; inode = dentry->d_inode; IMustLock(inode); err = -EISDIR; if (unlikely(S_ISDIR(inode->i_mode))) goto out_unlock; /* possible? */ bstart = au_dbstart(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, &dt, &pin); err = PTR_ERR(wh_dentry); if (IS_ERR(wh_dentry)) goto out_parent; h_path.mnt = au_sbr_mnt(dentry->d_sb, bstart); h_path.dentry = au_h_dptr(dentry, bstart); dget(h_path.dentry); if (bindex == bstart) { h_dir = au_pinned_h_dir(&pin); err = vfsub_unlink(h_dir, &h_path, /*force*/0); } else { /* dir inode is locked */ h_dir = wh_dentry->d_parent->d_inode; IMustLock(h_dir); err = 0; } if (!err) { vfsub_drop_nlink(inode); epilog(dir, dentry, bindex); /* update target timestamps */ if (bindex == bstart) { vfsub_update_h_iattr(&h_path, /*did*/NULL); /*ignore*/ inode->i_ctime = h_path.dentry->d_inode->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, &dt); if (rerr) err = rerr; } out_unpin: au_unpin(&pin); dput(wh_dentry); dput(h_path.dentry); out_parent: di_write_unlock(parent); out_unlock: aufs_read_unlock(dentry, AuLock_DW); out: return err; }
/* * initial procedure of adding a new entry. * prepare writable branch and the parent dir, lock it, * and lookup whiteout for the new entry. */ static struct dentry* lock_hdir_lkup_wh(struct dentry *dentry, struct au_dtime *dt, struct dentry *src_dentry, struct au_pin *pin, struct au_wr_dir_args *wr_dir_args) { struct dentry *wh_dentry, *h_parent; struct super_block *sb; struct au_branch *br; int err; unsigned int udba; aufs_bindex_t bcpup; AuDbg("%.*s\n", AuDLNPair(dentry)); err = au_wr_dir(dentry, src_dentry, wr_dir_args); bcpup = err; wh_dentry = ERR_PTR(err); if (unlikely(err < 0)) goto out; sb = dentry->d_sb; udba = au_opt_udba(sb); err = au_pin(pin, dentry, bcpup, udba, AuPin_DI_LOCKED | AuPin_MNT_WRITE); wh_dentry = ERR_PTR(err); if (unlikely(err)) goto out; h_parent = au_pinned_h_parent(pin); if (udba != AuOpt_UDBA_NONE && au_dbstart(dentry) == bcpup) err = au_may_add(dentry, bcpup, h_parent, au_ftest_wrdir(wr_dir_args->flags, ISDIR)); else if (unlikely(dentry->d_name.len > AUFS_MAX_NAMELEN)) err = -ENAMETOOLONG; wh_dentry = ERR_PTR(err); if (unlikely(err)) goto out_unpin; br = au_sbr(sb, bcpup); if (dt) { struct path tmp = { .dentry = h_parent, .mnt = br->br_mnt }; au_dtime_store(dt, au_pinned_parent(pin), &tmp); } wh_dentry = NULL; if (bcpup != au_dbwh(dentry)) goto out; /* success */ wh_dentry = au_wh_lkup(h_parent, &dentry->d_name, br); out_unpin: if (IS_ERR(wh_dentry)) au_unpin(pin); out: return wh_dentry; } /* ---------------------------------------------------------------------- */ enum { Mknod, Symlink, Creat }; struct simple_arg { int type; union { struct { int mode; struct nameidata *nd; } c; struct { const char *symname; } s; struct { int mode; dev_t dev; } m; } u; }; static int add_simple(struct inode *dir, struct dentry *dentry, struct simple_arg *arg) { int err; aufs_bindex_t bstart; unsigned char created; struct au_dtime dt; struct au_pin pin; struct path h_path; struct dentry *wh_dentry, *parent; struct inode *h_dir; struct au_wr_dir_args wr_dir_args = { .force_btgt = -1, .flags = AuWrDir_ADD_ENTRY }; AuDbg("%.*s\n", AuDLNPair(dentry)); IMustLock(dir); parent = dentry->d_parent; /* dir inode is locked */ err = aufs_read_lock(dentry, AuLock_DW | AuLock_GEN); if (unlikely(err)) goto out; err = au_d_may_add(dentry); if (unlikely(err)) goto out_unlock; di_write_lock_parent(parent); wh_dentry = lock_hdir_lkup_wh(dentry, &dt, /*src_dentry*/NULL, &pin, &wr_dir_args); err = PTR_ERR(wh_dentry); if (IS_ERR(wh_dentry)) goto out_parent; bstart = au_dbstart(dentry); h_path.dentry = au_h_dptr(dentry, bstart); h_path.mnt = au_sbr_mnt(dentry->d_sb, bstart); h_dir = au_pinned_h_dir(&pin); switch (arg->type) { case Creat: err = vfsub_create(h_dir, &h_path, arg->u.c.mode); break; case Symlink: err = vfsub_symlink(h_dir, &h_path, arg->u.s.symname); break; case Mknod: err = vfsub_mknod(h_dir, &h_path, arg->u.m.mode, arg->u.m.dev); break; default: BUG(); } created = !err; if (!err) err = epilog(dir, bstart, wh_dentry, dentry); /* revert */ if (unlikely(created && err && h_path.dentry->d_inode)) { int rerr; rerr = vfsub_unlink(h_dir, &h_path, /*force*/0); if (rerr) { AuIOErr("%.*s revert failure(%d, %d)\n", AuDLNPair(dentry), err, rerr); err = -EIO; } au_dtime_revert(&dt); } au_unpin(&pin); dput(wh_dentry); out_parent: di_write_unlock(parent); out_unlock: if (unlikely(err)) { au_update_dbstart(dentry); d_drop(dentry); } aufs_read_unlock(dentry, AuLock_DW); out: return err; } int aufs_mknod(struct inode *dir, struct dentry *dentry, int mode, dev_t dev) { struct simple_arg arg = { .type = Mknod, .u.m = { .mode = mode, .dev = dev } }; return add_simple(dir, dentry, &arg); } int aufs_symlink(struct inode *dir, struct dentry *dentry, const char *symname) { struct simple_arg arg = { .type = Symlink, .u.s.symname = symname }; return add_simple(dir, dentry, &arg); } int aufs_create(struct inode *dir, struct dentry *dentry, int mode, struct nameidata *nd) { struct simple_arg arg = { .type = Creat, .u.c = { .mode = mode, .nd = nd } }; return add_simple(dir, dentry, &arg); } /* ---------------------------------------------------------------------- */ struct au_link_args { aufs_bindex_t bdst, bsrc; struct au_pin pin; struct path h_path; struct dentry *src_parent, *parent; }; static int au_cpup_before_link(struct dentry *src_dentry, struct au_link_args *a) { int err; struct dentry *h_src_dentry; struct mutex *h_mtx; struct file *h_file; di_read_lock_parent(a->src_parent, AuLock_IR); err = au_test_and_cpup_dirs(src_dentry, a->bdst); if (unlikely(err)) goto out; h_src_dentry = au_h_dptr(src_dentry, a->bsrc); h_mtx = &h_src_dentry->d_inode->i_mutex; err = au_pin(&a->pin, src_dentry, a->bdst, au_opt_udba(src_dentry->d_sb), AuPin_DI_LOCKED | AuPin_MNT_WRITE); if (unlikely(err)) goto out; mutex_lock_nested(h_mtx, AuLsc_I_CHILD); h_file = au_h_open_pre(src_dentry, a->bsrc); if (IS_ERR(h_file)) { err = PTR_ERR(h_file); h_file = NULL; } else err = au_sio_cpup_simple(src_dentry, a->bdst, a->bsrc, AuCpup_DTIME /* | AuCpup_KEEPLINO */); mutex_unlock(h_mtx); au_h_open_post(src_dentry, a->bsrc, h_file); au_unpin(&a->pin); out: di_read_unlock(a->src_parent, AuLock_IR); return err; } static int au_cpup_or_link(struct dentry *src_dentry, struct au_link_args *a) { int err; unsigned char plink; struct inode *h_inode, *inode; struct dentry *h_src_dentry; struct super_block *sb; struct file *h_file; plink = 0; h_inode = NULL; sb = src_dentry->d_sb; inode = src_dentry->d_inode; if (au_ibstart(inode) <= a->bdst) h_inode = au_h_iptr(inode, a->bdst); if (!h_inode || !h_inode->i_nlink) { /* copyup src_dentry as the name of dentry. */ au_set_dbstart(src_dentry, a->bdst); au_set_h_dptr(src_dentry, a->bdst, dget(a->h_path.dentry)); h_inode = au_h_dptr(src_dentry, a->bsrc)->d_inode; mutex_lock_nested(&h_inode->i_mutex, AuLsc_I_CHILD); h_file = au_h_open_pre(src_dentry, a->bsrc); if (IS_ERR(h_file)) { err = PTR_ERR(h_file); h_file = NULL; } else err = au_sio_cpup_single(src_dentry, a->bdst, a->bsrc, -1, AuCpup_KEEPLINO, a->parent); mutex_unlock(&h_inode->i_mutex); au_h_open_post(src_dentry, a->bsrc, h_file); au_set_h_dptr(src_dentry, a->bdst, NULL); au_set_dbstart(src_dentry, a->bsrc); } else { /* the inode of src_dentry already exists on a.bdst branch */ h_src_dentry = d_find_alias(h_inode); if (!h_src_dentry && au_plink_test(inode)) { plink = 1; h_src_dentry = au_plink_lkup(inode, a->bdst); err = PTR_ERR(h_src_dentry); if (IS_ERR(h_src_dentry)) goto out; if (unlikely(!h_src_dentry->d_inode)) { dput(h_src_dentry); h_src_dentry = NULL; } } if (h_src_dentry) { err = vfsub_link(h_src_dentry, au_pinned_h_dir(&a->pin), &a->h_path); dput(h_src_dentry); } else { AuIOErr("no dentry found for hi%lu on b%d\n", h_inode->i_ino, a->bdst); err = -EIO; } } if (!err && !plink) au_plink_append(inode, a->bdst, a->h_path.dentry); out: AuTraceErr(err); return err; }
/* * returns the number of lower positive dentries, * otherwise an error. * can be called at unlinking with @type is zero. */ int au_lkup_dentry(struct dentry *dentry, aufs_bindex_t bstart, mode_t type) { int npositive, err; aufs_bindex_t bindex, btail, bdiropq; unsigned char isdir, dirperm1; struct qstr whname; struct au_do_lookup_args args = { .flags = 0, .type = type }; const struct qstr *name = &dentry->d_name; struct dentry *parent; struct super_block *sb; sb = dentry->d_sb; err = au_test_shwh(sb, name); if (unlikely(err)) goto out; err = au_wh_name_alloc(&whname, name); if (unlikely(err)) goto out; isdir = !!d_is_dir(dentry); if (!type) au_fset_lkup(args.flags, ALLOW_NEG); dirperm1 = !!au_opt_test(au_mntflags(sb), DIRPERM1); npositive = 0; parent = dget_parent(dentry); btail = au_dbtaildir(parent); for (bindex = bstart; bindex <= btail; bindex++) { struct dentry *h_parent, *h_dentry; struct inode *h_inode, *h_dir; h_dentry = au_h_dptr(dentry, bindex); if (h_dentry) { if (d_is_positive(h_dentry)) npositive++; if (type != S_IFDIR) break; continue; } h_parent = au_h_dptr(parent, bindex); if (!h_parent || !d_is_dir(h_parent)) continue; h_dir = d_inode(h_parent); mutex_lock_nested(&h_dir->i_mutex, AuLsc_I_PARENT); h_dentry = au_do_lookup(h_parent, dentry, bindex, &whname, &args); mutex_unlock(&h_dir->i_mutex); err = PTR_ERR(h_dentry); if (IS_ERR(h_dentry)) goto out_parent; if (h_dentry) au_fclr_lkup(args.flags, ALLOW_NEG); if (dirperm1) au_fset_lkup(args.flags, IGNORE_PERM); if (au_dbwh(dentry) >= 0) break; if (!h_dentry) continue; if (d_is_negative(h_dentry)) continue; h_inode = d_inode(h_dentry); npositive++; if (!args.type) args.type = h_inode->i_mode & S_IFMT; if (args.type != S_IFDIR) break; else if (isdir) { /* the type of lower may be different */ bdiropq = au_dbdiropq(dentry); if (bdiropq >= 0 && bdiropq <= bindex) break; } } if (npositive) { AuLabel(positive); au_update_dbstart(dentry); } err = npositive; if (unlikely(!au_opt_test(au_mntflags(sb), UDBA_NONE) && au_dbstart(dentry) < 0)) { err = -EIO; AuIOErr("both of real entry and whiteout found, %pd, err %d\n", dentry, err); } out_parent: dput(parent); kfree(whname.name); out: return err; } struct dentry *au_sio_lkup_one(struct qstr *name, struct dentry *parent) { struct dentry *dentry; int wkq_err; if (!au_test_h_perm_sio(d_inode(parent), MAY_EXEC)) dentry = vfsub_lkup_one(name, parent); else { struct vfsub_lkup_one_args args = { .errp = &dentry, .name = name, .parent = parent }; wkq_err = au_wkq_wait(vfsub_call_lkup_one, &args); if (unlikely(wkq_err)) dentry = ERR_PTR(wkq_err); } return dentry; } /* * lookup @dentry on @bindex which should be negative. */ int au_lkup_neg(struct dentry *dentry, aufs_bindex_t bindex, int wh) { int err; struct dentry *parent, *h_parent, *h_dentry; struct au_branch *br; parent = dget_parent(dentry); h_parent = au_h_dptr(parent, bindex); br = au_sbr(dentry->d_sb, bindex); if (wh) h_dentry = au_whtmp_lkup(h_parent, br, &dentry->d_name); else h_dentry = au_sio_lkup_one(&dentry->d_name, h_parent); err = PTR_ERR(h_dentry); if (IS_ERR(h_dentry)) goto out; if (unlikely(d_is_positive(h_dentry))) { err = -EIO; AuIOErr("%pd should be negative on b%d.\n", h_dentry, bindex); dput(h_dentry); goto out; } err = 0; if (bindex < au_dbstart(dentry)) au_set_dbstart(dentry, bindex); if (au_dbend(dentry) < bindex) au_set_dbend(dentry, bindex); au_set_h_dptr(dentry, bindex, h_dentry); out: dput(parent); return err; }
/* * 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, bstart; struct inode *inode, *h_dir, *delegated; struct dentry *parent, *wh_dentry; /* to reuduce 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? */ bstart = au_dbstart(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, bstart); a->h_path.dentry = au_h_dptr(dentry, bstart); dget(a->h_path.dentry); if (bindex == bstart) { 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 == bstart) { vfsub_update_h_iattr(&a->h_path, /*did*/NULL); /*ignore*/ 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: kfree(a); out: return err; }
static int set_inode(struct inode *inode, struct dentry *dentry) { int err; unsigned int flags; umode_t mode; aufs_bindex_t bindex, bstart, btail; unsigned char isdir; struct dentry *h_dentry; struct inode *h_inode; struct au_iinfo *iinfo; IiMustWriteLock(inode); err = 0; isdir = 0; bstart = au_dbstart(dentry); h_inode = au_h_dptr(dentry, bstart)->d_inode; mode = h_inode->i_mode; switch (mode & S_IFMT) { case S_IFREG: btail = au_dbtail(dentry); inode->i_op = &aufs_iop; inode->i_fop = &aufs_file_fop; inode->i_mapping->a_ops = &aufs_aop; break; case S_IFDIR: isdir = 1; btail = au_dbtaildir(dentry); inode->i_op = &aufs_dir_iop; inode->i_fop = &aufs_dir_fop; break; case S_IFLNK: btail = au_dbtail(dentry); inode->i_op = &aufs_symlink_iop; break; case S_IFBLK: case S_IFCHR: case S_IFIFO: case S_IFSOCK: btail = au_dbtail(dentry); inode->i_op = &aufs_iop; au_init_special_fop(inode, mode, h_inode->i_rdev); break; default: AuIOErr("Unknown file type 0%o\n", mode); err = -EIO; goto out; } /* do not set inotify for whiteouted dirs (SHWH mode) */ flags = au_hi_flags(inode, isdir); if (au_opt_test(au_mntflags(dentry->d_sb), SHWH) && au_ftest_hi(flags, HINOTIFY) && dentry->d_name.len > AUFS_WH_PFX_LEN && !memcmp(dentry->d_name.name, AUFS_WH_PFX, AUFS_WH_PFX_LEN)) au_fclr_hi(flags, HINOTIFY); iinfo = au_ii(inode); iinfo->ii_bstart = bstart; iinfo->ii_bend = btail; for (bindex = bstart; bindex <= btail; bindex++) { h_dentry = au_h_dptr(dentry, bindex); if (h_dentry) au_set_h_iptr(inode, bindex, au_igrab(h_dentry->d_inode), flags); } au_cpup_attr_all(inode, /*force*/1); out: return err; }
int aufs_link(struct dentry *src_dentry, struct inode *dir, struct dentry *dentry) { int err, rerr; struct au_dtime dt; struct au_link_args *a; struct dentry *wh_dentry, *h_src_dentry; struct inode *inode; struct super_block *sb; struct au_wr_dir_args wr_dir_args = { /* .force_btgt = -1, */ .flags = AuWrDir_ADD_ENTRY }; IMustLock(dir); inode = src_dentry->d_inode; IMustLock(inode); err = -ENOMEM; a = kzalloc(sizeof(*a), GFP_NOFS); if (unlikely(!a)) goto out; a->parent = dentry->d_parent; /* dir inode is locked */ err = aufs_read_and_write_lock2(dentry, src_dentry, AuLock_NOPLM | AuLock_GEN); if (unlikely(err)) goto out_kfree; err = au_d_hashed_positive(src_dentry); if (unlikely(err)) goto out_unlock; err = au_d_may_add(dentry); if (unlikely(err)) goto out_unlock; a->src_parent = dget_parent(src_dentry); wr_dir_args.force_btgt = au_ibstart(inode); di_write_lock_parent(a->parent); wr_dir_args.force_btgt = au_wbr(dentry, wr_dir_args.force_btgt); wh_dentry = lock_hdir_lkup_wh(dentry, &dt, src_dentry, &a->pin, &wr_dir_args); err = PTR_ERR(wh_dentry); if (IS_ERR(wh_dentry)) goto out_parent; err = 0; sb = dentry->d_sb; a->bdst = au_dbstart(dentry); a->h_path.dentry = au_h_dptr(dentry, a->bdst); a->h_path.mnt = au_sbr_mnt(sb, a->bdst); a->bsrc = au_ibstart(inode); h_src_dentry = au_h_d_alias(src_dentry, a->bsrc); if (!h_src_dentry) { a->bsrc = au_dbstart(src_dentry); h_src_dentry = au_h_d_alias(src_dentry, a->bsrc); AuDebugOn(!h_src_dentry); } else if (IS_ERR(h_src_dentry)) goto out_parent; if (au_opt_test(au_mntflags(sb), PLINK)) { if (a->bdst < a->bsrc /* && h_src_dentry->d_sb != a->h_path.dentry->d_sb */) err = au_cpup_or_link(src_dentry, a); else err = vfsub_link(h_src_dentry, au_pinned_h_dir(&a->pin), &a->h_path); dput(h_src_dentry); } else { /* * copyup src_dentry to the branch we process, * and then link(2) to it. */ dput(h_src_dentry); if (a->bdst < a->bsrc /* && h_src_dentry->d_sb != a->h_path.dentry->d_sb */) { au_unpin(&a->pin); di_write_unlock(a->parent); err = au_cpup_before_link(src_dentry, a); di_write_lock_parent(a->parent); if (!err) err = au_pin(&a->pin, dentry, a->bdst, au_opt_udba(sb), AuPin_DI_LOCKED | AuPin_MNT_WRITE); if (unlikely(err)) goto out_wh; } if (!err) { h_src_dentry = au_h_dptr(src_dentry, a->bdst); err = -ENOENT; if (h_src_dentry && h_src_dentry->d_inode) err = vfsub_link(h_src_dentry, au_pinned_h_dir(&a->pin), &a->h_path); } } if (unlikely(err)) goto out_unpin; if (wh_dentry) { a->h_path.dentry = wh_dentry; err = au_wh_unlink_dentry(au_pinned_h_dir(&a->pin), &a->h_path, dentry); if (unlikely(err)) goto out_revert; } dir->i_version++; if (au_ibstart(dir) == au_dbstart(dentry)) au_cpup_attr_timesizes(dir); inc_nlink(inode); inode->i_ctime = dir->i_ctime; d_instantiate(dentry, au_igrab(inode)); if (d_unhashed(a->h_path.dentry)) /* some filesystem calls d_drop() */ d_drop(dentry); goto out_unpin; /* success */ out_revert: rerr = vfsub_unlink(au_pinned_h_dir(&a->pin), &a->h_path, /*force*/0); if (unlikely(rerr)) { AuIOErr("%.*s reverting failed(%d, %d)\n", AuDLNPair(dentry), err, rerr); err = -EIO; } au_dtime_revert(&dt); out_unpin: au_unpin(&a->pin); out_wh: dput(wh_dentry); out_parent: di_write_unlock(a->parent); dput(a->src_parent); out_unlock: if (unlikely(err)) { au_update_dbstart(dentry); d_drop(dentry); } aufs_read_and_write_unlock2(dentry, src_dentry); out_kfree: kfree(a); out: return err; }