use crate::errno::Errno;
use crate::unistd::read;
use crate::NixPath;
use crate::Result;
use cfg_if::cfg_if;
use libc::{c_char, c_int};
use std::ffi::{CStr, OsStr, OsString};
use std::mem::{size_of, MaybeUninit};
use std::os::unix::ffi::OsStrExt;
use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, OwnedFd, RawFd};
use std::ptr;
libc_bitflags! {
pub struct AddWatchFlags: u32 {
IN_ACCESS;
IN_MODIFY;
IN_ATTRIB;
IN_CLOSE_WRITE;
IN_CLOSE_NOWRITE;
IN_OPEN;
IN_MOVED_FROM;
IN_MOVED_TO;
IN_CREATE;
IN_DELETE;
IN_DELETE_SELF;
IN_MOVE_SELF;
IN_UNMOUNT;
IN_Q_OVERFLOW;
IN_IGNORED;
IN_CLOSE;
IN_MOVE;
IN_ONLYDIR;
IN_DONT_FOLLOW;
IN_ISDIR;
IN_ONESHOT;
IN_ALL_EVENTS;
}
}
libc_bitflags! {
pub struct InitFlags: c_int {
IN_CLOEXEC;
IN_NONBLOCK;
}
}
#[derive(Debug)]
pub struct Inotify {
fd: OwnedFd,
}
#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, Ord, PartialOrd)]
pub struct WatchDescriptor {
wd: i32,
}
impl WatchDescriptor {
pub fn as_raw(self) -> i32 {
self.wd
}
}
#[derive(Debug)]
pub struct InotifyEvent {
pub wd: WatchDescriptor,
pub mask: AddWatchFlags,
pub cookie: u32,
pub name: Option<OsString>,
}
impl Inotify {
pub fn init(flags: InitFlags) -> Result<Inotify> {
let res = Errno::result(unsafe { libc::inotify_init1(flags.bits()) });
res.map(|fd| Inotify {
fd: unsafe { OwnedFd::from_raw_fd(fd) },
})
}
pub fn add_watch<P: ?Sized + NixPath>(
&self,
path: &P,
mask: AddWatchFlags,
) -> Result<WatchDescriptor> {
let res = path.with_nix_path(|cstr| unsafe {
libc::inotify_add_watch(
self.fd.as_raw_fd(),
cstr.as_ptr(),
mask.bits(),
)
})?;
Errno::result(res).map(|wd| WatchDescriptor { wd })
}
pub fn rm_watch(&self, wd: WatchDescriptor) -> Result<()> {
cfg_if! {
if #[cfg(target_os = "linux")] {
let arg = wd.wd;
} else if #[cfg(target_os = "android")] {
let arg = wd.wd as u32;
}
}
let res = unsafe { libc::inotify_rm_watch(self.fd.as_raw_fd(), arg) };
Errno::result(res).map(drop)
}
pub fn read_events(&self) -> Result<Vec<InotifyEvent>> {
let header_size = size_of::<libc::inotify_event>();
const BUFSIZ: usize = 4096;
let mut buffer = [0u8; BUFSIZ];
let mut events = Vec::new();
let mut offset = 0;
let nread = read(&self.fd, &mut buffer)?;
while (nread - offset) >= header_size {
let event = unsafe {
let mut event = MaybeUninit::<libc::inotify_event>::uninit();
ptr::copy_nonoverlapping(
buffer.as_ptr().add(offset),
event.as_mut_ptr().cast(),
(BUFSIZ - offset).min(header_size),
);
event.assume_init()
};
let name = match event.len {
0 => None,
_ => {
let ptr = unsafe {
buffer.as_ptr().add(offset + header_size)
as *const c_char
};
let cstr = unsafe { CStr::from_ptr(ptr) };
Some(OsStr::from_bytes(cstr.to_bytes()).to_owned())
}
};
events.push(InotifyEvent {
wd: WatchDescriptor { wd: event.wd },
mask: AddWatchFlags::from_bits_truncate(event.mask),
cookie: event.cookie,
name,
});
offset += header_size + event.len as usize;
}
Ok(events)
}
pub unsafe fn from_owned_fd(fd: OwnedFd) -> Self {
Self {
fd
}
}
}
impl FromRawFd for Inotify {
unsafe fn from_raw_fd(fd: RawFd) -> Self {
Inotify {
fd: unsafe { OwnedFd::from_raw_fd(fd) },
}
}
}
impl AsFd for Inotify {
fn as_fd(&'_ self) -> BorrowedFd<'_> {
self.fd.as_fd()
}
}
impl From<Inotify> for OwnedFd {
fn from(value: Inotify) -> Self {
value.fd
}
}