[PATCH 3/3] fanotify: support reporting events with fid on btrfs sub-volumes

Amir Goldstein
2023-10-25T13:51:08Z

Setting fanotify marks that report events with fid on btrfs sub-volumes
is not supported, because in the case of btrfs sub-volumes, there is no
uniform fsid that can be cached in the sb connector.

If filesystem supports the cheap ->get_fsid() operation, do not use the
cached fsid in connector and query fsid from inode on every event.
This allows setting fanotify marks that report events with fid on btrfs
sub-volumes.

Signed-off-by: Amir Goldstein <amir73il@gmail.com>
---
fs/notify/fanotify/fanotify.c | 28 +++++++++++++++++++++-------
fs/notify/fanotify/fanotify_user.c | 14 ++++++++++----
2 files changed, 31 insertions(+), 11 deletions(-)

diff --git a/fs/notify/fanotify/fanotify.c b/fs/notify/fanotify/fanotify.c
index 9dac7f6e72d2..25282ca0173d 100644
--- a/fs/notify/fanotify/fanotify.c
+++ b/fs/notify/fanotify/fanotify.c
@@ -535,6 +535,21 @@ static struct inode *fanotify_dfid_inode(u32 event_mask, const void *data,
return dir;
}

+/*
+ * If @inode is available and if filesystem supports ->get_fsid(), return the
+ * fsid associated with the inode. Otherwise, return the pre cached fsid.
+ */
+static __kernel_fsid_t fanotify_inode_fsid(struct inode *inode,
+ __kernel_fsid_t *fsid)
+{
+ __kernel_fsid_t __fsid;
+
+ if (inode && inode_get_fsid(inode, &__fsid) == 0)
+ return __fsid;
+
+ return *fsid;
+}
+
static struct fanotify_event *fanotify_alloc_path_event(const struct path *path,
unsigned int *hash,
gfp_t gfp)
@@ -586,8 +601,8 @@ static struct fanotify_event *fanotify_alloc_fid_event(struct inode *id,
return NULL;

ffe->fae.type = FANOTIFY_EVENT_TYPE_FID;
- ffe->fsid = *fsid;
- *hash ^= fanotify_hash_fsid(fsid);
+ ffe->fsid = fanotify_inode_fsid(id, fsid);
+ *hash ^= fanotify_hash_fsid(&ffe->fsid);
fanotify_encode_fh(&ffe->object_fh, id, fanotify_encode_fh_len(id),
hash, gfp);

@@ -627,8 +642,8 @@ static struct fanotify_event *fanotify_alloc_name_event(struct inode *dir,
return NULL;

fne->fae.type = FANOTIFY_EVENT_TYPE_FID_NAME;
- fne->fsid = *fsid;
- *hash ^= fanotify_hash_fsid(fsid);
+ fne->fsid = fanotify_inode_fsid(dir, fsid);
+ *hash ^= fanotify_hash_fsid(&fne->fsid);
info = &fne->info;
fanotify_info_init(info);
if (dir_fh_len) {
@@ -691,9 +706,10 @@ static struct fanotify_event *fanotify_alloc_error_event(
fee->fae.type = FANOTIFY_EVENT_TYPE_FS_ERROR;
fee->error = report->error;
fee->err_count = 1;
- fee->fsid = *fsid;

inode = report->inode;
+ fee->fsid = fanotify_inode_fsid(inode, fsid);
+ *hash ^= fanotify_hash_fsid(&fee->fsid);
fh_len = fanotify_encode_fh_len(inode);

/* Bad fh_len. Fallback to using an invalid fh. Should never happen. */
@@ -702,8 +718,6 @@ static struct fanotify_event *fanotify_alloc_error_event(

fanotify_encode_fh(&fee->object_fh, inode, fh_len, NULL, 0);

- *hash ^= fanotify_hash_fsid(fsid);
-
return &fee->fae;
}

diff --git a/fs/notify/fanotify/fanotify_user.c b/fs/notify/fanotify/fanotify_user.c
index 0eb9622e8a9f..ed67e5f973ab 100644
--- a/fs/notify/fanotify/fanotify_user.c
+++ b/fs/notify/fanotify/fanotify_user.c
@@ -1570,16 +1570,22 @@ static int fanotify_test_fsid(struct dentry *dentry, __kernel_fsid_t *fsid)
return -ENODEV;

/*
- * Make sure dentry is not of a filesystem subvolume (e.g. btrfs)
- * which uses a different fsid than sb root.
+ * If dentry is on a filesystem subvolume (e.g. btrfs), which uses a
+ * different fsid than sb root, then make sure that filesytem supports
+ * getting fsid from inode.
*/
err = vfs_get_fsid(dentry->d_sb->s_root, &root_fsid);
if (err)
return err;

if (root_fsid.val[0] != fsid->val[0] ||
- root_fsid.val[1] != fsid->val[1])
- return -EXDEV;
+ root_fsid.val[1] != fsid->val[1]) {
+ if (!dentry->d_sb->s_op->get_fsid)
+ return -EXDEV;
+
+ /* Cache root fsid in case inode is not available on event */
+ *fsid = root_fsid;
+ }

return 0;
}
--
2.34.1