aboutsummaryrefslogtreecommitdiffstats
path: root/fs/aufs/hfsnotify.c
diff options
context:
space:
mode:
Diffstat (limited to 'fs/aufs/hfsnotify.c')
-rw-r--r--fs/aufs/hfsnotify.c288
1 files changed, 288 insertions, 0 deletions
diff --git a/fs/aufs/hfsnotify.c b/fs/aufs/hfsnotify.c
new file mode 100644
index 000000000000..cb4eeb1e6069
--- /dev/null
+++ b/fs/aufs/hfsnotify.c
@@ -0,0 +1,288 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2005-2020 Junjiro R. Okajima
+ *
+ * This program, aufs is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * fsnotify for the lower directories
+ */
+
+#include "aufs.h"
+
+/* FS_IN_IGNORED is unnecessary */
+static const __u32 AuHfsnMask = (FS_MOVED_TO | FS_MOVED_FROM | FS_DELETE
+ | FS_CREATE | FS_EVENT_ON_CHILD);
+static DECLARE_WAIT_QUEUE_HEAD(au_hfsn_wq);
+static __cacheline_aligned_in_smp atomic64_t au_hfsn_ifree = ATOMIC64_INIT(0);
+
+static void au_hfsn_free_mark(struct fsnotify_mark *mark)
+{
+ struct au_hnotify *hn = container_of(mark, struct au_hnotify,
+ hn_mark);
+ /* AuDbg("here\n"); */
+ au_cache_free_hnotify(hn);
+ smp_mb__before_atomic(); /* for atomic64_dec */
+ if (atomic64_dec_and_test(&au_hfsn_ifree))
+ wake_up(&au_hfsn_wq);
+}
+
+static int au_hfsn_alloc(struct au_hinode *hinode)
+{
+ int err;
+ struct au_hnotify *hn;
+ struct super_block *sb;
+ struct au_branch *br;
+ struct fsnotify_mark *mark;
+ aufs_bindex_t bindex;
+
+ hn = hinode->hi_notify;
+ sb = hn->hn_aufs_inode->i_sb;
+ bindex = au_br_index(sb, hinode->hi_id);
+ br = au_sbr(sb, bindex);
+ AuDebugOn(!br->br_hfsn);
+
+ mark = &hn->hn_mark;
+ fsnotify_init_mark(mark, br->br_hfsn->hfsn_group);
+ mark->mask = AuHfsnMask;
+ /*
+ * by udba rename or rmdir, aufs assign a new inode to the known
+ * h_inode, so specify 1 to allow dups.
+ */
+ lockdep_off();
+ err = fsnotify_add_inode_mark(mark, hinode->hi_inode, /*allow_dups*/1);
+ lockdep_on();
+
+ return err;
+}
+
+static int au_hfsn_free(struct au_hinode *hinode, struct au_hnotify *hn)
+{
+ struct fsnotify_mark *mark;
+ unsigned long long ull;
+ struct fsnotify_group *group;
+
+ ull = atomic64_inc_return(&au_hfsn_ifree);
+ BUG_ON(!ull);
+
+ mark = &hn->hn_mark;
+ spin_lock(&mark->lock);
+ group = mark->group;
+ fsnotify_get_group(group);
+ spin_unlock(&mark->lock);
+ lockdep_off();
+ fsnotify_destroy_mark(mark, group);
+ fsnotify_put_mark(mark);
+ fsnotify_put_group(group);
+ lockdep_on();
+
+ /* free hn by myself */
+ return 0;
+}
+
+/* ---------------------------------------------------------------------- */
+
+static void au_hfsn_ctl(struct au_hinode *hinode, int do_set)
+{
+ struct fsnotify_mark *mark;
+
+ mark = &hinode->hi_notify->hn_mark;
+ spin_lock(&mark->lock);
+ if (do_set) {
+ AuDebugOn(mark->mask & AuHfsnMask);
+ mark->mask |= AuHfsnMask;
+ } else {
+ AuDebugOn(!(mark->mask & AuHfsnMask));
+ mark->mask &= ~AuHfsnMask;
+ }
+ spin_unlock(&mark->lock);
+ /* fsnotify_recalc_inode_mask(hinode->hi_inode); */
+}
+
+/* ---------------------------------------------------------------------- */
+
+/* #define AuDbgHnotify */
+#ifdef AuDbgHnotify
+static char *au_hfsn_name(u32 mask)
+{
+#ifdef CONFIG_AUFS_DEBUG
+#define test_ret(flag) \
+ do { \
+ if (mask & flag) \
+ return #flag; \
+ } while (0)
+ test_ret(FS_ACCESS);
+ test_ret(FS_MODIFY);
+ test_ret(FS_ATTRIB);
+ test_ret(FS_CLOSE_WRITE);
+ test_ret(FS_CLOSE_NOWRITE);
+ test_ret(FS_OPEN);
+ test_ret(FS_MOVED_FROM);
+ test_ret(FS_MOVED_TO);
+ test_ret(FS_CREATE);
+ test_ret(FS_DELETE);
+ test_ret(FS_DELETE_SELF);
+ test_ret(FS_MOVE_SELF);
+ test_ret(FS_UNMOUNT);
+ test_ret(FS_Q_OVERFLOW);
+ test_ret(FS_IN_IGNORED);
+ test_ret(FS_ISDIR);
+ test_ret(FS_IN_ONESHOT);
+ test_ret(FS_EVENT_ON_CHILD);
+ return "";
+#undef test_ret
+#else
+ return "??";
+#endif
+}
+#endif
+
+/* ---------------------------------------------------------------------- */
+
+static void au_hfsn_free_group(struct fsnotify_group *group)
+{
+ struct au_br_hfsnotify *hfsn = group->private;
+
+ /* AuDbg("here\n"); */
+ au_kfree_try_rcu(hfsn);
+}
+
+static int au_hfsn_handle_event(struct fsnotify_group *group,
+ struct inode *inode,
+ u32 mask, const void *data, int data_type,
+ const struct qstr *file_name, u32 cookie,
+ struct fsnotify_iter_info *iter_info)
+{
+ int err;
+ struct au_hnotify *hnotify;
+ struct inode *h_dir, *h_inode;
+ struct fsnotify_mark *inode_mark;
+
+ AuDebugOn(data_type != FSNOTIFY_EVENT_INODE);
+
+ err = 0;
+ /* if FS_UNMOUNT happens, there must be another bug */
+ AuDebugOn(mask & FS_UNMOUNT);
+ if (mask & (FS_IN_IGNORED | FS_UNMOUNT))
+ goto out;
+
+ h_dir = inode;
+ h_inode = NULL;
+#ifdef AuDbgHnotify
+ au_debug_on();
+ if (1 || h_child_qstr.len != sizeof(AUFS_XINO_FNAME) - 1
+ || strncmp(h_child_qstr.name, AUFS_XINO_FNAME, h_child_qstr.len)) {
+ AuDbg("i%lu, mask 0x%x %s, hcname %.*s, hi%lu\n",
+ h_dir->i_ino, mask, au_hfsn_name(mask),
+ AuLNPair(&h_child_qstr), h_inode ? h_inode->i_ino : 0);
+ /* WARN_ON(1); */
+ }
+ au_debug_off();
+#endif
+
+ inode_mark = fsnotify_iter_inode_mark(iter_info);
+ AuDebugOn(!inode_mark);
+ hnotify = container_of(inode_mark, struct au_hnotify, hn_mark);
+ err = au_hnotify(h_dir, hnotify, mask, file_name, h_inode);
+
+out:
+ return err;
+}
+
+static struct fsnotify_ops au_hfsn_ops = {
+ .handle_event = au_hfsn_handle_event,
+ .free_group_priv = au_hfsn_free_group,
+ .free_mark = au_hfsn_free_mark
+};
+
+/* ---------------------------------------------------------------------- */
+
+static void au_hfsn_fin_br(struct au_branch *br)
+{
+ struct au_br_hfsnotify *hfsn;
+
+ hfsn = br->br_hfsn;
+ if (hfsn) {
+ lockdep_off();
+ fsnotify_put_group(hfsn->hfsn_group);
+ lockdep_on();
+ }
+}
+
+static int au_hfsn_init_br(struct au_branch *br, int perm)
+{
+ int err;
+ struct fsnotify_group *group;
+ struct au_br_hfsnotify *hfsn;
+
+ err = 0;
+ br->br_hfsn = NULL;
+ if (!au_br_hnotifyable(perm))
+ goto out;
+
+ err = -ENOMEM;
+ hfsn = kmalloc(sizeof(*hfsn), GFP_NOFS);
+ if (unlikely(!hfsn))
+ goto out;
+
+ err = 0;
+ group = fsnotify_alloc_group(&au_hfsn_ops);
+ if (IS_ERR(group)) {
+ err = PTR_ERR(group);
+ pr_err("fsnotify_alloc_group() failed, %d\n", err);
+ goto out_hfsn;
+ }
+
+ group->private = hfsn;
+ hfsn->hfsn_group = group;
+ br->br_hfsn = hfsn;
+ goto out; /* success */
+
+out_hfsn:
+ au_kfree_try_rcu(hfsn);
+out:
+ return err;
+}
+
+static int au_hfsn_reset_br(unsigned int udba, struct au_branch *br, int perm)
+{
+ int err;
+
+ err = 0;
+ if (!br->br_hfsn)
+ err = au_hfsn_init_br(br, perm);
+
+ return err;
+}
+
+/* ---------------------------------------------------------------------- */
+
+static void au_hfsn_fin(void)
+{
+ AuDbg("au_hfsn_ifree %lld\n", (long long)atomic64_read(&au_hfsn_ifree));
+ wait_event(au_hfsn_wq, !atomic64_read(&au_hfsn_ifree));
+}
+
+const struct au_hnotify_op au_hnotify_op = {
+ .ctl = au_hfsn_ctl,
+ .alloc = au_hfsn_alloc,
+ .free = au_hfsn_free,
+
+ .fin = au_hfsn_fin,
+
+ .reset_br = au_hfsn_reset_br,
+ .fin_br = au_hfsn_fin_br,
+ .init_br = au_hfsn_init_br
+};