// SPDX-License-Identifier: GPL-2.0 /* * Copyright (C) 2005-2019 Junjiro R. Okajima */ /* * sub-routines for dentry cache */ #include "aufs.h" static void au_dpage_free(struct au_dpage *dpage) { int i; struct dentry **p; p = dpage->dentries; for (i = 0; i < dpage->ndentry; i++) dput(*p++); free_page((unsigned long)dpage->dentries); } int au_dpages_init(struct au_dcsub_pages *dpages, gfp_t gfp) { int err; void *p; err = -ENOMEM; dpages->dpages = kmalloc(sizeof(*dpages->dpages), gfp); if (unlikely(!dpages->dpages)) goto out; p = (void *)__get_free_page(gfp); if (unlikely(!p)) goto out_dpages; dpages->dpages[0].ndentry = 0; dpages->dpages[0].dentries = p; dpages->ndpage = 1; return 0; /* success */ out_dpages: au_kfree_try_rcu(dpages->dpages); out: return err; } void au_dpages_free(struct au_dcsub_pages *dpages) { int i; struct au_dpage *p; p = dpages->dpages; for (i = 0; i < dpages->ndpage; i++) au_dpage_free(p++); au_kfree_try_rcu(dpages->dpages); } static int au_dpages_append(struct au_dcsub_pages *dpages, struct dentry *dentry, gfp_t gfp) { int err, sz; struct au_dpage *dpage; void *p; dpage = dpages->dpages + dpages->ndpage - 1; sz = PAGE_SIZE / sizeof(dentry); if (unlikely(dpage->ndentry >= sz)) { AuLabel(new dpage); err = -ENOMEM; sz = dpages->ndpage * sizeof(*dpages->dpages); p = au_kzrealloc(dpages->dpages, sz, sz + sizeof(*dpages->dpages), gfp, /*may_shrink*/0); if (unlikely(!p)) goto out; dpages->dpages = p; dpage = dpages->dpages + dpages->ndpage; p = (void *)__get_free_page(gfp); if (unlikely(!p)) goto out; dpage->ndentry = 0; dpage->dentries = p; dpages->ndpage++; } AuDebugOn(au_dcount(dentry) <= 0); dpage->dentries[dpage->ndentry++] = dget_dlock(dentry); return 0; /* success */ out: return err; } /* todo: BAD approach */ /* copied from linux/fs/dcache.c */ enum d_walk_ret { D_WALK_CONTINUE, D_WALK_QUIT, D_WALK_NORETRY, D_WALK_SKIP, }; extern void d_walk(struct dentry *parent, void *data, enum d_walk_ret (*enter)(void *, struct dentry *)); struct ac_dpages_arg { int err; struct au_dcsub_pages *dpages; struct super_block *sb; au_dpages_test test; void *arg; }; static enum d_walk_ret au_call_dpages_append(void *_arg, struct dentry *dentry) { enum d_walk_ret ret; struct ac_dpages_arg *arg = _arg; ret = D_WALK_CONTINUE; if (dentry->d_sb == arg->sb && !IS_ROOT(dentry) && au_dcount(dentry) > 0 && au_di(dentry) && (!arg->test || arg->test(dentry, arg->arg))) { arg->err = au_dpages_append(arg->dpages, dentry, GFP_ATOMIC); if (unlikely(arg->err)) ret = D_WALK_QUIT; } return ret; } int au_dcsub_pages(struct au_dcsub_pages *dpages, struct dentry *root, au_dpages_test test, void *arg) { struct ac_dpages_arg args = { .err = 0, .dpages = dpages, .sb = root->d_sb, .test = test, .arg = arg }; d_walk(root, &args, au_call_dpages_append); return args.err; } int au_dcsub_pages_rev(struct au_dcsub_pages *dpages, struct dentry *dentry, int do_include, au_dpages_test test, void *arg) { int err; err = 0; write_seqlock(&rename_lock); spin_lock(&dentry->d_lock); if (do_include && au_dcount(dentry) > 0 && (!test || test(dentry, arg))) err = au_dpages_append(dpages, dentry, GFP_ATOMIC); spin_unlock(&dentry->d_lock); if (unlikely(err)) goto out; /* * RCU for vfsmount is unnecessary since this is a traverse in a single * mount */ while (!IS_ROOT(dentry)) { dentry = dentry->d_parent; /* rename_lock is locked */ spin_lock(&dentry->d_lock); if (au_dcount(dentry) > 0 && (!test || test(dentry, arg))) err = au_dpages_append(dpages, dentry, GFP_ATOMIC); spin_unlock(&dentry->d_lock); if (unlikely(err)) break; } out: write_sequnlock(&rename_lock); return err; } static inline int au_dcsub_dpages_aufs(struct dentry *dentry, void *arg) { return au_di(dentry) && dentry->d_sb == arg; } int au_dcsub_pages_rev_aufs(struct au_dcsub_pages *dpages, struct dentry *dentry, int do_include) { return au_dcsub_pages_rev(dpages, dentry, do_include, au_dcsub_dpages_aufs, dentry->d_sb); } int au_test_subdir(struct dentry *d1, struct dentry *d2) { struct path path[2] = { { .dentry = d1 }, { .dentry = d2 } }; return path_is_under(path + 0, path + 1); }