202 lines
6.5 KiB
Rust
202 lines
6.5 KiB
Rust
use std::{
|
|
ffi::CString,
|
|
mem::MaybeUninit,
|
|
os::fd::{AsRawFd, BorrowedFd, FromRawFd, OwnedFd},
|
|
ptr::null,
|
|
};
|
|
|
|
use crate::{
|
|
consts::{EventFFlags, InitFlags, MarkFlags, MaskFlags},
|
|
error::Errno,
|
|
};
|
|
|
|
pub struct Fanotify {
|
|
fd: OwnedFd,
|
|
}
|
|
|
|
#[derive(thiserror::Error, Debug)]
|
|
pub enum Error {
|
|
#[error("cannot convert path into c string: {0}")]
|
|
PathError(#[from] std::ffi::NulError),
|
|
|
|
#[error("fanotify returned errno: {0}")]
|
|
FanotifyError(#[from] Errno),
|
|
}
|
|
|
|
impl Fanotify {
|
|
pub fn init(init_flags: InitFlags, event_fd_flags: EventFFlags) -> Result<Self, Error> {
|
|
Self::try_init(init_flags, event_fd_flags)
|
|
}
|
|
pub fn try_init(init_flags: InitFlags, event_fd_flags: EventFFlags) -> Result<Self, Error> {
|
|
let fd = unsafe {
|
|
let ret = libc::fanotify_init(init_flags.bits(), event_fd_flags.bits());
|
|
if ret == -1 {
|
|
return Err(Error::FanotifyError(Errno::errno()));
|
|
}
|
|
OwnedFd::from_raw_fd(ret)
|
|
};
|
|
Ok(Self { fd })
|
|
}
|
|
|
|
pub fn mark<P: Into<String>>(
|
|
&self,
|
|
operation: MarkFlags,
|
|
mask: MaskFlags,
|
|
dirfd: Option<BorrowedFd>,
|
|
path: Option<P>,
|
|
) -> Result<(), Error> {
|
|
let dirfd = match dirfd {
|
|
Some(fd) => fd.as_raw_fd(),
|
|
None => libc::AT_FDCWD,
|
|
};
|
|
let result = unsafe {
|
|
// hold it here to prevent drop. don't merge two matches
|
|
if let Some(path) = path {
|
|
let path: String = path.into();
|
|
let cstr = CString::new(path)?;
|
|
libc::fanotify_mark(
|
|
self.fd.as_raw_fd(),
|
|
operation.bits(),
|
|
mask.bits(),
|
|
dirfd,
|
|
cstr.as_ptr(),
|
|
)
|
|
} else {
|
|
libc::fanotify_mark(
|
|
self.fd.as_raw_fd(),
|
|
operation.bits(),
|
|
mask.bits(),
|
|
dirfd,
|
|
null(),
|
|
)
|
|
}
|
|
};
|
|
|
|
if result != 0 {
|
|
return Err(Error::FanotifyError(Errno::errno()));
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub fn read_events(&self) -> Result<Vec<Event>, Error> {
|
|
const BUFFER_SIZE: usize = 4096;
|
|
const EVENT_SIZE: usize = size_of::<libc::fanotify_event_metadata>();
|
|
let mut buffer = [0u8; BUFFER_SIZE];
|
|
let mut result = Vec::new();
|
|
unsafe {
|
|
let nread = libc::read(self.fd.as_raw_fd(), buffer.as_mut_ptr().cast(), BUFFER_SIZE);
|
|
if nread < 0 {
|
|
return Err(Error::FanotifyError(Errno::new(nread as i32)));
|
|
}
|
|
let nread = nread as usize;
|
|
let mut offset = 0;
|
|
// #define FAN_EVENT_OK(meta, len) \
|
|
// ((long)(len) => (long)FAN_EVENT_METADATA_LEN) && // rest buffer can contain a metadata struct
|
|
// (long)(meta) ->event_len >= (long)FAN_FAN_EVENT_METADATA_LEN && // struct contains valid size (not implemented)
|
|
// (long)(meta) ->event_len <= (long)(len) // struct does not read over buffer boundary (not implemented)
|
|
while offset + EVENT_SIZE <= nread {
|
|
let mut uninited: MaybeUninit<libc::fanotify_event_metadata> = MaybeUninit::uninit();
|
|
std::ptr::copy(
|
|
buffer.as_ptr().add(offset),
|
|
uninited.as_mut_ptr().cast(),
|
|
EVENT_SIZE,
|
|
);
|
|
let event = uninited.assume_init();
|
|
|
|
// #define FAN_EVENT_NEXT(meta, len) ((len) -= (meta)->event_len, (struct fanotify_event_metadata*)(((char*)(meta)) + (meta) -> event_len)
|
|
// meta = FAN_EVENT_NEXT(meta, len) translate to:
|
|
// len -= meta->event_len; // shrink rest length
|
|
// , // comma operator, evaluate first express, but not using its result
|
|
//
|
|
// meta = (struct fanotify_event_metadata*) ( // cast pointer back to metadata type
|
|
// ((char*)(meta)) // discard metadata type to increase pointer by 1
|
|
// + (meta) -> event_len // add event_len to move to next
|
|
// );
|
|
offset += event.event_len as usize;
|
|
result.push(Event(event));
|
|
}
|
|
}
|
|
|
|
Ok(result)
|
|
}
|
|
|
|
pub fn write_response(&self, response: Response) -> Result<(), Errno> {
|
|
let n = unsafe {
|
|
libc::write(
|
|
self.fd.as_raw_fd(),
|
|
(&response.inner as *const libc::fanotify_response).cast(),
|
|
size_of::<libc::fanotify_response>(),
|
|
)
|
|
};
|
|
if n == -1 {
|
|
return Err(Errno::errno());
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
pub struct Event(pub libc::fanotify_event_metadata);
|
|
|
|
impl Event {
|
|
// compatible to nix::sys::fanotify::FanotifyEvent
|
|
pub fn metadata_version(&self) -> u8 {
|
|
self.0.vers
|
|
}
|
|
pub fn check_metadata_version(&self) -> bool {
|
|
self.0.vers == libc::FANOTIFY_METADATA_VERSION
|
|
}
|
|
pub fn fd(&self) -> Option<BorrowedFd> {
|
|
if self.0.fd == libc::FAN_NOFD {
|
|
None
|
|
} else {
|
|
Some(unsafe { BorrowedFd::borrow_raw(self.0.fd) })
|
|
}
|
|
}
|
|
pub fn pid(&self) -> i32 {
|
|
self.0.pid
|
|
}
|
|
pub fn mask(&self) -> MaskFlags {
|
|
MaskFlags::from_bits_truncate(self.0.mask)
|
|
}
|
|
|
|
// sometimes we don't want to close the fd immediately, so we forget about it, store it somewhere, and drop it later
|
|
// it is safe to just call this method without store it in variable, it will be dropped immediately due to the nature of rust
|
|
pub fn forget_fd(&mut self) -> OwnedFd {
|
|
let fd = self.0.fd;
|
|
self.0.fd = libc::FAN_NOFD;
|
|
|
|
unsafe { OwnedFd::from_raw_fd(fd) }
|
|
}
|
|
}
|
|
|
|
impl Drop for Event {
|
|
fn drop(&mut self) {
|
|
if self.0.fd == libc::FAN_NOFD {
|
|
return;
|
|
}
|
|
|
|
let e = unsafe { libc::close(self.0.fd) };
|
|
if !std::thread::panicking() && e == libc::EBADF {
|
|
panic!("Closing an invalid file descriptor!");
|
|
};
|
|
}
|
|
}
|
|
|
|
pub struct Response {
|
|
inner: libc::fanotify_response,
|
|
}
|
|
|
|
impl Response {
|
|
pub const FAN_ALLOW: u32 = libc::FAN_ALLOW;
|
|
pub const FAN_DENY: u32 = libc::FAN_DENY;
|
|
pub fn new(fd: BorrowedFd, response: u32) -> Self {
|
|
Self {
|
|
inner: libc::fanotify_response {
|
|
fd: fd.as_raw_fd(),
|
|
response,
|
|
},
|
|
}
|
|
}
|
|
}
|