mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2024-12-29 17:23:36 +00:00
\n
-----BEGIN PGP SIGNATURE----- iQEzBAABCAAdFiEEq1nRK9aeMoq1VSgcnJ2qBz9kQNkFAmGFN6IACgkQnJ2qBz9k QNkfYwgA1w5x/CsN2IMZdx6FTuZFgbOvQpBMTry8iuOPKK3UyIkZaUirTVLKR0cm k3QbBR9/vTfQTNg5weuFJcbPZZaCXKEvlPGvDh+pumMbfTkMwL3FADweNBoZ3PzO EiRrV45AbRgSMOzsfURzCz1T53Gd8fYM3pXxmNXG+bnE7+Ea+heKgor8/jFc4U3w kAKZTfyCiheo7KxVhFGnkGI3ZhIbnbZne4seY/CE4qtv7/bmBE7bhGpmv8LT5FUn h/JBDLjFU0fzJpplXE6n/VHXeGaUwb8adnYpzojWQ0lLYFrMIZFQ0KkDK6PNwmJF MKWGqRxDkf54oeWuEAJ9t4/OorqM9A== =ltE7 -----END PGP SIGNATURE----- Merge tag 'fsnotify_for_v5.16-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/jack/linux-fs Pull fsnotify updates from Jan Kara: "Support for reporting filesystem errors through fanotify so that system health monitoring daemons can watch for these and act instead of scraping system logs" * tag 'fsnotify_for_v5.16-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/jack/linux-fs: (34 commits) samples: remove duplicate include in fs-monitor.c samples: Fix warning in fsnotify sample docs: Fix formatting of literal sections in fanotify docs samples: Make fs-monitor depend on libc and headers docs: Document the FAN_FS_ERROR event samples: Add fs error monitoring example ext4: Send notifications on error fanotify: Allow users to request FAN_FS_ERROR events fanotify: Emit generic error info for error event fanotify: Report fid info for file related file system errors fanotify: WARN_ON against too large file handles fanotify: Add helpers to decide whether to report FID/DFID fanotify: Wrap object_fh inline space in a creator macro fanotify: Support merging of error events fanotify: Support enqueueing of error events fanotify: Pre-allocate pool of error events fanotify: Reserve UAPI bits for FAN_FS_ERROR fsnotify: Support FS_ERROR event type fanotify: Require fid_mode for any non-fd event fanotify: Encode empty file handle when no inode is provided ...
This commit is contained in:
commit
2acda7549e
78
Documentation/admin-guide/filesystem-monitoring.rst
Normal file
78
Documentation/admin-guide/filesystem-monitoring.rst
Normal file
@ -0,0 +1,78 @@
|
||||
.. SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
====================================
|
||||
File system Monitoring with fanotify
|
||||
====================================
|
||||
|
||||
File system Error Reporting
|
||||
===========================
|
||||
|
||||
Fanotify supports the FAN_FS_ERROR event type for file system-wide error
|
||||
reporting. It is meant to be used by file system health monitoring
|
||||
daemons, which listen for these events and take actions (notify
|
||||
sysadmin, start recovery) when a file system problem is detected.
|
||||
|
||||
By design, a FAN_FS_ERROR notification exposes sufficient information
|
||||
for a monitoring tool to know a problem in the file system has happened.
|
||||
It doesn't necessarily provide a user space application with semantics
|
||||
to verify an IO operation was successfully executed. That is out of
|
||||
scope for this feature. Instead, it is only meant as a framework for
|
||||
early file system problem detection and reporting recovery tools.
|
||||
|
||||
When a file system operation fails, it is common for dozens of kernel
|
||||
errors to cascade after the initial failure, hiding the original failure
|
||||
log, which is usually the most useful debug data to troubleshoot the
|
||||
problem. For this reason, FAN_FS_ERROR tries to report only the first
|
||||
error that occurred for a file system since the last notification, and
|
||||
it simply counts additional errors. This ensures that the most
|
||||
important pieces of information are never lost.
|
||||
|
||||
FAN_FS_ERROR requires the fanotify group to be setup with the
|
||||
FAN_REPORT_FID flag.
|
||||
|
||||
At the time of this writing, the only file system that emits FAN_FS_ERROR
|
||||
notifications is Ext4.
|
||||
|
||||
A FAN_FS_ERROR Notification has the following format::
|
||||
|
||||
::
|
||||
|
||||
[ Notification Metadata (Mandatory) ]
|
||||
[ Generic Error Record (Mandatory) ]
|
||||
[ FID record (Mandatory) ]
|
||||
|
||||
The order of records is not guaranteed, and new records might be added
|
||||
in the future. Therefore, applications must not rely on the order and
|
||||
must be prepared to skip over unknown records. Please refer to
|
||||
``samples/fanotify/fs-monitor.c`` for an example parser.
|
||||
|
||||
Generic error record
|
||||
--------------------
|
||||
|
||||
The generic error record provides enough information for a file system
|
||||
agnostic tool to learn about a problem in the file system, without
|
||||
providing any additional details about the problem. This record is
|
||||
identified by ``struct fanotify_event_info_header.info_type`` being set
|
||||
to FAN_EVENT_INFO_TYPE_ERROR.
|
||||
|
||||
::
|
||||
|
||||
struct fanotify_event_info_error {
|
||||
struct fanotify_event_info_header hdr;
|
||||
__s32 error;
|
||||
__u32 error_count;
|
||||
};
|
||||
|
||||
The `error` field identifies the type of error using errno values.
|
||||
`error_count` tracks the number of errors that occurred and were
|
||||
suppressed to preserve the original error information, since the last
|
||||
notification.
|
||||
|
||||
FID record
|
||||
----------
|
||||
|
||||
The FID record can be used to uniquely identify the inode that triggered
|
||||
the error through the combination of fsid and file handle. A file system
|
||||
specific application can use that information to attempt a recovery
|
||||
procedure. Errors that are not related to an inode are reported with an
|
||||
empty file handle of type FILEID_INVALID.
|
@ -82,6 +82,7 @@ configure specific aspects of kernel behavior to your liking.
|
||||
edid
|
||||
efi-stub
|
||||
ext4
|
||||
filesystem-monitoring
|
||||
nfs/index
|
||||
gpio/index
|
||||
highuid
|
||||
|
@ -46,6 +46,7 @@
|
||||
#include <linux/part_stat.h>
|
||||
#include <linux/kthread.h>
|
||||
#include <linux/freezer.h>
|
||||
#include <linux/fsnotify.h>
|
||||
|
||||
#include "ext4.h"
|
||||
#include "ext4_extents.h" /* Needed for trace points definition */
|
||||
@ -759,6 +760,8 @@ void __ext4_error(struct super_block *sb, const char *function,
|
||||
sb->s_id, function, line, current->comm, &vaf);
|
||||
va_end(args);
|
||||
}
|
||||
fsnotify_sb_error(sb, NULL, error ? error : EFSCORRUPTED);
|
||||
|
||||
ext4_handle_error(sb, force_ro, error, 0, block, function, line);
|
||||
}
|
||||
|
||||
@ -789,6 +792,8 @@ void __ext4_error_inode(struct inode *inode, const char *function,
|
||||
current->comm, &vaf);
|
||||
va_end(args);
|
||||
}
|
||||
fsnotify_sb_error(inode->i_sb, inode, error ? error : EFSCORRUPTED);
|
||||
|
||||
ext4_handle_error(inode->i_sb, false, error, inode->i_ino, block,
|
||||
function, line);
|
||||
}
|
||||
@ -827,6 +832,8 @@ void __ext4_error_file(struct file *file, const char *function,
|
||||
current->comm, path, &vaf);
|
||||
va_end(args);
|
||||
}
|
||||
fsnotify_sb_error(inode->i_sb, inode, EFSCORRUPTED);
|
||||
|
||||
ext4_handle_error(inode->i_sb, false, EFSCORRUPTED, inode->i_ino, block,
|
||||
function, line);
|
||||
}
|
||||
@ -894,6 +901,7 @@ void __ext4_std_error(struct super_block *sb, const char *function,
|
||||
printk(KERN_CRIT "EXT4-fs error (device %s) in %s:%d: %s\n",
|
||||
sb->s_id, function, line, errstr);
|
||||
}
|
||||
fsnotify_sb_error(sb, NULL, errno ? errno : EFSCORRUPTED);
|
||||
|
||||
ext4_handle_error(sb, false, -errno, 0, 0, function, line);
|
||||
}
|
||||
|
@ -602,6 +602,9 @@ nfsd_file_fsnotify_handle_event(struct fsnotify_mark *mark, u32 mask,
|
||||
struct inode *inode, struct inode *dir,
|
||||
const struct qstr *name, u32 cookie)
|
||||
{
|
||||
if (WARN_ON_ONCE(!inode))
|
||||
return 0;
|
||||
|
||||
trace_nfsd_file_fsnotify_handle_event(inode, mask);
|
||||
|
||||
/* Should be no marks on non-regular files */
|
||||
|
@ -111,6 +111,16 @@ static bool fanotify_name_event_equal(struct fanotify_name_event *fne1,
|
||||
return fanotify_info_equal(info1, info2);
|
||||
}
|
||||
|
||||
static bool fanotify_error_event_equal(struct fanotify_error_event *fee1,
|
||||
struct fanotify_error_event *fee2)
|
||||
{
|
||||
/* Error events against the same file system are always merged. */
|
||||
if (!fanotify_fsid_equal(&fee1->fsid, &fee2->fsid))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool fanotify_should_merge(struct fanotify_event *old,
|
||||
struct fanotify_event *new)
|
||||
{
|
||||
@ -141,6 +151,9 @@ static bool fanotify_should_merge(struct fanotify_event *old,
|
||||
case FANOTIFY_EVENT_TYPE_FID_NAME:
|
||||
return fanotify_name_event_equal(FANOTIFY_NE(old),
|
||||
FANOTIFY_NE(new));
|
||||
case FANOTIFY_EVENT_TYPE_FS_ERROR:
|
||||
return fanotify_error_event_equal(FANOTIFY_EE(old),
|
||||
FANOTIFY_EE(new));
|
||||
default:
|
||||
WARN_ON_ONCE(1);
|
||||
}
|
||||
@ -176,6 +189,10 @@ static int fanotify_merge(struct fsnotify_group *group,
|
||||
break;
|
||||
if (fanotify_should_merge(old, new)) {
|
||||
old->mask |= new->mask;
|
||||
|
||||
if (fanotify_is_error_event(old->mask))
|
||||
FANOTIFY_EE(old)->err_count++;
|
||||
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@ -343,13 +360,23 @@ static u32 fanotify_group_event_mask(struct fsnotify_group *group,
|
||||
static int fanotify_encode_fh_len(struct inode *inode)
|
||||
{
|
||||
int dwords = 0;
|
||||
int fh_len;
|
||||
|
||||
if (!inode)
|
||||
return 0;
|
||||
|
||||
exportfs_encode_inode_fh(inode, NULL, &dwords, NULL);
|
||||
fh_len = dwords << 2;
|
||||
|
||||
return dwords << 2;
|
||||
/*
|
||||
* struct fanotify_error_event might be preallocated and is
|
||||
* limited to MAX_HANDLE_SZ. This should never happen, but
|
||||
* safeguard by forcing an invalid file handle.
|
||||
*/
|
||||
if (WARN_ON_ONCE(fh_len > MAX_HANDLE_SZ))
|
||||
return 0;
|
||||
|
||||
return fh_len;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -370,8 +397,14 @@ static int fanotify_encode_fh(struct fanotify_fh *fh, struct inode *inode,
|
||||
fh->type = FILEID_ROOT;
|
||||
fh->len = 0;
|
||||
fh->flags = 0;
|
||||
|
||||
/*
|
||||
* Invalid FHs are used by FAN_FS_ERROR for errors not
|
||||
* linked to any inode. The f_handle won't be reported
|
||||
* back to userspace.
|
||||
*/
|
||||
if (!inode)
|
||||
return 0;
|
||||
goto out;
|
||||
|
||||
/*
|
||||
* !gpf means preallocated variable size fh, but fh_len could
|
||||
@ -403,8 +436,13 @@ static int fanotify_encode_fh(struct fanotify_fh *fh, struct inode *inode,
|
||||
fh->type = type;
|
||||
fh->len = fh_len;
|
||||
|
||||
/* Mix fh into event merge key */
|
||||
*hash ^= fanotify_hash_fh(fh);
|
||||
out:
|
||||
/*
|
||||
* Mix fh into event merge key. Hash might be NULL in case of
|
||||
* unhashed FID events (i.e. FAN_FS_ERROR).
|
||||
*/
|
||||
if (hash)
|
||||
*hash ^= fanotify_hash_fh(fh);
|
||||
|
||||
return FANOTIFY_FH_HDR_LEN + fh_len;
|
||||
|
||||
@ -452,7 +490,7 @@ static struct inode *fanotify_dfid_inode(u32 event_mask, const void *data,
|
||||
if (event_mask & ALL_FSNOTIFY_DIRENT_EVENTS)
|
||||
return dir;
|
||||
|
||||
if (S_ISDIR(inode->i_mode))
|
||||
if (inode && S_ISDIR(inode->i_mode))
|
||||
return inode;
|
||||
|
||||
return dir;
|
||||
@ -563,6 +601,44 @@ static struct fanotify_event *fanotify_alloc_name_event(struct inode *id,
|
||||
return &fne->fae;
|
||||
}
|
||||
|
||||
static struct fanotify_event *fanotify_alloc_error_event(
|
||||
struct fsnotify_group *group,
|
||||
__kernel_fsid_t *fsid,
|
||||
const void *data, int data_type,
|
||||
unsigned int *hash)
|
||||
{
|
||||
struct fs_error_report *report =
|
||||
fsnotify_data_error_report(data, data_type);
|
||||
struct inode *inode;
|
||||
struct fanotify_error_event *fee;
|
||||
int fh_len;
|
||||
|
||||
if (WARN_ON_ONCE(!report))
|
||||
return NULL;
|
||||
|
||||
fee = mempool_alloc(&group->fanotify_data.error_events_pool, GFP_NOFS);
|
||||
if (!fee)
|
||||
return NULL;
|
||||
|
||||
fee->fae.type = FANOTIFY_EVENT_TYPE_FS_ERROR;
|
||||
fee->error = report->error;
|
||||
fee->err_count = 1;
|
||||
fee->fsid = *fsid;
|
||||
|
||||
inode = report->inode;
|
||||
fh_len = fanotify_encode_fh_len(inode);
|
||||
|
||||
/* Bad fh_len. Fallback to using an invalid fh. Should never happen. */
|
||||
if (!fh_len && inode)
|
||||
inode = NULL;
|
||||
|
||||
fanotify_encode_fh(&fee->object_fh, inode, fh_len, NULL, 0);
|
||||
|
||||
*hash ^= fanotify_hash_fsid(fsid);
|
||||
|
||||
return &fee->fae;
|
||||
}
|
||||
|
||||
static struct fanotify_event *fanotify_alloc_event(struct fsnotify_group *group,
|
||||
u32 mask, const void *data,
|
||||
int data_type, struct inode *dir,
|
||||
@ -630,6 +706,9 @@ static struct fanotify_event *fanotify_alloc_event(struct fsnotify_group *group,
|
||||
|
||||
if (fanotify_is_perm_event(mask)) {
|
||||
event = fanotify_alloc_perm_event(path, gfp);
|
||||
} else if (fanotify_is_error_event(mask)) {
|
||||
event = fanotify_alloc_error_event(group, fsid, data,
|
||||
data_type, &hash);
|
||||
} else if (name_event && (file_name || child)) {
|
||||
event = fanotify_alloc_name_event(id, fsid, file_name, child,
|
||||
&hash, gfp);
|
||||
@ -702,6 +781,9 @@ static void fanotify_insert_event(struct fsnotify_group *group,
|
||||
|
||||
assert_spin_locked(&group->notification_lock);
|
||||
|
||||
if (!fanotify_is_hashed_event(event->mask))
|
||||
return;
|
||||
|
||||
pr_debug("%s: group=%p event=%p bucket=%u\n", __func__,
|
||||
group, event, bucket);
|
||||
|
||||
@ -738,8 +820,9 @@ static int fanotify_handle_event(struct fsnotify_group *group, u32 mask,
|
||||
BUILD_BUG_ON(FAN_ONDIR != FS_ISDIR);
|
||||
BUILD_BUG_ON(FAN_OPEN_EXEC != FS_OPEN_EXEC);
|
||||
BUILD_BUG_ON(FAN_OPEN_EXEC_PERM != FS_OPEN_EXEC_PERM);
|
||||
BUILD_BUG_ON(FAN_FS_ERROR != FS_ERROR);
|
||||
|
||||
BUILD_BUG_ON(HWEIGHT32(ALL_FANOTIFY_EVENT_BITS) != 19);
|
||||
BUILD_BUG_ON(HWEIGHT32(ALL_FANOTIFY_EVENT_BITS) != 20);
|
||||
|
||||
mask = fanotify_group_event_mask(group, iter_info, mask, data,
|
||||
data_type, dir);
|
||||
@ -778,9 +861,8 @@ static int fanotify_handle_event(struct fsnotify_group *group, u32 mask,
|
||||
}
|
||||
|
||||
fsn_event = &event->fse;
|
||||
ret = fsnotify_add_event(group, fsn_event, fanotify_merge,
|
||||
fanotify_is_hashed_event(mask) ?
|
||||
fanotify_insert_event : NULL);
|
||||
ret = fsnotify_insert_event(group, fsn_event, fanotify_merge,
|
||||
fanotify_insert_event);
|
||||
if (ret) {
|
||||
/* Permission events shouldn't be merged */
|
||||
BUG_ON(ret == 1 && mask & FANOTIFY_PERM_EVENTS);
|
||||
@ -805,6 +887,9 @@ static void fanotify_free_group_priv(struct fsnotify_group *group)
|
||||
if (group->fanotify_data.ucounts)
|
||||
dec_ucount(group->fanotify_data.ucounts,
|
||||
UCOUNT_FANOTIFY_GROUPS);
|
||||
|
||||
if (mempool_initialized(&group->fanotify_data.error_events_pool))
|
||||
mempool_exit(&group->fanotify_data.error_events_pool);
|
||||
}
|
||||
|
||||
static void fanotify_free_path_event(struct fanotify_event *event)
|
||||
@ -833,7 +918,16 @@ static void fanotify_free_name_event(struct fanotify_event *event)
|
||||
kfree(FANOTIFY_NE(event));
|
||||
}
|
||||
|
||||
static void fanotify_free_event(struct fsnotify_event *fsn_event)
|
||||
static void fanotify_free_error_event(struct fsnotify_group *group,
|
||||
struct fanotify_event *event)
|
||||
{
|
||||
struct fanotify_error_event *fee = FANOTIFY_EE(event);
|
||||
|
||||
mempool_free(fee, &group->fanotify_data.error_events_pool);
|
||||
}
|
||||
|
||||
static void fanotify_free_event(struct fsnotify_group *group,
|
||||
struct fsnotify_event *fsn_event)
|
||||
{
|
||||
struct fanotify_event *event;
|
||||
|
||||
@ -855,6 +949,9 @@ static void fanotify_free_event(struct fsnotify_event *fsn_event)
|
||||
case FANOTIFY_EVENT_TYPE_OVERFLOW:
|
||||
kfree(event);
|
||||
break;
|
||||
case FANOTIFY_EVENT_TYPE_FS_ERROR:
|
||||
fanotify_free_error_event(group, event);
|
||||
break;
|
||||
default:
|
||||
WARN_ON_ONCE(1);
|
||||
}
|
||||
|
@ -141,6 +141,7 @@ enum fanotify_event_type {
|
||||
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_NUM
|
||||
};
|
||||
|
||||
@ -170,12 +171,18 @@ static inline void fanotify_init_event(struct fanotify_event *event,
|
||||
event->pid = NULL;
|
||||
}
|
||||
|
||||
#define FANOTIFY_INLINE_FH(name, size) \
|
||||
struct { \
|
||||
struct fanotify_fh (name); \
|
||||
/* Space for object_fh.buf[] - access with fanotify_fh_buf() */ \
|
||||
unsigned char _inline_fh_buf[(size)]; \
|
||||
}
|
||||
|
||||
struct fanotify_fid_event {
|
||||
struct fanotify_event fae;
|
||||
__kernel_fsid_t fsid;
|
||||
struct fanotify_fh object_fh;
|
||||
/* Reserve space in object_fh.buf[] - access with fanotify_fh_buf() */
|
||||
unsigned char _inline_fh_buf[FANOTIFY_INLINE_FH_LEN];
|
||||
|
||||
FANOTIFY_INLINE_FH(object_fh, FANOTIFY_INLINE_FH_LEN);
|
||||
};
|
||||
|
||||
static inline struct fanotify_fid_event *
|
||||
@ -196,12 +203,30 @@ 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;
|
||||
}
|
||||
@ -213,6 +238,8 @@ static inline struct fanotify_fh *fanotify_event_object_fh(
|
||||
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;
|
||||
}
|
||||
@ -244,6 +271,19 @@ static inline int fanotify_event_dir_fh_len(struct fanotify_event *event)
|
||||
return info ? fanotify_info_dir_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;
|
||||
}
|
||||
|
||||
struct fanotify_path_event {
|
||||
struct fanotify_event fae;
|
||||
struct path path;
|
||||
@ -287,6 +327,11 @@ 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_event_has_path(struct fanotify_event *event)
|
||||
{
|
||||
return event->type == FANOTIFY_EVENT_TYPE_PATH ||
|
||||
@ -315,7 +360,8 @@ static inline struct path *fanotify_event_path(struct fanotify_event *event)
|
||||
*/
|
||||
static inline bool fanotify_is_hashed_event(u32 mask)
|
||||
{
|
||||
return !fanotify_is_perm_event(mask) && !(mask & FS_Q_OVERFLOW);
|
||||
return !(fanotify_is_perm_event(mask) ||
|
||||
fsnotify_is_overflow_event(mask));
|
||||
}
|
||||
|
||||
static inline unsigned int fanotify_event_hash_bucket(
|
||||
|
@ -30,6 +30,7 @@
|
||||
#define FANOTIFY_DEFAULT_MAX_EVENTS 16384
|
||||
#define FANOTIFY_OLD_DEFAULT_MAX_MARKS 8192
|
||||
#define FANOTIFY_DEFAULT_MAX_GROUPS 128
|
||||
#define FANOTIFY_DEFAULT_FEE_POOL_SIZE 32
|
||||
|
||||
/*
|
||||
* Legacy fanotify marks limits (8192) is per group and we introduced a tunable
|
||||
@ -114,6 +115,8 @@ struct kmem_cache *fanotify_perm_event_cachep __read_mostly;
|
||||
(sizeof(struct fanotify_event_info_fid) + sizeof(struct file_handle))
|
||||
#define FANOTIFY_PIDFD_INFO_HDR_LEN \
|
||||
sizeof(struct fanotify_event_info_pidfd)
|
||||
#define FANOTIFY_ERROR_INFO_LEN \
|
||||
(sizeof(struct fanotify_event_info_error))
|
||||
|
||||
static int fanotify_fid_info_len(int fh_len, int name_len)
|
||||
{
|
||||
@ -126,17 +129,26 @@ static int fanotify_fid_info_len(int fh_len, int name_len)
|
||||
FANOTIFY_EVENT_ALIGN);
|
||||
}
|
||||
|
||||
static int fanotify_event_info_len(unsigned int info_mode,
|
||||
struct fanotify_event *event)
|
||||
static size_t fanotify_event_len(unsigned int info_mode,
|
||||
struct fanotify_event *event)
|
||||
{
|
||||
struct fanotify_info *info = fanotify_event_info(event);
|
||||
int dir_fh_len = fanotify_event_dir_fh_len(event);
|
||||
int fh_len = fanotify_event_object_fh_len(event);
|
||||
int info_len = 0;
|
||||
size_t event_len = FAN_EVENT_METADATA_LEN;
|
||||
struct fanotify_info *info;
|
||||
int dir_fh_len;
|
||||
int fh_len;
|
||||
int dot_len = 0;
|
||||
|
||||
if (dir_fh_len) {
|
||||
info_len += fanotify_fid_info_len(dir_fh_len, info->name_len);
|
||||
if (!info_mode)
|
||||
return event_len;
|
||||
|
||||
if (fanotify_is_error_event(event->mask))
|
||||
event_len += FANOTIFY_ERROR_INFO_LEN;
|
||||
|
||||
info = fanotify_event_info(event);
|
||||
|
||||
if (fanotify_event_has_dir_fh(event)) {
|
||||
dir_fh_len = fanotify_event_dir_fh_len(event);
|
||||
event_len += fanotify_fid_info_len(dir_fh_len, info->name_len);
|
||||
} else if ((info_mode & FAN_REPORT_NAME) &&
|
||||
(event->mask & FAN_ONDIR)) {
|
||||
/*
|
||||
@ -147,12 +159,14 @@ static int fanotify_event_info_len(unsigned int info_mode,
|
||||
}
|
||||
|
||||
if (info_mode & FAN_REPORT_PIDFD)
|
||||
info_len += FANOTIFY_PIDFD_INFO_HDR_LEN;
|
||||
event_len += FANOTIFY_PIDFD_INFO_HDR_LEN;
|
||||
|
||||
if (fh_len)
|
||||
info_len += fanotify_fid_info_len(fh_len, dot_len);
|
||||
if (fanotify_event_has_object_fh(event)) {
|
||||
fh_len = fanotify_event_object_fh_len(event);
|
||||
event_len += fanotify_fid_info_len(fh_len, dot_len);
|
||||
}
|
||||
|
||||
return info_len;
|
||||
return event_len;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -181,7 +195,7 @@ static void fanotify_unhash_event(struct fsnotify_group *group,
|
||||
static struct fanotify_event *get_one_event(struct fsnotify_group *group,
|
||||
size_t count)
|
||||
{
|
||||
size_t event_size = FAN_EVENT_METADATA_LEN;
|
||||
size_t event_size;
|
||||
struct fanotify_event *event = NULL;
|
||||
struct fsnotify_event *fsn_event;
|
||||
unsigned int info_mode = FAN_GROUP_FLAG(group, FANOTIFY_INFO_MODES);
|
||||
@ -194,8 +208,7 @@ static struct fanotify_event *get_one_event(struct fsnotify_group *group,
|
||||
goto out;
|
||||
|
||||
event = FANOTIFY_E(fsn_event);
|
||||
if (info_mode)
|
||||
event_size += fanotify_event_info_len(info_mode, event);
|
||||
event_size = fanotify_event_len(info_mode, event);
|
||||
|
||||
if (event_size > count) {
|
||||
event = ERR_PTR(-EINVAL);
|
||||
@ -316,6 +329,28 @@ static int process_access_response(struct fsnotify_group *group,
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
static size_t copy_error_info_to_user(struct fanotify_event *event,
|
||||
char __user *buf, int count)
|
||||
{
|
||||
struct fanotify_event_info_error info;
|
||||
struct fanotify_error_event *fee = FANOTIFY_EE(event);
|
||||
|
||||
info.hdr.info_type = FAN_EVENT_INFO_TYPE_ERROR;
|
||||
info.hdr.pad = 0;
|
||||
info.hdr.len = FANOTIFY_ERROR_INFO_LEN;
|
||||
|
||||
if (WARN_ON(count < info.hdr.len))
|
||||
return -EFAULT;
|
||||
|
||||
info.error = fee->error;
|
||||
info.error_count = fee->err_count;
|
||||
|
||||
if (copy_to_user(buf, &info, sizeof(info)))
|
||||
return -EFAULT;
|
||||
|
||||
return info.hdr.len;
|
||||
}
|
||||
|
||||
static int copy_fid_info_to_user(__kernel_fsid_t *fsid, struct fanotify_fh *fh,
|
||||
int info_type, const char *name,
|
||||
size_t name_len,
|
||||
@ -331,9 +366,6 @@ static int copy_fid_info_to_user(__kernel_fsid_t *fsid, struct fanotify_fh *fh,
|
||||
pr_debug("%s: fh_len=%zu name_len=%zu, info_len=%zu, count=%zu\n",
|
||||
__func__, fh_len, name_len, info_len, count);
|
||||
|
||||
if (!fh_len)
|
||||
return 0;
|
||||
|
||||
if (WARN_ON_ONCE(len < sizeof(info) || len > count))
|
||||
return -EFAULT;
|
||||
|
||||
@ -368,6 +400,11 @@ static int copy_fid_info_to_user(__kernel_fsid_t *fsid, struct fanotify_fh *fh,
|
||||
|
||||
handle.handle_type = fh->type;
|
||||
handle.handle_bytes = fh_len;
|
||||
|
||||
/* Mangle handle_type for bad file_handle */
|
||||
if (!fh_len)
|
||||
handle.handle_type = FILEID_INVALID;
|
||||
|
||||
if (copy_to_user(buf, &handle, sizeof(handle)))
|
||||
return -EFAULT;
|
||||
|
||||
@ -444,7 +481,7 @@ static int copy_info_records_to_user(struct fanotify_event *event,
|
||||
/*
|
||||
* Event info records order is as follows: dir fid + name, child fid.
|
||||
*/
|
||||
if (fanotify_event_dir_fh_len(event)) {
|
||||
if (fanotify_event_has_dir_fh(event)) {
|
||||
info_type = info->name_len ? FAN_EVENT_INFO_TYPE_DFID_NAME :
|
||||
FAN_EVENT_INFO_TYPE_DFID;
|
||||
ret = copy_fid_info_to_user(fanotify_event_fsid(event),
|
||||
@ -460,7 +497,7 @@ static int copy_info_records_to_user(struct fanotify_event *event,
|
||||
total_bytes += ret;
|
||||
}
|
||||
|
||||
if (fanotify_event_object_fh_len(event)) {
|
||||
if (fanotify_event_has_object_fh(event)) {
|
||||
const char *dot = NULL;
|
||||
int dot_len = 0;
|
||||
|
||||
@ -520,6 +557,15 @@ static int copy_info_records_to_user(struct fanotify_event *event,
|
||||
total_bytes += ret;
|
||||
}
|
||||
|
||||
if (fanotify_is_error_event(event->mask)) {
|
||||
ret = copy_error_info_to_user(event, buf, count);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
buf += ret;
|
||||
count -= ret;
|
||||
total_bytes += ret;
|
||||
}
|
||||
|
||||
return total_bytes;
|
||||
}
|
||||
|
||||
@ -537,8 +583,7 @@ static ssize_t copy_event_to_user(struct fsnotify_group *group,
|
||||
|
||||
pr_debug("%s: group=%p event=%p\n", __func__, group, event);
|
||||
|
||||
metadata.event_len = FAN_EVENT_METADATA_LEN +
|
||||
fanotify_event_info_len(info_mode, event);
|
||||
metadata.event_len = fanotify_event_len(info_mode, event);
|
||||
metadata.metadata_len = FAN_EVENT_METADATA_LEN;
|
||||
metadata.vers = FANOTIFY_METADATA_VERSION;
|
||||
metadata.reserved = 0;
|
||||
@ -1049,6 +1094,15 @@ static struct fsnotify_mark *fanotify_add_new_mark(struct fsnotify_group *group,
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
|
||||
static int fanotify_group_init_error_pool(struct fsnotify_group *group)
|
||||
{
|
||||
if (mempool_initialized(&group->fanotify_data.error_events_pool))
|
||||
return 0;
|
||||
|
||||
return mempool_init_kmalloc_pool(&group->fanotify_data.error_events_pool,
|
||||
FANOTIFY_DEFAULT_FEE_POOL_SIZE,
|
||||
sizeof(struct fanotify_error_event));
|
||||
}
|
||||
|
||||
static int fanotify_add_mark(struct fsnotify_group *group,
|
||||
fsnotify_connp_t *connp, unsigned int type,
|
||||
@ -1057,6 +1111,7 @@ static int fanotify_add_mark(struct fsnotify_group *group,
|
||||
{
|
||||
struct fsnotify_mark *fsn_mark;
|
||||
__u32 added;
|
||||
int ret = 0;
|
||||
|
||||
mutex_lock(&group->mark_mutex);
|
||||
fsn_mark = fsnotify_find_mark(connp, group);
|
||||
@ -1067,13 +1122,26 @@ static int fanotify_add_mark(struct fsnotify_group *group,
|
||||
return PTR_ERR(fsn_mark);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Error events are pre-allocated per group, only if strictly
|
||||
* needed (i.e. FAN_FS_ERROR was requested).
|
||||
*/
|
||||
if (!(flags & FAN_MARK_IGNORED_MASK) && (mask & FAN_FS_ERROR)) {
|
||||
ret = fanotify_group_init_error_pool(group);
|
||||
if (ret)
|
||||
goto out;
|
||||
}
|
||||
|
||||
added = fanotify_mark_add_to_mask(fsn_mark, mask, flags);
|
||||
if (added & ~fsnotify_conn_mask(fsn_mark->connector))
|
||||
fsnotify_recalc_mask(fsn_mark->connector);
|
||||
|
||||
out:
|
||||
mutex_unlock(&group->mark_mutex);
|
||||
|
||||
fsnotify_put_mark(fsn_mark);
|
||||
return 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int fanotify_add_vfsmount_mark(struct fsnotify_group *group,
|
||||
@ -1295,16 +1363,15 @@ SYSCALL_DEFINE2(fanotify_init, unsigned int, flags, unsigned int, event_f_flags)
|
||||
return fd;
|
||||
}
|
||||
|
||||
/* Check if filesystem can encode a unique fid */
|
||||
static int fanotify_test_fid(struct path *path, __kernel_fsid_t *fsid)
|
||||
static int fanotify_test_fsid(struct dentry *dentry, __kernel_fsid_t *fsid)
|
||||
{
|
||||
__kernel_fsid_t root_fsid;
|
||||
int err;
|
||||
|
||||
/*
|
||||
* Make sure path is not in filesystem with zero fsid (e.g. tmpfs).
|
||||
* Make sure dentry is not of a filesystem with zero fsid (e.g. fuse).
|
||||
*/
|
||||
err = vfs_get_fsid(path->dentry, fsid);
|
||||
err = vfs_get_fsid(dentry, fsid);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
@ -1312,10 +1379,10 @@ static int fanotify_test_fid(struct path *path, __kernel_fsid_t *fsid)
|
||||
return -ENODEV;
|
||||
|
||||
/*
|
||||
* Make sure path is not inside a filesystem subvolume (e.g. btrfs)
|
||||
* Make sure dentry is not of a filesystem subvolume (e.g. btrfs)
|
||||
* which uses a different fsid than sb root.
|
||||
*/
|
||||
err = vfs_get_fsid(path->dentry->d_sb->s_root, &root_fsid);
|
||||
err = vfs_get_fsid(dentry->d_sb->s_root, &root_fsid);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
@ -1323,6 +1390,12 @@ static int fanotify_test_fid(struct path *path, __kernel_fsid_t *fsid)
|
||||
root_fsid.val[1] != fsid->val[1])
|
||||
return -EXDEV;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Check if filesystem can encode a unique fid */
|
||||
static int fanotify_test_fid(struct dentry *dentry)
|
||||
{
|
||||
/*
|
||||
* We need to make sure that the file system supports at least
|
||||
* encoding a file handle so user can use name_to_handle_at() to
|
||||
@ -1330,8 +1403,8 @@ static int fanotify_test_fid(struct path *path, __kernel_fsid_t *fsid)
|
||||
* objects. However, name_to_handle_at() requires that the
|
||||
* filesystem also supports decoding file handles.
|
||||
*/
|
||||
if (!path->dentry->d_sb->s_export_op ||
|
||||
!path->dentry->d_sb->s_export_op->fh_to_dentry)
|
||||
if (!dentry->d_sb->s_export_op ||
|
||||
!dentry->d_sb->s_export_op->fh_to_dentry)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
return 0;
|
||||
@ -1447,15 +1520,19 @@ static int do_fanotify_mark(int fanotify_fd, unsigned int flags, __u64 mask,
|
||||
group->priority == FS_PRIO_0)
|
||||
goto fput_and_out;
|
||||
|
||||
if (mask & FAN_FS_ERROR &&
|
||||
mark_type != FAN_MARK_FILESYSTEM)
|
||||
goto fput_and_out;
|
||||
|
||||
/*
|
||||
* Events with data type inode do not carry enough information to report
|
||||
* event->fd, so we do not allow setting a mask for inode events unless
|
||||
* group supports reporting fid.
|
||||
* inode events are not supported on a mount mark, because they do not
|
||||
* carry enough information (i.e. path) to be filtered by mount point.
|
||||
* Events that do not carry enough information to report
|
||||
* event->fd require a group that supports reporting fid. Those
|
||||
* events are not supported on a mount mark, because they do not
|
||||
* carry enough information (i.e. path) to be filtered by mount
|
||||
* point.
|
||||
*/
|
||||
fid_mode = FAN_GROUP_FLAG(group, FANOTIFY_FID_BITS);
|
||||
if (mask & FANOTIFY_INODE_EVENTS &&
|
||||
if (mask & ~(FANOTIFY_FD_EVENTS|FANOTIFY_EVENT_FLAGS) &&
|
||||
(!fid_mode || mark_type == FAN_MARK_MOUNT))
|
||||
goto fput_and_out;
|
||||
|
||||
@ -1482,7 +1559,11 @@ static int do_fanotify_mark(int fanotify_fd, unsigned int flags, __u64 mask,
|
||||
}
|
||||
|
||||
if (fid_mode) {
|
||||
ret = fanotify_test_fid(&path, &__fsid);
|
||||
ret = fanotify_test_fsid(path.dentry, &__fsid);
|
||||
if (ret)
|
||||
goto path_put_and_out;
|
||||
|
||||
ret = fanotify_test_fid(path.dentry);
|
||||
if (ret)
|
||||
goto path_put_and_out;
|
||||
|
||||
|
@ -252,6 +252,9 @@ static int fsnotify_handle_inode_event(struct fsnotify_group *group,
|
||||
if (WARN_ON_ONCE(!ops->handle_inode_event))
|
||||
return 0;
|
||||
|
||||
if (WARN_ON_ONCE(!inode && !dir))
|
||||
return 0;
|
||||
|
||||
if ((inode_mark->mask & FS_EXCL_UNLINK) &&
|
||||
path && d_unlinked(path->dentry))
|
||||
return 0;
|
||||
@ -455,16 +458,16 @@ static void fsnotify_iter_next(struct fsnotify_iter_info *iter_info)
|
||||
* @file_name is relative to
|
||||
* @file_name: optional file name associated with event
|
||||
* @inode: optional inode associated with event -
|
||||
* either @dir or @inode must be non-NULL.
|
||||
* if both are non-NULL event may be reported to both.
|
||||
* 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);
|
||||
struct fsnotify_iter_info iter_info = {};
|
||||
struct super_block *sb;
|
||||
struct mount *mnt = NULL;
|
||||
struct inode *parent = NULL;
|
||||
int ret = 0;
|
||||
@ -483,7 +486,6 @@ int fsnotify(__u32 mask, const void *data, int data_type, struct inode *dir,
|
||||
*/
|
||||
parent = dir;
|
||||
}
|
||||
sb = inode->i_sb;
|
||||
|
||||
/*
|
||||
* Optimization: srcu_read_lock() has a memory barrier which can
|
||||
|
@ -88,7 +88,7 @@ void fsnotify_destroy_group(struct fsnotify_group *group)
|
||||
* that deliberately ignores overflow events.
|
||||
*/
|
||||
if (group->overflow_event)
|
||||
group->ops->free_event(group->overflow_event);
|
||||
group->ops->free_event(group, group->overflow_event);
|
||||
|
||||
fsnotify_put_group(group);
|
||||
}
|
||||
|
@ -116,7 +116,7 @@ int inotify_handle_inode_event(struct fsnotify_mark *inode_mark, u32 mask,
|
||||
if (len)
|
||||
strcpy(event->name, name->name);
|
||||
|
||||
ret = fsnotify_add_event(group, fsn_event, inotify_merge, NULL);
|
||||
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);
|
||||
@ -177,7 +177,8 @@ static void inotify_free_group_priv(struct fsnotify_group *group)
|
||||
dec_inotify_instances(group->inotify_data.ucounts);
|
||||
}
|
||||
|
||||
static void inotify_free_event(struct fsnotify_event *fsn_event)
|
||||
static void inotify_free_event(struct fsnotify_group *group,
|
||||
struct fsnotify_event *fsn_event)
|
||||
{
|
||||
kfree(INOTIFY_E(fsn_event));
|
||||
}
|
||||
|
@ -94,10 +94,10 @@ static inline __u32 inotify_arg_to_mask(struct inode *inode, u32 arg)
|
||||
__u32 mask;
|
||||
|
||||
/*
|
||||
* Everything should accept their own ignored and should receive events
|
||||
* when the inode is unmounted. All directories care about children.
|
||||
* Everything should receive events when the inode is unmounted.
|
||||
* All directories care about children.
|
||||
*/
|
||||
mask = (FS_IN_IGNORED | FS_UNMOUNT);
|
||||
mask = (FS_UNMOUNT);
|
||||
if (S_ISDIR(inode->i_mode))
|
||||
mask |= FS_EVENT_ON_CHILD;
|
||||
|
||||
|
@ -64,7 +64,7 @@ void fsnotify_destroy_event(struct fsnotify_group *group,
|
||||
WARN_ON(!list_empty(&event->list));
|
||||
spin_unlock(&group->notification_lock);
|
||||
}
|
||||
group->ops->free_event(event);
|
||||
group->ops->free_event(group, event);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -78,12 +78,12 @@ void fsnotify_destroy_event(struct fsnotify_group *group,
|
||||
* 2 if the event was not queued - either the queue of events has overflown
|
||||
* or the group is shutting down.
|
||||
*/
|
||||
int fsnotify_add_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 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;
|
||||
|
@ -84,13 +84,20 @@ extern struct ctl_table fanotify_table[]; /* for sysctl */
|
||||
*/
|
||||
#define FANOTIFY_DIRENT_EVENTS (FAN_MOVE | FAN_CREATE | FAN_DELETE)
|
||||
|
||||
/* Events that can be reported with event->fd */
|
||||
#define FANOTIFY_FD_EVENTS (FANOTIFY_PATH_EVENTS | FANOTIFY_PERM_EVENTS)
|
||||
|
||||
/* Events that can only be reported with data type FSNOTIFY_EVENT_INODE */
|
||||
#define FANOTIFY_INODE_EVENTS (FANOTIFY_DIRENT_EVENTS | \
|
||||
FAN_ATTRIB | FAN_MOVE_SELF | FAN_DELETE_SELF)
|
||||
|
||||
/* Events that can only be reported with data type FSNOTIFY_EVENT_ERROR */
|
||||
#define FANOTIFY_ERROR_EVENTS (FAN_FS_ERROR)
|
||||
|
||||
/* Events that user can request to be notified on */
|
||||
#define FANOTIFY_EVENTS (FANOTIFY_PATH_EVENTS | \
|
||||
FANOTIFY_INODE_EVENTS)
|
||||
FANOTIFY_INODE_EVENTS | \
|
||||
FANOTIFY_ERROR_EVENTS)
|
||||
|
||||
/* Events that require a permission response from user */
|
||||
#define FANOTIFY_PERM_EVENTS (FAN_OPEN_PERM | FAN_ACCESS_PERM | \
|
||||
|
@ -26,20 +26,20 @@
|
||||
* FS_EVENT_ON_CHILD mask on the parent inode and will not be reported if only
|
||||
* the child is interested and not the parent.
|
||||
*/
|
||||
static inline void fsnotify_name(struct inode *dir, __u32 mask,
|
||||
struct inode *child,
|
||||
const struct qstr *name, u32 cookie)
|
||||
static inline int fsnotify_name(__u32 mask, const void *data, int data_type,
|
||||
struct inode *dir, const struct qstr *name,
|
||||
u32 cookie)
|
||||
{
|
||||
if (atomic_long_read(&dir->i_sb->s_fsnotify_connectors) == 0)
|
||||
return;
|
||||
return 0;
|
||||
|
||||
fsnotify(mask, child, FSNOTIFY_EVENT_INODE, dir, name, NULL, cookie);
|
||||
return fsnotify(mask, data, data_type, dir, name, NULL, cookie);
|
||||
}
|
||||
|
||||
static inline void fsnotify_dirent(struct inode *dir, struct dentry *dentry,
|
||||
__u32 mask)
|
||||
{
|
||||
fsnotify_name(dir, mask, d_inode(dentry), &dentry->d_name, 0);
|
||||
fsnotify_name(mask, dentry, FSNOTIFY_EVENT_DENTRY, dir, &dentry->d_name, 0);
|
||||
}
|
||||
|
||||
static inline void fsnotify_inode(struct inode *inode, __u32 mask)
|
||||
@ -86,7 +86,7 @@ static inline int fsnotify_parent(struct dentry *dentry, __u32 mask,
|
||||
*/
|
||||
static inline void fsnotify_dentry(struct dentry *dentry, __u32 mask)
|
||||
{
|
||||
fsnotify_parent(dentry, mask, d_inode(dentry), FSNOTIFY_EVENT_INODE);
|
||||
fsnotify_parent(dentry, mask, dentry, FSNOTIFY_EVENT_DENTRY);
|
||||
}
|
||||
|
||||
static inline int fsnotify_file(struct file *file, __u32 mask)
|
||||
@ -154,8 +154,10 @@ static inline void fsnotify_move(struct inode *old_dir, struct inode *new_dir,
|
||||
new_dir_mask |= FS_ISDIR;
|
||||
}
|
||||
|
||||
fsnotify_name(old_dir, old_dir_mask, source, old_name, fs_cookie);
|
||||
fsnotify_name(new_dir, new_dir_mask, source, new_name, fs_cookie);
|
||||
fsnotify_name(old_dir_mask, source, FSNOTIFY_EVENT_INODE,
|
||||
old_dir, old_name, fs_cookie);
|
||||
fsnotify_name(new_dir_mask, source, FSNOTIFY_EVENT_INODE,
|
||||
new_dir, new_name, fs_cookie);
|
||||
|
||||
if (target)
|
||||
fsnotify_link_count(target);
|
||||
@ -190,16 +192,22 @@ static inline void fsnotify_inoderemove(struct inode *inode)
|
||||
|
||||
/*
|
||||
* fsnotify_create - 'name' was linked in
|
||||
*
|
||||
* Caller must make sure that dentry->d_name is stable.
|
||||
* Note: some filesystems (e.g. kernfs) leave @dentry negative and instantiate
|
||||
* ->d_inode later
|
||||
*/
|
||||
static inline void fsnotify_create(struct inode *inode, struct dentry *dentry)
|
||||
static inline void fsnotify_create(struct inode *dir, struct dentry *dentry)
|
||||
{
|
||||
audit_inode_child(inode, dentry, AUDIT_TYPE_CHILD_CREATE);
|
||||
audit_inode_child(dir, dentry, AUDIT_TYPE_CHILD_CREATE);
|
||||
|
||||
fsnotify_dirent(inode, dentry, FS_CREATE);
|
||||
fsnotify_dirent(dir, dentry, FS_CREATE);
|
||||
}
|
||||
|
||||
/*
|
||||
* fsnotify_link - new hardlink in 'inode' directory
|
||||
*
|
||||
* Caller must make sure that new_dentry->d_name is stable.
|
||||
* Note: We have to pass also the linked inode ptr as some filesystems leave
|
||||
* new_dentry->d_inode NULL and instantiate inode pointer later
|
||||
*/
|
||||
@ -209,7 +217,8 @@ static inline void fsnotify_link(struct inode *dir, struct inode *inode,
|
||||
fsnotify_link_count(inode);
|
||||
audit_inode_child(dir, new_dentry, AUDIT_TYPE_CHILD_CREATE);
|
||||
|
||||
fsnotify_name(dir, FS_CREATE, inode, &new_dentry->d_name, 0);
|
||||
fsnotify_name(FS_CREATE, inode, FSNOTIFY_EVENT_INODE,
|
||||
dir, &new_dentry->d_name, 0);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -227,12 +236,16 @@ static inline void fsnotify_unlink(struct inode *dir, struct dentry *dentry)
|
||||
|
||||
/*
|
||||
* fsnotify_mkdir - directory 'name' was created
|
||||
*
|
||||
* Caller must make sure that dentry->d_name is stable.
|
||||
* Note: some filesystems (e.g. kernfs) leave @dentry negative and instantiate
|
||||
* ->d_inode later
|
||||
*/
|
||||
static inline void fsnotify_mkdir(struct inode *inode, struct dentry *dentry)
|
||||
static inline void fsnotify_mkdir(struct inode *dir, struct dentry *dentry)
|
||||
{
|
||||
audit_inode_child(inode, dentry, AUDIT_TYPE_CHILD_CREATE);
|
||||
audit_inode_child(dir, dentry, AUDIT_TYPE_CHILD_CREATE);
|
||||
|
||||
fsnotify_dirent(inode, dentry, FS_CREATE | FS_ISDIR);
|
||||
fsnotify_dirent(dir, dentry, FS_CREATE | FS_ISDIR);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -326,4 +339,17 @@ static inline void fsnotify_change(struct dentry *dentry, unsigned int ia_valid)
|
||||
fsnotify_dentry(dentry, mask);
|
||||
}
|
||||
|
||||
static inline int fsnotify_sb_error(struct super_block *sb, struct inode *inode,
|
||||
int error)
|
||||
{
|
||||
struct fs_error_report report = {
|
||||
.error = error,
|
||||
.inode = inode,
|
||||
.sb = sb,
|
||||
};
|
||||
|
||||
return fsnotify(FS_ERROR, &report, FSNOTIFY_EVENT_ERROR,
|
||||
NULL, NULL, NULL, 0);
|
||||
}
|
||||
|
||||
#endif /* _LINUX_FS_NOTIFY_H */
|
||||
|
@ -19,6 +19,7 @@
|
||||
#include <linux/atomic.h>
|
||||
#include <linux/user_namespace.h>
|
||||
#include <linux/refcount.h>
|
||||
#include <linux/mempool.h>
|
||||
|
||||
/*
|
||||
* IN_* from inotfy.h lines up EXACTLY with FS_*, this is so we can easily
|
||||
@ -42,6 +43,12 @@
|
||||
|
||||
#define FS_UNMOUNT 0x00002000 /* inode on umount fs */
|
||||
#define FS_Q_OVERFLOW 0x00004000 /* Event queued overflowed */
|
||||
#define FS_ERROR 0x00008000 /* Filesystem Error (fanotify) */
|
||||
|
||||
/*
|
||||
* FS_IN_IGNORED overloads FS_ERROR. It is only used internally by inotify
|
||||
* which does not support FS_ERROR.
|
||||
*/
|
||||
#define FS_IN_IGNORED 0x00008000 /* last inotify event here */
|
||||
|
||||
#define FS_OPEN_PERM 0x00010000 /* open event in an permission hook */
|
||||
@ -95,7 +102,8 @@
|
||||
#define ALL_FSNOTIFY_EVENTS (ALL_FSNOTIFY_DIRENT_EVENTS | \
|
||||
FS_EVENTS_POSS_ON_CHILD | \
|
||||
FS_DELETE_SELF | FS_MOVE_SELF | FS_DN_RENAME | \
|
||||
FS_UNMOUNT | FS_Q_OVERFLOW | FS_IN_IGNORED)
|
||||
FS_UNMOUNT | FS_Q_OVERFLOW | FS_IN_IGNORED | \
|
||||
FS_ERROR)
|
||||
|
||||
/* Extra flags that may be reported with event or control handling of events */
|
||||
#define ALL_FSNOTIFY_FLAGS (FS_EXCL_UNLINK | FS_ISDIR | FS_IN_ONESHOT | \
|
||||
@ -136,6 +144,7 @@ struct mem_cgroup;
|
||||
* @dir: optional directory associated with event -
|
||||
* if @file_name is not NULL, this is the directory that
|
||||
* @file_name is relative to.
|
||||
* Either @inode or @dir must be non-NULL.
|
||||
* @file_name: optional file name associated with event
|
||||
* @cookie: inotify rename cookie
|
||||
*
|
||||
@ -155,7 +164,7 @@ struct fsnotify_ops {
|
||||
const struct qstr *file_name, u32 cookie);
|
||||
void (*free_group_priv)(struct fsnotify_group *group);
|
||||
void (*freeing_mark)(struct fsnotify_mark *mark, struct fsnotify_group *group);
|
||||
void (*free_event)(struct fsnotify_event *event);
|
||||
void (*free_event)(struct fsnotify_group *group, struct fsnotify_event *event);
|
||||
/* called on final put+free to free memory */
|
||||
void (*free_mark)(struct fsnotify_mark *mark);
|
||||
};
|
||||
@ -238,6 +247,7 @@ struct fsnotify_group {
|
||||
int flags; /* flags from fanotify_init() */
|
||||
int f_flags; /* event_f_flags from fanotify_init() */
|
||||
struct ucounts *ucounts;
|
||||
mempool_t error_events_pool;
|
||||
} fanotify_data;
|
||||
#endif /* CONFIG_FANOTIFY */
|
||||
};
|
||||
@ -248,6 +258,14 @@ enum fsnotify_data_type {
|
||||
FSNOTIFY_EVENT_NONE,
|
||||
FSNOTIFY_EVENT_PATH,
|
||||
FSNOTIFY_EVENT_INODE,
|
||||
FSNOTIFY_EVENT_DENTRY,
|
||||
FSNOTIFY_EVENT_ERROR,
|
||||
};
|
||||
|
||||
struct fs_error_report {
|
||||
int error;
|
||||
struct inode *inode;
|
||||
struct super_block *sb;
|
||||
};
|
||||
|
||||
static inline struct inode *fsnotify_data_inode(const void *data, int data_type)
|
||||
@ -255,8 +273,25 @@ static inline struct inode *fsnotify_data_inode(const void *data, int data_type)
|
||||
switch (data_type) {
|
||||
case FSNOTIFY_EVENT_INODE:
|
||||
return (struct inode *)data;
|
||||
case FSNOTIFY_EVENT_DENTRY:
|
||||
return d_inode(data);
|
||||
case FSNOTIFY_EVENT_PATH:
|
||||
return d_inode(((const struct path *)data)->dentry);
|
||||
case FSNOTIFY_EVENT_ERROR:
|
||||
return ((struct fs_error_report *)data)->inode;
|
||||
default:
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static inline struct dentry *fsnotify_data_dentry(const void *data, int data_type)
|
||||
{
|
||||
switch (data_type) {
|
||||
case FSNOTIFY_EVENT_DENTRY:
|
||||
/* Non const is needed for dget() */
|
||||
return (struct dentry *)data;
|
||||
case FSNOTIFY_EVENT_PATH:
|
||||
return ((const struct path *)data)->dentry;
|
||||
default:
|
||||
return NULL;
|
||||
}
|
||||
@ -273,6 +308,35 @@ static inline const struct path *fsnotify_data_path(const void *data,
|
||||
}
|
||||
}
|
||||
|
||||
static inline struct super_block *fsnotify_data_sb(const void *data,
|
||||
int data_type)
|
||||
{
|
||||
switch (data_type) {
|
||||
case FSNOTIFY_EVENT_INODE:
|
||||
return ((struct inode *)data)->i_sb;
|
||||
case FSNOTIFY_EVENT_DENTRY:
|
||||
return ((struct dentry *)data)->d_sb;
|
||||
case FSNOTIFY_EVENT_PATH:
|
||||
return ((const struct path *)data)->dentry->d_sb;
|
||||
case FSNOTIFY_EVENT_ERROR:
|
||||
return ((struct fs_error_report *) data)->sb;
|
||||
default:
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static inline struct fs_error_report *fsnotify_data_error_report(
|
||||
const void *data,
|
||||
int data_type)
|
||||
{
|
||||
switch (data_type) {
|
||||
case FSNOTIFY_EVENT_ERROR:
|
||||
return (struct fs_error_report *) data;
|
||||
default:
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
enum fsnotify_obj_type {
|
||||
FSNOTIFY_OBJ_TYPE_INODE,
|
||||
FSNOTIFY_OBJ_TYPE_PARENT,
|
||||
@ -482,16 +546,30 @@ extern int fsnotify_fasync(int fd, struct file *file, int on);
|
||||
extern void fsnotify_destroy_event(struct fsnotify_group *group,
|
||||
struct fsnotify_event *event);
|
||||
/* attach the event to the group notification queue */
|
||||
extern int fsnotify_add_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 *));
|
||||
extern 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 *));
|
||||
|
||||
static inline int fsnotify_add_event(struct fsnotify_group *group,
|
||||
struct fsnotify_event *event,
|
||||
int (*merge)(struct fsnotify_group *,
|
||||
struct fsnotify_event *))
|
||||
{
|
||||
return fsnotify_insert_event(group, event, merge, NULL);
|
||||
}
|
||||
|
||||
/* Queue overflow event to a notification group */
|
||||
static inline void fsnotify_queue_overflow(struct fsnotify_group *group)
|
||||
{
|
||||
fsnotify_add_event(group, group->overflow_event, NULL, NULL);
|
||||
fsnotify_add_event(group, group->overflow_event, NULL);
|
||||
}
|
||||
|
||||
static inline bool fsnotify_is_overflow_event(u32 mask)
|
||||
{
|
||||
return mask & FS_Q_OVERFLOW;
|
||||
}
|
||||
|
||||
static inline bool fsnotify_notify_queue_is_empty(struct fsnotify_group *group)
|
||||
|
@ -20,6 +20,7 @@
|
||||
#define FAN_OPEN_EXEC 0x00001000 /* File was opened for exec */
|
||||
|
||||
#define FAN_Q_OVERFLOW 0x00004000 /* Event queued overflowed */
|
||||
#define FAN_FS_ERROR 0x00008000 /* Filesystem error */
|
||||
|
||||
#define FAN_OPEN_PERM 0x00010000 /* File open in perm check */
|
||||
#define FAN_ACCESS_PERM 0x00020000 /* File accessed in perm check */
|
||||
@ -125,6 +126,7 @@ struct fanotify_event_metadata {
|
||||
#define FAN_EVENT_INFO_TYPE_DFID_NAME 2
|
||||
#define FAN_EVENT_INFO_TYPE_DFID 3
|
||||
#define FAN_EVENT_INFO_TYPE_PIDFD 4
|
||||
#define FAN_EVENT_INFO_TYPE_ERROR 5
|
||||
|
||||
/* Variable length info record following event metadata */
|
||||
struct fanotify_event_info_header {
|
||||
@ -159,6 +161,12 @@ struct fanotify_event_info_pidfd {
|
||||
__s32 pidfd;
|
||||
};
|
||||
|
||||
struct fanotify_event_info_error {
|
||||
struct fanotify_event_info_header hdr;
|
||||
__s32 error;
|
||||
__u32 error_count;
|
||||
};
|
||||
|
||||
struct fanotify_response {
|
||||
__s32 fd;
|
||||
__u32 response;
|
||||
|
@ -160,8 +160,7 @@ static int audit_mark_handle_event(struct fsnotify_mark *inode_mark, u32 mask,
|
||||
|
||||
audit_mark = container_of(inode_mark, struct audit_fsnotify_mark, mark);
|
||||
|
||||
if (WARN_ON_ONCE(inode_mark->group != audit_fsnotify_group) ||
|
||||
WARN_ON_ONCE(!inode))
|
||||
if (WARN_ON_ONCE(inode_mark->group != audit_fsnotify_group))
|
||||
return 0;
|
||||
|
||||
if (mask & (FS_CREATE|FS_MOVED_TO|FS_DELETE|FS_MOVED_FROM)) {
|
||||
|
@ -473,8 +473,7 @@ static int audit_watch_handle_event(struct fsnotify_mark *inode_mark, u32 mask,
|
||||
|
||||
parent = container_of(inode_mark, struct audit_parent, mark);
|
||||
|
||||
if (WARN_ON_ONCE(inode_mark->group != audit_watch_group) ||
|
||||
WARN_ON_ONCE(!inode))
|
||||
if (WARN_ON_ONCE(inode_mark->group != audit_watch_group))
|
||||
return 0;
|
||||
|
||||
if (mask & (FS_CREATE|FS_MOVED_TO) && inode)
|
||||
|
@ -120,6 +120,15 @@ config SAMPLE_CONNECTOR
|
||||
with it.
|
||||
See also Documentation/driver-api/connector.rst
|
||||
|
||||
config SAMPLE_FANOTIFY_ERROR
|
||||
bool "Build fanotify error monitoring sample"
|
||||
depends on FANOTIFY && CC_CAN_LINK && HEADERS_INSTALL
|
||||
help
|
||||
When enabled, this builds an example code that uses the
|
||||
FAN_FS_ERROR fanotify mechanism to monitor filesystem
|
||||
errors.
|
||||
See also Documentation/admin-guide/filesystem-monitoring.rst.
|
||||
|
||||
config SAMPLE_HIDRAW
|
||||
bool "hidraw sample"
|
||||
depends on CC_CAN_LINK && HEADERS_INSTALL
|
||||
|
@ -5,6 +5,7 @@ subdir-$(CONFIG_SAMPLE_AUXDISPLAY) += auxdisplay
|
||||
subdir-$(CONFIG_SAMPLE_ANDROID_BINDERFS) += binderfs
|
||||
obj-$(CONFIG_SAMPLE_CONFIGFS) += configfs/
|
||||
obj-$(CONFIG_SAMPLE_CONNECTOR) += connector/
|
||||
obj-$(CONFIG_SAMPLE_FANOTIFY_ERROR) += fanotify/
|
||||
subdir-$(CONFIG_SAMPLE_HIDRAW) += hidraw
|
||||
obj-$(CONFIG_SAMPLE_HW_BREAKPOINT) += hw_breakpoint/
|
||||
obj-$(CONFIG_SAMPLE_KDB) += kdb/
|
||||
|
5
samples/fanotify/Makefile
Normal file
5
samples/fanotify/Makefile
Normal file
@ -0,0 +1,5 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
userprogs-always-y += fs-monitor
|
||||
|
||||
userccflags += -I usr/include -Wall
|
||||
|
142
samples/fanotify/fs-monitor.c
Normal file
142
samples/fanotify/fs-monitor.c
Normal file
@ -0,0 +1,142 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright 2021, Collabora Ltd.
|
||||
*/
|
||||
|
||||
#define _GNU_SOURCE
|
||||
#include <errno.h>
|
||||
#include <err.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/fanotify.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#ifndef FAN_FS_ERROR
|
||||
#define FAN_FS_ERROR 0x00008000
|
||||
#define FAN_EVENT_INFO_TYPE_ERROR 5
|
||||
|
||||
struct fanotify_event_info_error {
|
||||
struct fanotify_event_info_header hdr;
|
||||
__s32 error;
|
||||
__u32 error_count;
|
||||
};
|
||||
#endif
|
||||
|
||||
#ifndef FILEID_INO32_GEN
|
||||
#define FILEID_INO32_GEN 1
|
||||
#endif
|
||||
|
||||
#ifndef FILEID_INVALID
|
||||
#define FILEID_INVALID 0xff
|
||||
#endif
|
||||
|
||||
static void print_fh(struct file_handle *fh)
|
||||
{
|
||||
int i;
|
||||
uint32_t *h = (uint32_t *) fh->f_handle;
|
||||
|
||||
printf("\tfh: ");
|
||||
for (i = 0; i < fh->handle_bytes; i++)
|
||||
printf("%hhx", fh->f_handle[i]);
|
||||
printf("\n");
|
||||
|
||||
printf("\tdecoded fh: ");
|
||||
if (fh->handle_type == FILEID_INO32_GEN)
|
||||
printf("inode=%u gen=%u\n", h[0], h[1]);
|
||||
else if (fh->handle_type == FILEID_INVALID && !fh->handle_bytes)
|
||||
printf("Type %d (Superblock error)\n", fh->handle_type);
|
||||
else
|
||||
printf("Type %d (Unknown)\n", fh->handle_type);
|
||||
|
||||
}
|
||||
|
||||
static void handle_notifications(char *buffer, int len)
|
||||
{
|
||||
struct fanotify_event_metadata *event =
|
||||
(struct fanotify_event_metadata *) buffer;
|
||||
struct fanotify_event_info_header *info;
|
||||
struct fanotify_event_info_error *err;
|
||||
struct fanotify_event_info_fid *fid;
|
||||
int off;
|
||||
|
||||
for (; FAN_EVENT_OK(event, len); event = FAN_EVENT_NEXT(event, len)) {
|
||||
|
||||
if (event->mask != FAN_FS_ERROR) {
|
||||
printf("unexpected FAN MARK: %llx\n",
|
||||
(unsigned long long)event->mask);
|
||||
goto next_event;
|
||||
}
|
||||
|
||||
if (event->fd != FAN_NOFD) {
|
||||
printf("Unexpected fd (!= FAN_NOFD)\n");
|
||||
goto next_event;
|
||||
}
|
||||
|
||||
printf("FAN_FS_ERROR (len=%d)\n", event->event_len);
|
||||
|
||||
for (off = sizeof(*event) ; off < event->event_len;
|
||||
off += info->len) {
|
||||
info = (struct fanotify_event_info_header *)
|
||||
((char *) event + off);
|
||||
|
||||
switch (info->info_type) {
|
||||
case FAN_EVENT_INFO_TYPE_ERROR:
|
||||
err = (struct fanotify_event_info_error *) info;
|
||||
|
||||
printf("\tGeneric Error Record: len=%d\n",
|
||||
err->hdr.len);
|
||||
printf("\terror: %d\n", err->error);
|
||||
printf("\terror_count: %d\n", err->error_count);
|
||||
break;
|
||||
|
||||
case FAN_EVENT_INFO_TYPE_FID:
|
||||
fid = (struct fanotify_event_info_fid *) info;
|
||||
|
||||
printf("\tfsid: %x%x\n",
|
||||
fid->fsid.val[0], fid->fsid.val[1]);
|
||||
print_fh((struct file_handle *) &fid->handle);
|
||||
break;
|
||||
|
||||
default:
|
||||
printf("\tUnknown info type=%d len=%d:\n",
|
||||
info->info_type, info->len);
|
||||
}
|
||||
}
|
||||
next_event:
|
||||
printf("---\n\n");
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
int fd;
|
||||
|
||||
char buffer[BUFSIZ];
|
||||
|
||||
if (argc < 2) {
|
||||
printf("Missing path argument\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
fd = fanotify_init(FAN_CLASS_NOTIF|FAN_REPORT_FID, O_RDONLY);
|
||||
if (fd < 0)
|
||||
errx(1, "fanotify_init");
|
||||
|
||||
if (fanotify_mark(fd, FAN_MARK_ADD|FAN_MARK_FILESYSTEM,
|
||||
FAN_FS_ERROR, AT_FDCWD, argv[1])) {
|
||||
errx(1, "fanotify_mark");
|
||||
}
|
||||
|
||||
while (1) {
|
||||
int n = read(fd, buffer, BUFSIZ);
|
||||
|
||||
if (n < 0)
|
||||
errx(1, "read");
|
||||
|
||||
handle_notifications(buffer, n);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
Loading…
Reference in New Issue
Block a user