Skip to content

Commit

Permalink
Add support for fanotify on Linux
Browse files Browse the repository at this point in the history
Signed-off-by: Jason Rogena <null+github.com@rogena.me>
  • Loading branch information
Jason Rogena committed Oct 26, 2024
1 parent 492d7c2 commit e68655c
Show file tree
Hide file tree
Showing 7 changed files with 322 additions and 106 deletions.
60 changes: 60 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,6 @@ thiserror = "1.0.63"
bitflags = "2.6.0"
tokio-util = "0.7.10"
notify = { version = "6.1.1", default-features = false, features = ["macos_kqueue"] }
[target.'cfg(target_os = "linux")'.dependencies]
fanotify-rs = "0.3.1"
nix = "0.26.4"
126 changes: 126 additions & 0 deletions src/fs_notify/fanotify_notifier.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
use super::{Notification, Notifier};
#[cfg(target_os = "linux")]
use {
fanotify::{high_level::{Fanotify, FanotifyMode,FanEvent}, low_level::{FAN_CLOSE_WRITE, FAN_CREATE, FAN_MODIFY, FAN_MOVE_SELF}},
nix::poll::{poll, PollFd, PollFlags},
std::thread,
super::FsOp,
std::os::fd::AsFd,
std::os::fd::AsRawFd,
};
use std::{
collections::HashSet,
env::consts::OS,
path::PathBuf,
sync::mpsc::Sender,
};
use tokio_util::sync::CancellationToken;

#[allow(dead_code)]
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("The feature '{0}' is unsupported")]
UnsupportedFeature(String),
//#[error("An error was thrown by the filesystem notification system")]
//Faotify(#[from] fanotify::Error),
#[error("An error was thrown while trying to interract with the notification system")]
Send(#[from] std::sync::mpsc::SendError<bool>),
}

#[allow(dead_code)]
pub(crate) struct FanotifyNotifier {
stop_cancellation_token: CancellationToken,
}

impl FanotifyNotifier {
pub(crate) fn new() -> Self {
FanotifyNotifier {
stop_cancellation_token: CancellationToken::new(),
}
}

#[cfg(target_os = "linux")]
fn convert_op(events: Vec<FanEvent>) -> FsOp {
let mut fs_op = FsOp::OTHER;
for cur_event in events {
if cur_event == FanEvent::Delete || cur_event == FanEvent::DeleteSelf {
fs_op.insert(FsOp::REMOVE);
}
}

fs_op
}

#[cfg(target_os = "linux")]
fn start_watching_linux(
&mut self,
paths: &HashSet<PathBuf>,
notification_sender: Sender<Notification>,
) -> Result<(), Error> {
let stop_cancellation_token = self.stop_cancellation_token.clone();
let local_paths = paths.clone();
thread::spawn(move || {
let fd = match Fanotify::new_nonblocking(FanotifyMode::NOTIF) {
Ok(f) => f,
Err(e) => panic!("An error occurred while trying to initialise the fanotify watcher: {}", e),
};
for cur_path in local_paths {
fd.add_mountpoint(
FAN_CREATE | FAN_CLOSE_WRITE | FAN_MOVE_SELF | FAN_MODIFY,
(&cur_path).into(),
).unwrap();
}
let fd_handle = fd.as_fd();
let mut fds = [PollFd::new(fd_handle.as_raw_fd(), PollFlags::POLLIN)];
loop {
let poll_num = poll(&mut fds, -1).unwrap();
if poll_num > 0 {
for event in fd.read_event() {
let notification = Notification {
path: PathBuf::from(event.path),
op: Self::convert_op(event.events),
};
if let Err(e) = notification_sender.send(notification) {
eprint!("Unable to notify upwards a filesystem event: {:?}", e);
}
}
} else {
eprintln!("poll_num <= 0!");
break;
}

if stop_cancellation_token.is_cancelled() {
println!("Cancelling watching using FanotifyNotifier");
break;
}
}
});

Ok(())
}
}

impl Notifier for FanotifyNotifier {
fn start_watching(
&mut self,
_paths: &HashSet<PathBuf>,
_notification_sender: Sender<Notification>,
) -> Result<(), super::Error> {
#[cfg(target_os = "linux")]
self.start_watching_linux(_paths, _notification_sender)?;

Ok(())
}

fn stop_watching(&mut self) {
self.stop_cancellation_token.cancel();
}

fn is_supported(&self) -> bool {
if OS != "linux" {
return false;
}

true
}
}
96 changes: 0 additions & 96 deletions src/fs_notify/inotify.rs

This file was deleted.

31 changes: 24 additions & 7 deletions src/fs_notify/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ use std::sync::mpsc::{channel, Receiver, Sender};
use std::time::Duration;
use ttl_cache::TtlCache;

mod inotify;
mod notify_notifier;
mod fanotify_notifier;

#[cfg(test)]
#[cfg(target_family = "unix")]
Expand All @@ -16,12 +17,14 @@ mod tests_unsupported_os;

#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("An error was thrown by the filesystem notification system")]
Notify(#[from] notify::Error),
#[error("An error was thrown by NotifyNotifier")]
NotifyNotifier(#[from] notify_notifier::Error),
#[error("An error was thrown by NotifyNotifier")]
FanotifyNotifier(#[from] fanotify_notifier::Error),
#[error("An error was thrown while trying to interract with the notification system")]
Send(#[from] std::sync::mpsc::SendError<bool>),
#[error("An error was returned by the Inotify notification system")]
Inotify(#[from] inotify::Error),
#[error("The watching feature is unsupported")]
FeatureNotSupported(),
}

pub struct Notify<'a> {
Expand Down Expand Up @@ -59,6 +62,7 @@ trait Notifier {
notification_sender: Sender<Notification>,
) -> Result<(), Error>;
fn stop_watching(&mut self);
fn is_supported(&self) -> bool;
}

impl<'a> Notify<'a> {
Expand Down Expand Up @@ -121,8 +125,21 @@ impl<'a> Notify<'a> {
#[allow(dead_code)]
pub fn watch(&mut self) -> Result<(), Error> {
let (notification_sender, notification_receiver) = channel();
let mut i = inotify::Inotify::init();
i.start_watching(&self.paths, notification_sender)?;
let mut watching = false;
let mut i = notify_notifier::NotifyNotifier::new();
if i.is_supported() {
i.start_watching(&self.paths, notification_sender.clone())?;
watching = true;
}
let mut fa = fanotify_notifier::FanotifyNotifier::new();
if fa.is_supported() {
fa.start_watching(&self.paths, notification_sender)?;
watching = true;
}

if !watching {
return Err(Error::FeatureNotSupported());
}

loop {
match notification_receiver.recv() {
Expand Down
Loading

0 comments on commit e68655c

Please sign in to comment.