fanotify: introduce a generic info record copying helper

The copy_info_records_to_user() helper allows for the separation of
info record copying routines/conditionals from copy_event_to_user(),
which reduces the overall clutter within this function. This becomes
especially true as we start introducing additional info records in the
future i.e. struct fanotify_event_info_pidfd. On success, this helper
returns the total amount of bytes that have been copied into the user
supplied buffer and on error, a negative value is returned to the
caller.

The newly defined macro FANOTIFY_INFO_MODES can be used to obtain info
record types that have been enabled for a specific notification
group. This macro becomes useful in the subsequent patch when the
FAN_REPORT_PIDFD initialization flag is introduced.

Link: https://lore.kernel.org/r/8872947dfe12ce8ae6e9a7f2d49ea29bc8006af0.1628398044.git.repnop@google.com
Signed-off-by: Matthew Bobrowski <repnop@google.com>
Reviewed-by: Amir Goldstein <amir73il@gmail.com>
Signed-off-by: Jan Kara <jack@suse.cz>
This commit is contained in:
Matthew Bobrowski 2021-08-08 15:25:58 +10:00 committed by Jan Kara
parent d3424c9bac
commit 0aca67bb7f
2 changed files with 90 additions and 67 deletions

View file

@ -173,7 +173,7 @@ static struct fanotify_event *get_one_event(struct fsnotify_group *group,
size_t event_size = FAN_EVENT_METADATA_LEN;
struct fanotify_event *event = NULL;
struct fsnotify_event *fsn_event;
unsigned int fid_mode = FAN_GROUP_FLAG(group, FANOTIFY_FID_BITS);
unsigned int info_mode = FAN_GROUP_FLAG(group, FANOTIFY_INFO_MODES);
pr_debug("%s: group=%p count=%zd\n", __func__, group, count);
@ -183,8 +183,8 @@ static struct fanotify_event *get_one_event(struct fsnotify_group *group,
goto out;
event = FANOTIFY_E(fsn_event);
if (fid_mode)
event_size += fanotify_event_info_len(fid_mode, event);
if (info_mode)
event_size += fanotify_event_info_len(info_mode, event);
if (event_size > count) {
event = ERR_PTR(-EINVAL);
@ -401,6 +401,86 @@ static int copy_fid_info_to_user(__kernel_fsid_t *fsid, struct fanotify_fh *fh,
return info_len;
}
static int copy_info_records_to_user(struct fanotify_event *event,
struct fanotify_info *info,
unsigned int info_mode,
char __user *buf, size_t count)
{
int ret, total_bytes = 0, info_type = 0;
unsigned int fid_mode = info_mode & FANOTIFY_FID_BITS;
/*
* Event info records order is as follows: dir fid + name, child fid.
*/
if (fanotify_event_dir_fh_len(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),
fanotify_info_dir_fh(info),
info_type,
fanotify_info_name(info),
info->name_len, buf, count);
if (ret < 0)
return ret;
buf += ret;
count -= ret;
total_bytes += ret;
}
if (fanotify_event_object_fh_len(event)) {
const char *dot = NULL;
int dot_len = 0;
if (fid_mode == FAN_REPORT_FID || info_type) {
/*
* With only group flag FAN_REPORT_FID only type FID is
* reported. Second info record type is always FID.
*/
info_type = FAN_EVENT_INFO_TYPE_FID;
} else if ((fid_mode & FAN_REPORT_NAME) &&
(event->mask & FAN_ONDIR)) {
/*
* With group flag FAN_REPORT_NAME, if name was not
* recorded in an event on a directory, report the name
* "." with info type DFID_NAME.
*/
info_type = FAN_EVENT_INFO_TYPE_DFID_NAME;
dot = ".";
dot_len = 1;
} else if ((event->mask & ALL_FSNOTIFY_DIRENT_EVENTS) ||
(event->mask & FAN_ONDIR)) {
/*
* With group flag FAN_REPORT_DIR_FID, a single info
* record has type DFID for directory entry modification
* event and for event on a directory.
*/
info_type = FAN_EVENT_INFO_TYPE_DFID;
} else {
/*
* With group flags FAN_REPORT_DIR_FID|FAN_REPORT_FID,
* a single info record has type FID for event on a
* non-directory, when there is no directory to report.
* For example, on FAN_DELETE_SELF event.
*/
info_type = FAN_EVENT_INFO_TYPE_FID;
}
ret = copy_fid_info_to_user(fanotify_event_fsid(event),
fanotify_event_object_fh(event),
info_type, dot, dot_len,
buf, count);
if (ret < 0)
return ret;
buf += ret;
count -= ret;
total_bytes += ret;
}
return total_bytes;
}
static ssize_t copy_event_to_user(struct fsnotify_group *group,
struct fanotify_event *event,
char __user *buf, size_t count)
@ -408,15 +488,14 @@ static ssize_t copy_event_to_user(struct fsnotify_group *group,
struct fanotify_event_metadata metadata;
struct path *path = fanotify_event_path(event);
struct fanotify_info *info = fanotify_event_info(event);
unsigned int fid_mode = FAN_GROUP_FLAG(group, FANOTIFY_FID_BITS);
unsigned int info_mode = FAN_GROUP_FLAG(group, FANOTIFY_INFO_MODES);
struct file *f = NULL;
int ret, fd = FAN_NOFD;
int info_type = 0;
pr_debug("%s: group=%p event=%p\n", __func__, group, event);
metadata.event_len = FAN_EVENT_METADATA_LEN +
fanotify_event_info_len(fid_mode, event);
fanotify_event_info_len(info_mode, event);
metadata.metadata_len = FAN_EVENT_METADATA_LEN;
metadata.vers = FANOTIFY_METADATA_VERSION;
metadata.reserved = 0;
@ -465,69 +544,11 @@ static ssize_t copy_event_to_user(struct fsnotify_group *group,
if (f)
fd_install(fd, f);
/* Event info records order is: dir fid + name, child fid */
if (fanotify_event_dir_fh_len(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),
fanotify_info_dir_fh(info),
info_type,
fanotify_info_name(info),
info->name_len, buf, count);
if (info_mode) {
ret = copy_info_records_to_user(event, info, info_mode,
buf, count);
if (ret < 0)
goto out_close_fd;
buf += ret;
count -= ret;
}
if (fanotify_event_object_fh_len(event)) {
const char *dot = NULL;
int dot_len = 0;
if (fid_mode == FAN_REPORT_FID || info_type) {
/*
* With only group flag FAN_REPORT_FID only type FID is
* reported. Second info record type is always FID.
*/
info_type = FAN_EVENT_INFO_TYPE_FID;
} else if ((fid_mode & FAN_REPORT_NAME) &&
(event->mask & FAN_ONDIR)) {
/*
* With group flag FAN_REPORT_NAME, if name was not
* recorded in an event on a directory, report the
* name "." with info type DFID_NAME.
*/
info_type = FAN_EVENT_INFO_TYPE_DFID_NAME;
dot = ".";
dot_len = 1;
} else if ((event->mask & ALL_FSNOTIFY_DIRENT_EVENTS) ||
(event->mask & FAN_ONDIR)) {
/*
* With group flag FAN_REPORT_DIR_FID, a single info
* record has type DFID for directory entry modification
* event and for event on a directory.
*/
info_type = FAN_EVENT_INFO_TYPE_DFID;
} else {
/*
* With group flags FAN_REPORT_DIR_FID|FAN_REPORT_FID,
* a single info record has type FID for event on a
* non-directory, when there is no directory to report.
* For example, on FAN_DELETE_SELF event.
*/
info_type = FAN_EVENT_INFO_TYPE_FID;
}
ret = copy_fid_info_to_user(fanotify_event_fsid(event),
fanotify_event_object_fh(event),
info_type, dot, dot_len,
buf, count);
if (ret < 0)
goto out_close_fd;
buf += ret;
count -= ret;
}
return metadata.event_len;

View file

@ -27,6 +27,8 @@ extern struct ctl_table fanotify_table[]; /* for sysctl */
#define FANOTIFY_FID_BITS (FAN_REPORT_FID | FAN_REPORT_DFID_NAME)
#define FANOTIFY_INFO_MODES (FANOTIFY_FID_BITS)
/*
* fanotify_init() flags that require CAP_SYS_ADMIN.
* We do not allow unprivileged groups to request permission events.