static noinline_for_stack int cpup_iattr(struct dentry *dst, aufs_bindex_t bindex, struct dentry *h_src) { int err, sbits; struct iattr ia; struct path h_path; struct inode *h_isrc; h_path.dentry = au_h_dptr(dst, bindex); h_path.mnt = au_sbr_mnt(dst->d_sb, bindex); h_isrc = h_src->d_inode; ia.ia_valid = ATTR_FORCE | ATTR_MODE | ATTR_UID | ATTR_GID | ATTR_ATIME | ATTR_MTIME | ATTR_ATIME_SET | ATTR_MTIME_SET; ia.ia_mode = h_isrc->i_mode; ia.ia_uid = h_isrc->i_uid; ia.ia_gid = h_isrc->i_gid; ia.ia_atime = h_isrc->i_atime; ia.ia_mtime = h_isrc->i_mtime; sbits = !!(ia.ia_mode & (S_ISUID | S_ISGID)); au_cpup_attr_flags(h_path.dentry->d_inode, h_isrc); err = vfsub_notify_change(&h_path, &ia); /* is this nfs only? */ if (!err && sbits && au_test_nfs(h_path.dentry->d_sb)) { ia.ia_valid = ATTR_FORCE | ATTR_MODE; ia.ia_mode = h_isrc->i_mode; err = vfsub_notify_change(&h_path, &ia); } return err; }
static noinline_for_stack int cpup_iattr(struct dentry *dst, aufs_bindex_t bindex, struct dentry *h_src, struct au_cpup_reg_attr *h_src_attr) { int err, sbits; struct iattr ia; struct path h_path; struct inode *h_isrc, *h_idst; struct kstat *h_st; h_path.dentry = au_h_dptr(dst, bindex); h_idst = h_path.dentry->d_inode; h_path.mnt = au_sbr_mnt(dst->d_sb, bindex); h_isrc = h_src->d_inode; ia.ia_valid = ATTR_FORCE | ATTR_UID | ATTR_GID | ATTR_ATIME | ATTR_MTIME | ATTR_ATIME_SET | ATTR_MTIME_SET; if (h_src_attr && h_src_attr->valid) { h_st = &h_src_attr->st; ia.ia_uid = h_st->uid; ia.ia_gid = h_st->gid; ia.ia_atime = h_st->atime; ia.ia_mtime = h_st->mtime; if (h_idst->i_mode != h_st->mode && !S_ISLNK(h_idst->i_mode)) { ia.ia_valid |= ATTR_MODE; ia.ia_mode = h_st->mode; } sbits = !!(h_st->mode & (S_ISUID | S_ISGID)); au_cpup_attr_flags(h_idst, h_src_attr->iflags); } else { ia.ia_uid = h_isrc->i_uid; ia.ia_gid = h_isrc->i_gid; ia.ia_atime = h_isrc->i_atime; ia.ia_mtime = h_isrc->i_mtime; if (h_idst->i_mode != h_isrc->i_mode && !S_ISLNK(h_idst->i_mode)) { ia.ia_valid |= ATTR_MODE; ia.ia_mode = h_isrc->i_mode; } sbits = !!(h_isrc->i_mode & (S_ISUID | S_ISGID)); au_cpup_attr_flags(h_idst, h_isrc->i_flags); } err = vfsub_notify_change(&h_path, &ia); /* is this nfs only? */ if (!err && sbits && au_test_nfs(h_path.dentry->d_sb)) { ia.ia_valid = ATTR_FORCE | ATTR_MODE; ia.ia_mode = h_isrc->i_mode; err = vfsub_notify_change(&h_path, &ia); } return err; }
/* * decide the branch where we operate for @dentry. the branch index will be set * @rbcpup. after diciding it, 'pin' it and store the timestamps of the parent * dir for reverting. * when a new whiteout is necessary, create it. */ static struct dentry* lock_hdir_create_wh(struct dentry *dentry, int isdir, aufs_bindex_t *rbcpup, struct au_dtime *dt, struct au_pin *pin) { struct dentry *wh_dentry; struct super_block *sb; struct path h_path; int err, need_wh; unsigned int udba; aufs_bindex_t bcpup; need_wh = au_wr_dir_need_wh(dentry, isdir, rbcpup); wh_dentry = ERR_PTR(need_wh); if (unlikely(need_wh < 0)) goto out; sb = dentry->d_sb; udba = au_opt_udba(sb); bcpup = *rbcpup; err = au_pin(pin, dentry, bcpup, udba, AuPin_DI_LOCKED | AuPin_MNT_WRITE); wh_dentry = ERR_PTR(err); if (unlikely(err)) goto out; h_path.dentry = au_pinned_h_parent(pin); if (udba != AuOpt_UDBA_NONE && au_dbstart(dentry) == bcpup) { err = au_may_del(dentry, bcpup, h_path.dentry, isdir); wh_dentry = ERR_PTR(err); if (unlikely(err)) goto out_unpin; } h_path.mnt = au_sbr_mnt(sb, bcpup); au_dtime_store(dt, au_pinned_parent(pin), &h_path); wh_dentry = NULL; if (!need_wh) goto out; /* success, no need to create whiteout */ wh_dentry = au_wh_create(dentry, bcpup, h_path.dentry); if (IS_ERR(wh_dentry)) goto out_unpin; /* returns with the parent is locked and wh_dentry is dget-ed */ goto out; /* success */ out_unpin: 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; }
/* * unlink the topmost h_dentry * Note: the target file MAY be modified by UDBA between this mutex_unlock() and * mutex_lock() in vfs_unlink(). in this case, such changes may be lost. */ static int au_do_unlink(const unsigned char dmsg, struct au_mvd_args *a) { int err; struct path h_path; h_path.mnt = au_sbr_mnt(a->sb, a->mvd_bsrc); h_path.dentry = au_h_dptr(a->dentry, a->mvd_bsrc); err = vfsub_unlink(a->mvd_h_src_dir, &h_path, /*force*/0); if (unlikely(err)) AU_MVD_PR(dmsg, "unlink failed\n"); AuTraceErr(err); return err; }
int aufs_getattr(struct vfsmount *mnt, struct dentry *dentry, struct kstat *st) { int err; aufs_bindex_t bindex; struct inode *inode; struct dentry *h_dentry; struct super_block *sb; unsigned int mnt_flags; LKTRTrace("%.*s\n", AuDLNPair(dentry)); inode = dentry->d_inode; sb = dentry->d_sb; aufs_read_lock(dentry, AuLock_FLUSH | AuLock_IR); /* todo: refine it */ mnt_flags = au_mntflags(sb); if (au_opt_test(mnt_flags, PLINK) && au_plink_test(sb, inode)) goto plinked; h_dentry = au_h_dget_any(dentry, &bindex); err = PTR_ERR(h_dentry); if (IS_ERR(h_dentry)) goto out; err = -ENOENT; if (h_dentry->d_inode) err = vfsub_getattr(au_sbr_mnt(sb, bindex), h_dentry, st, au_test_dlgt(mnt_flags)); dput(h_dentry); if (!err) { au_cpup_attr_all(inode); plinked: generic_fillattr(inode, st); } out: aufs_read_unlock(dentry, AuLock_IR); AuTraceErr(err); return err; }
/* * unlink the topmost h_dentry */ static int au_do_unlink(const unsigned char dmsg, struct au_mvd_args *a) { int err; struct path h_path; struct inode *delegated; h_path.mnt = au_sbr_mnt(a->sb, a->mvd_bsrc); h_path.dentry = au_h_dptr(a->dentry, a->mvd_bsrc); delegated = NULL; err = vfsub_unlink(a->mvd_h_src_dir, &h_path, &delegated, /*force*/0); if (unlikely(err == -EWOULDBLOCK)) { pr_warn("cannot retry for NFSv4 delegation" " for an internal unlink\n"); iput(delegated); } if (unlikely(err)) AU_MVD_PR(dmsg, "unlink failed\n"); AuTraceErr(err); return err; }
/* reduce stack space */ union { struct au_opt_add *add; struct au_opt_del *del; struct au_opt_mod *mod; struct au_opt_xino *xino; struct au_opt_xino_itrunc *xino_itrunc; struct au_opt_wbr_create *create; } u; struct au_opt *opt; opt = opts->opt; while (opt->type != Opt_tail) { switch (opt->type) { case Opt_add: u.add = &opt->add; AuDbg("add {b%d, %s, 0x%x, %p}\n", u.add->bindex, u.add->pathname, u.add->perm, u.add->path.dentry); break; case Opt_del: case Opt_idel: u.del = &opt->del; AuDbg("del {%s, %p}\n", u.del->pathname, u.del->h_path.dentry); break; case Opt_mod: case Opt_imod: u.mod = &opt->mod; AuDbg("mod {%s, 0x%x, %p}\n", u.mod->path, u.mod->perm, u.mod->h_root); break; case Opt_append: u.add = &opt->add; AuDbg("append {b%d, %s, 0x%x, %p}\n", u.add->bindex, u.add->pathname, u.add->perm, u.add->path.dentry); break; case Opt_prepend: u.add = &opt->add; AuDbg("prepend {b%d, %s, 0x%x, %p}\n", u.add->bindex, u.add->pathname, u.add->perm, u.add->path.dentry); break; case Opt_dirwh: AuDbg("dirwh %d\n", opt->dirwh); break; case Opt_rdcache: AuDbg("rdcache %d\n", opt->rdcache); break; case Opt_rdblk: AuDbg("rdblk %u\n", opt->rdblk); break; case Opt_rdblk_def: AuDbg("rdblk_def\n"); break; case Opt_rdhash: AuDbg("rdhash %u\n", opt->rdhash); break; case Opt_rdhash_def: AuDbg("rdhash_def\n"); break; case Opt_xino: u.xino = &opt->xino; AuDbg("xino {%s %.*s}\n", u.xino->path, AuDLNPair(u.xino->file->f_dentry)); break; case Opt_trunc_xino: AuLabel(trunc_xino); break; case Opt_notrunc_xino: AuLabel(notrunc_xino); break; case Opt_trunc_xino_path: case Opt_itrunc_xino: u.xino_itrunc = &opt->xino_itrunc; AuDbg("trunc_xino %d\n", u.xino_itrunc->bindex); break; case Opt_noxino: AuLabel(noxino); break; case Opt_trunc_xib: AuLabel(trunc_xib); break; case Opt_notrunc_xib: AuLabel(notrunc_xib); break; case Opt_shwh: AuLabel(shwh); break; case Opt_noshwh: AuLabel(noshwh); break; case Opt_plink: AuLabel(plink); break; case Opt_noplink: AuLabel(noplink); break; case Opt_list_plink: AuLabel(list_plink); break; case Opt_udba: AuDbg("udba %d, %s\n", opt->udba, au_optstr_udba(opt->udba)); break; case Opt_dio: AuLabel(dio); break; case Opt_nodio: AuLabel(nodio); break; case Opt_diropq_a: AuLabel(diropq_a); break; case Opt_diropq_w: AuLabel(diropq_w); break; case Opt_warn_perm: AuLabel(warn_perm); break; case Opt_nowarn_perm: AuLabel(nowarn_perm); break; case Opt_refrof: AuLabel(refrof); break; case Opt_norefrof: AuLabel(norefrof); break; case Opt_verbose: AuLabel(verbose); break; case Opt_noverbose: AuLabel(noverbose); break; case Opt_sum: AuLabel(sum); break; case Opt_nosum: AuLabel(nosum); break; case Opt_wsum: AuLabel(wsum); break; case Opt_wbr_create: u.create = &opt->wbr_create; AuDbg("create %d, %s\n", u.create->wbr_create, au_optstr_wbr_create(u.create->wbr_create)); switch (u.create->wbr_create) { case AuWbrCreate_MFSV: case AuWbrCreate_PMFSV: AuDbg("%d sec\n", u.create->mfs_second); break; case AuWbrCreate_MFSRR: AuDbg("%llu watermark\n", u.create->mfsrr_watermark); break; case AuWbrCreate_MFSRRV: AuDbg("%llu watermark, %d sec\n", u.create->mfsrr_watermark, u.create->mfs_second); break; } break; case Opt_wbr_copyup: AuDbg("copyup %d, %s\n", opt->wbr_copyup, au_optstr_wbr_copyup(opt->wbr_copyup)); break; default: BUG(); } opt++; } #endif } void au_opts_free(struct au_opts *opts) { struct au_opt *opt; opt = opts->opt; while (opt->type != Opt_tail) { switch (opt->type) { case Opt_add: case Opt_append: case Opt_prepend: path_put(&opt->add.path); break; case Opt_del: case Opt_idel: path_put(&opt->del.h_path); break; case Opt_mod: case Opt_imod: dput(opt->mod.h_root); break; case Opt_xino: fput(opt->xino.file); break; } opt++; } } static int opt_add(struct au_opt *opt, char *opt_str, unsigned long sb_flags, aufs_bindex_t bindex) { int err; struct au_opt_add *add = &opt->add; char *p; add->bindex = bindex; add->perm = AuBrPerm_RO; add->pathname = opt_str; p = strchr(opt_str, '='); if (p) { *p++ = 0; if (*p) add->perm = br_perm_val(p); } err = vfsub_kern_path(add->pathname, lkup_dirflags, &add->path); if (!err) { if (!p) { add->perm = AuBrPerm_RO; if (au_test_fs_rr(add->path.dentry->d_sb)) add->perm = AuBrPerm_RR; else if (!bindex && !(sb_flags & MS_RDONLY)) add->perm = AuBrPerm_RW; } opt->type = Opt_add; goto out; } pr_err("lookup failed %s (%d)\n", add->pathname, err); err = -EINVAL; out: return err; } static int au_opts_parse_del(struct au_opt_del *del, substring_t args[]) { int err; del->pathname = args[0].from; AuDbg("del path %s\n", del->pathname); err = vfsub_kern_path(del->pathname, lkup_dirflags, &del->h_path); if (unlikely(err)) pr_err("lookup failed %s (%d)\n", del->pathname, err); return err; } #if 0 /* reserved for future use */ static int au_opts_parse_idel(struct super_block *sb, aufs_bindex_t bindex, struct au_opt_del *del, substring_t args[]) { int err; struct dentry *root; err = -EINVAL; root = sb->s_root; aufs_read_lock(root, AuLock_FLUSH); if (bindex < 0 || au_sbend(sb) < bindex) { pr_err("out of bounds, %d\n", bindex); goto out; } err = 0; del->h_path.dentry = dget(au_h_dptr(root, bindex)); del->h_path.mnt = mntget(au_sbr_mnt(sb, bindex)); out: aufs_read_unlock(root, !AuLock_IR); 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; }
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 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; }