diff --git a/fact-ebpf/src/bpf/events.h b/fact-ebpf/src/bpf/events.h index abe4d19c..26254778 100644 --- a/fact-ebpf/src/bpf/events.h +++ b/fact-ebpf/src/bpf/events.h @@ -17,10 +17,12 @@ __always_inline static void __submit_event(struct event_t* event, file_activity_type_t event_type, const char filename[PATH_MAX], inode_key_t* inode, + inode_key_t* parent_inode, bool use_bpf_d_path) { event->type = event_type; event->timestamp = bpf_ktime_get_boot_ns(); inode_copy_or_reset(&event->inode, inode); + inode_copy_or_reset(&event->parent_inode, parent_inode); bpf_probe_read_str(event->filename, PATH_MAX, filename); struct helper_t* helper = get_helper(); @@ -46,31 +48,34 @@ __always_inline static void __submit_event(struct event_t* event, __always_inline static void submit_open_event(struct metrics_by_hook_t* m, file_activity_type_t event_type, const char filename[PATH_MAX], - inode_key_t* inode) { + inode_key_t* inode, + inode_key_t* parent_inode) { struct event_t* event = bpf_ringbuf_reserve(&rb, sizeof(struct event_t), 0); if (event == NULL) { m->ringbuffer_full++; return; } - __submit_event(event, m, event_type, filename, inode, true); + __submit_event(event, m, event_type, filename, inode, parent_inode, true); } __always_inline static void submit_unlink_event(struct metrics_by_hook_t* m, const char filename[PATH_MAX], - inode_key_t* inode) { + inode_key_t* inode, + inode_key_t* parent_inode) { struct event_t* event = bpf_ringbuf_reserve(&rb, sizeof(struct event_t), 0); if (event == NULL) { m->ringbuffer_full++; return; } - __submit_event(event, m, FILE_ACTIVITY_UNLINK, filename, inode, path_hooks_support_bpf_d_path); + __submit_event(event, m, FILE_ACTIVITY_UNLINK, filename, inode, parent_inode, path_hooks_support_bpf_d_path); } __always_inline static void submit_mode_event(struct metrics_by_hook_t* m, const char filename[PATH_MAX], inode_key_t* inode, + inode_key_t* parent_inode, umode_t mode, umode_t old_mode) { struct event_t* event = bpf_ringbuf_reserve(&rb, sizeof(struct event_t), 0); @@ -82,12 +87,13 @@ __always_inline static void submit_mode_event(struct metrics_by_hook_t* m, event->chmod.new = mode; event->chmod.old = old_mode; - __submit_event(event, m, FILE_ACTIVITY_CHMOD, filename, inode, path_hooks_support_bpf_d_path); + __submit_event(event, m, FILE_ACTIVITY_CHMOD, filename, inode, parent_inode, path_hooks_support_bpf_d_path); } __always_inline static void submit_ownership_event(struct metrics_by_hook_t* m, const char filename[PATH_MAX], inode_key_t* inode, + inode_key_t* parent_inode, unsigned long long uid, unsigned long long gid, unsigned long long old_uid, @@ -103,14 +109,15 @@ __always_inline static void submit_ownership_event(struct metrics_by_hook_t* m, event->chown.old.uid = old_uid; event->chown.old.gid = old_gid; - __submit_event(event, m, FILE_ACTIVITY_CHOWN, filename, inode, path_hooks_support_bpf_d_path); + __submit_event(event, m, FILE_ACTIVITY_CHOWN, filename, inode, parent_inode, path_hooks_support_bpf_d_path); } __always_inline static void submit_rename_event(struct metrics_by_hook_t* m, const char new_filename[PATH_MAX], const char old_filename[PATH_MAX], inode_key_t* new_inode, - inode_key_t* old_inode) { + inode_key_t* old_inode, + inode_key_t* new_parent_inode) { struct event_t* event = bpf_ringbuf_reserve(&rb, sizeof(struct event_t), 0); if (event == NULL) { m->ringbuffer_full++; @@ -120,5 +127,5 @@ __always_inline static void submit_rename_event(struct metrics_by_hook_t* m, bpf_probe_read_str(event->rename.old_filename, PATH_MAX, old_filename); inode_copy_or_reset(&event->rename.old_inode, old_inode); - __submit_event(event, m, FILE_ACTIVITY_RENAME, new_filename, new_inode, path_hooks_support_bpf_d_path); + __submit_event(event, m, FILE_ACTIVITY_RENAME, new_filename, new_inode, new_parent_inode, path_hooks_support_bpf_d_path); } diff --git a/fact-ebpf/src/bpf/inode.h b/fact-ebpf/src/bpf/inode.h index 4e9a26dc..a247ec28 100644 --- a/fact-ebpf/src/bpf/inode.h +++ b/fact-ebpf/src/bpf/inode.h @@ -33,12 +33,12 @@ __always_inline static inode_key_t inode_to_key(struct inode* inode) { return key; } - unsigned long magic = inode->i_sb->s_magic; + unsigned long magic = BPF_CORE_READ(inode, i_sb, s_magic); switch (magic) { case BTRFS_SUPER_MAGIC: if (bpf_core_type_exists(struct btrfs_inode)) { struct btrfs_inode* btrfs_inode = container_of(inode, struct btrfs_inode, vfs_inode); - key.inode = inode->i_ino; + key.inode = BPF_CORE_READ(inode, i_ino); key.dev = BPF_CORE_READ(btrfs_inode, root, anon_dev); break; } @@ -46,8 +46,8 @@ __always_inline static inode_key_t inode_to_key(struct inode* inode) { // supported on the system. Fallback to the generic implementation // just in case. default: - key.inode = inode->i_ino; - key.dev = inode->i_sb->s_dev; + key.inode = BPF_CORE_READ(inode, i_ino); + key.dev = BPF_CORE_READ(inode, i_sb, s_dev); break; } @@ -65,6 +65,14 @@ __always_inline static inode_value_t* inode_get(struct inode_key_t* inode) { return bpf_map_lookup_elem(&inode_map, inode); } +__always_inline static long inode_add(struct inode_key_t* inode) { + if (inode == NULL) { + return -1; + } + inode_value_t value = 0; + return bpf_map_update_elem(&inode_map, inode, &value, BPF_ANY); +} + __always_inline static long inode_remove(struct inode_key_t* inode) { if (inode == NULL) { return 0; diff --git a/fact-ebpf/src/bpf/main.c b/fact-ebpf/src/bpf/main.c index e0a23522..cc877c68 100644 --- a/fact-ebpf/src/bpf/main.c +++ b/fact-ebpf/src/bpf/main.c @@ -47,11 +47,24 @@ int BPF_PROG(trace_file_open, struct file* file) { inode_key_t inode_key = inode_to_key(file->f_inode); inode_key_t* inode_to_submit = &inode_key; + // Extract parent inode + struct dentry* parent_dentry = BPF_CORE_READ(file, f_path.dentry, d_parent); + struct inode* parent_inode_ptr = parent_dentry ? BPF_CORE_READ(parent_dentry, d_inode) : NULL; + inode_key_t parent_key = inode_to_key(parent_inode_ptr); + + // For file creation events, check if the parent directory is being + // monitored. If so, add the new file's inode to the tracked set. + if (event_type == FILE_ACTIVITY_CREATION) { + if (inode_is_monitored(inode_get(&parent_key)) == MONITORED) { + inode_add(&inode_key); + } + } + if (!is_monitored(inode_key, path, &inode_to_submit)) { goto ignored; } - submit_open_event(&m->file_open, event_type, path->path, inode_to_submit); + submit_open_event(&m->file_open, event_type, path->path, inode_to_submit, &parent_key); return 0; @@ -86,7 +99,8 @@ int BPF_PROG(trace_path_unlink, struct path* dir, struct dentry* dentry) { submit_unlink_event(&m->path_unlink, path->path, - inode_to_submit); + inode_to_submit, + NULL); return 0; } @@ -118,6 +132,7 @@ int BPF_PROG(trace_path_chmod, struct path* path, umode_t mode) { submit_mode_event(&m->path_chmod, bound_path->path, inode_to_submit, + NULL, mode, old_mode); @@ -158,6 +173,7 @@ int BPF_PROG(trace_path_chown, struct path* path, unsigned long long uid, unsign submit_ownership_event(&m->path_chown, bound_path->path, inode_to_submit, + NULL, uid, gid, old_uid, @@ -207,7 +223,8 @@ int BPF_PROG(trace_path_rename, struct path* old_dir, new_path->path, old_path->path, old_inode_submit, - new_inode_submit); + new_inode_submit, + NULL); return 0; error: diff --git a/fact-ebpf/src/bpf/types.h b/fact-ebpf/src/bpf/types.h index 6a009e66..55005c00 100644 --- a/fact-ebpf/src/bpf/types.h +++ b/fact-ebpf/src/bpf/types.h @@ -62,6 +62,7 @@ struct event_t { process_t process; char filename[PATH_MAX]; inode_key_t inode; + inode_key_t parent_inode; file_activity_type_t type; union { struct { diff --git a/fact/src/event/mod.rs b/fact/src/event/mod.rs index 98a70162..10736ff8 100644 --- a/fact/src/event/mod.rs +++ b/fact/src/event/mod.rs @@ -93,6 +93,7 @@ impl Event { filename, host_file, inode: Default::default(), + parent_inode: Default::default(), }; let file = match data { EventTestData::Creation => FileData::Creation(inner), @@ -125,6 +126,10 @@ impl Event { }) } + pub fn is_creation(&self) -> bool { + matches!(self.file, FileData::Creation(_)) + } + /// Unwrap the inner FileData and return the inode that triggered /// the event. /// @@ -141,6 +146,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::Unlink(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, + } + } + /// Same as `get_inode` but returning the 'old' inode for operations /// like rename. For operations that involve a single inode, `None` /// will be returned. @@ -151,7 +168,7 @@ impl Event { } } - fn get_filename(&self) -> &PathBuf { + pub fn get_filename(&self) -> &PathBuf { match &self.file { FileData::Open(data) => &data.filename, FileData::Creation(data) => &data.filename, @@ -233,6 +250,7 @@ impl TryFrom<&event_t> for Event { value.type_, value.filename, value.inode, + value.parent_inode, value.__bindgen_anon_1, )?; @@ -282,9 +300,10 @@ impl FileData { event_type: file_activity_type_t, filename: [c_char; PATH_MAX as usize], inode: inode_key_t, + parent_inode: inode_key_t, extra_data: fact_ebpf::event_t__bindgen_ty_1, ) -> anyhow::Result { - let inner = BaseFileData::new(filename, inode)?; + let inner = BaseFileData::new(filename, inode, parent_inode)?; let file = match event_type { file_activity_type_t::FILE_ACTIVITY_OPEN => FileData::Open(inner), file_activity_type_t::FILE_ACTIVITY_CREATION => FileData::Creation(inner), @@ -312,7 +331,7 @@ impl FileData { let old_inode = unsafe { extra_data.rename.old_inode }; let data = RenameFileData { new: inner, - old: BaseFileData::new(old_filename, old_inode)?, + old: BaseFileData::new(old_filename, old_inode, Default::default())?, }; FileData::Rename(data) } @@ -376,14 +395,20 @@ pub struct BaseFileData { pub filename: PathBuf, host_file: PathBuf, inode: inode_key_t, + parent_inode: inode_key_t, } impl BaseFileData { - pub fn new(filename: [c_char; PATH_MAX as usize], inode: inode_key_t) -> anyhow::Result { + pub fn new( + filename: [c_char; PATH_MAX as usize], + inode: inode_key_t, + parent_inode: inode_key_t, + ) -> anyhow::Result { Ok(BaseFileData { filename: sanitize_d_path(&filename), host_file: PathBuf::new(), // this field is set by HostScanner inode, + parent_inode, }) } } diff --git a/fact/src/host_scanner.rs b/fact/src/host_scanner.rs index ad7db6f9..0fcf6945 100644 --- a/fact/src/host_scanner.rs +++ b/fact/src/host_scanner.rs @@ -131,6 +131,11 @@ impl HostScanner { self.update_entry(path.as_path()).with_context(|| { format!("Failed to update entry for {}", path.display()) })?; + } else if path.is_dir() { + self.metrics.scan_inc(ScanLabels::DirectoryScanned); + self.update_entry(path.as_path()).with_context(|| { + format!("Failed to update entry for {}", path.display()) + })?; } else { self.metrics.scan_inc(ScanLabels::FsItemIgnored); } @@ -154,17 +159,26 @@ impl HostScanner { dev: metadata.st_dev(), }; + let host_path = host_info::remove_host_mount(path); + self.update_entry_with_inode(&inode, host_path)?; + + debug!("Added entry for {}: {inode:?}", path.display()); + Ok(()) + } + + /// Similar to update_entry except we are are directly using the inode instead of the path. + fn update_entry_with_inode(&self, inode: &inode_key_t, path: PathBuf) -> anyhow::Result<()> { self.kernel_inode_map .borrow_mut() - .insert(inode, 0, 0) + .insert(*inode, 0, 0) .with_context(|| format!("Failed to insert kernel entry for {}", path.display()))?; + let mut inode_map = self.inode_map.borrow_mut(); - let entry = inode_map.entry(inode).or_default(); - *entry = host_info::remove_host_mount(path); + let entry = inode_map.entry(*inode).or_default(); + *entry = path; self.metrics.scan_inc(ScanLabels::FileUpdated); - debug!("Added entry for {}: {inode:?}", path.display()); Ok(()) } @@ -178,6 +192,34 @@ impl HostScanner { self.inode_map.borrow().get(inode?).cloned() } + /// Handle file creation events by adding new inodes to the map. + /// + /// We use the parent inode provided by the eBPF code + /// to look up the parent directory's host path, then construct the full + /// path by appending the new file's name. + fn handle_creation_event(&self, event: &Event) -> anyhow::Result<()> { + let inode = event.get_inode(); + let parent_inode = event.get_parent_inode(); + if self.get_host_path(Some(inode)).is_some() || parent_inode.empty() { + return Ok(()); + } + + if let Some(filename) = event.get_filename().file_name() { + if let Some(parent_host_path) = self.get_host_path(Some(parent_inode)) { + let host_path = parent_host_path.join(filename); + self.update_entry_with_inode(inode, host_path) + .with_context(|| { + format!( + "Failed to add creation event entry for {}", + filename.display() + ) + })?; + } + } + + Ok(()) + } + /// Periodically notify the host scanner main task that a scan needs /// to happen. /// @@ -219,6 +261,13 @@ impl HostScanner { }; self.metrics.events.added(); + // Handle file creation events by adding new inodes to the map + if event.is_creation() { + if let Err(e) = self.handle_creation_event(&event) { + warn!("Failed to handle creation event: {e}"); + } + } + if let Some(host_path) = self.get_host_path(Some(event.get_inode())) { self.metrics.scan_inc(ScanLabels::InodeHit); event.set_host_path(host_path); diff --git a/tests/conftest.py b/tests/conftest.py index 4267333e..826717b5 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -104,7 +104,14 @@ def dump_logs(container, file): def fact_config(request, monitored_dir, logs_dir): cwd = os.getcwd() config = { - 'paths': [f'{monitored_dir}/**/*', '/mounted/**/*', '/container-dir/**/*'], + 'paths': [ + f'{monitored_dir}', + f'{monitored_dir}/**/*', + '/mounted', + '/mounted/**/*', + '/container-dir', + '/container-dir/**/*', + ], 'grpc': { 'url': 'http://127.0.0.1:9999', }, diff --git a/tests/test_config_hotreload.py b/tests/test_config_hotreload.py index 3dfa445e..20942fde 100644 --- a/tests/test_config_hotreload.py +++ b/tests/test_config_hotreload.py @@ -99,7 +99,7 @@ def test_output_grpc_address_change(fact, fact_config, monitored_dir, server, al process = Process.from_proc() e = Event(process=process, event_type=EventType.CREATION, - file=fut, host_path='') + file=fut, host_path=fut) server.wait_events([e]) @@ -112,7 +112,7 @@ def test_output_grpc_address_change(fact, fact_config, monitored_dir, server, al f.write('This is another test') e = Event(process=process, event_type=EventType.OPEN, - file=fut, host_path='') + file=fut, host_path=fut) alternate_server.wait_events([e]) @@ -131,12 +131,12 @@ def test_paths(fact, fact_config, monitored_dir, ignored_dir, server): f.write('This is a test') e = Event(process=p, event_type=EventType.CREATION, - file=fut, host_path='') + file=fut, host_path=fut) server.wait_events([e]) config, config_file = fact_config - config['paths'] = [f'{ignored_dir}/**/*'] + config['paths'] = [f'{ignored_dir}', f'{ignored_dir}/**/*'] reload_config(fact, config, config_file) # At this point, the event in the ignored directory should show up @@ -202,7 +202,7 @@ def test_paths_then_remove(fact, fact_config, monitored_dir, server): f.write('This is a test') e = Event(process=p, event_type=EventType.CREATION, - file=fut, host_path='') + file=fut, host_path=fut) server.wait_events([e]) @@ -234,7 +234,7 @@ def test_paths_addition(fact, fact_config, monitored_dir, ignored_dir, server): f.write('This is a test') e = Event(process=p, event_type=EventType.CREATION, - file=fut, host_path='') + file=fut, host_path=fut) server.wait_events([e]) diff --git a/tests/test_editors/test_nvim.py b/tests/test_editors/test_nvim.py index 9f3d095d..101b3188 100644 --- a/tests/test_editors/test_nvim.py +++ b/tests/test_editors/test_nvim.py @@ -48,6 +48,8 @@ def test_open_file(editor_container, server): vi_test_file = get_vi_test_file('/mounted') + # TODO: host_path is empty for creation events in bind-mounted directories + # because the host-side parent directory (ignored_dir) is not scanned events = [ Event(process=touch, event_type=EventType.CREATION, file=fut, host_path=''), diff --git a/tests/test_editors/test_sed.py b/tests/test_editors/test_sed.py index c3f6a2f8..27a36fb3 100644 --- a/tests/test_editors/test_sed.py +++ b/tests/test_editors/test_sed.py @@ -27,6 +27,8 @@ def test_sed(vi_container, server): sed_tmp_file = re.compile(r'\/mounted\/sed[0-9a-zA-Z]{6}') + # TODO: host_path is empty for creation events in bind-mounted directories + # because the host-side parent directory (ignored_dir) is not scanned events = [ Event(process=shell, event_type=EventType.CREATION, file=fut, host_path=''), diff --git a/tests/test_editors/test_vi.py b/tests/test_editors/test_vi.py index 4301890a..be52eaba 100644 --- a/tests/test_editors/test_vi.py +++ b/tests/test_editors/test_vi.py @@ -19,6 +19,8 @@ def test_new_file(vi_container, server): container_id=vi_container.id[:12], ) + # TODO: host_path is empty for creation events in bind-mounted directories + # because the host-side parent directory (ignored_dir) is not scanned events = [ Event(process=process, event_type=EventType.CREATION, file=swap_file, host_path=''), @@ -112,6 +114,8 @@ def test_open_file(vi_container, server): container_id=container_id, ) + # TODO: host_path is empty for creation events in bind-mounted directories + # because the host-side parent directory (ignored_dir) is not scanned events = [ Event(process=touch_process, event_type=EventType.CREATION, file=fut, host_path=''), diff --git a/tests/test_editors/test_vim.py b/tests/test_editors/test_vim.py index 56cbb667..aa453c6c 100644 --- a/tests/test_editors/test_vim.py +++ b/tests/test_editors/test_vim.py @@ -107,6 +107,8 @@ def test_open_file(editor_container, server): container_id=container_id, ) + # TODO: host_path is empty for creation events in bind-mounted directories + # because the host-side parent directory (ignored_dir) is not scanned events = [ Event(process=touch_process, event_type=EventType.CREATION, file=fut, host_path=''), diff --git a/tests/test_file_open.py b/tests/test_file_open.py index aa1ff508..c43b8a15 100644 --- a/tests/test_file_open.py +++ b/tests/test_file_open.py @@ -35,7 +35,7 @@ def test_open(monitored_dir, server, filename): fut = path_to_string(fut) e = Event(process=Process.from_proc(), event_type=EventType.CREATION, - file=fut, host_path='') + file=fut, host_path=fut) server.wait_events([e]) @@ -59,7 +59,7 @@ def test_multiple(monitored_dir, server): f.write('This is a test') events.append( - Event(process=process, event_type=EventType.CREATION, file=fut, host_path='')) + Event(process=process, event_type=EventType.CREATION, file=fut, host_path=fut)) server.wait_events(events) @@ -139,9 +139,9 @@ def test_external_process(monitored_dir, server): p = Process.from_proc(proc.pid) creation = Event(process=p, event_type=EventType.CREATION, - file=fut, host_path='') + file=fut, host_path=fut) write_access = Event( - process=p, event_type=EventType.OPEN, file=fut, host_path='') + process=p, event_type=EventType.OPEN, file=fut, host_path=fut) try: server.wait_events([creation, write_access]) @@ -186,6 +186,7 @@ def test_mounted_dir(test_container, ignored_dir, server): name='touch', container_id=test_container.id[:12], ) + # ignored_dir is not monitored, so host_path should be blank event = Event(process=process, event_type=EventType.CREATION, file=fut, host_path='') diff --git a/tests/test_path_chmod.py b/tests/test_path_chmod.py index fde7589f..08cb483d 100644 --- a/tests/test_path_chmod.py +++ b/tests/test_path_chmod.py @@ -41,9 +41,9 @@ def test_chmod(monitored_dir, server, filename): # We expect both CREATION (from file creation) and PERMISSION (from chmod) events = [ Event(process=process, event_type=EventType.CREATION, - file=fut, host_path=''), + file=fut, host_path=fut), Event(process=process, event_type=EventType.PERMISSION, - file=fut, host_path='', mode=mode), + file=fut, host_path=fut, mode=mode), ] server.wait_events(events) @@ -69,9 +69,9 @@ def test_multiple(monitored_dir, server): events.extend([ Event(process=process, event_type=EventType.CREATION, - file=fut, host_path=''), + file=fut, host_path=fut), Event(process=process, event_type=EventType.PERMISSION, - file=fut, host_path='', mode=mode), + file=fut, host_path=fut, mode=mode), ]) server.wait_events(events) @@ -132,9 +132,9 @@ def test_external_process(monitored_dir, server): events = [ Event(process=process, event_type=EventType.CREATION, - file=fut, host_path='', mode=mode), + file=fut, host_path=fut, mode=mode), Event(process=process, event_type=EventType.PERMISSION, - file=fut, host_path='', mode=mode), + file=fut, host_path=fut, mode=mode), ] try: @@ -213,6 +213,7 @@ def test_mounted_dir(test_container, ignored_dir, server): name='chmod', container_id=test_container.id[:12], ) + # ignored_dir is not monitored, so host_path should be blank events = [ Event(process=touch, event_type=EventType.CREATION, file=fut, host_path=''), diff --git a/tests/test_path_rename.py b/tests/test_path_rename.py index 1d4f08b7..a3600eff 100644 --- a/tests/test_path_rename.py +++ b/tests/test_path_rename.py @@ -37,13 +37,20 @@ def test_rename(monitored_dir, server, filename): # Convert fut to string for the Event, replacing invalid UTF-8 with U+FFFD fut = path_to_string(fut) + # TODO: Current behavior is incorrect. The inode map should be updated + # during rename events so that host_path reflects the new path. + # Expected correct behavior: + # - First rename: host_path should be `fut` (new path), old_host_path should be `old_fut` + # - Second rename: host_path should be `old_fut`, old_host_path should be `fut` + # Current behavior: host_path remains the original path (old_fut) because + # the inode map is not updated on rename events. old_host_path is empty. events = [ Event(process=Process.from_proc(), event_type=EventType.CREATION, - file=old_fut, host_path=''), + file=old_fut, host_path=old_fut), Event(process=Process.from_proc(), event_type=EventType.RENAME, - file=fut, host_path='', old_file=old_fut, old_host_path=''), + file=fut, host_path=old_fut, old_file=old_fut, old_host_path=''), Event(process=Process.from_proc(), event_type=EventType.RENAME, - file=old_fut, host_path='', old_file=fut, old_host_path=''), + file=old_fut, host_path=old_fut, old_file=fut, old_host_path=''), ] server.wait_events(events) @@ -76,6 +83,10 @@ def test_ignored(monitored_dir, ignored_dir, server): os.rename(new_path, ignored_path) p = Process.from_proc() + # TODO: Current behavior is incorrect for rename events. + # Expected: When renaming from ignored to monitored, host_path should be new_path. + # When renaming from monitored to ignored, old_host_path should be new_path. + # Current: The inode map is not updated on renames, and old_host_path is not populated. events = [ Event(process=p, event_type=EventType.RENAME, file=new_path, host_path='', old_file=new_ignored_path, old_host_path=''), @@ -122,6 +133,11 @@ def test_rename_dir(monitored_dir, ignored_dir, server): os.rename(new_dut, ignored_dut) p = Process.from_proc() + # TODO: Current behavior is incorrect for rename events. + # Expected: host_path should reflect the new path after rename, + # old_host_path should reflect the old path if it was monitored. + # Current: The inode map is not updated on renames, so host_path remains empty + # or shows the wrong path. old_host_path is not populated. events = [ Event(process=p, event_type=EventType.RENAME, file=dut, host_path='', old_file=new_ignored_dut, old_host_path=''), @@ -188,6 +204,7 @@ def test_mounted_dir(test_container, ignored_dir, server): name='mv', container_id=test_container.id[:12], ) + # ignored_dir is not monitored, so host_path should be blank events = [ Event(process=touch, event_type=EventType.CREATION, file=fut, host_path=''), diff --git a/tests/test_path_unlink.py b/tests/test_path_unlink.py index 4dff11da..66a533e6 100644 --- a/tests/test_path_unlink.py +++ b/tests/test_path_unlink.py @@ -44,9 +44,9 @@ def test_remove(monitored_dir, server, filename): # We expect both CREATION (from file creation) and UNLINK (from removal) events = [ Event(process=process, event_type=EventType.CREATION, - file=fut, host_path=''), + file=fut, host_path=fut), Event(process=process, event_type=EventType.UNLINK, - file=fut, host_path=''), + file=fut, host_path=fut), ] server.wait_events(events) @@ -73,9 +73,9 @@ def test_multiple(monitored_dir, server): events.extend([ Event(process=process, event_type=EventType.CREATION, - file=fut, host_path=''), + file=fut, host_path=fut), Event(process=process, event_type=EventType.UNLINK, - file=fut, host_path=''), + file=fut, host_path=fut), ]) server.wait_events(events) @@ -135,9 +135,9 @@ def test_external_process(monitored_dir, server): events = [ Event(process=process, event_type=EventType.CREATION, - file=fut, host_path=''), + file=fut, host_path=fut), Event(process=process, event_type=EventType.UNLINK, - file=fut, host_path=''), + file=fut, host_path=fut), ] try: @@ -199,6 +199,7 @@ def test_mounted_dir(test_container, ignored_dir, server): name='rm', container_id=test_container.id[:12], ) + # ignored_dir is not monitored, so host_path should be blank events = [ Event(process=touch, event_type=EventType.CREATION, file=fut, host_path=''), diff --git a/tests/test_wildcard.py b/tests/test_wildcard.py index fd1728f3..a15821bf 100644 --- a/tests/test_wildcard.py +++ b/tests/test_wildcard.py @@ -36,6 +36,8 @@ def test_extension_wildcard(wildcard_config, monitored_dir, server): with open(txt_file, 'w') as f: f.write('This should be captured') + # TODO: host_path is empty because wildcard patterns don't include the parent + # directory, so the parent inode isn't tracked for path construction e = Event(process=process, event_type=EventType.CREATION, file=txt_file, host_path='') @@ -54,6 +56,8 @@ def test_prefix_wildcard(wildcard_config, monitored_dir, server): with open(test_log, 'w') as f: f.write('This should be captured') + # TODO: host_path is empty because wildcard patterns don't include the parent + # directory, so the parent inode isn't tracked for path construction e = Event(process=process, event_type=EventType.CREATION, file=test_log, host_path='') @@ -79,6 +83,8 @@ def test_recursive_wildcard(wildcard_config, monitored_dir, server): with open(nested_txt, 'w') as f: f.write('Nested txt') + # TODO: host_path is empty because wildcard patterns don't include the parent + # directory, so the parent inode isn't tracked for path construction events = [ Event(process=process, event_type=EventType.CREATION, file=root_txt, host_path=''), @@ -96,6 +102,8 @@ def test_nonrecursive_wildcard(wildcard_config, monitored_dir, server): with open(fut, 'w') as f: f.write('This should be captured') + # TODO: host_path is empty because wildcard patterns don't include the parent + # directory, so the parent inode isn't tracked for path construction e = Event(process=process, event_type=EventType.CREATION, file=fut, host_path='') @@ -118,6 +126,8 @@ def test_multiple_patterns(wildcard_config, monitored_dir, server): with open(log_file, 'w') as f: f.write('Log file') + # TODO: host_path is empty because wildcard patterns don't include the parent + # directory, so the parent inode isn't tracked for path construction events = [ Event(process=process, event_type=EventType.CREATION, file=txt_file, host_path=''),