static void au_do_dir_ts(void *arg) { struct au_dir_ts_arg *a = arg; struct au_dtime dt; struct path h_path; struct inode *dir, *h_dir; struct super_block *sb; struct au_branch *br; struct au_hinode *hdir; int err; aufs_bindex_t btop, bindex; sb = a->dentry->d_sb; if (d_really_is_negative(a->dentry)) goto out; /* no dir->i_mutex lock */ aufs_read_lock(a->dentry, AuLock_DW); /* noflush */ dir = d_inode(a->dentry); btop = au_ibtop(dir); bindex = au_br_index(sb, a->brid); if (bindex < btop) goto out_unlock; br = au_sbr(sb, bindex); h_path.dentry = au_h_dptr(a->dentry, bindex); if (!h_path.dentry) goto out_unlock; h_path.mnt = au_br_mnt(br); au_dtime_store(&dt, a->dentry, &h_path); br = au_sbr(sb, btop); if (!au_br_writable(br->br_perm)) goto out_unlock; h_path.dentry = au_h_dptr(a->dentry, btop); h_path.mnt = au_br_mnt(br); err = vfsub_mnt_want_write(h_path.mnt); if (err) goto out_unlock; hdir = au_hi(dir, btop); au_hn_inode_lock_nested(hdir, AuLsc_I_PARENT); h_dir = au_h_iptr(dir, btop); if (h_dir->i_nlink && timespec_compare(&h_dir->i_mtime, &dt.dt_mtime) < 0) { dt.dt_h_path = h_path; au_dtime_revert(&dt); } au_hn_inode_unlock(hdir); vfsub_mnt_drop_write(h_path.mnt); au_cpup_attr_timesizes(dir); out_unlock: aufs_read_unlock(a->dentry, AuLock_DW); out: dput(a->dentry); au_nwt_done(&au_sbi(sb)->si_nowait); kfree(arg); }
/* * if valid returns 1, otherwise 0. */ static int aufs_d_revalidate(struct dentry *dentry, struct nameidata *nd) { int valid, err; unsigned int sigen; unsigned char do_udba; struct super_block *sb; struct inode *inode; err = -EINVAL; sb = dentry->d_sb; inode = dentry->d_inode; aufs_read_lock(dentry, AuLock_FLUSH | AuLock_DW); sigen = au_sigen(sb); if (au_digen(dentry) != sigen) { AuDebugOn(IS_ROOT(dentry)); if (inode) err = au_reval_dpath(dentry, sigen); if (unlikely(err)) goto out_dgrade; AuDebugOn(au_digen(dentry) != sigen); } if (inode && au_iigen(inode) != sigen) { AuDebugOn(IS_ROOT(dentry)); err = au_refresh_hinode(inode, dentry); if (unlikely(err)) goto out_dgrade; AuDebugOn(au_iigen(inode) != sigen); } di_downgrade_lock(dentry, AuLock_IR); AuDebugOn(au_digen(dentry) != sigen); AuDebugOn(inode && au_iigen(inode) != sigen); err = -EINVAL; do_udba = !au_opt_test(au_mntflags(sb), UDBA_NONE); if (do_udba && inode) { aufs_bindex_t bstart = au_ibstart(inode); if (bstart >= 0 && au_test_higen(inode, au_h_iptr(inode, bstart))) goto out; } err = h_d_revalidate(dentry, inode, nd, do_udba); if (unlikely(!err && do_udba && au_dbstart(dentry) < 0)) /* both of real entry and whiteout found */ err = -EIO; goto out; out_dgrade: di_downgrade_lock(dentry, AuLock_IR); out: aufs_read_unlock(dentry, AuLock_IR); AuTraceErr(err); valid = !err; if (!valid) AuDbg("%.*s invalid\n", AuDLNPair(dentry)); return valid; }
static int au_wbr_fd(struct path *path) { int err, fd; aufs_bindex_t wbi, bindex, bend; struct file *h_file; struct super_block *sb; struct dentry *root; struct au_branch *wbr; err = get_unused_fd(); if (unlikely(err < 0)) goto out; fd = err; wbi = 0; sb = path->dentry->d_sb; root = sb->s_root; aufs_read_lock(root, AuLock_IR); wbr = au_sbr(sb, wbi); if (!(path->mnt->mnt_flags & MNT_READONLY) && !au_br_writable(wbr->br_perm)) { bend = au_sbend(sb); for (bindex = 1; bindex <= bend; bindex++) { wbr = au_sbr(sb, bindex); if (au_br_writable(wbr->br_perm)) { wbi = bindex; break; } } wbr = au_sbr(sb, wbi); } AuDbg("wbi %d\n", wbi); h_file = au_h_open(root, wbi, O_RDONLY | O_DIRECTORY | O_LARGEFILE, NULL); aufs_read_unlock(root, AuLock_IR); err = PTR_ERR(h_file); if (IS_ERR(h_file)) goto out_fd; atomic_dec(&wbr->br_count); /* cf. au_h_open() */ fd_install(fd, h_file); err = fd; goto out; /* success */ out_fd: put_unused_fd(fd); out: 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; }
/* 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; }
static int noinline_for_stack au_opts_parse_xino_itrunc_path(struct super_block *sb, struct au_opt_xino_itrunc *xino_itrunc, substring_t args[]) { int err; aufs_bindex_t bend, bindex; struct path path; struct dentry *root; err = vfsub_kern_path(args[0].from, lkup_dirflags, &path); if (unlikely(err)) { pr_err("lookup failed %s (%d)\n", args[0].from, err); goto out; } xino_itrunc->bindex = -1; root = sb->s_root; aufs_read_lock(root, AuLock_FLUSH); bend = au_sbend(sb); for (bindex = 0; bindex <= bend; bindex++) { if (au_h_dptr(root, bindex) == path.dentry) { xino_itrunc->bindex = bindex; break; } } aufs_read_unlock(root, !AuLock_IR); path_put(&path); if (unlikely(xino_itrunc->bindex < 0)) { pr_err("no such branch %s\n", args[0].from); err = -EINVAL; } out: 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; } #endif static int noinline_for_stack au_opts_parse_mod(struct au_opt_mod *mod, substring_t args[]) { int err; struct path path; char *p; err = -EINVAL; mod->path = args[0].from; p = strchr(mod->path, '='); if (unlikely(!p)) { pr_err("no permssion %s\n", args[0].from); goto out; } *p++ = 0; err = vfsub_kern_path(mod->path, lkup_dirflags, &path); if (unlikely(err)) { pr_err("lookup failed %s (%d)\n", mod->path, err); goto out; } mod->perm = br_perm_val(p); AuDbg("mod path %s, perm 0x%x, %s\n", mod->path, mod->perm, p); mod->h_root = dget(path.dentry); path_put(&path); out: return err; } #if 0 /* reserved for future use */ static int au_opts_parse_imod(struct super_block *sb, aufs_bindex_t bindex, struct au_opt_mod *mod, 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; mod->perm = br_perm_val(args[1].from); AuDbg("mod path %s, perm 0x%x, %s\n", mod->path, mod->perm, args[1].from); mod->h_root = dget(au_h_dptr(root, bindex)); out: aufs_read_unlock(root, !AuLock_IR); return err; }
int au_mvdown(struct dentry *dentry, struct aufs_mvdown __user *uarg) { int err, e; unsigned char dmsg; struct au_mvd_args *args; err = -EPERM; if (unlikely(!capable(CAP_SYS_ADMIN))) goto out; err = -ENOMEM; args = kmalloc(sizeof(*args), GFP_NOFS); if (unlikely(!args)) goto out; err = copy_from_user(&args->mvdown, uarg, sizeof(args->mvdown)); if (!err) err = !access_ok(VERIFY_WRITE, uarg, sizeof(*uarg)); if (unlikely(err)) { err = -EFAULT; AuTraceErr(err); goto out_free; } AuDbg("flags 0x%x\n", args->mvdown.flags); args->mvdown.flags &= ~(AUFS_MVDOWN_ROLOWER_R | AUFS_MVDOWN_ROUPPER_R); args->mvdown.au_errno = 0; args->dentry = dentry; args->inode = d_inode(dentry); args->sb = dentry->d_sb; err = -ENOENT; dmsg = !!(args->mvdown.flags & AUFS_MVDOWN_DMSG); args->parent = dget_parent(dentry); args->dir = d_inode(args->parent); mutex_lock_nested(&args->dir->i_mutex, I_MUTEX_PARENT); dput(args->parent); if (unlikely(args->parent != dentry->d_parent)) { AU_MVD_PR(dmsg, "parent dir is moved\n"); goto out_dir; } mutex_lock_nested(&args->inode->i_mutex, I_MUTEX_CHILD); err = aufs_read_lock(dentry, AuLock_DW | AuLock_FLUSH); if (unlikely(err)) goto out_inode; di_write_lock_parent(args->parent); err = au_mvd_args(dmsg, args); if (unlikely(err)) goto out_parent; err = au_do_mvdown(dmsg, args); if (unlikely(err)) goto out_parent; au_cpup_attr_timesizes(args->dir); au_cpup_attr_timesizes(args->inode); au_cpup_igen(args->inode, au_h_iptr(args->inode, args->mvd_bdst)); /* au_digen_dec(dentry); */ out_parent: di_write_unlock(args->parent); aufs_read_unlock(dentry, AuLock_DW); out_inode: mutex_unlock(&args->inode->i_mutex); out_dir: mutex_unlock(&args->dir->i_mutex); out_free: e = copy_to_user(uarg, &args->mvdown, sizeof(args->mvdown)); if (unlikely(e)) err = -EFAULT; kfree(args); out: AuTraceErr(err); return err; }
/* * if valid returns 1, otherwise 0. */ static int aufs_d_revalidate(struct dentry *dentry, struct nameidata *nd) { int valid, err; unsigned int sigen; unsigned char do_udba; struct super_block *sb; struct inode *inode; valid = 0; if (unlikely(!au_di(dentry))) goto out; valid = 1; sb = dentry->d_sb; inode = dentry->d_inode; /* * todo: very ugly * i_mutex of parent dir may be held, * but we should not return 'invalid' due to busy. */ err = aufs_read_lock(dentry, AuLock_FLUSH | AuLock_DW | AuLock_NOPLM); if (unlikely(err)) { valid = err; AuTraceErr(err); goto out; } if (unlikely(au_dbrange_test(dentry))) { err = -EINVAL; AuTraceErr(err); goto out_dgrade; } sigen = au_sigen(sb); if (au_digen_test(dentry, sigen)) { AuDebugOn(IS_ROOT(dentry)); err = au_reval_dpath(dentry, sigen); if (unlikely(err)) { AuTraceErr(err); goto out_dgrade; } } di_downgrade_lock(dentry, AuLock_IR); err = -EINVAL; if (inode && (IS_DEADDIR(inode) || !inode->i_nlink)) goto out_inval; do_udba = !au_opt_test(au_mntflags(sb), UDBA_NONE); if (do_udba && inode) { aufs_bindex_t bstart = au_ibstart(inode); struct inode *h_inode; if (bstart >= 0) { h_inode = au_h_iptr(inode, bstart); if (h_inode && au_test_higen(inode, h_inode)) goto out_inval; } } err = h_d_revalidate(dentry, inode, nd, do_udba); if (unlikely(!err && do_udba && au_dbstart(dentry) < 0)) { err = -EIO; AuDbg("both of real entry and whiteout found, %.*s, err %d\n", AuDLNPair(dentry), err); } goto out_inval; out_dgrade: di_downgrade_lock(dentry, AuLock_IR); out_inval: aufs_read_unlock(dentry, AuLock_IR); AuTraceErr(err); valid = !err; out: if (!valid) { AuDbg("%.*s invalid, %d\n", AuDLNPair(dentry), valid); d_drop(dentry); } return valid; }
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; }
static struct dentry * aufs_decode_fh(struct super_block *sb, __u32 *fh, int fh_len, int fh_type, int (*acceptable)(void *context, struct dentry *de), void *context) { struct dentry *dentry; ino_t ino, dir_ino; aufs_bindex_t bindex; struct au_nfsd_si_lock nsi_lock = { .sigen = fh[Fh_sigen], .br_id = fh[Fh_br_id], .force_lock = 0 }; LKTRTrace("%d, fh{br_id %u, sigen %u, i%u, diri%u, g%u}\n", fh_type, fh[Fh_br_id], fh[Fh_sigen], fh[Fh_ino], fh[Fh_dir_ino], fh[Fh_igen]); AuDebugOn(fh_len < Fh_tail); dentry = ERR_PTR(-ESTALE); /* branch id may be wrapped around */ bindex = si_nfsd_read_lock(sb, &nsi_lock); if (unlikely(bindex < 0)) goto out; nsi_lock.force_lock = 1; /* is this inode still cached? */ ino = decode_ino(fh + Fh_ino); AuDebugOn(ino == AUFS_ROOT_INO); dir_ino = decode_ino(fh + Fh_dir_ino); dentry = decode_by_ino(sb, ino, dir_ino); if (IS_ERR(dentry)) goto out_unlock; if (dentry) goto accept; /* is the parent dir cached? */ dentry = decode_by_dir_ino(sb, ino, dir_ino, &nsi_lock); if (IS_ERR(dentry)) goto out_unlock; if (dentry) goto accept; /* lookup path */ dentry = decode_by_path(sb, bindex, ino, fh, fh_len, &nsi_lock); if (IS_ERR(dentry)) goto out_unlock; if (unlikely(!dentry)) goto out_unlock; accept: LKTRLabel(accept); if (dentry->d_inode->i_generation == fh[Fh_igen] && acceptable(context, dentry)) goto out_unlock; /* success */ LKTRLabel(stale); dput(dentry); dentry = ERR_PTR(-ESTALE); out_unlock: LKTRLabel(out_unlock); si_read_unlock(sb); out: LKTRLabel(out); if (0 && IS_ERR(dentry)) dentry = ERR_PTR(-ESTALE); AuTraceErrPtr(dentry); return dentry; } #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 24) static struct dentry * aufs_fh_to_dentry(struct super_block *sb, struct fid *fid, int fh_len, int fh_type) { return aufs_decode_fh(sb, fid->raw, fh_len, fh_type, h_acceptable, /*context*/NULL); } #endif /* KERNEL_VERSION */ /* ---------------------------------------------------------------------- */ static int aufs_encode_fh(struct dentry *dentry, __u32 *fh, int *max_len, int connectable) { int err; aufs_bindex_t bindex, bend; struct super_block *sb, *h_sb; struct inode *inode; struct dentry *parent, *h_parent; struct au_branch *br; LKTRTrace("%.*s, max %d, conn %d\n", AuDLNPair(dentry), *max_len, connectable); AuDebugOn(au_test_anon(dentry)); parent = NULL; err = -ENOSPC; if (unlikely(*max_len <= Fh_tail)) { AuWarn1("NFSv2 client (max_len %d)?\n", *max_len); goto out; } err = 0; //FILEID_ROOT; if (IS_ROOT(dentry)) { AuDebugOn(dentry->d_inode->i_ino != AUFS_ROOT_INO); goto out; } err = -EIO; h_parent = NULL; sb = dentry->d_sb; aufs_read_lock(dentry, AuLock_FLUSH | AuLock_IR); parent = dget_parent(dentry); di_read_lock_parent(parent, !AuLock_IR); inode = dentry->d_inode; AuDebugOn(!inode); #ifdef CONFIG_AUFS_DEBUG { unsigned int mnt_flags = au_mntflags(sb); if (unlikely(!au_opt_test_xino(mnt_flags))) AuWarn1("NFS-exporting requires xino\n"); if (unlikely(0 && !au_opt_test(mnt_flags, UDBA_INOTIFY))) AuWarn1("udba=inotify is recommended " "for NFS-exporting\n"); } #endif bend = au_dbtaildir(parent); for (bindex = au_dbstart(parent); bindex <= bend; bindex++) { h_parent = au_h_dptr(parent, bindex); if (h_parent) { dget(h_parent); break; } } if (unlikely(!h_parent)) goto out_unlock; LKTRTrace("b%d\n", bindex); err = -EPERM; br = au_sbr(sb, bindex); h_sb = br->br_mnt->mnt_sb; if (unlikely(!h_sb->s_export_op)) { AuErr1("%s branch is not exportable\n", au_sbtype(h_sb)); goto out_dput; } fh[Fh_br_id] = br->br_id; fh[Fh_sigen] = au_sigen(sb); encode_ino(fh + Fh_ino, inode->i_ino); encode_ino(fh + Fh_dir_ino, parent->d_inode->i_ino); fh[Fh_igen] = inode->i_generation; #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 24) /* it should be set at exporting time */ if (unlikely(!h_sb->s_export_op->find_exported_dentry)) { AuWarn("set default find_exported_dentry for %s\n", au_sbtype(h_sb)); h_sb->s_export_op->find_exported_dentry = find_exported_dentry; } #endif *max_len -= Fh_tail; fh[Fh_h_type] = au_call_encode_fh(h_parent, fh + Fh_tail, max_len, /*connectable or subtreecheck*/0); err = fh[Fh_h_type]; *max_len += Fh_tail; /* todo: macros? */ if (err != 255) err = 99; else AuWarn1("%s encode_fh failed\n", au_sbtype(h_sb)); out_dput: dput(h_parent); out_unlock: di_read_unlock(parent, !AuLock_IR); dput(parent); aufs_read_unlock(dentry, AuLock_IR); out: AuTraceErr(err); if (unlikely(err < 0)) err = 255; return err; }
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; }
/* called without aufs lock */ int au_opts_parse(struct super_block *sb, char *str, struct au_opts *opts) { int err, n, token; aufs_bindex_t bindex; unsigned char skipped; struct dentry *root; struct au_opt *opt, *opt_tail; char *opt_str; /* reduce the stack space */ union { struct au_opt_xino_itrunc *xino_itrunc; struct au_opt_wbr_create *create; } u; struct { substring_t args[MAX_OPT_ARGS]; } *a; err = -ENOMEM; a = kmalloc(sizeof(*a), GFP_NOFS); if (unlikely(!a)) goto out; root = sb->s_root; err = 0; bindex = 0; opt = opts->opt; opt_tail = opt + opts->max_opt - 1; opt->type = Opt_tail; while (!err && (opt_str = strsep(&str, ",")) && *opt_str) { err = -EINVAL; skipped = 0; token = match_token(opt_str, options, a->args); switch (token) { case Opt_br: err = 0; while (!err && (opt_str = strsep(&a->args[0].from, ":")) && *opt_str) { err = opt_add(opt, opt_str, opts->sb_flags, bindex++); if (unlikely(!err && ++opt > opt_tail)) { err = -E2BIG; break; } opt->type = Opt_tail; skipped = 1; } break; case Opt_add: if (unlikely(match_int(&a->args[0], &n))) { pr_err("bad integer in %s\n", opt_str); break; } bindex = n; err = opt_add(opt, a->args[1].from, opts->sb_flags, bindex); if (!err) opt->type = token; break; case Opt_append: err = opt_add(opt, a->args[0].from, opts->sb_flags, /*dummy bindex*/1); if (!err) opt->type = token; break; case Opt_prepend: err = opt_add(opt, a->args[0].from, opts->sb_flags, /*bindex*/0); if (!err) opt->type = token; break; case Opt_del: err = au_opts_parse_del(&opt->del, a->args); if (!err) opt->type = token; break; #if 0 /* reserved for future use */ case Opt_idel: del->pathname = "(indexed)"; if (unlikely(match_int(&args[0], &n))) { pr_err("bad integer in %s\n", opt_str); break; } err = au_opts_parse_idel(sb, n, &opt->del, a->args); if (!err) opt->type = token; break; #endif case Opt_mod: err = au_opts_parse_mod(&opt->mod, a->args); if (!err) opt->type = token; break; #ifdef IMOD /* reserved for future use */ case Opt_imod: u.mod->path = "(indexed)"; if (unlikely(match_int(&a->args[0], &n))) { pr_err("bad integer in %s\n", opt_str); break; } err = au_opts_parse_imod(sb, n, &opt->mod, a->args); if (!err) opt->type = token; break; #endif case Opt_xino: err = au_opts_parse_xino(sb, &opt->xino, a->args); if (!err) opt->type = token; break; case Opt_trunc_xino_path: err = au_opts_parse_xino_itrunc_path (sb, &opt->xino_itrunc, a->args); if (!err) opt->type = token; break; case Opt_itrunc_xino: u.xino_itrunc = &opt->xino_itrunc; if (unlikely(match_int(&a->args[0], &n))) { pr_err("bad integer in %s\n", opt_str); break; } u.xino_itrunc->bindex = n; aufs_read_lock(root, AuLock_FLUSH); if (n < 0 || au_sbend(sb) < n) { pr_err("out of bounds, %d\n", n); aufs_read_unlock(root, !AuLock_IR); break; } aufs_read_unlock(root, !AuLock_IR); err = 0; opt->type = token; break; case Opt_dirwh: if (unlikely(match_int(&a->args[0], &opt->dirwh))) break; err = 0; opt->type = token; break; case Opt_rdcache: if (unlikely(match_int(&a->args[0], &n))) { pr_err("bad integer in %s\n", opt_str); break; } if (unlikely(n > AUFS_RDCACHE_MAX)) { pr_err("rdcache must be smaller than %d\n", AUFS_RDCACHE_MAX); break; } opt->rdcache = n; err = 0; opt->type = token; break; case Opt_rdblk: if (unlikely(match_int(&a->args[0], &n) || n < 0 || n > KMALLOC_MAX_SIZE)) { pr_err("bad integer in %s\n", opt_str); break; } if (unlikely(n && n < NAME_MAX)) { pr_err("rdblk must be larger than %d\n", NAME_MAX); break; } opt->rdblk = n; err = 0; opt->type = token; break; case Opt_rdhash: if (unlikely(match_int(&a->args[0], &n) || n < 0 || n * sizeof(struct hlist_head) > KMALLOC_MAX_SIZE)) { pr_err("bad integer in %s\n", opt_str); break; } opt->rdhash = n; err = 0; opt->type = token; break; case Opt_trunc_xino: case Opt_notrunc_xino: case Opt_noxino: case Opt_trunc_xib: case Opt_notrunc_xib: case Opt_shwh: case Opt_noshwh: case Opt_plink: case Opt_noplink: case Opt_list_plink: case Opt_dio: case Opt_nodio: case Opt_diropq_a: case Opt_diropq_w: case Opt_warn_perm: case Opt_nowarn_perm: case Opt_refrof: case Opt_norefrof: case Opt_verbose: case Opt_noverbose: case Opt_sum: case Opt_nosum: case Opt_wsum: case Opt_rdblk_def: case Opt_rdhash_def: err = 0; opt->type = token; break; case Opt_udba: opt->udba = udba_val(a->args[0].from); if (opt->udba >= 0) { err = 0; opt->type = token; } else pr_err("wrong value, %s\n", opt_str); break; case Opt_wbr_create: u.create = &opt->wbr_create; u.create->wbr_create = au_wbr_create_val(a->args[0].from, u.create); if (u.create->wbr_create >= 0) { err = 0; opt->type = token; } else pr_err("wrong value, %s\n", opt_str); break; case Opt_wbr_copyup: opt->wbr_copyup = au_wbr_copyup_val(a->args[0].from); if (opt->wbr_copyup >= 0) { err = 0; opt->type = token; } else pr_err("wrong value, %s\n", opt_str); break; case Opt_ignore: pr_warn("ignored %s\n", opt_str); /*FALLTHROUGH*/ case Opt_ignore_silent: skipped = 1; err = 0; break; case Opt_err: pr_err("unknown option %s\n", opt_str); break; } if (!err && !skipped) { if (unlikely(++opt > opt_tail)) { err = -E2BIG; opt--; opt->type = Opt_tail; break; } opt->type = Opt_tail; } } kfree(a); dump_opts(opts); if (unlikely(err)) au_opts_free(opts); out: return err; }
static struct dentry * aufs_fh_to_dentry(struct super_block *sb, struct fid *fid, int fh_len, int fh_type) { struct dentry *dentry; __u32 *fh = fid->raw; ino_t ino, dir_ino; aufs_bindex_t bindex; struct au_nfsd_si_lock nsi_lock = { .force_lock = 0 }; dentry = ERR_PTR(-ESTALE); /* it should never happen, but the file handle is unreliable */ if (unlikely(fh_len < Fh_tail)) goto out; nsi_lock.sigen = fh[Fh_sigen]; nsi_lock.br_id = fh[Fh_br_id]; /* branch id may be wrapped around */ bindex = si_nfsd_read_lock(sb, &nsi_lock); if (unlikely(bindex < 0)) goto out; nsi_lock.force_lock = 1; /* is this inode still cached? */ ino = decode_ino(fh + Fh_ino); /* it should never happen */ if (unlikely(ino == AUFS_ROOT_INO)) goto out; dir_ino = decode_ino(fh + Fh_dir_ino); dentry = decode_by_ino(sb, ino, dir_ino); if (IS_ERR(dentry)) goto out_unlock; if (dentry) goto accept; /* is the parent dir cached? */ dentry = decode_by_dir_ino(sb, ino, dir_ino, &nsi_lock); if (IS_ERR(dentry)) goto out_unlock; if (dentry) goto accept; /* lookup path */ dentry = decode_by_path(sb, bindex, ino, fh, fh_len, &nsi_lock); if (IS_ERR(dentry)) goto out_unlock; if (unlikely(!dentry)) /* todo?: make it ESTALE */ goto out_unlock; accept: if (dentry->d_inode->i_generation == fh[Fh_igen]) goto out_unlock; /* success */ dput(dentry); dentry = ERR_PTR(-ESTALE); out_unlock: si_read_unlock(sb); out: AuTraceErrPtr(dentry); return dentry; } #if 0 /* reserved for future use */ /* support subtreecheck option */ static struct dentry *aufs_fh_to_parent(struct super_block *sb, struct fid *fid, int fh_len, int fh_type) { struct dentry *parent; __u32 *fh = fid->raw; ino_t dir_ino; dir_ino = decode_ino(fh + Fh_dir_ino); parent = decode_by_ino(sb, dir_ino, 0); if (IS_ERR(parent)) goto out; if (!parent) parent = decode_by_path(sb, au_br_index(sb, fh[Fh_br_id]), dir_ino, fh, fh_len); out: AuTraceErrPtr(parent); return parent; } #endif /* ---------------------------------------------------------------------- */ static int aufs_encode_fh(struct dentry *dentry, __u32 *fh, int *max_len, int connectable) { int err; aufs_bindex_t bindex, bend; struct super_block *sb, *h_sb; struct inode *inode; struct dentry *parent, *h_parent; struct au_branch *br; AuDebugOn(au_test_anon(dentry)); parent = NULL; err = -ENOSPC; if (unlikely(*max_len <= Fh_tail)) { AuWarn1("NFSv2 client (max_len %d)?\n", *max_len); goto out; } err = FILEID_ROOT; if (IS_ROOT(dentry)) { AuDebugOn(dentry->d_inode->i_ino != AUFS_ROOT_INO); goto out; } err = -EIO; h_parent = NULL; sb = dentry->d_sb; aufs_read_lock(dentry, AuLock_FLUSH | AuLock_IR); parent = dget_parent(dentry); di_read_lock_parent(parent, !AuLock_IR); inode = dentry->d_inode; AuDebugOn(!inode); #ifdef CONFIG_AUFS_DEBUG if (unlikely(!au_opt_test(au_mntflags(sb), XINO))) AuWarn1("NFS-exporting requires xino\n"); #endif bend = au_dbtaildir(parent); for (bindex = au_dbstart(parent); bindex <= bend; bindex++) { h_parent = au_h_dptr(parent, bindex); if (h_parent) { dget(h_parent); break; } } if (unlikely(!h_parent)) goto out_unlock; err = -EPERM; br = au_sbr(sb, bindex); h_sb = br->br_mnt->mnt_sb; if (unlikely(!h_sb->s_export_op)) { AuErr1("%s branch is not exportable\n", au_sbtype(h_sb)); goto out_dput; } fh[Fh_br_id] = br->br_id; fh[Fh_sigen] = au_sigen(sb); encode_ino(fh + Fh_ino, inode->i_ino); encode_ino(fh + Fh_dir_ino, parent->d_inode->i_ino); fh[Fh_igen] = inode->i_generation; *max_len -= Fh_tail; fh[Fh_h_type] = exportfs_encode_fh(h_parent, (void *)(fh + Fh_tail), max_len, /*connectable or subtreecheck*/0); err = fh[Fh_h_type]; *max_len += Fh_tail; /* todo: macros? */ if (err != 255) err = 99; else AuWarn1("%s encode_fh failed\n", au_sbtype(h_sb)); out_dput: dput(h_parent); out_unlock: di_read_unlock(parent, !AuLock_IR); dput(parent); aufs_read_unlock(dentry, AuLock_IR); out: if (unlikely(err < 0)) err = 255; 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; }
int aufs_rmdir(struct inode *dir, struct dentry *dentry) { int err, rmdir_later; struct inode *inode, *hidden_dir; struct dentry *parent, *wh_dentry, *hidden_dentry, *hidden_parent; struct dtime dt; aufs_bindex_t bwh, bindex, bstart; struct rmdir_whtmp_arg *arg; struct aufs_nhash *whlist; LKTRTrace("i%lu, %.*s\n", dir->i_ino, DLNPair(dentry)); IMustLock(dir); inode = dentry->d_inode; if (unlikely(!inode)) return -ENOENT; // possible? IMustLock(inode); whlist = nhash_new(GFP_KERNEL); err = PTR_ERR(whlist); if (IS_ERR(whlist)) goto out; err = -ENOMEM; arg = kmalloc(sizeof(*arg), GFP_KERNEL); //arg = NULL; if (unlikely(!arg)) goto out_whlist; aufs_read_lock(dentry, AUFS_D_WLOCK); parent = dentry->d_parent; di_write_lock_parent(parent); err = test_empty(dentry, whlist); //err = -1; if (unlikely(err)) goto out_arg; bstart = dbstart(dentry); bwh = dbwh(dentry); bindex = -1; wh_dentry = lock_hdir_create_wh(dentry, /*isdir*/ 1, &bindex, &dt); //wh_dentry = ERR_PTR(-1); err = PTR_ERR(wh_dentry); if (IS_ERR(wh_dentry)) goto out_arg; hidden_dentry = au_h_dptr(dentry); dget(hidden_dentry); hidden_parent = hidden_dentry->d_parent; hidden_dir = hidden_parent->d_inode; rmdir_later = 0; if (bindex == bstart) { IMustLock(hidden_dir); err = renwh_and_rmdir(dentry, bstart, whlist, dir); //err = -1; if (err > 0) { rmdir_later = err; err = 0; } } else { DEBUG_ON(!wh_dentry); hidden_parent = wh_dentry->d_parent; DEBUG_ON(hidden_parent != au_h_dptr_i(parent, bindex)); hidden_dir = hidden_parent->d_inode; IMustLock(hidden_dir); err = 0; } if (!err) { au_reset_hinotify(inode, /*flags*/0); inode->i_nlink = 0; set_dbdiropq(dentry, -1); epilog(dir, dentry, bindex); if (rmdir_later) { kick_rmdir_whtmp(hidden_dentry, whlist, bstart, dir, inode, arg); arg = NULL; } goto out_unlock; /* success */ } /* revert */ LKTRLabel(revert); if (wh_dentry) { int rerr; rerr = do_revert(err, wh_dentry, dentry, bwh, &dt, need_dlgt(dir->i_sb)); if (rerr) err = rerr; } out_unlock: hdir_unlock(hidden_dir, dir, bindex); dput(wh_dentry); dput(hidden_dentry); out_arg: di_write_unlock(parent); aufs_read_unlock(dentry, AUFS_D_WLOCK); kfree(arg); out_whlist: nhash_del(whlist); out: TraceErr(err); return err; }
int aufs_unlink(struct inode *dir, struct dentry *dentry) { int err, dlgt; struct inode *inode, *hidden_dir; struct dentry *parent, *wh_dentry, *hidden_dentry, *hidden_parent; struct dtime dt; aufs_bindex_t bwh, bindex, bstart; LKTRTrace("i%lu, %.*s\n", dir->i_ino, DLNPair(dentry)); IMustLock(dir); inode = dentry->d_inode; if (unlikely(!inode)) return -ENOENT; // possible? IMustLock(inode); aufs_read_lock(dentry, AUFS_D_WLOCK); parent = dentry->d_parent; di_write_lock_parent(parent); bstart = dbstart(dentry); bwh = dbwh(dentry); bindex = -1; wh_dentry = lock_hdir_create_wh(dentry, /*isdir*/0, &bindex, &dt); //wh_dentry = ERR_PTR(-1); err = PTR_ERR(wh_dentry); if (IS_ERR(wh_dentry)) goto out; dlgt = need_dlgt(dir->i_sb); hidden_dentry = au_h_dptr(dentry); dget(hidden_dentry); hidden_parent = hidden_dentry->d_parent; hidden_dir = hidden_parent->d_inode; if (bindex == bstart) { err = vfsub_unlink(hidden_dir, hidden_dentry, dlgt); //err = -1; } else { DEBUG_ON(!wh_dentry); hidden_parent = wh_dentry->d_parent; DEBUG_ON(hidden_parent != au_h_dptr_i(parent, bindex)); hidden_dir = hidden_parent->d_inode; IMustLock(hidden_dir); err = 0; } if (!err) { inode->i_nlink--; epilog(dir, dentry, bindex); goto out_unlock; /* success */ } /* revert */ if (wh_dentry) { int rerr; rerr = do_revert(err, wh_dentry, dentry, bwh, &dt, dlgt); if (rerr) err = rerr; } out_unlock: hdir_unlock(hidden_dir, dir, bindex); dput(wh_dentry); dput(hidden_dentry); out: di_write_unlock(parent); aufs_read_unlock(dentry, AUFS_D_WLOCK); TraceErr(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; }
/* * if valid returns 1, otherwise 0. */ static int aufs_d_revalidate(struct dentry *dentry, unsigned int flags) { int valid, err; unsigned int sigen; unsigned char do_udba; struct super_block *sb; struct inode *inode; /* todo: support rcu-walk? */ if (flags & LOOKUP_RCU) return -ECHILD; valid = 0; if (unlikely(!au_di(dentry))) goto out; valid = 1; sb = dentry->d_sb; /* * todo: very ugly * i_mutex of parent dir may be held, * but we should not return 'invalid' due to busy. */ err = aufs_read_lock(dentry, AuLock_FLUSH | AuLock_DW | AuLock_NOPLM); if (unlikely(err)) { valid = err; AuTraceErr(err); goto out; } inode = NULL; if (d_really_is_positive(dentry)) inode = d_inode(dentry); if (unlikely(inode && is_bad_inode(inode))) { err = -EINVAL; AuTraceErr(err); goto out_dgrade; } if (unlikely(au_dbrange_test(dentry))) { err = -EINVAL; AuTraceErr(err); goto out_dgrade; } sigen = au_sigen(sb); if (au_digen_test(dentry, sigen)) { AuDebugOn(IS_ROOT(dentry)); err = au_reval_dpath(dentry, sigen); if (unlikely(err)) { AuTraceErr(err); goto out_dgrade; } } di_downgrade_lock(dentry, AuLock_IR); err = -EINVAL; if (!(flags & (LOOKUP_OPEN | LOOKUP_EMPTY)) && inode && !(inode->i_state && I_LINKABLE) && (IS_DEADDIR(inode) || !inode->i_nlink)) goto out_inval; do_udba = !au_opt_test(au_mntflags(sb), UDBA_NONE); if (do_udba && inode) { aufs_bindex_t bstart = au_ibstart(inode); struct inode *h_inode; if (bstart >= 0) { h_inode = au_h_iptr(inode, bstart); if (h_inode && au_test_higen(inode, h_inode)) goto out_inval; } } err = h_d_revalidate(dentry, inode, flags, do_udba); if (unlikely(!err && do_udba && au_dbstart(dentry) < 0)) { err = -EIO; AuDbg("both of real entry and whiteout found, %p, err %d\n", dentry, err); } goto out_inval; out_dgrade: di_downgrade_lock(dentry, AuLock_IR); out_inval: aufs_read_unlock(dentry, AuLock_IR); AuTraceErr(err); valid = !err; out: if (!valid) { AuDbg("%pd invalid, %d\n", dentry, valid); d_drop(dentry); } return valid; }
static int au_wbr_fd(struct path *path, struct aufs_wbr_fd __user *arg) { int err, fd; aufs_bindex_t wbi, bindex, bend; struct file *h_file; struct super_block *sb; struct dentry *root; struct au_branch *br; struct aufs_wbr_fd wbrfd = { .oflags = au_dir_roflags, .brid = -1 }; const int valid = O_RDONLY | O_NONBLOCK | O_LARGEFILE | O_DIRECTORY | O_NOATIME | O_CLOEXEC; AuDebugOn(wbrfd.oflags & ~valid); if (arg) { err = copy_from_user(&wbrfd, arg, sizeof(wbrfd)); if (unlikely(err)) { err = -EFAULT; goto out; } err = -EINVAL; AuDbg("wbrfd{0%o, %d}\n", wbrfd.oflags, wbrfd.brid); wbrfd.oflags |= au_dir_roflags; AuDbg("0%o\n", wbrfd.oflags); if (unlikely(wbrfd.oflags & ~valid)) goto out; } fd = get_unused_fd_flags(0); err = fd; if (unlikely(fd < 0)) goto out; h_file = ERR_PTR(-EINVAL); wbi = 0; br = NULL; sb = path->dentry->d_sb; root = sb->s_root; aufs_read_lock(root, AuLock_IR); bend = au_sbend(sb); if (wbrfd.brid >= 0) { wbi = au_br_index(sb, wbrfd.brid); if (unlikely(wbi < 0 || wbi > bend)) goto out_unlock; } h_file = ERR_PTR(-ENOENT); br = au_sbr(sb, wbi); if (!au_br_writable(br->br_perm)) { if (arg) goto out_unlock; bindex = wbi + 1; wbi = -1; for (; bindex <= bend; bindex++) { br = au_sbr(sb, bindex); if (au_br_writable(br->br_perm)) { wbi = bindex; br = au_sbr(sb, wbi); break; } } } AuDbg("wbi %d\n", wbi); if (wbi >= 0) h_file = au_h_open(root, wbi, wbrfd.oflags, NULL, /*force_wr*/0); out_unlock: aufs_read_unlock(root, AuLock_IR); err = PTR_ERR(h_file); if (IS_ERR(h_file)) goto out_fd; atomic_dec(&br->br_count); /* cf. au_h_open() */ fd_install(fd, h_file); err = fd; goto out; /* success */ out_fd: put_unused_fd(fd); out: AuTraceErr(err); return err; } /* ---------------------------------------------------------------------- */ long aufs_ioctl_dir(struct file *file, unsigned int cmd, unsigned long arg) { long err; struct dentry *dentry; switch (cmd) { case AUFS_CTL_RDU: case AUFS_CTL_RDU_INO: err = au_rdu_ioctl(file, cmd, arg); break; case AUFS_CTL_WBR_FD: err = au_wbr_fd(&file->f_path, (void __user *)arg); break; case AUFS_CTL_IBUSY: err = au_ibusy_ioctl(file, arg); break; case AUFS_CTL_BRINFO: err = au_brinfo_ioctl(file, arg); break; case AUFS_CTL_FHSM_FD: dentry = file->f_path.dentry; if (IS_ROOT(dentry)) err = au_fhsm_fd(dentry->d_sb, arg); else err = -ENOTTY; break; default: /* do not call the lower */ AuDbg("0x%x\n", cmd); err = -ENOTTY; } AuTraceErr(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 = 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; }