ROX-33036: add mount-related operations - #1059
Conversation
📝 WalkthroughWalkthroughMount, unmount, and move-mount hooks now emit events with dedicated metrics. Rust event handling, host scanning, integration-test container setup, and mount tracking tests support these operations. ChangesMount event tracking
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant KernelHooks
participant RingBuffer
participant FileData
participant HostScanner
KernelHooks->>RingBuffer: submit mount-related event
RingBuffer->>FileData: construct Mount, Umount, or MoveMount
FileData->>HostScanner: classify mount-related event
HostScanner->>HostScanner: perform full host scan
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 2📝 Generate docstrings 💡
🛠️ Fix failing CI checks 💡
🧪 Generate unit tests (beta)
Comment |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #1059 +/- ##
==========================================
- Coverage 35.48% 35.04% -0.45%
==========================================
Files 22 22
Lines 3235 3276 +41
Branches 3235 3276 +41
==========================================
Hits 1148 1148
- Misses 2082 2123 +41
Partials 5 5 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
3cc1980 to
b15f103
Compare
b15f103 to
0eb199b
Compare
0eb199b to
5e41f99
Compare
|
/retest |
a0d0d78 to
edc7efa
Compare
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
fact/src/event/mod.rs (1)
657-687: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
PartialEq for FileDatamissing arms forMount,Umount,MoveMount.Every other variant (including all pre-existing ones) has an explicit equality arm, but the three new mount variants fall through to
_ => false. This means two identicalMount/Umount/MoveMountevents will never compare equal.🐛 Proposed fix
(FileData::AclSet(this), FileData::AclSet(other)) => { this.inner == other.inner && this.acl_type == other.acl_type && this.entries == other.entries } + (FileData::Mount(this), FileData::Mount(other)) => this == other, + (FileData::Umount(this), FileData::Umount(other)) => this == other, + ( + FileData::MoveMount { to: l_to, from: l_from }, + FileData::MoveMount { to: r_to, from: r_from }, + ) => l_to == r_to && l_from == r_from, _ => false,🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@fact/src/event/mod.rs` around lines 657 - 687, Add explicit equality arms to PartialEq::eq for FileData covering Mount, Umount, and MoveMount, comparing each variant’s corresponding fields consistently with the other variant arms. Keep the fallback _ => false for differing variants.
🧹 Nitpick comments (3)
fact/src/host_scanner.rs (1)
429-439: 🚀 Performance & Scalability | 🔵 Trivial | 🏗️ Heavy liftMount events trigger a synchronous full scan per event, with no coalescing for bursts.
handle_mount_eventcallsself.scan()directly and inline in theselect!branch, blocking the task for the scan's duration and running once per mount-related event with no debouncing. The existingscan_trigger/Notifymechanism used for periodic andpaths.changed()scans already coalesces repeated triggers (multiplenotify_one()calls before consumption collapse to a single pending scan), but that path isn't reused here. Under a burst of mount/unmount activity this could serialize many redundant full scans and delay processing of other events in the loop (e.g. introspection queries).Also applies to: 493-497
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@fact/src/host_scanner.rs` around lines 429 - 439, Update handle_mount_event to signal the existing scan_trigger/Notify mechanism instead of calling self.scan() synchronously. Route mount events through the same coalesced scan path used by periodic and paths.changed() triggers, preserving a single pending scan during event bursts and keeping the select loop responsive.fact/src/event/mod.rs (1)
437-535: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy liftConsider adding Rust-side test coverage for the new
Mount/Umount/MoveMountvariants.Codecov flags 130 uncovered lines in this file for this PR, and the
PartialEqgap above went unnoticed likely due to this.EventTestData(used by the#[cfg(all(test, feature = "bpf-test"))]constructor) doesn't have variants for the new mount events either.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@fact/src/event/mod.rs` around lines 437 - 535, Add Rust-side tests covering FileData::Mount, FileData::Umount, and FileData::MoveMount, including their constructed fields and equality behavior. Extend EventTestData and its bpf-test constructor with corresponding mount-event variants so these cases can be exercised through the existing test path, and ensure the tests cover both source and destination data for MoveMount.tests/test_mount.py (1)
44-50: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winSubprocess calls don't check exit status.
mount/umountfailures (e.g. wrong tmpfs support, misconfigured environment) will pass silently, and the test would then just fail (or worse, pass) based on stale inode state rather than surfacing the real cause.♻️ Proposed fix
- subprocess.run( - ['mount', '-t', 'tmpfs', '-o', 'size=10M', 'tmpfs', monitored_dir] - ) + subprocess.run( + ['mount', '-t', 'tmpfs', '-o', 'size=10M', 'tmpfs', monitored_dir], + check=True, + ) assert_tracked_path(monitored_dir) - subprocess.run(['umount', monitored_dir]) + subprocess.run(['umount', monitored_dir], check=True)🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/test_mount.py` around lines 44 - 50, Update the subprocess.run calls in the mount/umount test to enforce successful exit status, so mount or unmount failures raise immediately instead of allowing assertions to use stale inode state. Preserve the existing command arguments and tracking assertions.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@CHANGELOG.md`:
- Line 9: Update the ROX-33036 changelog entry to hyphenate “mount-related” in
the description of the operations.
In `@fact-ebpf/src/bpf/main.c`:
- Around line 557-603: Update trace_move_mount so events are emitted for every
monitored classification except MONITORED_NOT, matching the sb_mount/sb_umount
handling and allowing parent/path-monitored moves to reach rescan. In the same
function, derive args.parent_inode from to’s actual parent inode rather than
reusing to->dentry->d_inode, and pass that value to is_monitored.
---
Outside diff comments:
In `@fact/src/event/mod.rs`:
- Around line 657-687: Add explicit equality arms to PartialEq::eq for FileData
covering Mount, Umount, and MoveMount, comparing each variant’s corresponding
fields consistently with the other variant arms. Keep the fallback _ => false
for differing variants.
---
Nitpick comments:
In `@fact/src/event/mod.rs`:
- Around line 437-535: Add Rust-side tests covering FileData::Mount,
FileData::Umount, and FileData::MoveMount, including their constructed fields
and equality behavior. Extend EventTestData and its bpf-test constructor with
corresponding mount-event variants so these cases can be exercised through the
existing test path, and ensure the tests cover both source and destination data
for MoveMount.
In `@fact/src/host_scanner.rs`:
- Around line 429-439: Update handle_mount_event to signal the existing
scan_trigger/Notify mechanism instead of calling self.scan() synchronously.
Route mount events through the same coalesced scan path used by periodic and
paths.changed() triggers, preserving a single pending scan during event bursts
and keeping the select loop responsive.
In `@tests/test_mount.py`:
- Around line 44-50: Update the subprocess.run calls in the mount/umount test to
enforce successful exit status, so mount or unmount failures raise immediately
instead of allowing assertions to use stale inode state. Preserve the existing
command arguments and tracking assertions.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Enterprise
Run ID: 05fc42f0-315c-46df-9f9b-5b00f13b4fd4
📒 Files selected for processing (12)
CHANGELOG.mdfact-ebpf/src/bpf/bound_path.hfact-ebpf/src/bpf/events.hfact-ebpf/src/bpf/main.cfact-ebpf/src/bpf/types.hfact-ebpf/src/lib.rsfact/src/event/mod.rsfact/src/host_scanner.rsfact/src/metrics/kernel_metrics.rstests/conftest.pytests/test_config_hotreload.pytests/test_mount.py
4a5ee00 to
72aa4a2
Compare
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@tests/test_mount.py`:
- Line 17: Update the requests.get call for FACT_INTROSPECTION_INODES in the
test to include a finite timeout, using the project’s established timeout
convention if available, so the test fails promptly when the local service is
unavailable or unresponsive.
- Around line 25-35: Update the retry loop around get_inodes so it refreshes res
by calling get_inodes() at the start of each attempt, rather than reusing the
initial snapshot. Preserve the existing assertion, KeyError logging, and retry
delay behavior.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Enterprise
Run ID: 37bf14d9-5342-4cbd-b40a-6d0974b27ddf
📒 Files selected for processing (12)
CHANGELOG.mdfact-ebpf/src/bpf/bound_path.hfact-ebpf/src/bpf/events.hfact-ebpf/src/bpf/main.cfact-ebpf/src/bpf/types.hfact-ebpf/src/lib.rsfact/src/event/mod.rsfact/src/host_scanner.rsfact/src/metrics/kernel_metrics.rstests/conftest.pytests/test_config_hotreload.pytests/test_mount.py
🚧 Files skipped from review as they are similar to previous changes (9)
- CHANGELOG.md
- fact/src/metrics/kernel_metrics.rs
- fact-ebpf/src/bpf/bound_path.h
- fact-ebpf/src/bpf/events.h
- fact/src/host_scanner.rs
- fact-ebpf/src/bpf/types.h
- fact-ebpf/src/bpf/main.c
- tests/test_config_hotreload.py
- fact/src/event/mod.rs
This is a small refactor done ahead of #1059, cleaning up getters in the `FileData` type by using better pattern matching. The `Rename` variant is also streamlined by using a struct variant instead of a tuple holding `RenameFileData`.
This is a small refactor done ahead of #1059, cleaning up getters in the `FileData` type by using better pattern matching. The `Rename` variant is also streamlined by using a struct variant instead of a tuple holding `RenameFileData`.
72aa4a2 to
3d17ab3
Compare
631caae to
ca94fc3
Compare
5f72749 to
8240b89
Compare
This is a small refactor done ahead of #1059, cleaning up getters in the `FileData` type by using better pattern matching. The `Rename` variant is also streamlined by using a struct variant instead of a tuple holding `RenameFileData`.
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.
Add a basic test for checking mount operations are properly tracked. This test works by checking the monitored directory is tracked using the inodes introspection endpoint, then a tmpfs is mounted on top of this directory and we validate we see the new inode, then we unmount and check the introspection endpoint one last time.
8240b89 to
b68ecb5
Compare
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
fact/src/event/mod.rs (1)
657-687: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
PartialEq for FileDatais missing arms forMount,Umount, andMoveMount.All other variants (including the newly reshaped
Rename { new, old }) have explicit same-variant match arms, butMount,Umount, andMoveMountfall through to the_ => falsecatch-all. Two identicalMount/Umount/MoveMountvalues will therefore never compare equal — unlike every pre-existing variant.🐛 Proposed fix
(FileData::SetXattr(this), FileData::SetXattr(other)) => this == other, (FileData::RemoveXattr(this), FileData::RemoveXattr(other)) => this == other, (FileData::AclSet(this), FileData::AclSet(other)) => { this.inner == other.inner && this.acl_type == other.acl_type && this.entries == other.entries } + (FileData::Mount(this), FileData::Mount(other)) => this == other, + (FileData::Umount(this), FileData::Umount(other)) => this == other, + ( + FileData::MoveMount { + to: l_to, + from: l_from, + }, + FileData::MoveMount { + to: r_to, + from: r_from, + }, + ) => l_to == r_to && l_from == r_from, _ => false, }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@fact/src/event/mod.rs` around lines 657 - 687, Update the PartialEq implementation for FileData by adding same-variant comparison arms for Mount, Umount, and MoveMount, comparing each variant’s payload with == like the existing variants. Leave the catch-all false behavior for differing variants unchanged.fact/src/host_scanner.rs (1)
538-546: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick winDisable the introspection arm once the channel closes.
recv()returningNoneleaves this branch permanently ready, so the scanner can spin here and miss shutdown. Store the receiver asOption<...>and drop the arm after closure, or break the loop when introspection is gone.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@fact/src/host_scanner.rs` around lines 538 - 546, Update the introspection receive arm in the scanner loop to handle channel closure permanently: when self.introspection.recv() returns None, disable or remove the receiver (or exit the loop) instead of continuing. Preserve request handling and response serialization for open channels while ensuring the closed channel cannot remain continuously ready and prevent shutdown.
🧹 Nitpick comments (1)
tests/test_mount.py (1)
44-57: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy liftAdd integration coverage for move-mount.
This test validates mount and unmount only, while the PR also adds
lsm/move_mount. Add a move-mount scenario, or verify that equivalent coverage exists elsewhere, so regressions in that hook are detected.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/test_mount.py` around lines 44 - 57, Add move-mount integration coverage to test_mount, using the existing mounted monitored_dir and cleanup flow to invoke the move-mount operation and assert_tracked_path before and after it. If equivalent coverage already exists elsewhere, verify it exercises lsm/move_mount and retain this test’s existing mount/unmount assertions.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@fact/src/host_scanner.rs`:
- Around line 434-437: Update handle_mount_event so a failed self.scan() is
retained as pending work or retried before the mount event is considered
handled, including the corresponding failure path around the other mount-event
handling block. Preserve the existing warning log while ensuring scan_interval =
0 still guarantees a later rescan after a transient failure.
- Around line 429-438: Update the scan flow used by handle_mount_event() to
reconcile tracked paths whose st_dev:st_ino identity changed after a mount or
unmount. Before or during rescanning, remove stale inode records and kernel-map
entries for affected paths (including moved or unmounted subtrees), then
register the current identities so later inode events cannot resolve to old
paths.
---
Outside diff comments:
In `@fact/src/event/mod.rs`:
- Around line 657-687: Update the PartialEq implementation for FileData by
adding same-variant comparison arms for Mount, Umount, and MoveMount, comparing
each variant’s payload with == like the existing variants. Leave the catch-all
false behavior for differing variants unchanged.
In `@fact/src/host_scanner.rs`:
- Around line 538-546: Update the introspection receive arm in the scanner loop
to handle channel closure permanently: when self.introspection.recv() returns
None, disable or remove the receiver (or exit the loop) instead of continuing.
Preserve request handling and response serialization for open channels while
ensuring the closed channel cannot remain continuously ready and prevent
shutdown.
---
Nitpick comments:
In `@tests/test_mount.py`:
- Around line 44-57: Add move-mount integration coverage to test_mount, using
the existing mounted monitored_dir and cleanup flow to invoke the move-mount
operation and assert_tracked_path before and after it. If equivalent coverage
already exists elsewhere, verify it exercises lsm/move_mount and retain this
test’s existing mount/unmount assertions.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Enterprise
Run ID: 5febde9e-5e12-4628-aafd-b8c4e73cea3f
📒 Files selected for processing (12)
CHANGELOG.mdfact-ebpf/src/bpf/bound_path.hfact-ebpf/src/bpf/events.hfact-ebpf/src/bpf/main.cfact-ebpf/src/bpf/types.hfact-ebpf/src/lib.rsfact/src/event/mod.rsfact/src/host_scanner.rsfact/src/metrics/kernel_metrics.rstests/conftest.pytests/test_config_hotreload.pytests/test_mount.py
🚧 Files skipped from review as they are similar to previous changes (1)
- CHANGELOG.md
| /// Handle a mount being modified in a monitored directory. | ||
| /// | ||
| /// This should really do a partial scan of the directory where the | ||
| /// mount is being changed, 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) { | ||
| if let Err(e) = self.scan() { | ||
| warn!("Host scan failed: {e:?}"); | ||
| } | ||
| } |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift
Reconcile inode identity during mount scans.
handle_mount_event() relies on scan(), but scan() retains entries when their paths still exist. Mounting or unmounting changes st_dev:st_ino while the path remains present, so the new inode is added without removing the old inode/kernel-map entry. This leaves stale /inodes records and can associate later inode events with the wrong path. Compare each tracked path’s current inode identity or purge the affected subtree before rescanning.
The PR objective explicitly requires inode-map cleanup for moved or unmounted directories.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@fact/src/host_scanner.rs` around lines 429 - 438, Update the scan flow used
by handle_mount_event() to reconcile tracked paths whose st_dev:st_ino identity
changed after a mount or unmount. Before or during rescanning, remove stale
inode records and kernel-map entries for affected paths (including moved or
unmounted subtrees), then register the current identities so later inode events
cannot resolve to old paths.
| fn handle_mount_event(&self) { | ||
| if let Err(e) = self.scan() { | ||
| warn!("Host scan failed: {e:?}"); | ||
| } |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift
Retry or preserve failed mount scans.
handle_mount_event() only logs scan errors, and the caller then discards the mount event. With scan_interval = 0, there is no guaranteed periodic retry, so one transient scan failure can leave inode tracking stale indefinitely. Keep a pending rescan or retry before treating the event as handled.
The integration fixture explicitly uses a zero scan interval.
Also applies to: 493-496
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@fact/src/host_scanner.rs` around lines 434 - 437, Update handle_mount_event
so a failed self.scan() is retained as pending work or retried before the mount
event is considered handled, including the corresponding failure path around the
other mount-event handling block. Preserve the existing warning log while
ensuring scan_interval = 0 still guarantees a later rescan after a transient
failure.
Description
This was originally going to be about adding just
lsm/sb_mount, however while adding this hook it became pretty clear we neededlsm/sb_umountandlsm/move_mountfor 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.
Checklist
Automated testing
If any of these don't apply, please comment below.
Testing Performed
Added integration tests.
Summary by CodeRabbit
/metricsnow returns HTTP 503.