forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
288 lines
6.8 KiB
288 lines
6.8 KiB
// 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 |
|
};
|
|
|