/* an exception for the policy other than tdp */ static int au_wbr_create_exp(struct dentry *dentry) { int err; aufs_bindex_t bwh, bdiropq; struct dentry *parent; err = -1; bwh = au_dbwh(dentry); parent = dget_parent(dentry); bdiropq = au_dbdiropq(parent); if (bwh >= 0) { if (bdiropq >= 0) err = min(bdiropq, bwh); else err = bwh; AuDbg("%d\n", err); } else if (bdiropq >= 0) { err = bdiropq; AuDbg("%d\n", err); } dput(parent); if (err >= 0) err = au_wbr_nonopq(dentry, err); if (err >= 0 && au_br_rdonly(au_sbr(dentry->d_sb, err))) err = -1; AuDbg("%d\n", err); 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 = au_br_mnt(br) }; 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; }
/* * 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; }
aufs_bindex_t au_dbtail(struct dentry *dentry) { aufs_bindex_t bend, bwh; bend = au_dbend(dentry); if (0 <= bend) { bwh = au_dbwh(dentry); if (!bwh) return bwh; if (0 < bwh && bwh < bend) return bwh - 1; } return bend; }
/* mainly for link(2) and rename(2) */ int au_wbr(struct dentry *dentry, aufs_bindex_t btgt) { aufs_bindex_t bdiropq, bwh; struct dentry *parent; struct au_branch *br; parent = dentry->d_parent; IMustLock(parent->d_inode); /* dir is locked */ bdiropq = au_dbdiropq(parent); bwh = au_dbwh(dentry); br = au_sbr(dentry->d_sb, btgt); if (au_br_rdonly(br) || (0 <= bdiropq && bdiropq < btgt) || (0 <= bwh && bwh < btgt)) btgt = -1; AuDbg("btgt %d\n", btgt); return btgt; }
int aufs_rmdir(struct inode *dir, struct dentry *dentry) { int err, rmdir_later; aufs_bindex_t bwh, bindex, bstart; struct au_dtime dt; struct au_pin pin; struct inode *inode; struct dentry *parent, *wh_dentry, *h_dentry; struct au_whtmp_rmdir *args; IMustLock(dir); err = aufs_read_lock(dentry, AuLock_DW | AuLock_FLUSH | AuLock_GEN); if (unlikely(err)) goto out; err = au_alive_dir(dentry); if (unlikely(err)) goto out_unlock; inode = dentry->d_inode; IMustLock(inode); err = -ENOTDIR; if (unlikely(!S_ISDIR(inode->i_mode))) goto out_unlock; /* possible? */ err = -ENOMEM; args = au_whtmp_rmdir_alloc(dir->i_sb, GFP_NOFS); if (unlikely(!args)) goto out_unlock; parent = dentry->d_parent; /* dir inode is locked */ di_write_lock_parent(parent); err = au_test_empty(dentry, &args->whlist); if (unlikely(err)) goto out_parent; bstart = au_dbstart(dentry); bwh = au_dbwh(dentry); bindex = -1; wh_dentry = lock_hdir_create_wh(dentry, /*isdir*/1, &bindex, &dt, &pin); err = PTR_ERR(wh_dentry); if (IS_ERR(wh_dentry)) goto out_parent; h_dentry = au_h_dptr(dentry, bstart); dget(h_dentry); rmdir_later = 0; if (bindex == bstart) { err = renwh_and_rmdir(dentry, bstart, &args->whlist, dir); if (err > 0) { rmdir_later = err; err = 0; } } else { /* stop monitoring */ au_hn_free(au_hi(inode, bstart)); /* dir inode is locked */ IMustLock(wh_dentry->d_parent->d_inode); err = 0; } if (!err) { vfsub_dead_dir(inode); au_set_dbdiropq(dentry, -1); epilog(dir, dentry, bindex); if (rmdir_later) { au_whtmp_kick_rmdir(dir, bstart, h_dentry, args); args = NULL; } goto out_unpin; /* success */ } /* revert */ AuLabel(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_dentry); out_parent: di_write_unlock(parent); if (args) au_whtmp_rmdir_free(args); out_unlock: aufs_read_unlock(dentry, AuLock_DW); 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("%.*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; }
/* * 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; }
/* * initial procedure of adding a new entry. * prepare writable branch and the parent dir, lock it, * 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; int err; unsigned int mnt_flags; unsigned char pin_flags; aufs_bindex_t bstart, bcpup; struct au_ndx ndx; LKTRTrace("%.*s, src %p\n", AuDLNPair(dentry), src_dentry); bstart = au_dbstart(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; mnt_flags = au_mntflags(sb); pin_flags = AuPin_DI_LOCKED | AuPin_MNT_WRITE; if (dt && au_opt_test(mnt_flags, UDBA_INOTIFY)) au_fset_pin(pin_flags, DO_GPARENT); err = au_pin(pin, dentry, bcpup, pin_flags); wh_dentry = ERR_PTR(err); if (unlikely(err)) goto out; ndx.nfsmnt = au_nfsmnt(sb, bcpup); ndx.flags = 0; if (au_test_dlgt(mnt_flags)) au_fset_ndx(ndx.flags, DLGT); ndx.nd = NULL; /* ndx.br = NULL; */ /* ndx.nd_file = NULL; */ h_parent = au_pinned_h_parent(pin); if (!au_opt_test(mnt_flags, UDBA_NONE) && au_dbstart(dentry) == bcpup) { struct nameidata nd; if (ndx.nfsmnt) { /* todo: dirty? */ ndx.nd = &nd; ndx.br = au_sbr(sb, bcpup); memset(&nd, 0, sizeof(nd)); nd.flags = LOOKUP_CREATE; nd.intent.open.flags = O_EXCL; } err = au_may_add(dentry, bcpup, h_parent, au_ftest_wrdir(wr_dir_args->flags, ISDIR), &ndx); wh_dentry = ERR_PTR(err); if (unlikely(err)) goto out_unpin; ndx.nd = NULL; ndx.br = NULL; } if (dt) au_dtime_store(dt, au_pinned_parent(pin), h_parent, au_pinned_hdir(pin), au_pinned_hgdir(pin)); wh_dentry = NULL; if (/* bcpup != bstart || */ bcpup != au_dbwh(dentry)) goto out; /* success */ wh_dentry = au_wh_lkup(h_parent, &dentry->d_name, &ndx); out_unpin: if (IS_ERR(wh_dentry)) au_unpin(pin); out: AuTraceErrPtr(wh_dentry); return wh_dentry; }
static int do_rename(struct au_ren_args *a) { int err; struct dentry *d, *h_d; /* prepare workqueue args for asynchronous rmdir */ h_d = a->dst_h_dentry; if (au_ftest_ren(a->flags, ISDIR) && h_d->d_inode) { err = -ENOMEM; a->thargs = au_whtmp_rmdir_alloc(a->src_dentry->d_sb, GFP_NOFS); if (unlikely(!a->thargs)) goto out; a->h_dst = dget(h_d); } /* create whiteout for src_dentry */ if (au_ftest_ren(a->flags, WHSRC)) { a->src_bwh = au_dbwh(a->src_dentry); AuDebugOn(a->src_bwh >= 0); a->src_wh_dentry = au_wh_create(a->src_dentry, a->btgt, a->src_h_parent); err = PTR_ERR(a->src_wh_dentry); if (IS_ERR(a->src_wh_dentry)) goto out_thargs; } /* lookup whiteout for dentry */ if (au_ftest_ren(a->flags, WHDST)) { h_d = au_wh_lkup(a->dst_h_parent, &a->dst_dentry->d_name, a->br); err = PTR_ERR(h_d); if (IS_ERR(h_d)) goto out_whsrc; if (!h_d->d_inode) dput(h_d); else a->dst_wh_dentry = h_d; } /* rename dentry to tmpwh */ if (a->thargs) { err = au_whtmp_ren(a->dst_h_dentry, a->br); if (unlikely(err)) goto out_whdst; d = a->dst_dentry; au_set_h_dptr(d, a->btgt, NULL); err = au_lkup_neg(d, a->btgt); if (unlikely(err)) goto out_whtmp; a->dst_h_dentry = au_h_dptr(d, a->btgt); } /* cpup src */ if (a->dst_h_dentry->d_inode && a->src_bstart != a->btgt) { struct mutex *h_mtx = &a->src_h_dentry->d_inode->i_mutex; struct file *h_file; mutex_lock_nested(h_mtx, AuLsc_I_CHILD); AuDebugOn(au_dbstart(a->src_dentry) != a->src_bstart); h_file = au_h_open_pre(a->src_dentry, a->src_bstart); if (IS_ERR(h_file)) { err = PTR_ERR(h_file); h_file = NULL; } else err = au_sio_cpup_simple(a->src_dentry, a->btgt, -1, !AuCpup_DTIME); mutex_unlock(h_mtx); au_h_open_post(a->src_dentry, a->src_bstart, h_file); if (unlikely(err)) goto out_whtmp; } /* rename by vfs_rename or cpup */ d = a->dst_dentry; if (au_ftest_ren(a->flags, ISDIR) && (a->dst_wh_dentry || au_dbdiropq(d) == a->btgt /* hide the lower to keep xino */ || a->btgt < au_dbend(d) || au_opt_test(au_mntflags(d->d_sb), ALWAYS_DIROPQ))) au_fset_ren(a->flags, DIROPQ); err = au_ren_or_cpup(a); if (unlikely(err)) /* leave the copied-up one */ goto out_whtmp; /* make dir opaque */ if (au_ftest_ren(a->flags, DIROPQ)) { err = au_ren_diropq(a); if (unlikely(err)) goto out_rename; } /* update target timestamps */ AuDebugOn(au_dbstart(a->src_dentry) != a->btgt); a->h_path.dentry = au_h_dptr(a->src_dentry, a->btgt); vfsub_update_h_iattr(&a->h_path, /*did*/NULL); /*ignore*/ a->src_inode->i_ctime = a->h_path.dentry->d_inode->i_ctime; /* remove whiteout for dentry */ if (a->dst_wh_dentry) { a->h_path.dentry = a->dst_wh_dentry; err = au_wh_unlink_dentry(a->dst_h_dir, &a->h_path, a->dst_dentry); if (unlikely(err)) goto out_diropq; } /* remove whtmp */ if (a->thargs) au_ren_del_whtmp(a); /* ignore this error */ err = 0; goto out_success; out_diropq: if (au_ftest_ren(a->flags, DIROPQ)) au_ren_rev_diropq(err, a); out_rename: if (!au_ftest_ren(a->flags, CPUP)) au_ren_rev_rename(err, a); else au_ren_rev_cpup(err, a); dput(a->h_dst); out_whtmp: if (a->thargs) au_ren_rev_whtmp(err, a); out_whdst: dput(a->dst_wh_dentry); a->dst_wh_dentry = NULL; out_whsrc: if (a->src_wh_dentry) au_ren_rev_whsrc(err, a); out_success: dput(a->src_wh_dentry); dput(a->dst_wh_dentry); out_thargs: if (a->thargs) { dput(a->h_dst); au_whtmp_rmdir_free(a->thargs); a->thargs = NULL; } out: return err; }
static int au_mvd_args_intermediate(const unsigned char dmsg, struct au_mvd_args *a) { int err; struct au_dinfo *dinfo, *tmp; /* lookup the next lower positive entry */ err = -ENOMEM; tmp = au_di_alloc(a->sb, AuLsc_DI_TMP); if (unlikely(!tmp)) goto out; a->bfound = -1; a->bwh = -1; dinfo = au_di(a->dentry); au_di_cp(tmp, dinfo); au_di_swap(tmp, dinfo); /* returns the number of positive dentries */ err = au_lkup_dentry(a->dentry, a->mvd_bsrc + 1, /*type*/0); if (!err) a->bwh = au_dbwh(a->dentry); else if (err > 0) a->bfound = au_dbstart(a->dentry); au_di_swap(tmp, dinfo); au_rw_write_unlock(&tmp->di_rwsem); au_di_free(tmp); if (unlikely(err < 0)) AU_MVD_PR(dmsg, "failed look-up lower\n"); /* * here, we have these cases. * bfound == -1 * no positive dentry under bsrc. there are more sub-cases. * bwh < 0 * there no whiteout, we can safely move-down. * bwh <= bsrc * impossible * bsrc < bwh && bwh < bdst * there is a whiteout on RO branch. cannot proceed. * bwh == bdst * there is a whiteout on the RW target branch. it should * be removed. * bdst < bwh * there is a whiteout somewhere unrelated branch. * -1 < bfound && bfound <= bsrc * impossible. * bfound < bdst * found, but it is on RO branch between bsrc and bdst. cannot * proceed. * bfound == bdst * found, replace it if AUFS_MVDOWN_FORCE is set. otherwise return * error. * bdst < bfound * found, after we create the file on bdst, it will be hidden. */ AuDebugOn(a->bfound == -1 && a->bwh != -1 && a->bwh <= a->mvd_bsrc); AuDebugOn(-1 < a->bfound && a->bfound <= a->mvd_bsrc); err = -EINVAL; if (a->bfound == -1 && a->mvd_bsrc < a->bwh && a->bwh != -1 && a->bwh < a->mvd_bdst) { a->mvd_errno = EAU_MVDOWN_WHITEOUT; AU_MVD_PR(dmsg, "bsrc %d, bdst %d, bfound %d, bwh %d\n", a->mvd_bsrc, a->mvd_bdst, a->bfound, a->bwh); goto out; } else if (a->bfound != -1 && a->bfound < a->mvd_bdst) { a->mvd_errno = EAU_MVDOWN_UPPER; AU_MVD_PR(dmsg, "bdst %d, bfound %d\n", a->mvd_bdst, a->bfound); goto out; } err = 0; /* success */ out: AuTraceErr(err); 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; }
/* * 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; }
/* * 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; }