Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+7
View File
@@ -0,0 +1,7 @@
# SPDX-License-Identifier: GPL-2.0-only
config FSNOTIFY
def_bool n
source "fs/notify/dnotify/Kconfig"
source "fs/notify/inotify/Kconfig"
source "fs/notify/fanotify/Kconfig"
+7
View File
@@ -0,0 +1,7 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_FSNOTIFY) += fsnotify.o notification.o group.o mark.o \
fdinfo.o
obj-y += dnotify/
obj-y += inotify/
obj-y += fanotify/
+12
View File
@@ -0,0 +1,12 @@
# SPDX-License-Identifier: GPL-2.0-only
config DNOTIFY
bool "Dnotify support"
select FSNOTIFY
default y
help
Dnotify is a directory-based per-fd file change notification system
that uses signals to communicate events to user-space. There exist
superior alternatives, but some applications may still rely on
dnotify.
If unsure, say Y.
+2
View File
@@ -0,0 +1,2 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-$(CONFIG_DNOTIFY) += dnotify.o
+413
View File
@@ -0,0 +1,413 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Directory notifications for Linux.
*
* Copyright (C) 2000,2001,2002 Stephen Rothwell
*
* Copyright (C) 2009 Eric Paris <Red Hat Inc>
* dnotify was largly rewritten to use the new fsnotify infrastructure
*/
#include <linux/fs.h>
#include <linux/module.h>
#include <linux/sched.h>
#include <linux/sched/signal.h>
#include <linux/dnotify.h>
#include <linux/init.h>
#include <linux/security.h>
#include <linux/spinlock.h>
#include <linux/slab.h>
#include <linux/fsnotify_backend.h>
static int dir_notify_enable __read_mostly = 1;
#ifdef CONFIG_SYSCTL
static const struct ctl_table dnotify_sysctls[] = {
{
.procname = "dir-notify-enable",
.data = &dir_notify_enable,
.maxlen = sizeof(int),
.mode = 0644,
.proc_handler = proc_dointvec,
},
};
static void __init dnotify_sysctl_init(void)
{
register_sysctl_init("fs", dnotify_sysctls);
}
#else
#define dnotify_sysctl_init() do { } while (0)
#endif
static struct kmem_cache *dnotify_struct_cache __ro_after_init;
static struct kmem_cache *dnotify_mark_cache __ro_after_init;
static struct fsnotify_group *dnotify_group __ro_after_init;
/*
* dnotify will attach one of these to each inode (i_fsnotify_marks) which
* is being watched by dnotify. If multiple userspace applications are watching
* the same directory with dnotify their information is chained in dn
*/
struct dnotify_mark {
struct fsnotify_mark fsn_mark;
struct dnotify_struct *dn;
};
/*
* When a process starts or stops watching an inode the set of events which
* dnotify cares about for that inode may change. This function runs the
* list of everything receiving dnotify events about this directory and calculates
* the set of all those events. After it updates what dnotify is interested in
* it calls the fsnotify function so it can update the set of all events relevant
* to this inode.
*/
static void dnotify_recalc_inode_mask(struct fsnotify_mark *fsn_mark)
{
__u32 new_mask = 0;
struct dnotify_struct *dn;
struct dnotify_mark *dn_mark = container_of(fsn_mark,
struct dnotify_mark,
fsn_mark);
assert_spin_locked(&fsn_mark->lock);
for (dn = dn_mark->dn; dn != NULL; dn = dn->dn_next)
new_mask |= (dn->dn_mask & ~FS_DN_MULTISHOT);
if (fsn_mark->mask == new_mask)
return;
fsn_mark->mask = new_mask;
fsnotify_recalc_mask(fsn_mark->connector);
}
/*
* Mains fsnotify call where events are delivered to dnotify.
* Find the dnotify mark on the relevant inode, run the list of dnotify structs
* on that mark and determine which of them has expressed interest in receiving
* events of this type. When found send the correct process and signal and
* destroy the dnotify struct if it was not registered to receive multiple
* events.
*/
static int dnotify_handle_event(struct fsnotify_mark *inode_mark, u32 mask,
struct inode *inode, struct inode *dir,
const struct qstr *name, u32 cookie)
{
struct dnotify_mark *dn_mark;
struct dnotify_struct *dn;
struct dnotify_struct **prev;
struct fown_struct *fown;
__u32 test_mask = mask & ~FS_EVENT_ON_CHILD;
/* not a dir, dnotify doesn't care */
if (!dir && !(mask & FS_ISDIR))
return 0;
dn_mark = container_of(inode_mark, struct dnotify_mark, fsn_mark);
spin_lock(&inode_mark->lock);
prev = &dn_mark->dn;
while ((dn = *prev) != NULL) {
if ((dn->dn_mask & test_mask) == 0) {
prev = &dn->dn_next;
continue;
}
fown = file_f_owner(dn->dn_filp);
send_sigio(fown, dn->dn_fd, POLL_MSG);
if (dn->dn_mask & FS_DN_MULTISHOT)
prev = &dn->dn_next;
else {
*prev = dn->dn_next;
kmem_cache_free(dnotify_struct_cache, dn);
dnotify_recalc_inode_mask(inode_mark);
}
}
spin_unlock(&inode_mark->lock);
return 0;
}
static void dnotify_free_mark(struct fsnotify_mark *fsn_mark)
{
struct dnotify_mark *dn_mark = container_of(fsn_mark,
struct dnotify_mark,
fsn_mark);
BUG_ON(dn_mark->dn);
kmem_cache_free(dnotify_mark_cache, dn_mark);
}
static const struct fsnotify_ops dnotify_fsnotify_ops = {
.handle_inode_event = dnotify_handle_event,
.free_mark = dnotify_free_mark,
};
/*
* Called every time a file is closed. Looks first for a dnotify mark on the
* inode. If one is found run all of the ->dn structures attached to that
* mark for one relevant to this process closing the file and remove that
* dnotify_struct. If that was the last dnotify_struct also remove the
* fsnotify_mark.
*/
void dnotify_flush(struct file *filp, fl_owner_t id)
{
struct fsnotify_mark *fsn_mark;
struct dnotify_mark *dn_mark;
struct dnotify_struct *dn;
struct dnotify_struct **prev;
struct inode *inode;
bool free = false;
inode = file_inode(filp);
if (!S_ISDIR(inode->i_mode))
return;
fsn_mark = fsnotify_find_inode_mark(inode, dnotify_group);
if (!fsn_mark)
return;
dn_mark = container_of(fsn_mark, struct dnotify_mark, fsn_mark);
fsnotify_group_lock(dnotify_group);
spin_lock(&fsn_mark->lock);
prev = &dn_mark->dn;
while ((dn = *prev) != NULL) {
if ((dn->dn_owner == id) && (dn->dn_filp == filp)) {
*prev = dn->dn_next;
kmem_cache_free(dnotify_struct_cache, dn);
dnotify_recalc_inode_mask(fsn_mark);
break;
}
prev = &dn->dn_next;
}
spin_unlock(&fsn_mark->lock);
/* nothing else could have found us thanks to the dnotify_groups
mark_mutex */
if (dn_mark->dn == NULL) {
fsnotify_detach_mark(fsn_mark);
free = true;
}
fsnotify_group_unlock(dnotify_group);
if (free)
fsnotify_free_mark(fsn_mark);
fsnotify_put_mark(fsn_mark);
}
/* this conversion is done only at watch creation */
static __u32 convert_arg(unsigned int arg)
{
__u32 new_mask = FS_EVENT_ON_CHILD;
if (arg & DN_MULTISHOT)
new_mask |= FS_DN_MULTISHOT;
if (arg & DN_DELETE)
new_mask |= (FS_DELETE | FS_MOVED_FROM);
if (arg & DN_MODIFY)
new_mask |= FS_MODIFY;
if (arg & DN_ACCESS)
new_mask |= FS_ACCESS;
if (arg & DN_ATTRIB)
new_mask |= FS_ATTRIB;
if (arg & DN_RENAME)
new_mask |= FS_RENAME;
if (arg & DN_CREATE)
new_mask |= (FS_CREATE | FS_MOVED_TO);
return new_mask;
}
/*
* If multiple processes watch the same inode with dnotify there is only one
* dnotify mark in inode->i_fsnotify_marks but we chain a dnotify_struct
* onto that mark. This function either attaches the new dnotify_struct onto
* that list, or it |= the mask onto an existing dnofiy_struct.
*/
static int attach_dn(struct dnotify_struct *dn, struct dnotify_mark *dn_mark,
fl_owner_t id, int fd, struct file *filp, __u32 mask)
{
struct dnotify_struct *odn;
odn = dn_mark->dn;
while (odn != NULL) {
/* adding more events to existing dnofiy_struct? */
if ((odn->dn_owner == id) && (odn->dn_filp == filp)) {
odn->dn_fd = fd;
odn->dn_mask |= mask;
return -EEXIST;
}
odn = odn->dn_next;
}
dn->dn_mask = mask;
dn->dn_fd = fd;
dn->dn_filp = filp;
dn->dn_owner = id;
dn->dn_next = dn_mark->dn;
dn_mark->dn = dn;
return 0;
}
/*
* When a process calls fcntl to attach a dnotify watch to a directory it ends
* up here. Allocate both a mark for fsnotify to add and a dnotify_struct to be
* attached to the fsnotify_mark.
*/
int fcntl_dirnotify(int fd, struct file *filp, unsigned int arg)
{
struct dnotify_mark *new_dn_mark, *dn_mark;
struct fsnotify_mark *new_fsn_mark, *fsn_mark;
struct dnotify_struct *dn;
struct inode *inode;
fl_owner_t id = current->files;
struct file *f = NULL;
int destroy = 0, error = 0;
__u32 mask;
/* we use these to tell if we need to kfree */
new_fsn_mark = NULL;
dn = NULL;
if (!dir_notify_enable) {
error = -EINVAL;
goto out_err;
}
/* a 0 mask means we are explicitly removing the watch */
if ((arg & ~DN_MULTISHOT) == 0) {
dnotify_flush(filp, id);
error = 0;
goto out_err;
}
/* dnotify only works on directories */
inode = file_inode(filp);
if (!S_ISDIR(inode->i_mode)) {
error = -ENOTDIR;
goto out_err;
}
/*
* convert the userspace DN_* "arg" to the internal FS_*
* defined in fsnotify
*/
mask = convert_arg(arg);
error = security_path_notify(&filp->f_path, mask,
FSNOTIFY_OBJ_TYPE_INODE);
if (error)
goto out_err;
/* expect most fcntl to add new rather than augment old */
dn = kmem_cache_alloc(dnotify_struct_cache, GFP_KERNEL);
if (!dn) {
error = -ENOMEM;
goto out_err;
}
error = file_f_owner_allocate(filp);
if (error)
goto out_err;
/* new fsnotify mark, we expect most fcntl calls to add a new mark */
new_dn_mark = kmem_cache_alloc(dnotify_mark_cache, GFP_KERNEL);
if (!new_dn_mark) {
error = -ENOMEM;
goto out_err;
}
/* set up the new_fsn_mark and new_dn_mark */
new_fsn_mark = &new_dn_mark->fsn_mark;
fsnotify_init_mark(new_fsn_mark, dnotify_group);
new_fsn_mark->mask = mask;
new_dn_mark->dn = NULL;
/* this is needed to prevent the fcntl/close race described below */
fsnotify_group_lock(dnotify_group);
/* add the new_fsn_mark or find an old one. */
fsn_mark = fsnotify_find_inode_mark(inode, dnotify_group);
if (fsn_mark) {
dn_mark = container_of(fsn_mark, struct dnotify_mark, fsn_mark);
spin_lock(&fsn_mark->lock);
} else {
error = fsnotify_add_inode_mark_locked(new_fsn_mark, inode, 0);
if (error) {
fsnotify_group_unlock(dnotify_group);
goto out_err;
}
spin_lock(&new_fsn_mark->lock);
fsn_mark = new_fsn_mark;
dn_mark = new_dn_mark;
/* we used new_fsn_mark, so don't free it */
new_fsn_mark = NULL;
}
f = fget_raw(fd);
/* if (f != filp) means that we lost a race and another task/thread
* actually closed the fd we are still playing with before we grabbed
* the dnotify_groups mark_mutex and fsn_mark->lock. Since closing the
* fd is the only time we clean up the marks we need to get our mark
* off the list. */
if (f != filp) {
/* if we added ourselves, shoot ourselves, it's possible that
* the flush actually did shoot this fsn_mark. That's fine too
* since multiple calls to destroy_mark is perfectly safe, if
* we found a dn_mark already attached to the inode, just sod
* off silently as the flush at close time dealt with it.
*/
if (dn_mark == new_dn_mark)
destroy = 1;
error = 0;
goto out;
}
__f_setown(filp, task_pid(current), PIDTYPE_TGID, 0);
error = attach_dn(dn, dn_mark, id, fd, filp, mask);
/* !error means that we attached the dn to the dn_mark, so don't free it */
if (!error)
dn = NULL;
/* -EEXIST means that we didn't add this new dn and used an old one.
* that isn't an error (and the unused dn should be freed) */
else if (error == -EEXIST)
error = 0;
dnotify_recalc_inode_mask(fsn_mark);
out:
spin_unlock(&fsn_mark->lock);
if (destroy)
fsnotify_detach_mark(fsn_mark);
fsnotify_group_unlock(dnotify_group);
if (destroy)
fsnotify_free_mark(fsn_mark);
fsnotify_put_mark(fsn_mark);
out_err:
if (new_fsn_mark)
fsnotify_put_mark(new_fsn_mark);
if (dn)
kmem_cache_free(dnotify_struct_cache, dn);
if (f)
fput(f);
return error;
}
static int __init dnotify_init(void)
{
dnotify_struct_cache = KMEM_CACHE(dnotify_struct,
SLAB_PANIC|SLAB_ACCOUNT);
dnotify_mark_cache = KMEM_CACHE(dnotify_mark, SLAB_PANIC|SLAB_ACCOUNT);
dnotify_group = fsnotify_alloc_group(&dnotify_fsnotify_ops, 0);
if (IS_ERR(dnotify_group))
panic("unable to allocate fsnotify group for dnotify\n");
dnotify_sysctl_init();
return 0;
}
module_init(dnotify_init)
+26
View File
@@ -0,0 +1,26 @@
# SPDX-License-Identifier: GPL-2.0-only
config FANOTIFY
bool "Filesystem wide access notification"
select FSNOTIFY
select EXPORTFS
default n
help
Say Y here to enable fanotify support. fanotify is a file access
notification system which differs from inotify in that it sends
an open file descriptor to the userspace listener along with
the event.
If unsure, say Y.
config FANOTIFY_ACCESS_PERMISSIONS
bool "fanotify permissions checking"
depends on FANOTIFY
default n
help
Say Y here is you want fanotify listeners to be able to make permissions
decisions concerning filesystem events. This is used by some fanotify
listeners which need to scan files before allowing the system access to
use those files. This is used by some anti-malware vendors and by some
hierarchical storage management systems.
If unsure, say N.
+2
View File
@@ -0,0 +1,2 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-$(CONFIG_FANOTIFY) += fanotify.o fanotify_user.o
File diff suppressed because it is too large Load Diff
+555
View File
@@ -0,0 +1,555 @@
/* SPDX-License-Identifier: GPL-2.0 */
#include <linux/fsnotify_backend.h>
#include <linux/path.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/exportfs.h>
#include <linux/hashtable.h>
extern struct kmem_cache *fanotify_mark_cache;
extern struct kmem_cache *fanotify_fid_event_cachep;
extern struct kmem_cache *fanotify_path_event_cachep;
extern struct kmem_cache *fanotify_perm_event_cachep;
extern struct kmem_cache *fanotify_mnt_event_cachep;
/* Possible states of the permission event */
enum {
FAN_EVENT_INIT,
FAN_EVENT_REPORTED,
FAN_EVENT_ANSWERED,
FAN_EVENT_CANCELED,
};
/*
* 3 dwords are sufficient for most local fs (64bit ino, 32bit generation).
* fh buf should be dword aligned. On 64bit arch, the ext_buf pointer is
* stored in either the first or last 2 dwords.
*/
#define FANOTIFY_INLINE_FH_LEN (3 << 2)
#define FANOTIFY_FH_HDR_LEN sizeof(struct fanotify_fh)
/* Fixed size struct for file handle */
struct fanotify_fh {
u8 type;
u8 len;
#define FANOTIFY_FH_FLAG_EXT_BUF 1
u8 flags;
u8 pad;
} __aligned(4);
/* Variable size struct for dir file handle + child file handle + name */
struct fanotify_info {
/* size of dir_fh/file_fh including fanotify_fh hdr size */
u8 dir_fh_totlen;
u8 dir2_fh_totlen;
u8 file_fh_totlen;
u8 name_len;
u8 name2_len;
u8 pad[3];
unsigned char buf[];
/*
* (struct fanotify_fh) dir_fh starts at buf[0]
* (optional) dir2_fh starts at buf[dir_fh_totlen]
* (optional) file_fh starts at buf[dir_fh_totlen + dir2_fh_totlen]
* name starts at buf[dir_fh_totlen + dir2_fh_totlen + file_fh_totlen]
* ...
*/
#define FANOTIFY_DIR_FH_SIZE(info) ((info)->dir_fh_totlen)
#define FANOTIFY_DIR2_FH_SIZE(info) ((info)->dir2_fh_totlen)
#define FANOTIFY_FILE_FH_SIZE(info) ((info)->file_fh_totlen)
#define FANOTIFY_NAME_SIZE(info) ((info)->name_len + 1)
#define FANOTIFY_NAME2_SIZE(info) ((info)->name2_len + 1)
#define FANOTIFY_DIR_FH_OFFSET(info) 0
#define FANOTIFY_DIR2_FH_OFFSET(info) \
(FANOTIFY_DIR_FH_OFFSET(info) + FANOTIFY_DIR_FH_SIZE(info))
#define FANOTIFY_FILE_FH_OFFSET(info) \
(FANOTIFY_DIR2_FH_OFFSET(info) + FANOTIFY_DIR2_FH_SIZE(info))
#define FANOTIFY_NAME_OFFSET(info) \
(FANOTIFY_FILE_FH_OFFSET(info) + FANOTIFY_FILE_FH_SIZE(info))
#define FANOTIFY_NAME2_OFFSET(info) \
(FANOTIFY_NAME_OFFSET(info) + FANOTIFY_NAME_SIZE(info))
#define FANOTIFY_DIR_FH_BUF(info) \
((info)->buf + FANOTIFY_DIR_FH_OFFSET(info))
#define FANOTIFY_DIR2_FH_BUF(info) \
((info)->buf + FANOTIFY_DIR2_FH_OFFSET(info))
#define FANOTIFY_FILE_FH_BUF(info) \
((info)->buf + FANOTIFY_FILE_FH_OFFSET(info))
#define FANOTIFY_NAME_BUF(info) \
((info)->buf + FANOTIFY_NAME_OFFSET(info))
#define FANOTIFY_NAME2_BUF(info) \
((info)->buf + FANOTIFY_NAME2_OFFSET(info))
} __aligned(4);
static inline bool fanotify_fh_has_ext_buf(struct fanotify_fh *fh)
{
return (fh->flags & FANOTIFY_FH_FLAG_EXT_BUF);
}
static inline char **fanotify_fh_ext_buf_ptr(struct fanotify_fh *fh)
{
BUILD_BUG_ON(FANOTIFY_FH_HDR_LEN % 4);
BUILD_BUG_ON(__alignof__(char *) - 4 + sizeof(char *) >
FANOTIFY_INLINE_FH_LEN);
return (char **)ALIGN((unsigned long)(fh + 1), __alignof__(char *));
}
static inline void *fanotify_fh_ext_buf(struct fanotify_fh *fh)
{
return *fanotify_fh_ext_buf_ptr(fh);
}
static inline void *fanotify_fh_buf(struct fanotify_fh *fh)
{
return fanotify_fh_has_ext_buf(fh) ? fanotify_fh_ext_buf(fh) : fh + 1;
}
static inline int fanotify_info_dir_fh_len(struct fanotify_info *info)
{
if (!info->dir_fh_totlen ||
WARN_ON_ONCE(info->dir_fh_totlen < FANOTIFY_FH_HDR_LEN))
return 0;
return info->dir_fh_totlen - FANOTIFY_FH_HDR_LEN;
}
static inline struct fanotify_fh *fanotify_info_dir_fh(struct fanotify_info *info)
{
BUILD_BUG_ON(offsetof(struct fanotify_info, buf) % 4);
return (struct fanotify_fh *)FANOTIFY_DIR_FH_BUF(info);
}
static inline int fanotify_info_dir2_fh_len(struct fanotify_info *info)
{
if (!info->dir2_fh_totlen ||
WARN_ON_ONCE(info->dir2_fh_totlen < FANOTIFY_FH_HDR_LEN))
return 0;
return info->dir2_fh_totlen - FANOTIFY_FH_HDR_LEN;
}
static inline struct fanotify_fh *fanotify_info_dir2_fh(struct fanotify_info *info)
{
return (struct fanotify_fh *)FANOTIFY_DIR2_FH_BUF(info);
}
static inline int fanotify_info_file_fh_len(struct fanotify_info *info)
{
if (!info->file_fh_totlen ||
WARN_ON_ONCE(info->file_fh_totlen < FANOTIFY_FH_HDR_LEN))
return 0;
return info->file_fh_totlen - FANOTIFY_FH_HDR_LEN;
}
static inline struct fanotify_fh *fanotify_info_file_fh(struct fanotify_info *info)
{
return (struct fanotify_fh *)FANOTIFY_FILE_FH_BUF(info);
}
static inline char *fanotify_info_name(struct fanotify_info *info)
{
if (!info->name_len)
return NULL;
return FANOTIFY_NAME_BUF(info);
}
static inline char *fanotify_info_name2(struct fanotify_info *info)
{
if (!info->name2_len)
return NULL;
return FANOTIFY_NAME2_BUF(info);
}
static inline void fanotify_info_init(struct fanotify_info *info)
{
BUILD_BUG_ON(FANOTIFY_FH_HDR_LEN + MAX_HANDLE_SZ > U8_MAX);
BUILD_BUG_ON(NAME_MAX > U8_MAX);
info->dir_fh_totlen = 0;
info->dir2_fh_totlen = 0;
info->file_fh_totlen = 0;
info->name_len = 0;
info->name2_len = 0;
}
/* These set/copy helpers MUST be called by order */
static inline void fanotify_info_set_dir_fh(struct fanotify_info *info,
unsigned int totlen)
{
if (WARN_ON_ONCE(info->dir2_fh_totlen > 0) ||
WARN_ON_ONCE(info->file_fh_totlen > 0) ||
WARN_ON_ONCE(info->name_len > 0) ||
WARN_ON_ONCE(info->name2_len > 0))
return;
info->dir_fh_totlen = totlen;
}
static inline void fanotify_info_set_dir2_fh(struct fanotify_info *info,
unsigned int totlen)
{
if (WARN_ON_ONCE(info->file_fh_totlen > 0) ||
WARN_ON_ONCE(info->name_len > 0) ||
WARN_ON_ONCE(info->name2_len > 0))
return;
info->dir2_fh_totlen = totlen;
}
static inline void fanotify_info_set_file_fh(struct fanotify_info *info,
unsigned int totlen)
{
if (WARN_ON_ONCE(info->name_len > 0) ||
WARN_ON_ONCE(info->name2_len > 0))
return;
info->file_fh_totlen = totlen;
}
static inline void fanotify_info_copy_name(struct fanotify_info *info,
const struct qstr *name)
{
if (WARN_ON_ONCE(name->len > NAME_MAX) ||
WARN_ON_ONCE(info->name2_len > 0))
return;
info->name_len = name->len;
strscpy(fanotify_info_name(info), name->name, name->len + 1);
}
static inline void fanotify_info_copy_name2(struct fanotify_info *info,
const struct qstr *name)
{
if (WARN_ON_ONCE(name->len > NAME_MAX))
return;
info->name2_len = name->len;
strscpy(fanotify_info_name2(info), name->name, name->len + 1);
}
/*
* Common structure for fanotify events. Concrete structs are allocated in
* fanotify_handle_event() and freed when the information is retrieved by
* userspace. The type of event determines how it was allocated, how it will
* be freed and which concrete struct it may be cast to.
*/
enum fanotify_event_type {
FANOTIFY_EVENT_TYPE_FID, /* fixed length */
FANOTIFY_EVENT_TYPE_FID_NAME, /* variable length */
FANOTIFY_EVENT_TYPE_PATH,
FANOTIFY_EVENT_TYPE_PATH_PERM,
FANOTIFY_EVENT_TYPE_OVERFLOW, /* struct fanotify_event */
FANOTIFY_EVENT_TYPE_FS_ERROR, /* struct fanotify_error_event */
FANOTIFY_EVENT_TYPE_MNT,
__FANOTIFY_EVENT_TYPE_NUM
};
#define FANOTIFY_EVENT_TYPE_BITS \
(ilog2(__FANOTIFY_EVENT_TYPE_NUM - 1) + 1)
#define FANOTIFY_EVENT_HASH_BITS \
(32 - FANOTIFY_EVENT_TYPE_BITS)
struct fanotify_event {
struct fsnotify_event fse;
struct hlist_node merge_list; /* List for hashed merge */
u32 mask;
struct {
unsigned int type : FANOTIFY_EVENT_TYPE_BITS;
unsigned int hash : FANOTIFY_EVENT_HASH_BITS;
};
struct pid *pid;
};
static inline void fanotify_init_event(struct fanotify_event *event,
unsigned int hash, u32 mask)
{
fsnotify_init_event(&event->fse);
INIT_HLIST_NODE(&event->merge_list);
event->hash = hash;
event->mask = mask;
event->pid = NULL;
}
#define FANOTIFY_INLINE_FH(name, size) \
struct { \
struct fanotify_fh name; \
/* Space for filehandle - access with fanotify_fh_buf() */ \
unsigned char _inline_fh_buf[size]; \
}
struct fanotify_fid_event {
struct fanotify_event fae;
__kernel_fsid_t fsid;
FANOTIFY_INLINE_FH(object_fh, FANOTIFY_INLINE_FH_LEN);
};
static inline struct fanotify_fid_event *
FANOTIFY_FE(struct fanotify_event *event)
{
return container_of(event, struct fanotify_fid_event, fae);
}
struct fanotify_name_event {
struct fanotify_event fae;
__kernel_fsid_t fsid;
struct fanotify_info info;
};
static inline struct fanotify_name_event *
FANOTIFY_NE(struct fanotify_event *event)
{
return container_of(event, struct fanotify_name_event, fae);
}
struct fanotify_error_event {
struct fanotify_event fae;
s32 error; /* Error reported by the Filesystem. */
u32 err_count; /* Suppressed errors count */
__kernel_fsid_t fsid; /* FSID this error refers to. */
FANOTIFY_INLINE_FH(object_fh, MAX_HANDLE_SZ);
};
static inline struct fanotify_error_event *
FANOTIFY_EE(struct fanotify_event *event)
{
return container_of(event, struct fanotify_error_event, fae);
}
static inline __kernel_fsid_t *fanotify_event_fsid(struct fanotify_event *event)
{
if (event->type == FANOTIFY_EVENT_TYPE_FID)
return &FANOTIFY_FE(event)->fsid;
else if (event->type == FANOTIFY_EVENT_TYPE_FID_NAME)
return &FANOTIFY_NE(event)->fsid;
else if (event->type == FANOTIFY_EVENT_TYPE_FS_ERROR)
return &FANOTIFY_EE(event)->fsid;
else
return NULL;
}
static inline struct fanotify_fh *fanotify_event_object_fh(
struct fanotify_event *event)
{
if (event->type == FANOTIFY_EVENT_TYPE_FID)
return &FANOTIFY_FE(event)->object_fh;
else if (event->type == FANOTIFY_EVENT_TYPE_FID_NAME)
return fanotify_info_file_fh(&FANOTIFY_NE(event)->info);
else if (event->type == FANOTIFY_EVENT_TYPE_FS_ERROR)
return &FANOTIFY_EE(event)->object_fh;
else
return NULL;
}
static inline struct fanotify_info *fanotify_event_info(
struct fanotify_event *event)
{
if (event->type == FANOTIFY_EVENT_TYPE_FID_NAME)
return &FANOTIFY_NE(event)->info;
else
return NULL;
}
static inline int fanotify_event_object_fh_len(struct fanotify_event *event)
{
struct fanotify_info *info = fanotify_event_info(event);
struct fanotify_fh *fh = fanotify_event_object_fh(event);
if (info)
return info->file_fh_totlen ? fh->len : 0;
else
return fh ? fh->len : 0;
}
static inline int fanotify_event_dir_fh_len(struct fanotify_event *event)
{
struct fanotify_info *info = fanotify_event_info(event);
return info ? fanotify_info_dir_fh_len(info) : 0;
}
static inline int fanotify_event_dir2_fh_len(struct fanotify_event *event)
{
struct fanotify_info *info = fanotify_event_info(event);
return info ? fanotify_info_dir2_fh_len(info) : 0;
}
static inline bool fanotify_event_has_object_fh(struct fanotify_event *event)
{
/* For error events, even zeroed fh are reported. */
if (event->type == FANOTIFY_EVENT_TYPE_FS_ERROR)
return true;
return fanotify_event_object_fh_len(event) > 0;
}
static inline bool fanotify_event_has_dir_fh(struct fanotify_event *event)
{
return fanotify_event_dir_fh_len(event) > 0;
}
static inline bool fanotify_event_has_dir2_fh(struct fanotify_event *event)
{
return fanotify_event_dir2_fh_len(event) > 0;
}
static inline bool fanotify_event_has_any_dir_fh(struct fanotify_event *event)
{
return fanotify_event_has_dir_fh(event) ||
fanotify_event_has_dir2_fh(event);
}
struct fanotify_path_event {
struct fanotify_event fae;
struct path path;
};
struct fanotify_mnt_event {
struct fanotify_event fae;
u64 mnt_id;
};
static inline struct fanotify_path_event *
FANOTIFY_PE(struct fanotify_event *event)
{
return container_of(event, struct fanotify_path_event, fae);
}
static inline struct fanotify_mnt_event *
FANOTIFY_ME(struct fanotify_event *event)
{
return container_of(event, struct fanotify_mnt_event, fae);
}
/*
* Structure for permission fanotify events. It gets allocated and freed in
* fanotify_handle_event() since we wait there for user response. When the
* information is retrieved by userspace the structure is moved from
* group->notification_list to group->fanotify_data.access_list to wait for
* user response.
*/
struct fanotify_perm_event {
struct fanotify_event fae;
struct path path;
const loff_t *ppos; /* optional file range info */
size_t count;
u32 response; /* userspace answer to the event */
unsigned short state; /* state of the event */
unsigned short watchdog_cnt; /* already scanned by watchdog? */
int fd; /* fd we passed to userspace for this event */
pid_t recv_pid; /* pid of task receiving the event */
union {
struct fanotify_response_info_header hdr;
struct fanotify_response_info_audit_rule audit_rule;
};
};
static inline struct fanotify_perm_event *
FANOTIFY_PERM(struct fanotify_event *event)
{
return container_of(event, struct fanotify_perm_event, fae);
}
static inline bool fanotify_is_perm_event(u32 mask)
{
return IS_ENABLED(CONFIG_FANOTIFY_ACCESS_PERMISSIONS) &&
mask & FANOTIFY_PERM_EVENTS;
}
static inline bool fanotify_event_has_access_range(struct fanotify_event *event)
{
if (!(event->mask & FANOTIFY_PRE_CONTENT_EVENTS))
return false;
return FANOTIFY_PERM(event)->ppos;
}
static inline struct fanotify_event *FANOTIFY_E(struct fsnotify_event *fse)
{
return container_of(fse, struct fanotify_event, fse);
}
static inline bool fanotify_is_error_event(u32 mask)
{
return mask & FAN_FS_ERROR;
}
static inline bool fanotify_is_mnt_event(u32 mask)
{
return mask & (FAN_MNT_ATTACH | FAN_MNT_DETACH);
}
static inline const struct path *fanotify_event_path(struct fanotify_event *event)
{
if (event->type == FANOTIFY_EVENT_TYPE_PATH)
return &FANOTIFY_PE(event)->path;
else if (event->type == FANOTIFY_EVENT_TYPE_PATH_PERM)
return &FANOTIFY_PERM(event)->path;
else
return NULL;
}
/*
* Use 128 size hash table to speed up events merge.
*/
#define FANOTIFY_HTABLE_BITS (7)
#define FANOTIFY_HTABLE_SIZE (1 << FANOTIFY_HTABLE_BITS)
#define FANOTIFY_HTABLE_MASK (FANOTIFY_HTABLE_SIZE - 1)
/*
* Permission events and overflow event do not get merged - don't hash them.
*/
static inline bool fanotify_is_hashed_event(u32 mask)
{
return !(fanotify_is_perm_event(mask) ||
fsnotify_is_overflow_event(mask));
}
static inline unsigned int fanotify_event_hash_bucket(
struct fsnotify_group *group,
struct fanotify_event *event)
{
return event->hash & FANOTIFY_HTABLE_MASK;
}
struct fanotify_mark {
struct fsnotify_mark fsn_mark;
__kernel_fsid_t fsid;
};
static inline struct fanotify_mark *FANOTIFY_MARK(struct fsnotify_mark *mark)
{
return container_of(mark, struct fanotify_mark, fsn_mark);
}
static inline bool fanotify_fsid_equal(__kernel_fsid_t *fsid1,
__kernel_fsid_t *fsid2)
{
return fsid1->val[0] == fsid2->val[0] && fsid1->val[1] == fsid2->val[1];
}
static inline unsigned int fanotify_mark_user_flags(struct fsnotify_mark *mark)
{
unsigned int mflags = 0;
if (mark->flags & FSNOTIFY_MARK_FLAG_IGNORED_SURV_MODIFY)
mflags |= FAN_MARK_IGNORED_SURV_MODIFY;
if (mark->flags & FSNOTIFY_MARK_FLAG_NO_IREF)
mflags |= FAN_MARK_EVICTABLE;
if (mark->flags & FSNOTIFY_MARK_FLAG_HAS_IGNORE_FLAGS)
mflags |= FAN_MARK_IGNORE;
return mflags;
}
static inline u32 fanotify_get_response_errno(int res)
{
return (res >> FAN_ERRNO_SHIFT) & FAN_ERRNO_MASK;
}
File diff suppressed because it is too large Load Diff
+153
View File
@@ -0,0 +1,153 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/file.h>
#include <linux/fs.h>
#include <linux/fsnotify_backend.h>
#include <linux/idr.h>
#include <linux/init.h>
#include <linux/inotify.h>
#include <linux/fanotify.h>
#include <linux/kernel.h>
#include <linux/namei.h>
#include <linux/sched.h>
#include <linux/types.h>
#include <linux/seq_file.h>
#include <linux/exportfs.h>
#include "inotify/inotify.h"
#include "fanotify/fanotify.h"
#include "fdinfo.h"
#include "fsnotify.h"
#include "../internal.h"
#if defined(CONFIG_PROC_FS)
#if defined(CONFIG_INOTIFY_USER) || defined(CONFIG_FANOTIFY)
static void show_fdinfo(struct seq_file *m, struct file *f,
void (*show)(struct seq_file *m,
struct fsnotify_mark *mark))
{
struct fsnotify_group *group = f->private_data;
struct fsnotify_mark *mark;
fsnotify_group_lock(group);
list_for_each_entry(mark, &group->marks_list, g_list) {
show(m, mark);
if (seq_has_overflowed(m))
break;
}
fsnotify_group_unlock(group);
}
#if defined(CONFIG_EXPORTFS)
static void show_mark_fhandle(struct seq_file *m, struct inode *inode)
{
DEFINE_FLEX(struct file_handle, f, f_handle, handle_bytes, MAX_HANDLE_SZ);
int size, ret, i;
size = f->handle_bytes >> 2;
if (!super_trylock_shared(inode->i_sb))
return;
ret = exportfs_encode_fid(inode, (struct fid *)f->f_handle, &size);
up_read(&inode->i_sb->s_umount);
if ((ret == FILEID_INVALID) || (ret < 0))
return;
f->handle_type = ret;
f->handle_bytes = size * sizeof(u32);
seq_printf(m, "fhandle-bytes:%x fhandle-type:%x f_handle:",
f->handle_bytes, f->handle_type);
for (i = 0; i < f->handle_bytes; i++)
seq_printf(m, "%02x", (int)f->f_handle[i]);
}
#else
static void show_mark_fhandle(struct seq_file *m, struct inode *inode)
{
}
#endif
#ifdef CONFIG_INOTIFY_USER
static void inotify_fdinfo(struct seq_file *m, struct fsnotify_mark *mark)
{
struct inotify_inode_mark *inode_mark;
struct inode *inode;
if (mark->connector->type != FSNOTIFY_OBJ_TYPE_INODE)
return;
inode_mark = container_of(mark, struct inotify_inode_mark, fsn_mark);
inode = igrab(fsnotify_conn_inode(mark->connector));
if (inode) {
seq_printf(m, "inotify wd:%x ino:%llx sdev:%x mask:%x ignored_mask:0 ",
inode_mark->wd, inode->i_ino, inode->i_sb->s_dev,
inotify_mark_user_mask(mark));
show_mark_fhandle(m, inode);
seq_putc(m, '\n');
iput(inode);
}
}
void inotify_show_fdinfo(struct seq_file *m, struct file *f)
{
show_fdinfo(m, f, inotify_fdinfo);
}
#endif /* CONFIG_INOTIFY_USER */
#ifdef CONFIG_FANOTIFY
static void fanotify_fdinfo(struct seq_file *m, struct fsnotify_mark *mark)
{
unsigned int mflags = fanotify_mark_user_flags(mark);
struct inode *inode;
if (mark->connector->type == FSNOTIFY_OBJ_TYPE_INODE) {
inode = igrab(fsnotify_conn_inode(mark->connector));
if (!inode)
return;
seq_printf(m, "fanotify ino:%llx sdev:%x mflags:%x mask:%x ignored_mask:%x ",
inode->i_ino, inode->i_sb->s_dev,
mflags, mark->mask, mark->ignore_mask);
show_mark_fhandle(m, inode);
seq_putc(m, '\n');
iput(inode);
} else if (mark->connector->type == FSNOTIFY_OBJ_TYPE_VFSMOUNT) {
struct mount *mnt = fsnotify_conn_mount(mark->connector);
seq_printf(m, "fanotify mnt_id:%x mflags:%x mask:%x ignored_mask:%x\n",
mnt->mnt_id, mflags, mark->mask, mark->ignore_mask);
} else if (mark->connector->type == FSNOTIFY_OBJ_TYPE_SB) {
struct super_block *sb = fsnotify_conn_sb(mark->connector);
seq_printf(m, "fanotify sdev:%x mflags:%x mask:%x ignored_mask:%x\n",
sb->s_dev, mflags, mark->mask, mark->ignore_mask);
} else if (mark->connector->type == FSNOTIFY_OBJ_TYPE_MNTNS) {
struct mnt_namespace *mnt_ns = fsnotify_conn_mntns(mark->connector);
seq_printf(m, "fanotify mnt_ns:%u mflags:%x mask:%x ignored_mask:%x\n",
mnt_ns->ns.inum, mflags, mark->mask, mark->ignore_mask);
}
}
void fanotify_show_fdinfo(struct seq_file *m, struct file *f)
{
struct fsnotify_group *group = f->private_data;
seq_printf(m, "fanotify flags:%x event-flags:%x\n",
group->fanotify_data.flags & FANOTIFY_INIT_FLAGS,
group->fanotify_data.f_flags);
show_fdinfo(m, f, fanotify_fdinfo);
}
#endif /* CONFIG_FANOTIFY */
#endif /* CONFIG_INOTIFY_USER || CONFIG_FANOTIFY */
#endif /* CONFIG_PROC_FS */
+28
View File
@@ -0,0 +1,28 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef __FSNOTIFY_FDINFO_H__
#define __FSNOTIFY_FDINFO_H__
#include <linux/errno.h>
#include <linux/proc_fs.h>
struct seq_file;
struct file;
#ifdef CONFIG_PROC_FS
#ifdef CONFIG_INOTIFY_USER
void inotify_show_fdinfo(struct seq_file *m, struct file *f);
#endif
#ifdef CONFIG_FANOTIFY
void fanotify_show_fdinfo(struct seq_file *m, struct file *f);
#endif
#else /* CONFIG_PROC_FS */
#define inotify_show_fdinfo NULL
#define fanotify_show_fdinfo NULL
#endif /* CONFIG_PROC_FS */
#endif /* __FSNOTIFY_FDINFO_H__ */
+733
View File
@@ -0,0 +1,733 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2008 Red Hat, Inc., Eric Paris <eparis@redhat.com>
*/
#include <linux/dcache.h>
#include <linux/fs.h>
#include <linux/gfp.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/mount.h>
#include <linux/srcu.h>
#include <linux/fsnotify_backend.h>
#include "fsnotify.h"
#define CREATE_TRACE_POINTS
#include <trace/events/fsnotify.h>
/*
* Clear all of the marks on an inode when it is being evicted from core
*/
void __fsnotify_inode_delete(struct inode *inode)
{
fsnotify_clear_marks_by_inode(inode);
}
EXPORT_SYMBOL_GPL(__fsnotify_inode_delete);
void __fsnotify_vfsmount_delete(struct vfsmount *mnt)
{
fsnotify_clear_marks_by_mount(mnt);
}
void __fsnotify_mntns_delete(struct mnt_namespace *mntns)
{
fsnotify_clear_marks_by_mntns(mntns);
}
void fsnotify_sb_delete(struct super_block *sb)
{
struct fsnotify_sb_info *sbinfo = fsnotify_sb_info(sb);
/* Were any marks ever added to any object on this sb? */
if (!sbinfo)
return;
fsnotify_unmount_inodes(sbinfo);
fsnotify_clear_marks_by_sb(sb);
/* Wait for outstanding object references from connectors */
wait_var_event(fsnotify_sb_watched_objects(sb),
!atomic_long_read(fsnotify_sb_watched_objects(sb)));
WARN_ON(fsnotify_sb_has_priority_watchers(sb, FSNOTIFY_PRIO_CONTENT));
WARN_ON(fsnotify_sb_has_priority_watchers(sb,
FSNOTIFY_PRIO_PRE_CONTENT));
}
void fsnotify_sb_free(struct super_block *sb)
{
if (sb->s_fsnotify_info) {
WARN_ON_ONCE(!list_empty(&sb->s_fsnotify_info->inode_conn_list));
kfree(sb->s_fsnotify_info);
}
}
/*
* Given an inode, first check if we care what happens to our children. Inotify
* and dnotify both tell their parents about events. If we care about any event
* on a child we run all of our children and set a dentry flag saying that the
* parent cares. Thus when an event happens on a child it can quickly tell
* if there is a need to find a parent and send the event to the parent.
*/
void fsnotify_set_children_dentry_flags(struct inode *inode)
{
struct dentry *alias;
if (!S_ISDIR(inode->i_mode))
return;
spin_lock(&inode->i_lock);
/* run all of the dentries associated with this inode. Since this is a
* directory, there damn well better only be one item on this list */
for_each_alias(alias, inode) {
struct dentry *child;
/* run all of the children of the original inode and fix their
* d_flags to indicate parental interest (their parent is the
* original inode) */
spin_lock(&alias->d_lock);
hlist_for_each_entry(child, &alias->d_children, d_sib) {
if (!child->d_inode)
continue;
spin_lock_nested(&child->d_lock, DENTRY_D_LOCK_NESTED);
child->d_flags |= DCACHE_FSNOTIFY_PARENT_WATCHED;
spin_unlock(&child->d_lock);
}
spin_unlock(&alias->d_lock);
}
spin_unlock(&inode->i_lock);
}
/*
* Lazily clear false positive PARENT_WATCHED flag for child whose parent had
* stopped watching children.
*/
static void fsnotify_clear_child_dentry_flag(struct inode *pinode,
struct dentry *dentry)
{
spin_lock(&dentry->d_lock);
/*
* d_lock is a sufficient barrier to prevent observing a non-watched
* parent state from before the fsnotify_set_children_dentry_flags()
* or fsnotify_update_flags() call that had set PARENT_WATCHED.
*/
if (!fsnotify_inode_watches_children(pinode))
dentry->d_flags &= ~DCACHE_FSNOTIFY_PARENT_WATCHED;
spin_unlock(&dentry->d_lock);
}
/* Are inode/sb/mount interested in parent and name info with this event? */
static bool fsnotify_event_needs_parent(struct inode *inode, __u32 mnt_mask,
__u32 mask)
{
__u32 marks_mask = 0;
/* We only send parent/name to inode/sb/mount for events on non-dir */
if (mask & FS_ISDIR)
return false;
/*
* All events that are possible on child can also may be reported with
* parent/name info to inode/sb/mount. Otherwise, a watching parent
* could result in events reported with unexpected name info to sb/mount.
*/
BUILD_BUG_ON(FS_EVENTS_POSS_ON_CHILD & ~FS_EVENTS_POSS_TO_PARENT);
/* Did either inode/sb/mount subscribe for events with parent/name? */
marks_mask |= fsnotify_parent_needed_mask(
READ_ONCE(inode->i_fsnotify_mask));
marks_mask |= fsnotify_parent_needed_mask(
READ_ONCE(inode->i_sb->s_fsnotify_mask));
marks_mask |= fsnotify_parent_needed_mask(mnt_mask);
/* Did they subscribe for this event with parent/name info? */
return mask & marks_mask;
}
/* Are there any inode/mount/sb objects that watch for these events? */
static inline __u32 fsnotify_object_watched(struct inode *inode, __u32 mnt_mask,
__u32 mask)
{
__u32 marks_mask = READ_ONCE(inode->i_fsnotify_mask) | mnt_mask |
READ_ONCE(inode->i_sb->s_fsnotify_mask);
return mask & marks_mask & ALL_FSNOTIFY_EVENTS;
}
/* Report pre-content event with optional range info */
int fsnotify_pre_content(const struct path *path, const loff_t *ppos,
size_t count)
{
struct file_range range;
/* Report page aligned range only when pos is known */
if (!ppos)
return fsnotify_path(path, FS_PRE_ACCESS);
range.path = path;
range.pos = PAGE_ALIGN_DOWN(*ppos);
range.count = PAGE_ALIGN(*ppos + count) - range.pos;
return fsnotify_parent(path->dentry, FS_PRE_ACCESS, &range,
FSNOTIFY_EVENT_FILE_RANGE);
}
/*
* Notify this dentry's parent about a child's events with child name info
* if parent is watching or if inode/sb/mount are interested in events with
* parent and name info.
*
* Notify only the child without name info if parent is not watching and
* inode/sb/mount are not interested in events with parent and name info.
*/
int __fsnotify_parent(struct dentry *dentry, __u32 mask, const void *data,
int data_type)
{
const struct path *path = fsnotify_data_path(data, data_type);
__u32 mnt_mask = path ?
READ_ONCE(real_mount(path->mnt)->mnt_fsnotify_mask) : 0;
struct inode *inode = d_inode(dentry);
struct dentry *parent;
bool parent_watched = dentry->d_flags & DCACHE_FSNOTIFY_PARENT_WATCHED;
bool parent_needed, parent_interested;
__u32 p_mask;
struct inode *p_inode = NULL;
struct name_snapshot name;
struct qstr *file_name = NULL;
int ret = 0;
/* Optimize the likely case of nobody watching this path */
if (likely(!parent_watched &&
!fsnotify_object_watched(inode, mnt_mask, mask)))
return 0;
parent = NULL;
parent_needed = fsnotify_event_needs_parent(inode, mnt_mask, mask);
if (!parent_watched && !parent_needed)
goto notify;
/* Does parent inode care about events on children? */
parent = dget_parent(dentry);
p_inode = parent->d_inode;
p_mask = fsnotify_inode_watches_children(p_inode);
if (unlikely(parent_watched && !p_mask))
fsnotify_clear_child_dentry_flag(p_inode, dentry);
/*
* Include parent/name in notification either if some notification
* groups require parent info or the parent is interested in this event.
* The parent interest in ACCESS/MODIFY events does not apply to special
* files, where read/write are not on the filesystem of the parent and
* events can provide an undesirable side-channel for information
* exfiltration.
*/
parent_interested = mask & p_mask & ALL_FSNOTIFY_EVENTS &&
!(data_type == FSNOTIFY_EVENT_PATH &&
d_is_special(dentry) &&
(mask & (FS_ACCESS | FS_MODIFY)));
if (parent_needed || parent_interested) {
/* When notifying parent, child should be passed as data */
WARN_ON_ONCE(inode != fsnotify_data_inode(data, data_type));
/* Notify both parent and child with child name info */
take_dentry_name_snapshot(&name, dentry);
file_name = &name.name;
if (parent_interested)
mask |= FS_EVENT_ON_CHILD;
}
notify:
ret = fsnotify(mask, data, data_type, p_inode, file_name, inode, 0);
if (file_name)
release_dentry_name_snapshot(&name);
dput(parent);
return ret;
}
EXPORT_SYMBOL_GPL(__fsnotify_parent);
static int fsnotify_handle_inode_event(struct fsnotify_group *group,
struct fsnotify_mark *inode_mark,
u32 mask, const void *data, int data_type,
struct inode *dir, const struct qstr *name,
u32 cookie)
{
const struct path *path = fsnotify_data_path(data, data_type);
struct inode *inode = fsnotify_data_inode(data, data_type);
const struct fsnotify_ops *ops = group->ops;
if (WARN_ON_ONCE(!ops->handle_inode_event))
return 0;
if (WARN_ON_ONCE(!inode && !dir))
return 0;
if ((inode_mark->flags & FSNOTIFY_MARK_FLAG_EXCL_UNLINK) &&
path && d_unlinked(path->dentry))
return 0;
/* Check interest of this mark in case event was sent with two marks */
if (!(mask & inode_mark->mask & ALL_FSNOTIFY_EVENTS))
return 0;
return ops->handle_inode_event(inode_mark, mask, inode, dir, name, cookie);
}
static int fsnotify_handle_event(struct fsnotify_group *group, __u32 mask,
const void *data, int data_type,
struct inode *dir, const struct qstr *name,
u32 cookie, struct fsnotify_iter_info *iter_info)
{
struct fsnotify_mark *inode_mark = fsnotify_iter_inode_mark(iter_info);
struct fsnotify_mark *parent_mark = fsnotify_iter_parent_mark(iter_info);
int ret;
if (WARN_ON_ONCE(fsnotify_iter_sb_mark(iter_info)) ||
WARN_ON_ONCE(fsnotify_iter_vfsmount_mark(iter_info)))
return 0;
/*
* For FS_RENAME, 'dir' is old dir and 'data' is new dentry.
* The only ->handle_inode_event() backend that supports FS_RENAME is
* dnotify, where it means file was renamed within same parent.
*/
if (mask & FS_RENAME) {
struct dentry *moved = fsnotify_data_dentry(data, data_type);
if (dir != moved->d_parent->d_inode)
return 0;
}
if (parent_mark) {
ret = fsnotify_handle_inode_event(group, parent_mark, mask,
data, data_type, dir, name, 0);
if (ret)
return ret;
}
if (!inode_mark)
return 0;
/*
* Some events can be sent on both parent dir and child marks (e.g.
* FS_ATTRIB). If both parent dir and child are watching, report the
* event once to parent dir with name (if interested) and once to child
* without name (if interested).
*
* In any case regardless whether the parent is watching or not, the
* child watcher is expecting an event without the FS_EVENT_ON_CHILD
* flag. The file name is expected if and only if this is a directory
* event.
*/
mask &= ~FS_EVENT_ON_CHILD;
if (!(mask & ALL_FSNOTIFY_DIRENT_EVENTS)) {
dir = NULL;
name = NULL;
}
return fsnotify_handle_inode_event(group, inode_mark, mask, data, data_type,
dir, name, cookie);
}
static int send_to_group(__u32 mask, const void *data, int data_type,
struct inode *dir, const struct qstr *file_name,
u32 cookie, struct fsnotify_iter_info *iter_info)
{
struct fsnotify_group *group = NULL;
__u32 test_mask = (mask & ALL_FSNOTIFY_EVENTS);
__u32 marks_mask = 0;
__u32 marks_ignore_mask = 0;
bool is_dir = mask & FS_ISDIR;
struct fsnotify_mark *mark;
int type;
if (!iter_info->report_mask)
return 0;
/* clear ignored on inode modification */
if (mask & FS_MODIFY) {
fsnotify_foreach_iter_mark_type(iter_info, mark, type) {
if (!(mark->flags &
FSNOTIFY_MARK_FLAG_IGNORED_SURV_MODIFY))
mark->ignore_mask = 0;
}
}
/* Are any of the group marks interested in this event? */
fsnotify_foreach_iter_mark_type(iter_info, mark, type) {
group = mark->group;
marks_mask |= mark->mask;
marks_ignore_mask |=
fsnotify_effective_ignore_mask(mark, is_dir, type);
}
pr_debug("%s: group=%p mask=%x marks_mask=%x marks_ignore_mask=%x data=%p data_type=%d dir=%p cookie=%d\n",
__func__, group, mask, marks_mask, marks_ignore_mask,
data, data_type, dir, cookie);
if (!(test_mask & marks_mask & ~marks_ignore_mask))
return 0;
if (group->ops->handle_event) {
return group->ops->handle_event(group, mask, data, data_type, dir,
file_name, cookie, iter_info);
}
return fsnotify_handle_event(group, mask, data, data_type, dir,
file_name, cookie, iter_info);
}
static struct fsnotify_mark *fsnotify_first_mark(struct fsnotify_mark_connector *const *connp)
{
struct fsnotify_mark_connector *conn;
struct hlist_node *node = NULL;
conn = srcu_dereference(*connp, &fsnotify_mark_srcu);
if (conn)
node = srcu_dereference(conn->list.first, &fsnotify_mark_srcu);
return hlist_entry_safe(node, struct fsnotify_mark, obj_list);
}
struct fsnotify_mark *fsnotify_next_mark(struct fsnotify_mark *mark)
{
struct hlist_node *node = NULL;
if (mark)
node = srcu_dereference(mark->obj_list.next,
&fsnotify_mark_srcu);
return hlist_entry_safe(node, struct fsnotify_mark, obj_list);
}
/*
* iter_info is a multi head priority queue of marks.
* Pick a subset of marks from queue heads, all with the same group
* and set the report_mask to a subset of the selected marks.
* Returns false if there are no more groups to iterate.
*/
static bool fsnotify_iter_select_report_types(
struct fsnotify_iter_info *iter_info)
{
struct fsnotify_group *max_prio_group = NULL;
struct fsnotify_mark *mark;
int type;
/* Choose max prio group among groups of all queue heads */
fsnotify_foreach_iter_type(type) {
mark = iter_info->marks[type];
if (mark &&
fsnotify_compare_groups(max_prio_group, mark->group) > 0)
max_prio_group = mark->group;
}
if (!max_prio_group)
return false;
/* Set the report mask for marks from same group as max prio group */
iter_info->current_group = max_prio_group;
iter_info->report_mask = 0;
fsnotify_foreach_iter_type(type) {
mark = iter_info->marks[type];
if (mark && mark->group == iter_info->current_group) {
/*
* FSNOTIFY_ITER_TYPE_PARENT indicates that this inode
* is watching children and interested in this event,
* which is an event possible on child.
* But is *this mark* watching children?
*/
if (type == FSNOTIFY_ITER_TYPE_PARENT &&
!(mark->mask & FS_EVENT_ON_CHILD) &&
!(fsnotify_ignore_mask(mark) & FS_EVENT_ON_CHILD))
continue;
fsnotify_iter_set_report_type(iter_info, type);
}
}
return true;
}
/*
* Pop from iter_info multi head queue, the marks that belong to the group of
* current iteration step.
*/
static void fsnotify_iter_next(struct fsnotify_iter_info *iter_info)
{
struct fsnotify_mark *mark;
int type;
/*
* We cannot use fsnotify_foreach_iter_mark_type() here because we
* may need to advance a mark of type X that belongs to current_group
* but was not selected for reporting.
*/
fsnotify_foreach_iter_type(type) {
mark = iter_info->marks[type];
if (mark && mark->group == iter_info->current_group)
iter_info->marks[type] =
fsnotify_next_mark(iter_info->marks[type]);
}
}
/*
* fsnotify - This is the main call to fsnotify.
*
* The VFS calls into hook specific functions in linux/fsnotify.h.
* Those functions then in turn call here. Here will call out to all of the
* registered fsnotify_group. Those groups can then use the notification event
* in whatever means they feel necessary.
*
* @mask: event type and flags
* @data: object that event happened on
* @data_type: type of object for fanotify_data_XXX() accessors
* @dir: optional directory associated with event -
* if @file_name is not NULL, this is the directory that
* @file_name is relative to
* @file_name: optional file name associated with event
* @inode: optional inode associated with event -
* If @dir and @inode are both non-NULL, event may be
* reported to both.
* @cookie: inotify rename cookie
*/
int fsnotify(__u32 mask, const void *data, int data_type, struct inode *dir,
const struct qstr *file_name, struct inode *inode, u32 cookie)
{
const struct path *path = fsnotify_data_path(data, data_type);
struct super_block *sb = fsnotify_data_sb(data, data_type);
const struct fsnotify_mnt *mnt_data = fsnotify_data_mnt(data, data_type);
struct fsnotify_sb_info *sbinfo = sb ? fsnotify_sb_info(sb) : NULL;
struct fsnotify_iter_info iter_info = {};
struct mount *mnt = NULL;
struct inode *inode2 = NULL;
struct dentry *moved;
int inode2_type;
int ret = 0;
__u32 test_mask, marks_mask = 0;
trace_fsnotify(mask, data, data_type, dir, file_name, inode, cookie);
if (path)
mnt = real_mount(path->mnt);
if (!inode) {
/* Dirent event - report on TYPE_INODE to dir */
inode = dir;
/* For FS_RENAME, inode is old_dir and inode2 is new_dir */
if (mask & FS_RENAME) {
moved = fsnotify_data_dentry(data, data_type);
inode2 = moved->d_parent->d_inode;
inode2_type = FSNOTIFY_ITER_TYPE_INODE2;
}
} else if (mask & FS_EVENT_ON_CHILD) {
/*
* Event on child - report on TYPE_PARENT to dir if it is
* watching children and on TYPE_INODE to child.
*/
inode2 = dir;
inode2_type = FSNOTIFY_ITER_TYPE_PARENT;
}
/*
* Optimization: srcu_read_lock() has a memory barrier which can
* be expensive. It protects walking the *_fsnotify_marks lists.
* However, if we do not walk the lists, we do not have to do
* SRCU because we have no references to any objects and do not
* need SRCU to keep them "alive".
*/
if ((!sbinfo || !sbinfo->sb_marks) &&
(!mnt || !mnt->mnt_fsnotify_marks) &&
(!inode || !inode->i_fsnotify_marks) &&
(!inode2 || !inode2->i_fsnotify_marks) &&
(!mnt_data || !mnt_data->ns->n_fsnotify_marks))
return 0;
if (sb)
marks_mask |= READ_ONCE(sb->s_fsnotify_mask);
if (mnt)
marks_mask |= READ_ONCE(mnt->mnt_fsnotify_mask);
if (inode)
marks_mask |= READ_ONCE(inode->i_fsnotify_mask);
if (inode2)
marks_mask |= READ_ONCE(inode2->i_fsnotify_mask);
if (mnt_data)
marks_mask |= READ_ONCE(mnt_data->ns->n_fsnotify_mask);
/*
* If this is a modify event we may need to clear some ignore masks.
* In that case, the object with ignore masks will have the FS_MODIFY
* event in its mask.
* Otherwise, return if none of the marks care about this type of event.
*/
test_mask = (mask & ALL_FSNOTIFY_EVENTS);
if (!(test_mask & marks_mask))
return 0;
iter_info.srcu_idx = srcu_read_lock(&fsnotify_mark_srcu);
if (sbinfo) {
iter_info.marks[FSNOTIFY_ITER_TYPE_SB] =
fsnotify_first_mark(&sbinfo->sb_marks);
}
if (mnt) {
iter_info.marks[FSNOTIFY_ITER_TYPE_VFSMOUNT] =
fsnotify_first_mark(&mnt->mnt_fsnotify_marks);
}
if (inode) {
iter_info.marks[FSNOTIFY_ITER_TYPE_INODE] =
fsnotify_first_mark(&inode->i_fsnotify_marks);
}
if (inode2) {
iter_info.marks[inode2_type] =
fsnotify_first_mark(&inode2->i_fsnotify_marks);
}
if (mnt_data) {
iter_info.marks[FSNOTIFY_ITER_TYPE_MNTNS] =
fsnotify_first_mark(&mnt_data->ns->n_fsnotify_marks);
}
/*
* We need to merge inode/vfsmount/sb mark lists so that e.g. inode mark
* ignore masks are properly reflected for mount/sb mark notifications.
* That's why this traversal is so complicated...
*/
while (fsnotify_iter_select_report_types(&iter_info)) {
ret = send_to_group(mask, data, data_type, dir, file_name,
cookie, &iter_info);
if (ret && (mask & ALL_FSNOTIFY_PERM_EVENTS))
goto out;
fsnotify_iter_next(&iter_info);
}
ret = 0;
out:
srcu_read_unlock(&fsnotify_mark_srcu, iter_info.srcu_idx);
return ret;
}
EXPORT_SYMBOL_GPL(fsnotify);
#ifdef CONFIG_FANOTIFY_ACCESS_PERMISSIONS
/*
* At open time we check fsnotify_sb_has_priority_watchers(), call the open perm
* hook and set the FMODE_NONOTIFY_ mode bits accordignly.
* Later, fsnotify permission hooks do not check if there are permission event
* watches, but that there were permission event watches at open time.
*/
int fsnotify_open_perm_and_set_mode(struct file *file)
{
struct dentry *dentry = file->f_path.dentry, *parent;
struct super_block *sb = dentry->d_sb;
__u32 mnt_mask, p_mask = 0;
/* Is it a file opened by fanotify? */
if (FMODE_FSNOTIFY_NONE(file->f_mode))
return 0;
/*
* Permission events is a super set of pre-content events, so if there
* are no permission event watchers, there are also no pre-content event
* watchers and this is implied from the single FMODE_NONOTIFY_PERM bit.
*/
if (likely(!fsnotify_sb_has_priority_watchers(sb,
FSNOTIFY_PRIO_CONTENT))) {
file_set_fsnotify_mode(file, FMODE_NONOTIFY_PERM);
return 0;
}
/*
* OK, there are some permission event watchers. Check if anybody is
* watching for permission events on *this* file.
*/
mnt_mask = READ_ONCE(real_mount(file->f_path.mnt)->mnt_fsnotify_mask);
p_mask = fsnotify_object_watched(d_inode(dentry), mnt_mask,
ALL_FSNOTIFY_PERM_EVENTS);
if (dentry->d_flags & DCACHE_FSNOTIFY_PARENT_WATCHED) {
parent = dget_parent(dentry);
p_mask |= fsnotify_inode_watches_children(d_inode(parent));
dput(parent);
}
/*
* Legacy FAN_ACCESS_PERM events have very high performance overhead,
* so unlikely to be used in the wild. If they are used there will be
* no optimizations at all.
*/
if (unlikely(p_mask & FS_ACCESS_PERM)) {
/* Enable all permission and pre-content events */
file_set_fsnotify_mode(file, 0);
goto open_perm;
}
/*
* Pre-content events are only supported on regular files.
* If there are pre-content event watchers and no permission access
* watchers, set FMODE_NONOTIFY | FMODE_NONOTIFY_PERM to indicate that.
* That is the common case with HSM service.
*/
if (d_is_reg(dentry) && (p_mask & FSNOTIFY_PRE_CONTENT_EVENTS)) {
file_set_fsnotify_mode(file, FMODE_NONOTIFY |
FMODE_NONOTIFY_PERM);
goto open_perm;
}
/* Nobody watching permission and pre-content events on this file */
file_set_fsnotify_mode(file, FMODE_NONOTIFY_PERM);
open_perm:
/*
* Send open perm events depending on object masks and regardless of
* FMODE_NONOTIFY_PERM.
*/
if (file->f_flags & __FMODE_EXEC && p_mask & FS_OPEN_EXEC_PERM) {
int ret = fsnotify_path(&file->f_path, FS_OPEN_EXEC_PERM);
if (ret)
return ret;
}
if (p_mask & FS_OPEN_PERM)
return fsnotify_path(&file->f_path, FS_OPEN_PERM);
return 0;
}
#endif
void fsnotify_mnt(__u32 mask, struct mnt_namespace *ns, struct vfsmount *mnt)
{
struct fsnotify_mnt data = {
.ns = ns,
.mnt_id = real_mount(mnt)->mnt_id_unique,
};
if (WARN_ON_ONCE(!ns))
return;
/*
* This is an optimization as well as making sure fsnotify_init() has
* been called.
*/
if (!ns->n_fsnotify_marks)
return;
fsnotify(mask, &data, FSNOTIFY_EVENT_MNT, NULL, NULL, NULL, 0);
}
static __init int fsnotify_init(void)
{
int ret;
BUILD_BUG_ON(HWEIGHT32(ALL_FSNOTIFY_BITS) != 26);
ret = init_srcu_struct(&fsnotify_mark_srcu);
if (ret)
panic("initializing fsnotify_mark_srcu");
fsnotify_init_connector_caches();
return 0;
}
core_initcall(fsnotify_init);
+114
View File
@@ -0,0 +1,114 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef __FS_NOTIFY_FSNOTIFY_H_
#define __FS_NOTIFY_FSNOTIFY_H_
#include <linux/list.h>
#include <linux/fsnotify.h>
#include <linux/srcu.h>
#include <linux/types.h>
#include "../mount.h"
/*
* fsnotify_connp_t is what we embed in objects which connector can be attached
* to.
*/
typedef struct fsnotify_mark_connector __rcu *fsnotify_connp_t;
static inline struct inode *fsnotify_conn_inode(
struct fsnotify_mark_connector *conn)
{
return conn->obj;
}
static inline struct mount *fsnotify_conn_mount(
struct fsnotify_mark_connector *conn)
{
return real_mount(conn->obj);
}
static inline struct super_block *fsnotify_conn_sb(
struct fsnotify_mark_connector *conn)
{
return conn->obj;
}
static inline struct mnt_namespace *fsnotify_conn_mntns(
struct fsnotify_mark_connector *conn)
{
return conn->obj;
}
static inline struct super_block *fsnotify_object_sb(void *obj,
enum fsnotify_obj_type obj_type)
{
switch (obj_type) {
case FSNOTIFY_OBJ_TYPE_INODE:
return ((struct inode *)obj)->i_sb;
case FSNOTIFY_OBJ_TYPE_VFSMOUNT:
return ((struct vfsmount *)obj)->mnt_sb;
case FSNOTIFY_OBJ_TYPE_SB:
return (struct super_block *)obj;
default:
return NULL;
}
}
static inline struct super_block *fsnotify_connector_sb(
struct fsnotify_mark_connector *conn)
{
return fsnotify_object_sb(conn->obj, conn->type);
}
static inline fsnotify_connp_t *fsnotify_sb_marks(struct super_block *sb)
{
struct fsnotify_sb_info *sbinfo = fsnotify_sb_info(sb);
return sbinfo ? &sbinfo->sb_marks : NULL;
}
/* destroy all events sitting in this groups notification queue */
extern void fsnotify_flush_notify(struct fsnotify_group *group);
/* protects reads of inode and vfsmount marks list */
extern struct srcu_struct fsnotify_mark_srcu;
/* compare two groups for sorting of marks lists */
extern int fsnotify_compare_groups(struct fsnotify_group *a,
struct fsnotify_group *b);
/* Destroy all inode marks for given superblock */
void fsnotify_unmount_inodes(struct fsnotify_sb_info *sbinfo);
/* Destroy all marks attached to an object via connector */
extern void fsnotify_destroy_marks(fsnotify_connp_t *connp);
/* run the list of all marks associated with inode and destroy them */
static inline void fsnotify_clear_marks_by_inode(struct inode *inode)
{
fsnotify_destroy_marks(&inode->i_fsnotify_marks);
}
/* run the list of all marks associated with vfsmount and destroy them */
static inline void fsnotify_clear_marks_by_mount(struct vfsmount *mnt)
{
fsnotify_destroy_marks(&real_mount(mnt)->mnt_fsnotify_marks);
}
/* run the list of all marks associated with sb and destroy them */
static inline void fsnotify_clear_marks_by_sb(struct super_block *sb)
{
fsnotify_destroy_marks(fsnotify_sb_marks(sb));
}
static inline void fsnotify_clear_marks_by_mntns(struct mnt_namespace *mntns)
{
fsnotify_destroy_marks(&mntns->n_fsnotify_marks);
}
/*
* update the dentry->d_flags of all of inode's children to indicate if inode cares
* about events that happen to its children.
*/
extern void fsnotify_set_children_dentry_flags(struct inode *inode);
void fsnotify_init_connector_caches(void);
#endif /* __FS_NOTIFY_FSNOTIFY_H_ */
+160
View File
@@ -0,0 +1,160 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2008 Red Hat, Inc., Eric Paris <eparis@redhat.com>
*/
#include <linux/list.h>
#include <linux/mutex.h>
#include <linux/slab.h>
#include <linux/srcu.h>
#include <linux/rculist.h>
#include <linux/wait.h>
#include <linux/memcontrol.h>
#include <linux/fsnotify_backend.h>
#include "fsnotify.h"
#include <linux/atomic.h>
/*
* Final freeing of a group
*/
static void fsnotify_final_destroy_group(struct fsnotify_group *group)
{
if (group->ops->free_group_priv)
group->ops->free_group_priv(group);
mem_cgroup_put(group->memcg);
mutex_destroy(&group->mark_mutex);
kfree(group);
}
/*
* Stop queueing new events for this group. Once this function returns
* fsnotify_add_event() will not add any new events to the group's queue.
*/
void fsnotify_group_stop_queueing(struct fsnotify_group *group)
{
spin_lock(&group->notification_lock);
group->shutdown = true;
spin_unlock(&group->notification_lock);
}
/*
* Trying to get rid of a group. Remove all marks, flush all events and release
* the group reference.
* Note that another thread calling fsnotify_clear_marks_by_group() may still
* hold a ref to the group.
*/
void fsnotify_destroy_group(struct fsnotify_group *group)
{
/*
* Stop queueing new events. The code below is careful enough to not
* require this but fanotify needs to stop queuing events even before
* fsnotify_destroy_group() is called and this makes the other callers
* of fsnotify_destroy_group() to see the same behavior.
*/
fsnotify_group_stop_queueing(group);
/* Clear all marks for this group and queue them for destruction */
fsnotify_clear_marks_by_group(group, FSNOTIFY_OBJ_TYPE_ANY);
/*
* Some marks can still be pinned when waiting for response from
* userspace. Wait for those now. fsnotify_prepare_user_wait() will
* not succeed now so this wait is race-free.
*/
wait_event(group->notification_waitq, !atomic_read(&group->user_waits));
/*
* Wait until all marks get really destroyed. We could actually destroy
* them ourselves instead of waiting for worker to do it, however that
* would be racy as worker can already be processing some marks before
* we even entered fsnotify_destroy_group().
*/
fsnotify_wait_marks_destroyed();
/*
* Since we have waited for fsnotify_mark_srcu in
* fsnotify_mark_destroy_list() there can be no outstanding event
* notification against this group. So clearing the notification queue
* of all events is reliable now.
*/
fsnotify_flush_notify(group);
/*
* Destroy overflow event (we cannot use fsnotify_destroy_event() as
* that deliberately ignores overflow events.
*/
if (group->overflow_event)
group->ops->free_event(group, group->overflow_event);
fsnotify_put_group(group);
}
/*
* Get reference to a group.
*/
void fsnotify_get_group(struct fsnotify_group *group)
{
refcount_inc(&group->refcnt);
}
/*
* Drop a reference to a group. Free it if it's through.
*/
void fsnotify_put_group(struct fsnotify_group *group)
{
if (refcount_dec_and_test(&group->refcnt))
fsnotify_final_destroy_group(group);
}
EXPORT_SYMBOL_GPL(fsnotify_put_group);
static struct fsnotify_group *__fsnotify_alloc_group(
const struct fsnotify_ops *ops,
int flags, gfp_t gfp)
{
struct fsnotify_group *group;
group = kzalloc_obj(struct fsnotify_group, gfp);
if (!group)
return ERR_PTR(-ENOMEM);
/* set to 0 when there a no external references to this group */
refcount_set(&group->refcnt, 1);
atomic_set(&group->user_waits, 0);
spin_lock_init(&group->notification_lock);
INIT_LIST_HEAD(&group->notification_list);
init_waitqueue_head(&group->notification_waitq);
group->max_events = UINT_MAX;
mutex_init(&group->mark_mutex);
INIT_LIST_HEAD(&group->marks_list);
group->ops = ops;
group->flags = flags;
return group;
}
/*
* Create a new fsnotify_group and hold a reference for the group returned.
*/
struct fsnotify_group *fsnotify_alloc_group(const struct fsnotify_ops *ops,
int flags)
{
gfp_t gfp = (flags & FSNOTIFY_GROUP_USER) ? GFP_KERNEL_ACCOUNT :
GFP_KERNEL;
return __fsnotify_alloc_group(ops, flags, gfp);
}
EXPORT_SYMBOL_GPL(fsnotify_alloc_group);
int fsnotify_fasync(int fd, struct file *file, int on)
{
struct fsnotify_group *group = file->private_data;
return fasync_helper(fd, file, on, &group->fsn_fa) >= 0 ? 0 : -EIO;
}
+17
View File
@@ -0,0 +1,17 @@
# SPDX-License-Identifier: GPL-2.0-only
config INOTIFY_USER
bool "Inotify support for userspace"
select FSNOTIFY
default y
help
Say Y here to enable inotify support for userspace, including the
associated system calls. Inotify allows monitoring of both files and
directories via a single open fd. Events are read from the file
descriptor, which is also select()- and poll()-able.
Inotify fixes numerous shortcomings in dnotify and introduces several
new features including multiple file events, one-shot support, and
unmount notification.
For more information, see <file:Documentation/filesystems/inotify.rst>
If unsure, say Y.
+2
View File
@@ -0,0 +1,2 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-$(CONFIG_INOTIFY_USER) += inotify_fsnotify.o inotify_user.o
+69
View File
@@ -0,0 +1,69 @@
/* SPDX-License-Identifier: GPL-2.0 */
#include <linux/fsnotify_backend.h>
#include <linux/inotify.h>
#include <linux/slab.h> /* struct kmem_cache */
struct inotify_event_info {
struct fsnotify_event fse;
u32 mask;
int wd;
u32 sync_cookie;
int name_len;
char name[];
};
struct inotify_inode_mark {
struct fsnotify_mark fsn_mark;
int wd;
};
static inline struct inotify_event_info *INOTIFY_E(struct fsnotify_event *fse)
{
return container_of(fse, struct inotify_event_info, fse);
}
/*
* INOTIFY_USER_FLAGS represents all of the mask bits that we expose to
* userspace. There is at least one bit (FS_EVENT_ON_CHILD) which is
* used only internally to the kernel.
*/
#define INOTIFY_USER_MASK (IN_ALL_EVENTS)
static inline __u32 inotify_mark_user_mask(struct fsnotify_mark *fsn_mark)
{
__u32 mask = fsn_mark->mask & INOTIFY_USER_MASK;
if (fsn_mark->flags & FSNOTIFY_MARK_FLAG_EXCL_UNLINK)
mask |= IN_EXCL_UNLINK;
if (fsn_mark->flags & FSNOTIFY_MARK_FLAG_IN_ONESHOT)
mask |= IN_ONESHOT;
return mask;
}
extern void inotify_ignored_and_remove_idr(struct fsnotify_mark *fsn_mark,
struct fsnotify_group *group);
extern int inotify_handle_inode_event(struct fsnotify_mark *inode_mark,
u32 mask, struct inode *inode,
struct inode *dir,
const struct qstr *name, u32 cookie);
extern const struct fsnotify_ops inotify_fsnotify_ops;
extern struct kmem_cache *inotify_inode_mark_cachep;
#ifdef CONFIG_INOTIFY_USER
static inline void dec_inotify_instances(struct ucounts *ucounts)
{
dec_ucount(ucounts, UCOUNT_INOTIFY_INSTANCES);
}
static inline struct ucounts *inc_inotify_watches(struct ucounts *ucounts)
{
return inc_ucount(ucounts->ns, ucounts->uid, UCOUNT_INOTIFY_WATCHES);
}
static inline void dec_inotify_watches(struct ucounts *ucounts)
{
dec_ucount(ucounts, UCOUNT_INOTIFY_WATCHES);
}
#endif
@@ -0,0 +1,209 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* fs/inotify_user.c - inotify support for userspace
*
* Authors:
* John McCutchan <ttb@tentacle.dhs.org>
* Robert Love <rml@novell.com>
*
* Copyright (C) 2005 John McCutchan
* Copyright 2006 Hewlett-Packard Development Company, L.P.
*
* Copyright (C) 2009 Eric Paris <Red Hat Inc>
* inotify was largely rewritten to make use of the fsnotify infrastructure
*/
#include <linux/dcache.h> /* d_unlinked */
#include <linux/fs.h> /* struct inode */
#include <linux/fsnotify_backend.h>
#include <linux/inotify.h>
#include <linux/path.h> /* struct path */
#include <linux/slab.h> /* kmem_* */
#include <linux/types.h>
#include <linux/sched.h>
#include <linux/sched/user.h>
#include <linux/sched/mm.h>
#include "inotify.h"
/*
* Check if 2 events contain the same information.
*/
static bool event_compare(struct fsnotify_event *old_fsn,
struct fsnotify_event *new_fsn)
{
struct inotify_event_info *old, *new;
old = INOTIFY_E(old_fsn);
new = INOTIFY_E(new_fsn);
if (old->mask & FS_IN_IGNORED)
return false;
if ((old->mask == new->mask) &&
(old->wd == new->wd) &&
(old->name_len == new->name_len) &&
(!old->name_len || !strcmp(old->name, new->name)))
return true;
return false;
}
static int inotify_merge(struct fsnotify_group *group,
struct fsnotify_event *event)
{
struct list_head *list = &group->notification_list;
struct fsnotify_event *last_event;
last_event = list_entry(list->prev, struct fsnotify_event, list);
return event_compare(last_event, event);
}
int inotify_handle_inode_event(struct fsnotify_mark *inode_mark, u32 mask,
struct inode *inode, struct inode *dir,
const struct qstr *name, u32 cookie)
{
struct inotify_inode_mark *i_mark;
struct inotify_event_info *event;
struct fsnotify_event *fsn_event;
struct fsnotify_group *group = inode_mark->group;
int ret;
int len = 0, wd;
int alloc_len = sizeof(struct inotify_event_info);
struct mem_cgroup *old_memcg;
if (name) {
len = name->len;
alloc_len += len + 1;
}
pr_debug("%s: group=%p mark=%p mask=%x\n", __func__, group, inode_mark,
mask);
i_mark = container_of(inode_mark, struct inotify_inode_mark,
fsn_mark);
/*
* We can be racing with mark being detached. Don't report event with
* invalid wd.
*/
wd = READ_ONCE(i_mark->wd);
if (wd == -1)
return 0;
/*
* Whoever is interested in the event, pays for the allocation. Do not
* trigger OOM killer in the target monitoring memcg as it may have
* security repercussion.
*/
old_memcg = set_active_memcg(group->memcg);
event = kmalloc(alloc_len, GFP_KERNEL_ACCOUNT | __GFP_RETRY_MAYFAIL);
set_active_memcg(old_memcg);
if (unlikely(!event)) {
/*
* Treat lost event due to ENOMEM the same way as queue
* overflow to let userspace know event was lost.
*/
fsnotify_queue_overflow(group);
return -ENOMEM;
}
/*
* We now report FS_ISDIR flag with MOVE_SELF and DELETE_SELF events
* for fanotify. inotify never reported IN_ISDIR with those events.
* It looks like an oversight, but to avoid the risk of breaking
* existing inotify programs, mask the flag out from those events.
*/
if (mask & (IN_MOVE_SELF | IN_DELETE_SELF))
mask &= ~IN_ISDIR;
fsn_event = &event->fse;
fsnotify_init_event(fsn_event);
event->mask = mask;
event->wd = wd;
event->sync_cookie = cookie;
event->name_len = len;
if (len)
strscpy(event->name, name->name, event->name_len + 1);
ret = fsnotify_add_event(group, fsn_event, inotify_merge);
if (ret) {
/* Our event wasn't used in the end. Free it. */
fsnotify_destroy_event(group, fsn_event);
}
if (inode_mark->flags & FSNOTIFY_MARK_FLAG_IN_ONESHOT)
fsnotify_destroy_mark(inode_mark, group);
return 0;
}
static void inotify_freeing_mark(struct fsnotify_mark *fsn_mark, struct fsnotify_group *group)
{
inotify_ignored_and_remove_idr(fsn_mark, group);
}
/*
* This is NEVER supposed to be called. Inotify marks should either have been
* removed from the idr when the watch was removed or in the
* fsnotify_destroy_mark_by_group() call when the inotify instance was being
* torn down. This is only called if the idr is about to be freed but there
* are still marks in it.
*/
static int idr_callback(int id, void *p, void *data)
{
struct fsnotify_mark *fsn_mark;
struct inotify_inode_mark *i_mark;
static bool warned = false;
if (warned)
return 0;
warned = true;
fsn_mark = p;
i_mark = container_of(fsn_mark, struct inotify_inode_mark, fsn_mark);
WARN(1, "inotify closing but id=%d for fsn_mark=%p in group=%p still in "
"idr. Probably leaking memory\n", id, p, data);
/*
* I'm taking the liberty of assuming that the mark in question is a
* valid address and I'm dereferencing it. This might help to figure
* out why we got here and the panic is no worse than the original
* BUG() that was here.
*/
if (fsn_mark)
printk(KERN_WARNING "fsn_mark->group=%p wd=%d\n",
fsn_mark->group, i_mark->wd);
return 0;
}
static void inotify_free_group_priv(struct fsnotify_group *group)
{
/* ideally the idr is empty and we won't hit the BUG in the callback */
idr_for_each(&group->inotify_data.idr, idr_callback, group);
idr_destroy(&group->inotify_data.idr);
if (group->inotify_data.ucounts)
dec_inotify_instances(group->inotify_data.ucounts);
}
static void inotify_free_event(struct fsnotify_group *group,
struct fsnotify_event *fsn_event)
{
kfree(INOTIFY_E(fsn_event));
}
/* ding dong the mark is dead */
static void inotify_free_mark(struct fsnotify_mark *fsn_mark)
{
struct inotify_inode_mark *i_mark;
i_mark = container_of(fsn_mark, struct inotify_inode_mark, fsn_mark);
kmem_cache_free(inotify_inode_mark_cachep, i_mark);
}
const struct fsnotify_ops inotify_fsnotify_ops = {
.handle_inode_event = inotify_handle_inode_event,
.free_group_priv = inotify_free_group_priv,
.free_event = inotify_free_event,
.freeing_mark = inotify_freeing_mark,
.free_mark = inotify_free_mark,
};
@@ -0,0 +1,861 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* fs/inotify_user.c - inotify support for userspace
*
* Authors:
* John McCutchan <ttb@tentacle.dhs.org>
* Robert Love <rml@novell.com>
*
* Copyright (C) 2005 John McCutchan
* Copyright 2006 Hewlett-Packard Development Company, L.P.
*
* Copyright (C) 2009 Eric Paris <Red Hat Inc>
* inotify was largely rewriten to make use of the fsnotify infrastructure
*/
#include <linux/file.h>
#include <linux/fs.h> /* struct inode */
#include <linux/fsnotify_backend.h>
#include <linux/idr.h>
#include <linux/init.h> /* fs_initcall */
#include <linux/inotify.h>
#include <linux/kernel.h> /* roundup() */
#include <linux/namei.h> /* LOOKUP_FOLLOW */
#include <linux/sched/signal.h>
#include <linux/slab.h> /* struct kmem_cache */
#include <linux/syscalls.h>
#include <linux/types.h>
#include <linux/anon_inodes.h>
#include <linux/uaccess.h>
#include <linux/poll.h>
#include <linux/wait.h>
#include <linux/memcontrol.h>
#include <linux/security.h>
#include "inotify.h"
#include "../fdinfo.h"
#include <asm/ioctls.h>
/*
* An inotify watch requires allocating an inotify_inode_mark structure as
* well as pinning the watched inode. Doubling the size of a VFS inode
* should be more than enough to cover the additional filesystem inode
* size increase.
*/
#define INOTIFY_WATCH_COST (sizeof(struct inotify_inode_mark) + \
2 * sizeof(struct inode))
/* configurable via /proc/sys/fs/inotify/ */
static int inotify_max_queued_events __read_mostly;
struct kmem_cache *inotify_inode_mark_cachep __ro_after_init;
#ifdef CONFIG_SYSCTL
#include <linux/sysctl.h>
static long it_zero = 0;
static long it_int_max = INT_MAX;
static const struct ctl_table inotify_table[] = {
{
.procname = "max_user_instances",
.data = &init_user_ns.ucount_max[UCOUNT_INOTIFY_INSTANCES],
.maxlen = sizeof(long),
.mode = 0644,
.proc_handler = proc_doulongvec_minmax,
.extra1 = &it_zero,
.extra2 = &it_int_max,
},
{
.procname = "max_user_watches",
.data = &init_user_ns.ucount_max[UCOUNT_INOTIFY_WATCHES],
.maxlen = sizeof(long),
.mode = 0644,
.proc_handler = proc_doulongvec_minmax,
.extra1 = &it_zero,
.extra2 = &it_int_max,
},
{
.procname = "max_queued_events",
.data = &inotify_max_queued_events,
.maxlen = sizeof(int),
.mode = 0644,
.proc_handler = proc_dointvec_minmax,
.extra1 = SYSCTL_ZERO
},
};
static void __init inotify_sysctls_init(void)
{
register_sysctl("fs/inotify", inotify_table);
}
#else
#define inotify_sysctls_init() do { } while (0)
#endif /* CONFIG_SYSCTL */
static inline __u32 inotify_arg_to_mask(struct inode *inode, u32 arg)
{
__u32 mask;
/*
* Everything should receive events when the inode is unmounted.
* All directories care about children.
*/
mask = (FS_UNMOUNT);
if (S_ISDIR(inode->i_mode))
mask |= FS_EVENT_ON_CHILD;
/* mask off the flags used to open the fd */
mask |= (arg & INOTIFY_USER_MASK);
return mask;
}
#define INOTIFY_MARK_FLAGS \
(FSNOTIFY_MARK_FLAG_EXCL_UNLINK | FSNOTIFY_MARK_FLAG_IN_ONESHOT)
static inline unsigned int inotify_arg_to_flags(u32 arg)
{
unsigned int flags = 0;
if (arg & IN_EXCL_UNLINK)
flags |= FSNOTIFY_MARK_FLAG_EXCL_UNLINK;
if (arg & IN_ONESHOT)
flags |= FSNOTIFY_MARK_FLAG_IN_ONESHOT;
return flags;
}
static inline u32 inotify_mask_to_arg(__u32 mask)
{
return mask & (IN_ALL_EVENTS | IN_ISDIR | IN_UNMOUNT | IN_IGNORED |
IN_Q_OVERFLOW);
}
/* inotify userspace file descriptor functions */
static __poll_t inotify_poll(struct file *file, poll_table *wait)
{
struct fsnotify_group *group = file->private_data;
__poll_t ret = 0;
poll_wait(file, &group->notification_waitq, wait);
spin_lock(&group->notification_lock);
if (!fsnotify_notify_queue_is_empty(group))
ret = EPOLLIN | EPOLLRDNORM;
spin_unlock(&group->notification_lock);
return ret;
}
static int round_event_name_len(struct fsnotify_event *fsn_event)
{
struct inotify_event_info *event;
event = INOTIFY_E(fsn_event);
if (!event->name_len)
return 0;
return roundup(event->name_len + 1, sizeof(struct inotify_event));
}
/*
* Get an inotify_kernel_event if one exists and is small
* enough to fit in "count". Return an error pointer if
* not large enough.
*
* Called with the group->notification_lock held.
*/
static struct fsnotify_event *get_one_event(struct fsnotify_group *group,
size_t count)
{
size_t event_size = sizeof(struct inotify_event);
struct fsnotify_event *event;
event = fsnotify_peek_first_event(group);
if (!event)
return NULL;
pr_debug("%s: group=%p event=%p\n", __func__, group, event);
event_size += round_event_name_len(event);
if (event_size > count)
return ERR_PTR(-EINVAL);
/* held the notification_lock the whole time, so this is the
* same event we peeked above */
fsnotify_remove_first_event(group);
return event;
}
/*
* Copy an event to user space, returning how much we copied.
*
* We already checked that the event size is smaller than the
* buffer we had in "get_one_event()" above.
*/
static ssize_t copy_event_to_user(struct fsnotify_group *group,
struct fsnotify_event *fsn_event,
char __user *buf)
{
struct inotify_event inotify_event;
struct inotify_event_info *event;
size_t event_size = sizeof(struct inotify_event);
size_t name_len;
size_t pad_name_len;
pr_debug("%s: group=%p event=%p\n", __func__, group, fsn_event);
event = INOTIFY_E(fsn_event);
name_len = event->name_len;
/*
* round up name length so it is a multiple of event_size
* plus an extra byte for the terminating '\0'.
*/
pad_name_len = round_event_name_len(fsn_event);
inotify_event.len = pad_name_len;
inotify_event.mask = inotify_mask_to_arg(event->mask);
inotify_event.wd = event->wd;
inotify_event.cookie = event->sync_cookie;
/* send the main event */
if (copy_to_user(buf, &inotify_event, event_size))
return -EFAULT;
buf += event_size;
/*
* fsnotify only stores the pathname, so here we have to send the pathname
* and then pad that pathname out to a multiple of sizeof(inotify_event)
* with zeros.
*/
if (pad_name_len) {
/* copy the path name */
if (copy_to_user(buf, event->name, name_len))
return -EFAULT;
buf += name_len;
/* fill userspace with 0's */
if (clear_user(buf, pad_name_len - name_len))
return -EFAULT;
event_size += pad_name_len;
}
return event_size;
}
static ssize_t inotify_read(struct file *file, char __user *buf,
size_t count, loff_t *pos)
{
struct fsnotify_group *group;
struct fsnotify_event *kevent;
char __user *start;
int ret;
DEFINE_WAIT_FUNC(wait, woken_wake_function);
start = buf;
group = file->private_data;
add_wait_queue(&group->notification_waitq, &wait);
while (1) {
spin_lock(&group->notification_lock);
kevent = get_one_event(group, count);
spin_unlock(&group->notification_lock);
pr_debug("%s: group=%p kevent=%p\n", __func__, group, kevent);
if (kevent) {
ret = PTR_ERR(kevent);
if (IS_ERR(kevent))
break;
ret = copy_event_to_user(group, kevent, buf);
fsnotify_destroy_event(group, kevent);
if (ret < 0)
break;
buf += ret;
count -= ret;
continue;
}
ret = -EAGAIN;
if (file->f_flags & O_NONBLOCK)
break;
ret = -ERESTARTSYS;
if (signal_pending(current))
break;
if (start != buf)
break;
wait_woken(&wait, TASK_INTERRUPTIBLE, MAX_SCHEDULE_TIMEOUT);
}
remove_wait_queue(&group->notification_waitq, &wait);
if (start != buf && ret != -EFAULT)
ret = buf - start;
return ret;
}
static int inotify_release(struct inode *ignored, struct file *file)
{
struct fsnotify_group *group = file->private_data;
pr_debug("%s: group=%p\n", __func__, group);
/* free this group, matching get was inotify_init->fsnotify_obtain_group */
fsnotify_destroy_group(group);
return 0;
}
static long inotify_ioctl(struct file *file, unsigned int cmd,
unsigned long arg)
{
struct fsnotify_group *group;
struct fsnotify_event *fsn_event;
void __user *p;
int ret = -ENOTTY;
size_t send_len = 0;
group = file->private_data;
p = (void __user *) arg;
pr_debug("%s: group=%p cmd=%u\n", __func__, group, cmd);
switch (cmd) {
case FIONREAD:
spin_lock(&group->notification_lock);
list_for_each_entry(fsn_event, &group->notification_list,
list) {
send_len += sizeof(struct inotify_event);
send_len += round_event_name_len(fsn_event);
}
spin_unlock(&group->notification_lock);
ret = put_user(send_len, (int __user *) p);
break;
#ifdef CONFIG_CHECKPOINT_RESTORE
case INOTIFY_IOC_SETNEXTWD:
ret = -EINVAL;
if (arg >= 1 && arg <= INT_MAX) {
struct inotify_group_private_data *data;
data = &group->inotify_data;
spin_lock(&data->idr_lock);
idr_set_cursor(&data->idr, (unsigned int)arg);
spin_unlock(&data->idr_lock);
ret = 0;
}
break;
#endif /* CONFIG_CHECKPOINT_RESTORE */
}
return ret;
}
static const struct file_operations inotify_fops = {
.show_fdinfo = inotify_show_fdinfo,
.poll = inotify_poll,
.read = inotify_read,
.fasync = fsnotify_fasync,
.release = inotify_release,
.unlocked_ioctl = inotify_ioctl,
.compat_ioctl = inotify_ioctl,
.llseek = noop_llseek,
};
/*
* find_inode - resolve a user-given path to a specific inode
*/
static int inotify_find_inode(const char __user *dirname, struct path *path,
unsigned int flags, __u64 mask)
{
int error;
error = user_path_at(AT_FDCWD, dirname, flags, path);
if (error)
return error;
/* you can only watch an inode if you have read permissions on it */
error = path_permission(path, MAY_READ);
if (error) {
path_put(path);
return error;
}
error = security_path_notify(path, mask,
FSNOTIFY_OBJ_TYPE_INODE);
if (error)
path_put(path);
return error;
}
static int inotify_add_to_idr(struct idr *idr, spinlock_t *idr_lock,
struct inotify_inode_mark *i_mark)
{
int ret;
idr_preload(GFP_KERNEL);
spin_lock(idr_lock);
ret = idr_alloc_cyclic(idr, i_mark, 1, 0, GFP_NOWAIT);
if (ret >= 0) {
/* we added the mark to the idr, take a reference */
i_mark->wd = ret;
fsnotify_get_mark(&i_mark->fsn_mark);
}
spin_unlock(idr_lock);
idr_preload_end();
return ret < 0 ? ret : 0;
}
static struct inotify_inode_mark *inotify_idr_find_locked(struct fsnotify_group *group,
int wd)
{
struct idr *idr = &group->inotify_data.idr;
spinlock_t *idr_lock = &group->inotify_data.idr_lock;
struct inotify_inode_mark *i_mark;
assert_spin_locked(idr_lock);
i_mark = idr_find(idr, wd);
if (i_mark) {
struct fsnotify_mark *fsn_mark = &i_mark->fsn_mark;
fsnotify_get_mark(fsn_mark);
/* One ref for being in the idr, one ref we just took */
BUG_ON(refcount_read(&fsn_mark->refcnt) < 2);
}
return i_mark;
}
static struct inotify_inode_mark *inotify_idr_find(struct fsnotify_group *group,
int wd)
{
struct inotify_inode_mark *i_mark;
spinlock_t *idr_lock = &group->inotify_data.idr_lock;
spin_lock(idr_lock);
i_mark = inotify_idr_find_locked(group, wd);
spin_unlock(idr_lock);
return i_mark;
}
/*
* Remove the mark from the idr (if present) and drop the reference
* on the mark because it was in the idr.
*/
static void inotify_remove_from_idr(struct fsnotify_group *group,
struct inotify_inode_mark *i_mark)
{
struct idr *idr = &group->inotify_data.idr;
spinlock_t *idr_lock = &group->inotify_data.idr_lock;
struct inotify_inode_mark *found_i_mark = NULL;
int wd;
spin_lock(idr_lock);
wd = i_mark->wd;
/*
* does this i_mark think it is in the idr? we shouldn't get called
* if it wasn't....
*/
if (wd == -1) {
WARN_ONCE(1, "%s: i_mark=%p i_mark->wd=%d i_mark->group=%p\n",
__func__, i_mark, i_mark->wd, i_mark->fsn_mark.group);
goto out;
}
/* Lets look in the idr to see if we find it */
found_i_mark = inotify_idr_find_locked(group, wd);
if (unlikely(!found_i_mark)) {
WARN_ONCE(1, "%s: i_mark=%p i_mark->wd=%d i_mark->group=%p\n",
__func__, i_mark, i_mark->wd, i_mark->fsn_mark.group);
goto out;
}
/*
* We found an mark in the idr at the right wd, but it's
* not the mark we were told to remove. eparis seriously
* fucked up somewhere.
*/
if (unlikely(found_i_mark != i_mark)) {
WARN_ONCE(1, "%s: i_mark=%p i_mark->wd=%d i_mark->group=%p "
"found_i_mark=%p found_i_mark->wd=%d "
"found_i_mark->group=%p\n", __func__, i_mark,
i_mark->wd, i_mark->fsn_mark.group, found_i_mark,
found_i_mark->wd, found_i_mark->fsn_mark.group);
goto out;
}
/*
* One ref for being in the idr
* one ref grabbed by inotify_idr_find
*/
if (unlikely(refcount_read(&i_mark->fsn_mark.refcnt) < 2)) {
printk(KERN_ERR "%s: i_mark=%p i_mark->wd=%d i_mark->group=%p\n",
__func__, i_mark, i_mark->wd, i_mark->fsn_mark.group);
/* we can't really recover with bad ref cnting.. */
BUG();
}
idr_remove(idr, wd);
/* Removed from the idr, drop that ref. */
fsnotify_put_mark(&i_mark->fsn_mark);
out:
i_mark->wd = -1;
spin_unlock(idr_lock);
/* match the ref taken by inotify_idr_find_locked() */
if (found_i_mark)
fsnotify_put_mark(&found_i_mark->fsn_mark);
}
/*
* Send IN_IGNORED for this wd, remove this wd from the idr.
*/
void inotify_ignored_and_remove_idr(struct fsnotify_mark *fsn_mark,
struct fsnotify_group *group)
{
struct inotify_inode_mark *i_mark;
/* Queue ignore event for the watch */
inotify_handle_inode_event(fsn_mark, FS_IN_IGNORED, NULL, NULL, NULL,
0);
i_mark = container_of(fsn_mark, struct inotify_inode_mark, fsn_mark);
/* remove this mark from the idr */
inotify_remove_from_idr(group, i_mark);
dec_inotify_watches(group->inotify_data.ucounts);
}
static int inotify_update_existing_watch(struct fsnotify_group *group,
struct inode *inode,
u32 arg)
{
struct fsnotify_mark *fsn_mark;
struct inotify_inode_mark *i_mark;
__u32 old_mask, new_mask;
int replace = !(arg & IN_MASK_ADD);
int create = (arg & IN_MASK_CREATE);
int ret;
fsn_mark = fsnotify_find_inode_mark(inode, group);
if (!fsn_mark)
return -ENOENT;
else if (create) {
ret = -EEXIST;
goto out;
}
i_mark = container_of(fsn_mark, struct inotify_inode_mark, fsn_mark);
spin_lock(&fsn_mark->lock);
old_mask = fsn_mark->mask;
if (replace) {
fsn_mark->mask = 0;
fsn_mark->flags &= ~INOTIFY_MARK_FLAGS;
}
fsn_mark->mask |= inotify_arg_to_mask(inode, arg);
fsn_mark->flags |= inotify_arg_to_flags(arg);
new_mask = fsn_mark->mask;
spin_unlock(&fsn_mark->lock);
if (old_mask != new_mask) {
/* more bits in old than in new? */
int dropped = (old_mask & ~new_mask);
/* more bits in this fsn_mark than the inode's mask? */
int do_inode = (new_mask & ~READ_ONCE(inode->i_fsnotify_mask));
/* update the inode with this new fsn_mark */
if (dropped || do_inode)
fsnotify_recalc_mask(fsn_mark->connector);
}
/* return the wd */
ret = i_mark->wd;
out:
/* match the get from fsnotify_find_mark() */
fsnotify_put_mark(fsn_mark);
return ret;
}
static int inotify_new_watch(struct fsnotify_group *group,
struct inode *inode,
u32 arg)
{
struct inotify_inode_mark *tmp_i_mark;
int ret;
struct idr *idr = &group->inotify_data.idr;
spinlock_t *idr_lock = &group->inotify_data.idr_lock;
tmp_i_mark = kmem_cache_alloc(inotify_inode_mark_cachep, GFP_KERNEL);
if (unlikely(!tmp_i_mark))
return -ENOMEM;
fsnotify_init_mark(&tmp_i_mark->fsn_mark, group);
tmp_i_mark->fsn_mark.mask = inotify_arg_to_mask(inode, arg);
tmp_i_mark->fsn_mark.flags = inotify_arg_to_flags(arg);
tmp_i_mark->wd = -1;
ret = inotify_add_to_idr(idr, idr_lock, tmp_i_mark);
if (ret)
goto out_err;
/* increment the number of watches the user has */
if (!inc_inotify_watches(group->inotify_data.ucounts)) {
inotify_remove_from_idr(group, tmp_i_mark);
ret = -ENOSPC;
goto out_err;
}
/* we are on the idr, now get on the inode */
ret = fsnotify_add_inode_mark_locked(&tmp_i_mark->fsn_mark, inode, 0);
if (ret) {
/* we failed to get on the inode, get off the idr */
inotify_remove_from_idr(group, tmp_i_mark);
dec_inotify_watches(group->inotify_data.ucounts);
goto out_err;
}
/* return the watch descriptor for this new mark */
ret = tmp_i_mark->wd;
out_err:
/* match the ref from fsnotify_init_mark() */
fsnotify_put_mark(&tmp_i_mark->fsn_mark);
return ret;
}
static int inotify_update_watch(struct fsnotify_group *group, struct inode *inode, u32 arg)
{
int ret = 0;
fsnotify_group_lock(group);
/* try to update and existing watch with the new arg */
ret = inotify_update_existing_watch(group, inode, arg);
/* no mark present, try to add a new one */
if (ret == -ENOENT)
ret = inotify_new_watch(group, inode, arg);
fsnotify_group_unlock(group);
return ret;
}
static struct fsnotify_group *inotify_new_group(unsigned int max_events)
{
struct fsnotify_group *group;
struct inotify_event_info *oevent;
group = fsnotify_alloc_group(&inotify_fsnotify_ops,
FSNOTIFY_GROUP_USER);
if (IS_ERR(group))
return group;
oevent = kmalloc_obj(struct inotify_event_info, GFP_KERNEL_ACCOUNT);
if (unlikely(!oevent)) {
fsnotify_destroy_group(group);
return ERR_PTR(-ENOMEM);
}
group->overflow_event = &oevent->fse;
fsnotify_init_event(group->overflow_event);
oevent->mask = FS_Q_OVERFLOW;
oevent->wd = -1;
oevent->sync_cookie = 0;
oevent->name_len = 0;
group->max_events = max_events;
group->memcg = get_mem_cgroup_from_mm(current->mm);
spin_lock_init(&group->inotify_data.idr_lock);
idr_init(&group->inotify_data.idr);
group->inotify_data.ucounts = inc_ucount(current_user_ns(),
current_euid(),
UCOUNT_INOTIFY_INSTANCES);
if (!group->inotify_data.ucounts) {
fsnotify_destroy_group(group);
return ERR_PTR(-EMFILE);
}
return group;
}
/* inotify syscalls */
static int do_inotify_init(int flags)
{
struct fsnotify_group *group;
int ret;
/* Check the IN_* constants for consistency. */
BUILD_BUG_ON(IN_CLOEXEC != O_CLOEXEC);
BUILD_BUG_ON(IN_NONBLOCK != O_NONBLOCK);
if (flags & ~(IN_CLOEXEC | IN_NONBLOCK))
return -EINVAL;
/* fsnotify_obtain_group took a reference to group, we put this when we kill the file in the end */
group = inotify_new_group(inotify_max_queued_events);
if (IS_ERR(group))
return PTR_ERR(group);
ret = anon_inode_getfd("inotify", &inotify_fops, group,
O_RDONLY | flags);
if (ret < 0)
fsnotify_destroy_group(group);
return ret;
}
SYSCALL_DEFINE1(inotify_init1, int, flags)
{
return do_inotify_init(flags);
}
SYSCALL_DEFINE0(inotify_init)
{
return do_inotify_init(0);
}
SYSCALL_DEFINE3(inotify_add_watch, int, fd, const char __user *, pathname,
u32, mask)
{
struct fsnotify_group *group;
struct inode *inode;
struct path path;
int ret;
unsigned flags = 0;
/*
* We share a lot of code with fs/dnotify. We also share
* the bit layout between inotify's IN_* and the fsnotify
* FS_*. This check ensures that only the inotify IN_*
* bits get passed in and set in watches/events.
*/
if (unlikely(mask & ~ALL_INOTIFY_BITS))
return -EINVAL;
/*
* Require at least one valid bit set in the mask.
* Without _something_ set, we would have no events to
* watch for.
*/
if (unlikely(!(mask & ALL_INOTIFY_BITS)))
return -EINVAL;
CLASS(fd, f)(fd);
if (fd_empty(f))
return -EBADF;
/* IN_MASK_ADD and IN_MASK_CREATE don't make sense together */
if (unlikely((mask & IN_MASK_ADD) && (mask & IN_MASK_CREATE)))
return -EINVAL;
/* verify that this is indeed an inotify instance */
if (unlikely(fd_file(f)->f_op != &inotify_fops))
return -EINVAL;
if (!(mask & IN_DONT_FOLLOW))
flags |= LOOKUP_FOLLOW;
if (mask & IN_ONLYDIR)
flags |= LOOKUP_DIRECTORY;
ret = inotify_find_inode(pathname, &path, flags,
(mask & IN_ALL_EVENTS));
if (ret)
return ret;
/* inode held in place by reference to path; group by fget on fd */
inode = path.dentry->d_inode;
group = fd_file(f)->private_data;
/* create/update an inode mark */
ret = inotify_update_watch(group, inode, mask);
path_put(&path);
return ret;
}
SYSCALL_DEFINE2(inotify_rm_watch, int, fd, __s32, wd)
{
struct fsnotify_group *group;
struct inotify_inode_mark *i_mark;
CLASS(fd, f)(fd);
if (fd_empty(f))
return -EBADF;
/* verify that this is indeed an inotify instance */
if (unlikely(fd_file(f)->f_op != &inotify_fops))
return -EINVAL;
group = fd_file(f)->private_data;
i_mark = inotify_idr_find(group, wd);
if (unlikely(!i_mark))
return -EINVAL;
fsnotify_destroy_mark(&i_mark->fsn_mark, group);
/* match ref taken by inotify_idr_find */
fsnotify_put_mark(&i_mark->fsn_mark);
return 0;
}
/*
* inotify_user_setup - Our initialization function. Note that we cannot return
* error because we have compiled-in VFS hooks. So an (unlikely) failure here
* must result in panic().
*/
static int __init inotify_user_setup(void)
{
unsigned long watches_max;
struct sysinfo si;
si_meminfo(&si);
/*
* Allow up to 1% of addressable memory to be allocated for inotify
* watches (per user) limited to the range [8192, 1048576].
*/
watches_max = (((si.totalram - si.totalhigh) / 100) << PAGE_SHIFT) /
INOTIFY_WATCH_COST;
watches_max = clamp(watches_max, 8192UL, 1048576UL);
BUILD_BUG_ON(IN_ACCESS != FS_ACCESS);
BUILD_BUG_ON(IN_MODIFY != FS_MODIFY);
BUILD_BUG_ON(IN_ATTRIB != FS_ATTRIB);
BUILD_BUG_ON(IN_CLOSE_WRITE != FS_CLOSE_WRITE);
BUILD_BUG_ON(IN_CLOSE_NOWRITE != FS_CLOSE_NOWRITE);
BUILD_BUG_ON(IN_OPEN != FS_OPEN);
BUILD_BUG_ON(IN_MOVED_FROM != FS_MOVED_FROM);
BUILD_BUG_ON(IN_MOVED_TO != FS_MOVED_TO);
BUILD_BUG_ON(IN_CREATE != FS_CREATE);
BUILD_BUG_ON(IN_DELETE != FS_DELETE);
BUILD_BUG_ON(IN_DELETE_SELF != FS_DELETE_SELF);
BUILD_BUG_ON(IN_MOVE_SELF != FS_MOVE_SELF);
BUILD_BUG_ON(IN_UNMOUNT != FS_UNMOUNT);
BUILD_BUG_ON(IN_Q_OVERFLOW != FS_Q_OVERFLOW);
BUILD_BUG_ON(IN_IGNORED != FS_IN_IGNORED);
BUILD_BUG_ON(IN_ISDIR != FS_ISDIR);
BUILD_BUG_ON(HWEIGHT32(ALL_INOTIFY_BITS) != 22);
inotify_inode_mark_cachep = KMEM_CACHE(inotify_inode_mark,
SLAB_PANIC|SLAB_ACCOUNT);
inotify_max_queued_events = 16384;
init_user_ns.ucount_max[UCOUNT_INOTIFY_INSTANCES] = 128;
init_user_ns.ucount_max[UCOUNT_INOTIFY_WATCHES] = watches_max;
inotify_sysctls_init();
return 0;
}
fs_initcall(inotify_user_setup);
File diff suppressed because it is too large Load Diff
+193
View File
@@ -0,0 +1,193 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2008 Red Hat, Inc., Eric Paris <eparis@redhat.com>
*/
/*
* Basic idea behind the notification queue: An fsnotify group (like inotify)
* sends the userspace notification about events asynchronously some time after
* the event happened. When inotify gets an event it will need to add that
* event to the group notify queue. Since a single event might need to be on
* multiple group's notification queues we can't add the event directly to each
* queue and instead add a small "event_holder" to each queue. This event_holder
* has a pointer back to the original event. Since the majority of events are
* going to end up on one, and only one, notification queue we embed one
* event_holder into each event. This means we have a single allocation instead
* of always needing two. If the embedded event_holder is already in use by
* another group a new event_holder (from fsnotify_event_holder_cachep) will be
* allocated and used.
*/
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/mount.h>
#include <linux/mutex.h>
#include <linux/namei.h>
#include <linux/path.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/atomic.h>
#include <linux/fsnotify_backend.h>
#include "fsnotify.h"
static atomic_t fsnotify_sync_cookie = ATOMIC_INIT(0);
/**
* fsnotify_get_cookie - return a unique cookie for use in synchronizing events.
* Called from fsnotify_move, which is inlined into filesystem modules.
*/
u32 fsnotify_get_cookie(void)
{
return atomic_inc_return(&fsnotify_sync_cookie);
}
EXPORT_SYMBOL_GPL(fsnotify_get_cookie);
void fsnotify_destroy_event(struct fsnotify_group *group,
struct fsnotify_event *event)
{
/* Overflow events are per-group and we don't want to free them */
if (!event || event == group->overflow_event)
return;
/*
* If the event is still queued, we have a problem... Do an unreliable
* lockless check first to avoid locking in the common case. The
* locking may be necessary for permission events which got removed
* from the list by a different CPU than the one freeing the event.
*/
if (!list_empty(&event->list)) {
spin_lock(&group->notification_lock);
WARN_ON(!list_empty(&event->list));
spin_unlock(&group->notification_lock);
}
group->ops->free_event(group, event);
}
/*
* Try to add an event to the notification queue.
* The group can later pull this event off the queue to deal with.
* The group can use the @merge hook to merge the event with a queued event.
* The group can use the @insert hook to insert the event into hash table.
* The function returns:
* 0 if the event was added to a queue
* 1 if the event was merged with some other queued event
* 2 if the event was not queued - either the queue of events has overflown
* or the group is shutting down.
*/
int fsnotify_insert_event(struct fsnotify_group *group,
struct fsnotify_event *event,
int (*merge)(struct fsnotify_group *,
struct fsnotify_event *),
void (*insert)(struct fsnotify_group *,
struct fsnotify_event *))
{
int ret = 0;
struct list_head *list = &group->notification_list;
pr_debug("%s: group=%p event=%p\n", __func__, group, event);
spin_lock(&group->notification_lock);
if (group->shutdown) {
spin_unlock(&group->notification_lock);
return 2;
}
if (event == group->overflow_event ||
group->q_len >= group->max_events) {
ret = 2;
/* Queue overflow event only if it isn't already queued */
if (!list_empty(&group->overflow_event->list)) {
spin_unlock(&group->notification_lock);
return ret;
}
event = group->overflow_event;
goto queue;
}
if (!list_empty(list) && merge) {
ret = merge(group, event);
if (ret) {
spin_unlock(&group->notification_lock);
return ret;
}
}
queue:
group->q_len++;
list_add_tail(&event->list, list);
if (insert)
insert(group, event);
spin_unlock(&group->notification_lock);
wake_up(&group->notification_waitq);
kill_fasync(&group->fsn_fa, SIGIO, POLL_IN);
return ret;
}
void fsnotify_remove_queued_event(struct fsnotify_group *group,
struct fsnotify_event *event)
{
assert_spin_locked(&group->notification_lock);
/*
* We need to init list head for the case of overflow event so that
* check in fsnotify_add_event() works
*/
list_del_init(&event->list);
group->q_len--;
}
/*
* Return the first event on the notification list without removing it.
* Returns NULL if the list is empty.
*/
struct fsnotify_event *fsnotify_peek_first_event(struct fsnotify_group *group)
{
assert_spin_locked(&group->notification_lock);
if (fsnotify_notify_queue_is_empty(group))
return NULL;
return list_first_entry(&group->notification_list,
struct fsnotify_event, list);
}
/*
* Remove and return the first event from the notification list. It is the
* responsibility of the caller to destroy the obtained event
*/
struct fsnotify_event *fsnotify_remove_first_event(struct fsnotify_group *group)
{
struct fsnotify_event *event = fsnotify_peek_first_event(group);
if (!event)
return NULL;
pr_debug("%s: group=%p event=%p\n", __func__, group, event);
fsnotify_remove_queued_event(group, event);
return event;
}
/*
* Called when a group is being torn down to clean up any outstanding
* event notifications.
*/
void fsnotify_flush_notify(struct fsnotify_group *group)
{
struct fsnotify_event *event;
spin_lock(&group->notification_lock);
while (!fsnotify_notify_queue_is_empty(group)) {
event = fsnotify_remove_first_event(group);
spin_unlock(&group->notification_lock);
fsnotify_destroy_event(group, event);
spin_lock(&group->notification_lock);
}
spin_unlock(&group->notification_lock);
}