/* * 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; }
/* lock them all */ static int au_do_lock(const unsigned char dmsg, struct au_mvd_args *a) { int err; struct dentry *h_trap; a->mvd_h_src_sb = au_sbr_sb(a->sb, a->mvd_bsrc); a->mvd_h_dst_sb = au_sbr_sb(a->sb, a->mvd_bdst); err = au_pin(&a->mvd_pin_dst, a->dentry, a->mvd_bdst, au_opt_udba(a->sb), AuPin_MNT_WRITE | AuPin_DI_LOCKED); AuTraceErr(err); if (unlikely(err)) { AU_MVD_PR(dmsg, "pin_dst failed\n"); goto out; } if (a->mvd_h_src_sb != a->mvd_h_dst_sb) { a->rename_lock = 0; au_pin_init(&a->mvd_pin_src, a->dentry, a->mvd_bsrc, AuLsc_DI_PARENT, AuLsc_I_PARENT3, au_opt_udba(a->sb), AuPin_MNT_WRITE | AuPin_DI_LOCKED); err = au_do_pin(&a->mvd_pin_src); AuTraceErr(err); a->mvd_h_src_dir = d_inode(a->mvd_h_src_parent); if (unlikely(err)) { AU_MVD_PR(dmsg, "pin_src failed\n"); goto out_dst; } goto out; /* success */ } a->rename_lock = 1; au_pin_hdir_unlock(&a->mvd_pin_dst); err = au_pin(&a->mvd_pin_src, a->dentry, a->mvd_bsrc, au_opt_udba(a->sb), AuPin_MNT_WRITE | AuPin_DI_LOCKED); AuTraceErr(err); a->mvd_h_src_dir = d_inode(a->mvd_h_src_parent); if (unlikely(err)) { AU_MVD_PR(dmsg, "pin_src failed\n"); au_pin_hdir_lock(&a->mvd_pin_dst); goto out_dst; } au_pin_hdir_unlock(&a->mvd_pin_src); h_trap = vfsub_lock_rename(a->mvd_h_src_parent, a->mvd_hdir_src, a->mvd_h_dst_parent, a->mvd_hdir_dst); if (h_trap) { err = (h_trap != a->mvd_h_src_parent); if (err) err = (h_trap != a->mvd_h_dst_parent); } BUG_ON(err); /* it should never happen */ if (unlikely(a->mvd_h_src_dir != au_pinned_h_dir(&a->mvd_pin_src))) { err = -EBUSY; AuTraceErr(err); vfsub_unlock_rename(a->mvd_h_src_parent, a->mvd_hdir_src, a->mvd_h_dst_parent, a->mvd_hdir_dst); au_pin_hdir_lock(&a->mvd_pin_src); au_unpin(&a->mvd_pin_src); au_pin_hdir_lock(&a->mvd_pin_dst); goto out_dst; } goto out; /* success */ out_dst: au_unpin(&a->mvd_pin_dst); out: AuTraceErr(err); 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; }
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; }
/* * 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; }
/* * 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; }