From 3cc1980ba1ab0f17972aad114323917dec6f0b8d Mon Sep 17 00:00:00 2001 From: Mauro Ezequiel Moltrasio Date: Thu, 9 Jul 2026 16:25:25 +0200 Subject: [PATCH] ROX-33036: add mount related operations This was originally going to be about adding just `lsm/sb_mount`, however while adding this hook it became pretty clear we needed `lsm/sb_umount` and `lsm/move_mount` for a comprehensive implementation and it really didn't add too much code, so they are all added in. These operations are not currently intended to be forwarded via gRPC, they only trigger inode tracking related behavior (scans on new/moved mounts, inode map cleanups on moved/unmounted directories). The move mount operation shares quite a bit of similarities with rename, so there is a bit of refactoring mixed in so they can be reused while keeping the code clean. TODO: add integration tests. --- fact-ebpf/src/bpf/bound_path.h | 8 +- fact-ebpf/src/bpf/events.h | 39 ++++- fact-ebpf/src/bpf/main.c | 109 ++++++++++++- fact-ebpf/src/bpf/types.h | 8 +- fact-ebpf/src/lib.rs | 3 + fact/src/event/mod.rs | 243 ++++++++++++++++++----------- fact/src/host_scanner.rs | 77 +++++++++ fact/src/metrics/kernel_metrics.rs | 3 + 8 files changed, 389 insertions(+), 101 deletions(-) diff --git a/fact-ebpf/src/bpf/bound_path.h b/fact-ebpf/src/bpf/bound_path.h index 7a2091f9..cf249b68 100644 --- a/fact-ebpf/src/bpf/bound_path.h +++ b/fact-ebpf/src/bpf/bound_path.h @@ -38,8 +38,12 @@ __always_inline static struct bound_path_t* _path_read(struct path* path, bound_ return bound_path; } -__always_inline static struct bound_path_t* path_read_unchecked(struct path* path) { - return _path_read(path, BOUND_PATH_MAIN, true); +__always_inline static struct bound_path_t* path_read_unchecked(struct path* path, bool use_bpf_d_path) { + return _path_read(path, BOUND_PATH_MAIN, use_bpf_d_path); +} + +__always_inline static struct bound_path_t* path_read_alt_unchecked(struct path* path, bool use_bpf_d_path) { + return _path_read(path, BOUND_PATH_ALTERNATE, use_bpf_d_path); } __always_inline static struct bound_path_t* path_read(struct path* path) { diff --git a/fact-ebpf/src/bpf/events.h b/fact-ebpf/src/bpf/events.h index 00d91027..249daf53 100644 --- a/fact-ebpf/src/bpf/events.h +++ b/fact-ebpf/src/bpf/events.h @@ -123,9 +123,9 @@ __always_inline static void submit_rename_event(struct submit_event_args_t* args } args->event->type = FILE_ACTIVITY_RENAME; - bpf_probe_read_str(args->event->rename.filename, PATH_MAX, old_filename); - inode_copy(&args->event->rename.inode, old_inode); - args->event->rename.monitored = old_monitored; + bpf_probe_read_str(args->event->from.filename, PATH_MAX, old_filename); + inode_copy(&args->event->from.inode, old_inode); + args->event->from.monitored = old_monitored; __submit_event(args, path_hooks_support_bpf_d_path); } @@ -212,3 +212,36 @@ __always_inline static void submit_acl_event(struct submit_event_args_t* args, // inode_set_acl does not support bpf_d_path (no struct path available) __submit_event(args, false); } + +__always_inline static void submit_mount_event(struct submit_event_args_t* args) { + if (!reserve_event(args)) { + return; + } + args->event->type = FILE_ACTIVITY_MOUNT; + + __submit_event(args, true); +} + +__always_inline static void submit_umount_event(struct submit_event_args_t* args) { + if (!reserve_event(args)) { + return; + } + args->event->type = FILE_ACTIVITY_UMOUNT; + + __submit_event(args, true); +} + +__always_inline static void submit_move_mount_event(struct submit_event_args_t* args, + const char from_filename[PATH_MAX], + inode_key_t* from_inode, + monitored_t from_monitored) { + if (!reserve_event(args)) { + return; + } + args->event->type = FILE_ACTIVITY_MOVE_MOUNT; + bpf_probe_read_str(args->event->from.filename, PATH_MAX, from_filename); + inode_copy(&args->event->from.inode, from_inode); + args->event->from.monitored = from_monitored; + + __submit_event(args, false); +} diff --git a/fact-ebpf/src/bpf/main.c b/fact-ebpf/src/bpf/main.c index b880a573..7f2a5919 100644 --- a/fact-ebpf/src/bpf/main.c +++ b/fact-ebpf/src/bpf/main.c @@ -54,7 +54,7 @@ int BPF_PROG(trace_file_open, struct file* file) { } } - struct bound_path_t* path = path_read_unchecked(&file->f_path); + struct bound_path_t* path = path_read_unchecked(&file->f_path, true); if (path == NULL) { bpf_printk("Failed to read path"); m->file_open.error++; @@ -484,3 +484,110 @@ int BPF_PROG(trace_path_rmdir, struct path* dir, struct dentry* dentry) { submit_rmdir_event(&args); return 0; } + +SEC("lsm/sb_mount") +int BPF_PROG(trace_sb_mount, const char* dev_name, struct path* path, const char* type, unsigned long flags, void* data) { + struct metrics_t* m = get_metrics(); + if (m == NULL) { + return 0; + } + struct submit_event_args_t args = {.metrics = &m->sb_mount}; + args.metrics->total++; + + struct bound_path_t* bound_path = path_read_unchecked(path, true); + if (bound_path == NULL) { + bpf_printk("Failed to read mount directory"); + args.metrics->error++; + return 0; + } + args.filename = bound_path->path; + + args.inode = inode_to_key(path->dentry->d_inode); + + struct dentry* parent_dentry = BPF_CORE_READ(path, dentry, d_parent); + struct inode* parent_inode_ptr = parent_dentry ? BPF_CORE_READ(parent_dentry, d_inode) : NULL; + args.parent_inode = inode_to_key(parent_inode_ptr); + + args.monitored = is_monitored(&args.inode, bound_path, &args.parent_inode); + if (args.monitored == NOT_MONITORED) { + args.metrics->ignored++; + return 0; + } + + submit_mount_event(&args); + + return 0; +} + +SEC("lsm/sb_umount") +int BPF_PROG(trace_sb_umount, struct vfsmount* mnt, int flags) { + struct metrics_t* m = get_metrics(); + if (m == NULL) { + return 0; + } + struct submit_event_args_t args = {.metrics = &m->sb_mount}; + args.metrics->total++; + + struct path p = {.dentry = BPF_CORE_READ(mnt, mnt_root), .mnt = mnt}; + struct bound_path_t* bound_path = _path_read(&p, BOUND_PATH_MAIN, false); + if (bound_path == NULL) { + bpf_printk("Failed to read umount directory"); + args.metrics->error++; + return 0; + } + args.filename = bound_path->path; + + args.inode = inode_to_key(mnt->mnt_root->d_inode); + + struct dentry* parent_dentry = BPF_CORE_READ(mnt, mnt_root, d_parent); + struct inode* parent_inode_ptr = parent_dentry ? BPF_CORE_READ(parent_dentry, d_inode) : NULL; + args.parent_inode = inode_to_key(parent_inode_ptr); + + args.monitored = is_monitored(&args.inode, bound_path, &args.parent_inode); + if (args.monitored == NOT_MONITORED) { + args.metrics->ignored++; + return 0; + } + + submit_umount_event(&args); + + return 0; +} + +SEC("lsm/move_mount") +int BPF_PROG(trace_move_mount, struct path* from, struct path* to) { + struct metrics_t* m = get_metrics(); + if (m == NULL) { + return 0; + } + struct submit_event_args_t args = {.metrics = &m->move_mount}; + + args.metrics->total++; + + struct bound_path_t* to_path = path_read_unchecked(to, false); + if (to_path == NULL) { + bpf_printk("Failed to read to_path"); + goto error; + } + args.filename = to_path->path; + + struct bound_path_t* from_path = path_read_alt_unchecked(from, false); + if (from_path == NULL) { + bpf_printk("Failed to read from_path"); + goto error; + } + + args.inode = inode_to_key(to->dentry->d_inode); + args.parent_inode = inode_to_key(to->dentry->d_inode); + args.monitored = is_monitored(&args.inode, to_path, &args.parent_inode); + + inode_key_t from_inode = inode_to_key(from->dentry->d_inode); + monitored_t from_monitored = is_monitored(&from_inode, from_path, NULL); + + submit_move_mount_event(&args, from_path->path, &from_inode, from_monitored); + return 0; + +error: + args.metrics->error++; + return 0; +} diff --git a/fact-ebpf/src/bpf/types.h b/fact-ebpf/src/bpf/types.h index ce496672..80b94d22 100644 --- a/fact-ebpf/src/bpf/types.h +++ b/fact-ebpf/src/bpf/types.h @@ -115,6 +115,9 @@ typedef enum file_activity_type_t { FILE_ACTIVITY_SETXATTR, FILE_ACTIVITY_REMOVEXATTR, FILE_ACTIVITY_ACL_SET, + FILE_ACTIVITY_MOUNT, + FILE_ACTIVITY_UMOUNT, + FILE_ACTIVITY_MOVE_MOUNT, } file_activity_type_t; struct event_t { @@ -140,7 +143,7 @@ struct event_t { char filename[PATH_MAX]; inode_key_t inode; monitored_t monitored; - } rename; + } from; // Used by events that have two paths (like rename or move_mount). struct { char name[XATTR_NAME_MAX_LEN]; } xattr; @@ -194,4 +197,7 @@ struct metrics_t { struct metrics_by_hook_t inode_setxattr; struct metrics_by_hook_t inode_removexattr; struct metrics_by_hook_t inode_set_acl; + struct metrics_by_hook_t sb_mount; + struct metrics_by_hook_t sb_umount; + struct metrics_by_hook_t move_mount; }; diff --git a/fact-ebpf/src/lib.rs b/fact-ebpf/src/lib.rs index 4117e983..57acf1c0 100644 --- a/fact-ebpf/src/lib.rs +++ b/fact-ebpf/src/lib.rs @@ -158,6 +158,9 @@ impl_metrics_t!( inode_setxattr, inode_removexattr, inode_set_acl, + sb_mount, + sb_umount, + move_mount, ); unsafe impl Pod for metrics_t {} diff --git a/fact/src/event/mod.rs b/fact/src/event/mod.rs index 5792e174..c38c543f 100644 --- a/fact/src/event/mod.rs +++ b/fact/src/event/mod.rs @@ -109,16 +109,13 @@ impl Event { }; FileData::Chmod(data) } - EventTestData::Rename(old_path) => { - let data = RenameFileData { - new: inner, - old: BaseFileData { - filename: old_path, - ..Default::default() - }, - }; - FileData::Rename(data) - } + EventTestData::Rename(old_path) => FileData::Rename { + new: inner, + old: BaseFileData { + filename: old_path, + ..Default::default() + }, + }, }; Ok(Event { @@ -150,7 +147,19 @@ impl Event { } pub fn is_rename(&self) -> bool { - matches!(self.file, FileData::Rename(_)) + matches!(self.file, FileData::Rename { .. }) + } + + pub fn is_mount(&self) -> bool { + matches!(self.file, FileData::Mount(_)) + } + + pub fn is_umount(&self) -> bool { + matches!(self.file, FileData::Umount(_)) + } + + pub fn is_move_mount(&self) -> bool { + matches!(self.file, FileData::MoveMount { .. }) } /// Unwrap the inner FileData and return the inode that triggered @@ -160,14 +169,18 @@ impl Event { /// the 'new' inode will be returned. pub fn get_inode(&self) -> &inode_key_t { match &self.file { - FileData::Open(data) => &data.inode, - FileData::Creation(data) => &data.inode, - FileData::MkDir(data) => &data.inode, - FileData::RmDir(data) => &data.inode, - FileData::Unlink(data) => &data.inode, + FileData::Open(data) + | FileData::Creation(data) + | FileData::MkDir(data) + | FileData::RmDir(data) + | FileData::Unlink(data) + | FileData::Mount(data) + | FileData::Umount(data) => &data.inode, FileData::Chmod(data) => &data.inner.inode, FileData::Chown(data) => &data.inner.inode, - FileData::Rename(data) => &data.new.inode, + FileData::Rename { new: data, .. } | FileData::MoveMount { to: data, .. } => { + &data.inode + } FileData::SetXattr(data) => &data.inner.inode, FileData::RemoveXattr(data) => &data.inner.inode, FileData::AclSet(data) => &data.inner.inode, @@ -177,14 +190,18 @@ impl Event { /// Get the parent inode for the file in this event. pub fn get_parent_inode(&self) -> &inode_key_t { match &self.file { - FileData::Open(data) => &data.parent_inode, - FileData::Creation(data) => &data.parent_inode, - FileData::MkDir(data) => &data.parent_inode, - FileData::RmDir(data) => &data.parent_inode, - FileData::Unlink(data) => &data.parent_inode, + FileData::Open(data) + | FileData::Creation(data) + | FileData::MkDir(data) + | FileData::RmDir(data) + | FileData::Unlink(data) + | FileData::Mount(data) + | FileData::Umount(data) => &data.parent_inode, FileData::Chmod(data) => &data.inner.parent_inode, FileData::Chown(data) => &data.inner.parent_inode, - FileData::Rename(data) => &data.new.parent_inode, + FileData::Rename { new: data, .. } | FileData::MoveMount { to: data, .. } => { + &data.parent_inode + } FileData::SetXattr(data) => &data.inner.parent_inode, FileData::RemoveXattr(data) => &data.inner.parent_inode, FileData::AclSet(data) => &data.inner.parent_inode, @@ -196,21 +213,27 @@ impl Event { /// will be returned. pub fn get_old_inode(&self) -> Option<&inode_key_t> { match &self.file { - FileData::Rename(data) => Some(&data.old.inode), + FileData::Rename { old: data, .. } | FileData::MoveMount { from: data, .. } => { + Some(&data.inode) + } _ => None, } } pub fn get_filename(&self) -> &PathBuf { match &self.file { - FileData::Open(data) => &data.filename, - FileData::Creation(data) => &data.filename, - FileData::MkDir(data) => &data.filename, - FileData::RmDir(data) => &data.filename, - FileData::Unlink(data) => &data.filename, + FileData::Open(data) + | FileData::Creation(data) + | FileData::MkDir(data) + | FileData::RmDir(data) + | FileData::Unlink(data) + | FileData::Mount(data) + | FileData::Umount(data) => &data.filename, FileData::Chmod(data) => &data.inner.filename, FileData::Chown(data) => &data.inner.filename, - FileData::Rename(data) => &data.new.filename, + FileData::Rename { new: data, .. } | FileData::MoveMount { to: data, .. } => { + &data.filename + } FileData::SetXattr(data) => &data.inner.filename, FileData::RemoveXattr(data) => &data.inner.filename, FileData::AclSet(data) => &data.inner.filename, @@ -219,21 +242,27 @@ impl Event { pub fn get_old_filename(&self) -> Option<&PathBuf> { match &self.file { - FileData::Rename(data) => Some(&data.old.filename), + FileData::Rename { old: data, .. } | FileData::MoveMount { from: data, .. } => { + Some(&data.filename) + } _ => None, } } pub fn get_host_path(&self) -> &PathBuf { match &self.file { - FileData::Open(data) => &data.host_file, - FileData::Creation(data) => &data.host_file, - FileData::MkDir(data) => &data.host_file, - FileData::RmDir(data) => &data.host_file, - FileData::Unlink(data) => &data.host_file, + FileData::Open(data) + | FileData::Creation(data) + | FileData::MkDir(data) + | FileData::RmDir(data) + | FileData::Unlink(data) + | FileData::Mount(data) + | FileData::Umount(data) => &data.host_file, FileData::Chmod(data) => &data.inner.host_file, FileData::Chown(data) => &data.inner.host_file, - FileData::Rename(data) => &data.new.host_file, + FileData::Rename { new: data, .. } | FileData::MoveMount { to: data, .. } => { + &data.host_file + } FileData::SetXattr(data) => &data.inner.host_file, FileData::RemoveXattr(data) => &data.inner.host_file, FileData::AclSet(data) => &data.inner.host_file, @@ -242,7 +271,9 @@ impl Event { pub fn get_old_host_path(&self) -> Option<&PathBuf> { match &self.file { - FileData::Rename(data) => Some(&data.old.host_file), + FileData::Rename { old: data, .. } | FileData::MoveMount { from: data, .. } => { + Some(&data.host_file) + } _ => None, } } @@ -253,14 +284,18 @@ impl Event { /// the 'new' host_file will be set. pub fn set_host_path(&mut self, host_path: PathBuf) { match &mut self.file { - FileData::Open(data) => data.host_file = host_path, - FileData::Creation(data) => data.host_file = host_path, - FileData::MkDir(data) => data.host_file = host_path, - FileData::RmDir(data) => data.host_file = host_path, - FileData::Unlink(data) => data.host_file = host_path, + FileData::Open(data) + | FileData::Creation(data) + | FileData::MkDir(data) + | FileData::RmDir(data) + | FileData::Unlink(data) + | FileData::Mount(data) + | FileData::Umount(data) => data.host_file = host_path, FileData::Chmod(data) => data.inner.host_file = host_path, FileData::Chown(data) => data.inner.host_file = host_path, - FileData::Rename(data) => data.new.host_file = host_path, + FileData::Rename { new: data, .. } | FileData::MoveMount { to: data, .. } => { + data.host_file = host_path + } FileData::SetXattr(data) => data.inner.host_file = host_path, FileData::RemoveXattr(data) => data.inner.host_file = host_path, FileData::AclSet(data) => data.inner.host_file = host_path, @@ -270,21 +305,28 @@ impl Event { /// Same as `set_host_path` but setting the 'old' host_file for /// operations that have one, like rename. pub fn set_old_host_path(&mut self, host_path: PathBuf) { - if let FileData::Rename(data) = &mut self.file { - data.old.host_file = host_path + match &mut self.file { + FileData::Rename { old: data, .. } | FileData::MoveMount { from: data, .. } => { + data.host_file = host_path + } + _ => unreachable!("Called set_old_host_path on invalid type"), } } pub fn get_monitored(&self) -> monitored_t { match &self.file { - FileData::Open(data) => data.monitored, - FileData::Creation(data) => data.monitored, - FileData::MkDir(data) => data.monitored, - FileData::RmDir(data) => data.monitored, - FileData::Unlink(data) => data.monitored, + FileData::Open(data) + | FileData::Creation(data) + | FileData::MkDir(data) + | FileData::RmDir(data) + | FileData::Unlink(data) + | FileData::Mount(data) + | FileData::Umount(data) => data.monitored, FileData::Chmod(data) => data.inner.monitored, FileData::Chown(data) => data.inner.monitored, - FileData::Rename(data) => data.new.monitored, + FileData::Rename { new: data, .. } | FileData::MoveMount { to: data, .. } => { + data.monitored + } FileData::SetXattr(data) => data.inner.monitored, FileData::RemoveXattr(data) => data.inner.monitored, FileData::AclSet(data) => data.inner.monitored, @@ -293,7 +335,9 @@ impl Event { pub fn get_old_monitored(&self) -> Option { match &self.file { - FileData::Rename(data) => Some(data.old.monitored), + FileData::Rename { old: data, .. } | FileData::MoveMount { from: data, .. } => { + Some(data.monitored) + } _ => None, } } @@ -379,10 +423,19 @@ pub enum FileData { Unlink(BaseFileData), Chmod(ChmodFileData), Chown(ChownFileData), - Rename(RenameFileData), + Rename { + new: BaseFileData, + old: BaseFileData, + }, SetXattr(XattrFileData), RemoveXattr(XattrFileData), AclSet(AclSetFileData), + Mount(BaseFileData), + MoveMount { + to: BaseFileData, + from: BaseFileData, + }, + Umount(BaseFileData), } impl FileData { @@ -394,6 +447,16 @@ impl FileData { monitored: monitored_t, extra_data: fact_ebpf::event_t__bindgen_ty_1, ) -> anyhow::Result { + fn read_from_data( + extra_data: fact_ebpf::event_t__bindgen_ty_1, + ) -> anyhow::Result { + let filename = unsafe { extra_data.from.filename }; + let inode = unsafe { extra_data.from.inode }; + let monitored = unsafe { extra_data.from.monitored }; + + BaseFileData::new(filename, inode, Default::default(), monitored) + } + let inner = BaseFileData::new(filename, inode, parent_inode, monitored)?; let file = match event_type { file_activity_type_t::FILE_ACTIVITY_OPEN => FileData::Open(inner), @@ -420,19 +483,8 @@ impl FileData { FileData::Chown(data) } file_activity_type_t::FILE_ACTIVITY_RENAME => { - let old_filename = unsafe { extra_data.rename.filename }; - let old_inode = unsafe { extra_data.rename.inode }; - let old_monitored = unsafe { extra_data.rename.monitored }; - let data = RenameFileData { - new: inner, - old: BaseFileData::new( - old_filename, - old_inode, - Default::default(), - old_monitored, - )?, - }; - FileData::Rename(data) + let old = read_from_data(extra_data)?; + FileData::Rename { new: inner, old } } file_activity_type_t::FILE_ACTIVITY_SETXATTR => { let xattr_name = slice_to_string( @@ -461,6 +513,12 @@ impl FileData { entries, }) } + file_activity_type_t::FILE_ACTIVITY_MOUNT => FileData::Mount(inner), + file_activity_type_t::FILE_ACTIVITY_UMOUNT => FileData::Umount(inner), + file_activity_type_t::FILE_ACTIVITY_MOVE_MOUNT => { + let from = read_from_data(extra_data)?; + FileData::MoveMount { to: inner, from } + } invalid => unreachable!("Invalid event type: {invalid:?}"), }; @@ -508,14 +566,26 @@ impl From for fact_api::file_activity::File { let f_act = fact_api::FileOwnershipChange::from(event); fact_api::file_activity::File::Ownership(f_act) } - FileData::Rename(event) => { - let f_act = fact_api::FileRename::from(event); + FileData::Rename { new, old } => { + let f_act = fact_api::FileRename { + new: Some(new.into()), + old: Some(old.into()), + }; fact_api::file_activity::File::Rename(f_act) } FileData::AclSet(event) => { let f_act = fact_api::FileAclChange::from(event); fact_api::file_activity::File::Acl(f_act) } + FileData::Mount(_) => { + unreachable!("Mount event reached protobuf conversion"); + } + FileData::MoveMount { .. } => { + unreachable!("MoveMount event reached protobuf conversion"); + } + FileData::Umount(_) => { + unreachable!("Umount event reached protobuf conversion"); + } } } } @@ -531,7 +601,16 @@ impl PartialEq for FileData { (FileData::Unlink(this), FileData::Unlink(other)) => this == other, (FileData::Chmod(this), FileData::Chmod(other)) => this == other, (FileData::Chown(this), FileData::Chown(other)) => this == other, - (FileData::Rename(this), FileData::Rename(other)) => this == other, + ( + FileData::Rename { + new: l_new, + old: l_old, + }, + FileData::Rename { + new: r_new, + old: r_old, + }, + ) => l_new == r_new && l_old == r_old, (FileData::SetXattr(this), FileData::SetXattr(other)) => this == other, (FileData::RemoveXattr(this), FileData::RemoveXattr(other)) => this == other, (FileData::AclSet(this), FileData::AclSet(other)) => { @@ -656,12 +735,6 @@ impl From for fact_api::FileOwnershipChange { } } -#[derive(Debug, Clone, Serialize)] -pub struct RenameFileData { - new: BaseFileData, - old: BaseFileData, -} - #[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq)] pub enum AclTag { UserObj, @@ -726,17 +799,6 @@ pub struct AclSetFileData { entries: Vec, } -impl From for fact_api::FileRename { - fn from(RenameFileData { new, old }: RenameFileData) -> Self { - let new = fact_api::FileActivityBase::from(new); - let old = fact_api::FileActivityBase::from(old); - fact_api::FileRename { - old: Some(old), - new: Some(new), - } - } -} - impl From for i32 { fn from(tag: AclTag) -> Self { match tag { @@ -781,13 +843,6 @@ impl From for fact_api::FileAclChange { } } -#[cfg(test)] -impl PartialEq for RenameFileData { - fn eq(&self, other: &Self) -> bool { - self.new == other.new && self.old == other.old - } -} - #[derive(Debug, Clone, Serialize)] pub struct XattrFileData { inner: BaseFileData, diff --git a/fact/src/host_scanner.rs b/fact/src/host_scanner.rs index 273bd5df..41f997e3 100644 --- a/fact/src/host_scanner.rs +++ b/fact/src/host_scanner.rs @@ -384,6 +384,70 @@ You can increase this limit with: } } + fn handle_mount_inner(&self, path: &Path, monitored: monitored_t) { + if monitored != monitored_t::MONITORED_BY_INODE || path.as_os_str().is_empty() { + return; + } + + if let Err(e) = self.scan() { + warn!("Host scan failed: {e:?}"); + } + } + + /// Handle a mount being created in a monitored directory. + /// + /// This should really do a partial scan of the directory where the + /// mount is being created, but we don't have an easy way to do that + /// at the moment, so we trigger a full scan instead. + fn handle_mount_event(&self, event: &Event) { + self.handle_mount_inner(event.get_host_path(), event.get_monitored()); + } + + fn handle_umount_inner(&self, prefix: &Path, monitored: monitored_t) { + if monitored != monitored_t::MONITORED_BY_INODE || prefix.as_os_str().is_empty() { + return; + } + + // Cleanup any items that are no longer mounted + self.inode_map.borrow_mut().retain(|inode, path| { + if path.starts_with(prefix) && path != prefix { + if let Err(e) = self.kernel_inode_map.borrow_mut().remove(inode) { + warn!( + "Failed to remove kernel entry for {}: {e:?}", + path.display() + ); + } + false + } else { + true + } + }); + } + + /// Handle a monitored directory being unmounted. + /// + /// Iterates over the inode_map entries finding files that have a + /// prefix that matches the host path in the event and removing them. + fn handle_umount_event(&self, event: &Event) { + self.handle_umount_inner(event.get_host_path(), event.get_monitored()); + } + + /// Handle a move mount event. + /// + /// In practise, a move mount event behaves as a mount at the 'to' + /// location and a umount at the 'from' location, so this method + /// behaves just like that. + fn handle_move_mount_event(&self, event: &Event) { + self.handle_mount_inner(event.get_host_path(), event.get_monitored()); + + // unwrap is safe here because get_old_* always return Some() + // for move mount events. + self.handle_umount_inner( + event.get_old_host_path().unwrap(), + event.get_old_monitored().unwrap(), + ); + } + /// Periodically notify the host scanner main task that a scan needs /// to happen. /// @@ -457,6 +521,19 @@ You can increase this limit with: continue; } + // Handle mount events and move on. + if event.is_mount() { + self.handle_mount_event(&event); + continue; + } else if event.is_umount() { + self.handle_umount_event(&event); + continue; + } else if event.is_move_mount() { + self.handle_move_mount_event(&event); + continue; + } + + if event.is_rename() { self.handle_rename_event(&mut event); } if event.is_monitored_by_parent() && diff --git a/fact/src/metrics/kernel_metrics.rs b/fact/src/metrics/kernel_metrics.rs index d1e77bfb..37238c4b 100644 --- a/fact/src/metrics/kernel_metrics.rs +++ b/fact/src/metrics/kernel_metrics.rs @@ -73,4 +73,7 @@ define_kernel_metrics!( inode_setxattr, inode_removexattr, inode_set_acl, + sb_mount, + sb_umount, + move_mount, );