The FAN_ERROR mark is used by filesystem wide monitoring tools to receive notifications of type FS_ERROR_EVENT, emited by filesystems when a problem is detected. The error notification includes a generic error descriptor, an optional location record and a filesystem specific blob. Signed-off-by: Gabriel Krisman Bertazi <krisman@xxxxxxxxxxxxx> --- fs/notify/fanotify/fanotify.c | 48 +++++++++++++++++++---- fs/notify/fanotify/fanotify.h | 8 ++++ fs/notify/fanotify/fanotify_user.c | 63 ++++++++++++++++++++++++++++++ include/linux/fanotify.h | 9 ++++- include/uapi/linux/fanotify.h | 2 + 5 files changed, 120 insertions(+), 10 deletions(-) diff --git a/fs/notify/fanotify/fanotify.c b/fs/notify/fanotify/fanotify.c index 98591a8155a7..6bae23d42e5e 100644 --- a/fs/notify/fanotify/fanotify.c +++ b/fs/notify/fanotify/fanotify.c @@ -240,12 +240,14 @@ static u32 fanotify_group_event_mask(struct fsnotify_group *group, __func__, iter_info->report_mask, event_mask, data, data_type); if (!fid_mode) { - /* Do we have path to open a file descriptor? */ - if (!path) - return 0; - /* Path type events are only relevant for files and dirs */ - if (!d_is_reg(path->dentry) && !d_can_lookup(path->dentry)) - return 0; + if (!fanotify_is_error_event(event_mask)) { + /* Do we have path to open a file descriptor? */ + if (!path) + return 0; + /* Path type events are only relevant for files and dirs */ + if (!d_is_reg(path->dentry) && !d_can_lookup(path->dentry)) + return 0; + } } else if (!(fid_mode & FAN_REPORT_FID)) { /* Do we have a directory inode to report? */ if (!dir && !(event_mask & FS_ISDIR)) @@ -458,6 +460,25 @@ static struct fanotify_event *fanotify_alloc_perm_event(const struct path *path, return &pevent->fae; } +static void fanotify_init_error_event(struct fanotify_event *fae, + const struct fs_error_report *report, + __kernel_fsid_t *fsid) +{ + struct fanotify_error_event *fee; + + fae->type = FANOTIFY_EVENT_TYPE_ERROR; + fee = FANOTIFY_EE(fae); + fee->error = report->error; + fee->fsid = *fsid; + + fee->loc.line = report->line; + fee->loc.function = report->function; + + fee->fs_data_size = report->fs_data_size; + + memcpy(&fee->fs_data, report->fs_data, report->fs_data_size); +} + static struct fanotify_event *fanotify_alloc_fid_event(struct inode *id, __kernel_fsid_t *fsid, gfp_t gfp) @@ -618,6 +639,13 @@ static struct fanotify_event *fanotify_ring_get_slot(struct fsnotify_group *grou { size_t size = 0; + if (fanotify_is_error_event(mask)) { + const struct fs_error_report *report = data; + size = sizeof(struct fanotify_error_event) + report->fs_data_size; + } else { + return ERR_PTR(-EINVAL); + } + pr_debug("%s: group=%p mask=%x size=%lu\n", __func__, group, mask, size); return FANOTIFY_E(fsnotify_ring_alloc_event_slot(group, size)); @@ -629,6 +657,9 @@ static void fanotify_ring_write_event(struct fsnotify_group *group, { fanotify_init_event(group, event, 0, mask); + if (fanotify_is_error_event(mask)) + fanotify_init_error_event(event, data, fsid); + event->pid = get_pid(task_tgid(current)); } @@ -695,8 +726,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_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); @@ -714,7 +746,7 @@ static int fanotify_handle_event(struct fsnotify_group *group, u32 mask, return 0; } - if (FAN_GROUP_FLAG(group, FANOTIFY_FID_BITS)) { + if (FAN_GROUP_FLAG(group, FANOTIFY_FID_BITS) || mask == FAN_ERROR) { fsid = fanotify_get_fsid(iter_info); /* Racing with mark destruction or creation? */ if (!fsid.val[0] && !fsid.val[1]) diff --git a/fs/notify/fanotify/fanotify.h b/fs/notify/fanotify/fanotify.h index 0d1b4cb8b005..097667be9079 100644 --- a/fs/notify/fanotify/fanotify.h +++ b/fs/notify/fanotify/fanotify.h @@ -135,6 +135,7 @@ enum fanotify_event_type { FANOTIFY_EVENT_TYPE_PATH, FANOTIFY_EVENT_TYPE_PATH_PERM, FANOTIFY_EVENT_TYPE_OVERFLOW, /* struct fanotify_event */ + FANOTIFY_EVENT_TYPE_ERROR, }; struct fanotify_event { @@ -207,6 +208,8 @@ static inline __kernel_fsid_t *fanotify_event_fsid(struct fanotify_event *event) 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_ERROR) + return &FANOTIFY_EE(event)->fsid; else return NULL; } @@ -292,6 +295,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 & FANOTIFY_ERROR_EVENTS; +} + static inline bool fanotify_event_has_path(struct fanotify_event *event) { return event->type == FANOTIFY_EVENT_TYPE_PATH || diff --git a/fs/notify/fanotify/fanotify_user.c b/fs/notify/fanotify/fanotify_user.c index e2f4599dfc25..6270083bee36 100644 --- a/fs/notify/fanotify/fanotify_user.c +++ b/fs/notify/fanotify/fanotify_user.c @@ -96,6 +96,24 @@ static size_t fanotify_event_len(struct fanotify_event *event, int fh_len; int dot_len = 0; + if (fanotify_is_error_event(event->mask)) { + struct fanotify_error_event *fee = FANOTIFY_EE(event); + /* + * Error events (FAN_ERROR) have a different format + * as follows: + * [ event_metadata ] + * [ fs-generic error header ] + * [ error location (optional) ] + * [ fs-specific blob ] + */ + event_len = fanotify_error_info_len(fee); + if (fee->loc.function) + event_len += fanotify_location_info_len(&fee->loc); + if (fee->fs_data) + event_len += fanotify_error_fsdata_len(fee); + return event_len; + } + if (!fid_mode) return event_len; @@ -322,6 +340,38 @@ static ssize_t copy_error_fsdata_info_to_user(struct fanotify_error_event *fee, return info.hdr.len; } +static int copy_error_event_to_user(struct fanotify_event *event, + char __user *buf, int count) +{ + struct fanotify_error_event *fee = FANOTIFY_EE(event); + ssize_t len; + int original_count = count; + + len = copy_error_info_to_user(fee, buf, count); + if (len < 0) + return -EFAULT; + buf += len; + count -= len; + + if (fee->loc.function) { + len = copy_location_info_to_user(&fee->loc, buf, count); + if (len < 0) + return len; + buf += len; + count -= len; + } + + if (fee->fs_data_size) { + len = copy_error_fsdata_info_to_user(fee, buf, count); + if (len < 0) + return len; + buf += len; + count -= len; + } + + return original_count - count; +} + static int copy_info_to_user(__kernel_fsid_t *fsid, struct fanotify_fh *fh, int info_type, const char *name, size_t name_len, char __user *buf, size_t count) @@ -528,6 +578,9 @@ static ssize_t copy_event_to_user(struct fsnotify_group *group, count -= ret; } + if (fanotify_is_error_event(event->mask)) + ret = copy_error_event_to_user(event, buf, count); + return metadata.event_len; out_close_fd: @@ -1328,6 +1381,10 @@ static int do_fanotify_mark(int fanotify_fd, unsigned int flags, __u64 mask, (mask & ~FANOTIFY_SUBMISSION_BUFFER_EVENTS)) goto fput_and_out; + if (fanotify_is_error_event(mask) && + !(group->flags & FSN_SUBMISSION_RING_BUFFER)) + goto fput_and_out; + ret = fanotify_find_path(dfd, pathname, &path, flags, (mask & ALL_FSNOTIFY_EVENTS), obj_type); if (ret) @@ -1350,6 +1407,12 @@ static int do_fanotify_mark(int fanotify_fd, unsigned int flags, __u64 mask, fsid = &__fsid; } + if (mask & FAN_ERROR) { + ret = fanotify_check_path_fsid(&path, &__fsid); + if (ret) + goto path_put_and_out; + fsid = &__fsid; + } /* inode held in place by reference to path; group by fget on fd */ if (mark_type == FAN_MARK_INODE) diff --git a/include/linux/fanotify.h b/include/linux/fanotify.h index 5a4cefb4b1c3..e08be5fae14a 100644 --- a/include/linux/fanotify.h +++ b/include/linux/fanotify.h @@ -56,9 +56,13 @@ #define FANOTIFY_INODE_EVENTS (FANOTIFY_DIRENT_EVENTS | \ FAN_ATTRIB | FAN_MOVE_SELF | FAN_DELETE_SELF) +#define FANOTIFY_ERROR_EVENTS (FAN_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 | \ @@ -70,9 +74,10 @@ /* Events that may be reported to user */ #define FANOTIFY_OUTGOING_EVENTS (FANOTIFY_EVENTS | \ FANOTIFY_PERM_EVENTS | \ + FANOTIFY_ERROR_EVENTS | \ FAN_Q_OVERFLOW | FAN_ONDIR) -#define FANOTIFY_SUBMISSION_BUFFER_EVENTS 0 +#define FANOTIFY_SUBMISSION_BUFFER_EVENTS FANOTIFY_ERROR_EVENTS #define ALL_FANOTIFY_EVENT_BITS (FANOTIFY_OUTGOING_EVENTS | \ FANOTIFY_EVENT_FLAGS) diff --git a/include/uapi/linux/fanotify.h b/include/uapi/linux/fanotify.h index 49808c857ee1..ee0ae8b1e20b 100644 --- a/include/uapi/linux/fanotify.h +++ b/include/uapi/linux/fanotify.h @@ -25,6 +25,8 @@ #define FAN_ACCESS_PERM 0x00020000 /* File accessed in perm check */ #define FAN_OPEN_EXEC_PERM 0x00040000 /* File open/exec in perm check */ +#define FAN_ERROR 0x00100000 /* Filesystem error */ + #define FAN_EVENT_ON_CHILD 0x08000000 /* Interested in child events */ #define FAN_ONDIR 0x40000000 /* Event occurred against dir */ -- 2.31.0