Crates.io | fs_usage_sys |
lib.rs | fs_usage_sys |
version | 0.1.4 |
created_at | 2025-07-07 03:32:57.794288+00 |
updated_at | 2025-07-08 13:10:18.322231+00 |
description | A Rust wrapper for macOS fs_usage to monitor file system events with advanced filtering |
homepage | https://github.com/madhavajay/fs_usage_sys |
repository | https://github.com/madhavajay/fs_usage_sys |
max_upload_size | |
id | 1740674 |
size | 183,818 |
A Rust library that wraps macOS's fs_usage
command to monitor file system events with support for glob patterns, process filtering, and operation type filtering.
fs_usage
/Users/*/Documents/**/*.txt
)use fs_usage_sys::{FsUsageMonitorBuilder, OperationType};
// Monitor file system operations
let mut monitor = FsUsageMonitorBuilder::new()
.watch_path("/Users/*/Documents/**/*")
.exclude_process("mds")
.exclude_process("mdworker")
.build()?;
monitor.start()?;
while let Ok(event) = monitor.recv() {
// Detect write operations (see docs/detecting_writes.md for patterns)
if is_write_operation(&event) {
if event.process_name.contains("Cursor") {
println!("🤖 AI modified: {}", event.path);
} else {
println!("👤 Manual edit: {}", event.path);
}
}
}
Important: Don't use watch_writes_only()
as it filters too aggressively. See Detecting Write Operations for comprehensive write detection patterns.
Add to your Cargo.toml
:
[dependencies]
fs_usage_sys = "0.1.0"
Or use cargo add:
cargo add fs_usage_sys
fs_usage
command)fs_usage
The library categorizes file system operations into logical groups:
Read
- Reading file contents (read
, pread
, RdData
)Write
- Writing file contents (write
, pwrite
, WrData
, ftruncate
)Create
- Creating files/directories (open
, creat
, mkdir
)Delete
- Removing files/directories (unlink
, rmdir
)Move
- Renaming/moving (rename
, renameat
)Access
- Checking file existence/permissions (access
, stat64
)Metadata
- Reading file attributes (getxattr
, getattrlist
)All
- No filtering (default)pub struct FsEvent {
pub timestamp: String, // "23:52:52.781431"
pub process_name: String, // "vim", "Cursor", "touch"
pub pid: u32, // Process ID
pub operation: String, // "write", "open", "unlink"
pub path: String, // "/tmp/test.txt"
pub result: String, // "OK" or error code
}
use fs_usage_sys::FsUsageMonitorBuilder;
let mut monitor = FsUsageMonitorBuilder::new()
.watch_path("/tmp/**/*")
.build()?;
monitor.start()?;
while let Ok(event) = monitor.recv() {
println!("{} {} {}", event.process_name, event.operation, event.path);
}
use fs_usage_sys::{FsUsageMonitorBuilder, OperationType};
use std::process;
let monitor = FsUsageMonitorBuilder::new()
.watch_path("/path/to/project/**/*")
.watch_writes_only() // Only modifications, not reads
.exclude_pid(process::id()) // Exclude self
.exclude_process("mds")
.build()?;
monitor.start()?;
while let Ok(event) = monitor.recv() {
match classify_source(&event.process_name) {
SourceType::AiAssistant => {
println!("🤖 AI modified: {}", event.path);
// Decision logic: approve/reject AI changes
if should_reject_ai_change(&event) {
println!("❌ Rejecting AI change");
// Implement rollback logic
}
}
SourceType::TextEditor => {
println!("✏️ Manual edit: {}", event.path);
}
SourceType::System => {
println!("🔧 System process: {}", event.path);
}
}
}
fn classify_source(process_name: &str) -> SourceType {
if ["Cursor", "Code", "code", "node"].iter().any(|&p| process_name.contains(p)) {
SourceType::AiAssistant
} else if ["vim", "nvim", "emacs", "nano"].iter().any(|&p| process_name.contains(p)) {
SourceType::TextEditor
} else {
SourceType::System
}
}
use fs_usage_sys::{FsUsageMonitorBuilder, OperationType};
// Monitor specific operations only
let monitor = FsUsageMonitorBuilder::new()
.watch_path("/Users/*/Documents/**/*")
.watch_operations([
OperationType::Write,
OperationType::Create,
OperationType::Delete
])
.exclude_processes(["mds", "mdworker", "Spotlight"])
.build()?;
// Or use convenience methods
let monitor = FsUsageMonitorBuilder::new()
.watch_writes_only() // Writes + Creates + Deletes + Moves
.exclude_metadata() // Skip stat/lstat operations
.build()?;
use fs_usage_sys::FsUsageMonitorBuilder;
// Monitor only specific processes
let monitor = FsUsageMonitorBuilder::new()
.watch_pid(12345) // Specific PID
.watch_pids([12345, 67890]) // Multiple PIDs
.build()?;
// Or exclude specific processes
let monitor = FsUsageMonitorBuilder::new()
.exclude_process("Spotlight")
.exclude_pids([process::id()]) // Exclude self
.build()?;
watch_path(path)
- Add a glob pattern to monitorwatch_paths(paths)
- Add multiple glob patternswatch_pid(pid)
- Monitor only specific process IDwatch_pids(pids)
- Monitor multiple specific PIDsexclude_pid(pid)
- Exclude a specific PIDexclude_pids(pids)
- Exclude multiple PIDsexclude_process(name)
- Exclude processes by nameexclude_processes(names)
- Exclude multiple processeswatch_operations(types)
- Custom operation filteringwatch_writes_only()
- Only writes, creates, deletes, moveswatch_reads_only()
- Only read operationsexclude_metadata()
- Skip stat/lstat operationsThe library supports standard glob patterns:
*
- Match any number of characters (except /
)**
- Match any number of directories?
- Match a single character[abc]
- Match any character in bracketsExamples:
/tmp/*
- Files directly in /tmp
/tmp/**/*
- All files in /tmp
and subdirectories/Users/*/Documents/*.txt
- Text files in any user's Documents/path/to/project/**/*.{rs,toml}
- Rust and TOML filesAll examples require sudo to access fs_usage
:
# Basic monitoring
sudo cargo run --example basic_monitor
# Monitor with path filtering
sudo cargo run --example basic_monitor '/tmp/**/*'
# Process filtering and categorization
sudo cargo run --example process_filter '/Users/*/Documents/**/*'
# Only write operations (no reads/stats)
sudo cargo run --example writes_only '/tmp/**/*'
# Debug mode - see all parsing
sudo RUST_LOG=fs_usage_sys=debug cargo run --example debug_monitor
Monitor your project directory for changes during development:
let monitor = FsUsageMonitorBuilder::new()
.watch_path("/path/to/project/**/*.{rs,toml,md}")
.watch_writes_only()
.exclude_process("target") // Exclude build artifacts
.build()?;
Detect unauthorized file modifications:
let monitor = FsUsageMonitorBuilder::new()
.watch_path("/etc/**/*")
.watch_path("/usr/local/bin/**/*")
.watch_operations([OperationType::Write, OperationType::Delete])
.build()?;
Monitor when files are modified for backup purposes:
let monitor = FsUsageMonitorBuilder::new()
.watch_path("/Users/*/Documents/**/*")
.watch_writes_only()
.exclude_processes(["Time Machine", "Spotlight"])
.build()?;
The library uses anyhow
for error handling:
use anyhow::Result;
fn monitor_files() -> Result<()> {
let mut monitor = FsUsageMonitorBuilder::new()
.watch_path("/invalid/glob/[") // This will fail
.build()?;
monitor.start()?; // This requires sudo
// Handle events with timeout
loop {
match monitor.events().recv_timeout(Duration::from_secs(1)) {
Ok(event) => println!("Event: {:?}", event),
Err(RecvTimeoutError::Timeout) => continue,
Err(e) => return Err(e.into()),
}
}
}
exclude_metadata()
)# fs_usage requires root permissions
sudo cargo run --example basic_monitor
watch_writes_only()
to reduce noiseMIT
Contributions welcome! Please ensure:
cargo test
sudo cargo run --example basic_monitor