// SPDX-License-Identifier: GPL-2.0 /* * Copyright (C) 2005-2019 Junjiro R. Okajima */ /* * abstraction to notify the direct changes on lower directories */ /* #include */ #include "aufs.h" int au_hn_alloc(struct au_hinode *hinode, struct inode *inode) { int err; struct au_hnotify *hn; err = -ENOMEM; hn = au_cache_alloc_hnotify(); if (hn) { hn->hn_aufs_inode = inode; hinode->hi_notify = hn; err = au_hnotify_op.alloc(hinode); AuTraceErr(err); if (unlikely(err)) { hinode->hi_notify = NULL; au_cache_free_hnotify(hn); /* * The upper dir was removed by udba, but the same named * dir left. In this case, aufs assigns a new inode * number and set the monitor again. * For the lower dir, the old monitor is still left. */ if (err == -EEXIST) err = 0; } } AuTraceErr(err); return err; } void au_hn_free(struct au_hinode *hinode) { struct au_hnotify *hn; hn = hinode->hi_notify; if (hn) { hinode->hi_notify = NULL; if (au_hnotify_op.free(hinode, hn)) au_cache_free_hnotify(hn); } } /* ---------------------------------------------------------------------- */ void au_hn_ctl(struct au_hinode *hinode, int do_set) { if (hinode->hi_notify) au_hnotify_op.ctl(hinode, do_set); } void au_hn_reset(struct inode *inode, unsigned int flags) { aufs_bindex_t bindex, bbot; struct inode *hi; struct dentry *iwhdentry; bbot = au_ibbot(inode); for (bindex = au_ibtop(inode); bindex <= bbot; bindex++) { hi = au_h_iptr(inode, bindex); if (!hi) continue; /* inode_lock_nested(hi, AuLsc_I_CHILD); */ iwhdentry = au_hi_wh(inode, bindex); if (iwhdentry) dget(iwhdentry); au_igrab(hi); au_set_h_iptr(inode, bindex, NULL, 0); au_set_h_iptr(inode, bindex, au_igrab(hi), flags & ~AuHi_XINO); iput(hi); dput(iwhdentry); /* inode_unlock(hi); */ } } /* ---------------------------------------------------------------------- */ static int hn_xino(struct inode *inode, struct inode *h_inode) { int err; aufs_bindex_t bindex, bbot, bfound, btop; struct inode *h_i; err = 0; if (unlikely(inode->i_ino == AUFS_ROOT_INO)) { pr_warn("branch root dir was changed\n"); goto out; } bfound = -1; bbot = au_ibbot(inode); btop = au_ibtop(inode); #if 0 /* reserved for future use */ if (bindex == bbot) { /* keep this ino in rename case */ goto out; } #endif for (bindex = btop; bindex <= bbot; bindex++) if (au_h_iptr(inode, bindex) == h_inode) { bfound = bindex; break; } if (bfound < 0) goto out; for (bindex = btop; bindex <= bbot; bindex++) { h_i = au_h_iptr(inode, bindex); if (!h_i) continue; err = au_xino_write(inode->i_sb, bindex, h_i->i_ino, /*ino*/0); /* ignore this error */ /* bad action? */ } /* children inode number will be broken */ out: AuTraceErr(err); return err; } static int hn_gen_tree(struct dentry *dentry) { int err, i, j, ndentry; struct au_dcsub_pages dpages; struct au_dpage *dpage; struct dentry **dentries; err = au_dpages_init(&dpages, GFP_NOFS); if (unlikely(err)) goto out; err = au_dcsub_pages(&dpages, dentry, NULL, NULL); if (unlikely(err)) goto out_dpages; for (i = 0; i < dpages.ndpage; i++) { dpage = dpages.dpages + i; dentries = dpage->dentries; ndentry = dpage->ndentry; for (j = 0; j < ndentry; j++) { struct dentry *d; d = dentries[j]; if (IS_ROOT(d)) continue; au_digen_dec(d); if (d_really_is_positive(d)) /* todo: reset children xino? cached children only? */ au_iigen_dec(d_inode(d)); } } out_dpages: au_dpages_free(&dpages); #if 0 /* discard children */ dentry_unhash(dentry); dput(dentry); #endif out: return err; } /* * return 0 if processed. */ static int hn_gen_by_inode(char *name, unsigned int nlen, struct inode *inode, const unsigned int isdir) { int err; struct dentry *d; struct qstr *dname; err = 1; if (unlikely(inode->i_ino == AUFS_ROOT_INO)) { pr_warn("branch root dir was changed\n"); err = 0; goto out; } if (!isdir) { AuDebugOn(!name); au_iigen_dec(inode); spin_lock(&inode->i_lock); hlist_for_each_entry(d, &inode->i_dentry, d_u.d_alias) { spin_lock(&d->d_lock); dname = &d->d_name; if (dname->len != nlen && memcmp(dname->name, name, nlen)) { spin_unlock(&d->d_lock); continue; } err = 0; au_digen_dec(d); spin_unlock(&d->d_lock); break; } spin_unlock(&inode->i_lock); } else { au_fset_si(au_sbi(inode->i_sb), FAILED_REFRESH_DIR); d = d_find_any_alias(inode); if (!d) { au_iigen_dec(inode); goto out; } spin_lock(&d->d_lock); dname = &d->d_name; if (dname->len == nlen && !memcmp(dname->name, name, nlen)) { spin_unlock(&d->d_lock); err = hn_gen_tree(d); spin_lock(&d->d_lock); } spin_unlock(&d->d_lock); dput(d); } out: AuTraceErr(err); return err; } static int hn_gen_by_name(struct dentry *dentry, const unsigned int isdir) { int err; if (IS_ROOT(dentry)) { pr_warn("branch root dir was changed\n"); return 0; } err = 0; if (!isdir) { au_digen_dec(dentry); if (d_really_is_positive(dentry)) au_iigen_dec(d_inode(dentry)); } else { au_fset_si(au_sbi(dentry->d_sb), FAILED_REFRESH_DIR); if (d_really_is_positive(dentry)) err = hn_gen_tree(dentry); } AuTraceErr(err); return err; } /* ---------------------------------------------------------------------- */ /* hnotify job flags */ #define AuHnJob_XINO0 1 #define AuHnJob_GEN (1 << 1) #define AuHnJob_DIRENT (1 << 2) #define AuHnJob_ISDIR (1 << 3) #define AuHnJob_TRYXINO0 (1 << 4) #define AuHnJob_MNTPNT (1 << 5) #define au_ftest_hnjob(flags, name) ((flags) & AuHnJob_##name) #define au_fset_hnjob(flags, name) \ do { (flags) |= AuHnJob_##name; } while (0) #define au_fclr_hnjob(flags, name) \ do { (flags) &= ~AuHnJob_##name; } while (0) enum { AuHn_CHILD, AuHn_PARENT, AuHnLast }; struct au_hnotify_args { struct inode *h_dir, *dir, *h_child_inode; u32 mask; unsigned int flags[AuHnLast]; unsigned int h_child_nlen; char h_child_name[]; }; struct hn_job_args { unsigned int flags; struct inode *inode, *h_inode, *dir, *h_dir; struct dentry *dentry; char *h_name; int h_nlen; }; static int hn_job(struct hn_job_args *a) { const unsigned int isdir = au_ftest_hnjob(a->flags, ISDIR); int e; /* reset xino */ if (au_ftest_hnjob(a->flags, XINO0) && a->inode) hn_xino(a->inode, a->h_inode); /* ignore this error */ if (au_ftest_hnjob(a->flags, TRYXINO0) && a->inode && a->h_inode) { inode_lock_shared_nested(a->h_inode, AuLsc_I_CHILD); if (!a->h_inode->i_nlink && !(a->h_inode->i_state & I_LINKABLE)) hn_xino(a->inode, a->h_inode); /* ignore this error */ inode_unlock_shared(a->h_inode); } /* make the generation obsolete */ if (au_ftest_hnjob(a->flags, GEN)) { e = -1; if (a->inode) e = hn_gen_by_inode(a->h_name, a->h_nlen, a->inode, isdir); if (e && a->dentry) hn_gen_by_name(a->dentry, isdir); /* ignore this error */ } /* make dir entries obsolete */ if (au_ftest_hnjob(a->flags, DIRENT) && a->inode) { struct au_vdir *vdir; vdir = au_ivdir(a->inode); if (vdir) vdir->vd_jiffy = 0; /* IMustLock(a->inode); */ /* inode_inc_iversion(a->inode); */ } /* can do nothing but warn */ if (au_ftest_hnjob(a->flags, MNTPNT) && a->dentry && d_mountpoint(a->dentry)) pr_warn("mount-point %pd is removed or renamed\n", a->dentry); return 0; } /* ---------------------------------------------------------------------- */ static struct dentry *lookup_wlock_by_name(char *name, unsigned int nlen, struct inode *dir) { struct dentry *dentry, *d, *parent; struct qstr *dname; parent = d_find_any_alias(dir); if (!parent) return NULL; dentry = NULL; spin_lock(&parent->d_lock); list_for_each_entry(d, &parent->d_subdirs, d_child) { /* AuDbg("%pd\n", d); */ spin_lock_nested(&d->d_lock, DENTRY_D_LOCK_NESTED); dname = &d->d_name; if (dname->len != nlen || memcmp(dname->name, name, nlen)) goto cont_unlock; if (au_di(d)) au_digen_dec(d); else goto cont_unlock; if (au_dcount(d) > 0) { dentry = dget_dlock(d); spin_unlock(&d->d_lock); break; } cont_unlock: spin_unlock(&d->d_lock); } spin_unlock(&parent->d_lock); dput(parent); if (dentry) di_write_lock_child(dentry); return dentry; } static struct inode *lookup_wlock_by_ino(struct super_block *sb, aufs_bindex_t bindex, ino_t h_ino) { struct inode *inode; ino_t ino; int err; inode = NULL; err = au_xino_read(sb, bindex, h_ino, &ino); if (!err && ino) inode = ilookup(sb, ino); if (!inode) goto out; if (unlikely(inode->i_ino == AUFS_ROOT_INO)) { pr_warn("wrong root branch\n"); iput(inode); inode = NULL; goto out; } ii_write_lock_child(inode); out: return inode; } static void au_hn_bh(void *_args) { struct au_hnotify_args *a = _args; struct super_block *sb; aufs_bindex_t bindex, bbot, bfound; unsigned char xino, try_iput; int err; struct inode *inode; ino_t h_ino; struct hn_job_args args; struct dentry *dentry; struct au_sbinfo *sbinfo; AuDebugOn(!_args); AuDebugOn(!a->h_dir); AuDebugOn(!a->dir); AuDebugOn(!a->mask); AuDbg("mask 0x%x, i%lu, hi%lu, hci%lu\n", a->mask, a->dir->i_ino, a->h_dir->i_ino, a->h_child_inode ? a->h_child_inode->i_ino : 0); inode = NULL; dentry = NULL; /* * do not lock a->dir->i_mutex here * because of d_revalidate() may cause a deadlock. */ sb = a->dir->i_sb; AuDebugOn(!sb); sbinfo = au_sbi(sb); AuDebugOn(!sbinfo); si_write_lock(sb, AuLock_NOPLMW); if (au_opt_test(sbinfo->si_mntflags, DIRREN)) switch (a->mask & FS_EVENTS_POSS_ON_CHILD) { case FS_MOVED_FROM: case FS_MOVED_TO: AuWarn1("DIRREN with UDBA may not work correctly " "for the direct rename(2)\n"); } ii_read_lock_parent(a->dir); bfound = -1; bbot = au_ibbot(a->dir); for (bindex = au_ibtop(a->dir); bindex <= bbot; bindex++) if (au_h_iptr(a->dir, bindex) == a->h_dir) { bfound = bindex; break; } ii_read_unlock(a->dir); if (unlikely(bfound < 0)) goto out; xino = !!au_opt_test(au_mntflags(sb), XINO); h_ino = 0; if (a->h_child_inode) h_ino = a->h_child_inode->i_ino; if (a->h_child_nlen && (au_ftest_hnjob(a->flags[AuHn_CHILD], GEN) || au_ftest_hnjob(a->flags[AuHn_CHILD], MNTPNT))) dentry = lookup_wlock_by_name(a->h_child_name, a->h_child_nlen, a->dir); try_iput = 0; if (dentry && d_really_is_positive(dentry)) inode = d_inode(dentry); if (xino && !inode && h_ino && (au_ftest_hnjob(a->flags[AuHn_CHILD], XINO0) || au_ftest_hnjob(a->flags[AuHn_CHILD], TRYXINO0) || au_ftest_hnjob(a->flags[AuHn_CHILD], GEN))) { inode = lookup_wlock_by_ino(sb, bfound, h_ino); try_iput = 1; } args.flags = a->flags[AuHn_CHILD]; args.dentry = dentry; args.inode = inode; args.h_inode = a->h_child_inode; args.dir = a->dir; args.h_dir = a->h_dir; args.h_name = a->h_child_name; args.h_nlen = a->h_child_nlen; err = hn_job(&args); if (dentry) { if (au_di(dentry)) di_write_unlock(dentry); dput(dentry); } if (inode && try_iput) { ii_write_unlock(inode); iput(inode); } ii_write_lock_parent(a->dir); args.flags = a->flags[AuHn_PARENT]; args.dentry = NULL; args.inode = a->dir; args.h_inode = a->h_dir; args.dir = NULL; args.h_dir = NULL; args.h_name = NULL; args.h_nlen = 0; err = hn_job(&args); ii_write_unlock(a->dir); out: iput(a->h_child_inode); iput(a->h_dir); iput(a->dir); si_write_unlock(sb); au_nwt_done(&sbinfo->si_nowait); au_kfree_rcu(a); } /* ---------------------------------------------------------------------- */ int au_hnotify(struct inode *h_dir, struct au_hnotify *hnotify, u32 mask, const struct qstr *h_child_qstr, struct inode *h_child_inode) { int err, len; unsigned int flags[AuHnLast], f; unsigned char isdir, isroot, wh; struct inode *dir; struct au_hnotify_args *args; char *p, *h_child_name; err = 0; AuDebugOn(!hnotify || !hnotify->hn_aufs_inode); dir = igrab(hnotify->hn_aufs_inode); if (!dir) goto out; isroot = (dir->i_ino == AUFS_ROOT_INO); wh = 0; h_child_name = (void *)h_child_qstr->name; len = h_child_qstr->len; if (h_child_name) { if (len > AUFS_WH_PFX_LEN && !memcmp(h_child_name, AUFS_WH_PFX, AUFS_WH_PFX_LEN)) { h_child_name += AUFS_WH_PFX_LEN; len -= AUFS_WH_PFX_LEN; wh = 1; } } isdir = 0; if (h_child_inode) isdir = !!S_ISDIR(h_child_inode->i_mode); flags[AuHn_PARENT] = AuHnJob_ISDIR; flags[AuHn_CHILD] = 0; if (isdir) flags[AuHn_CHILD] = AuHnJob_ISDIR; au_fset_hnjob(flags[AuHn_PARENT], DIRENT); au_fset_hnjob(flags[AuHn_CHILD], GEN); switch (mask & ALL_FSNOTIFY_DIRENT_EVENTS) { case FS_MOVED_FROM: case FS_MOVED_TO: au_fset_hnjob(flags[AuHn_CHILD], XINO0); au_fset_hnjob(flags[AuHn_CHILD], MNTPNT); /*FALLTHROUGH*/ case FS_CREATE: AuDebugOn(!h_child_name); break; case FS_DELETE: /* * aufs never be able to get this child inode. * revalidation should be in d_revalidate() * by checking i_nlink, i_generation or d_unhashed(). */ AuDebugOn(!h_child_name); au_fset_hnjob(flags[AuHn_CHILD], TRYXINO0); au_fset_hnjob(flags[AuHn_CHILD], MNTPNT); break; default: AuDebugOn(1); } if (wh) h_child_inode = NULL; err = -ENOMEM; /* iput() and kfree() will be called in au_hnotify() */ args = kmalloc(sizeof(*args) + len + 1, GFP_NOFS); if (unlikely(!args)) { AuErr1("no memory\n"); iput(dir); goto out; } args->flags[AuHn_PARENT] = flags[AuHn_PARENT]; args->flags[AuHn_CHILD] = flags[AuHn_CHILD]; args->mask = mask; args->dir = dir; args->h_dir = igrab(h_dir); if (h_child_inode) h_child_inode = igrab(h_child_inode); /* can be NULL */ args->h_child_inode = h_child_inode; args->h_child_nlen = len; if (len) { p = (void *)args; p += sizeof(*args); memcpy(p, h_child_name, len); p[len] = 0; } /* NFS fires the event for silly-renamed one from kworker */ f = 0; if (!dir->i_nlink || (au_test_nfs(h_dir->i_sb) && (mask & FS_DELETE))) f = AuWkq_NEST; err = au_wkq_nowait(au_hn_bh, args, dir->i_sb, f); if (unlikely(err)) { pr_err("wkq %d\n", err); iput(args->h_child_inode); iput(args->h_dir); iput(args->dir); au_kfree_rcu(args); } out: return err; } /* ---------------------------------------------------------------------- */ int au_hnotify_reset_br(unsigned int udba, struct au_branch *br, int perm) { int err; AuDebugOn(!(udba & AuOptMask_UDBA)); err = 0; if (au_hnotify_op.reset_br) err = au_hnotify_op.reset_br(udba, br, perm); return err; } int au_hnotify_init_br(struct au_branch *br, int perm) { int err; err = 0; if (au_hnotify_op.init_br) err = au_hnotify_op.init_br(br, perm); return err; } void au_hnotify_fin_br(struct au_branch *br) { if (au_hnotify_op.fin_br) au_hnotify_op.fin_br(br); } static void au_hn_destroy_cache(void) { kmem_cache_destroy(au_cache[AuCache_HNOTIFY]); au_cache[AuCache_HNOTIFY] = NULL; } int __init au_hnotify_init(void) { int err; err = -ENOMEM; au_cache[AuCache_HNOTIFY] = AuCache(au_hnotify); if (au_cache[AuCache_HNOTIFY]) { err = 0; if (au_hnotify_op.init) err = au_hnotify_op.init(); if (unlikely(err)) au_hn_destroy_cache(); } AuTraceErr(err); return err; } void au_hnotify_fin(void) { if (au_hnotify_op.fin) au_hnotify_op.fin(); /* cf. au_cache_fin() */ if (au_cache[AuCache_HNOTIFY]) au_hn_destroy_cache(); }