use core::panic;
use std::ffi::{OsStr, c_int};
use std::mem::{align_of, size_of};
use std::os::fd::AsRawFd;
use std::os::unix::ffi::OsStrExt;
use std::{io, slice};
use bitflags::bitflags;
use mio::unix::SourceFd;
use mio::{Events, Interest, Poll};
use rustix::fd::{AsFd, OwnedFd};
use rustix::fs::inotify::{self, CreateFlags, WatchFlags};
use rustix::io::Errno;
const INOTIFY: mio::Token = mio::Token(0);
pub const MESSAGE: mio::Token = mio::Token(1);
#[derive(Debug, PartialEq, Eq, Hash)]
#[repr(transparent)]
pub(super) struct Watch(c_int);
impl Watch {
pub fn is_invalid(&self) -> bool {
self.0 < 0
}
}
#[derive(Debug)]
pub(super) struct Inotify {
fd: OwnedFd,
}
impl Inotify {
pub(super) fn new() -> io::Result<Inotify> {
let fd = inotify::init(CreateFlags::CLOEXEC | CreateFlags::NONBLOCK)?;
Ok(Inotify { fd })
}
pub(super) fn add_directory_watch(&self, path: impl rustix::path::Arg) -> io::Result<Watch> {
let watch = inotify::add_watch(
self.as_fd(),
path,
WatchFlags::ATTRIB
| WatchFlags::CREATE
| WatchFlags::DELETE
| WatchFlags::DELETE_SELF
| WatchFlags::MODIFY
| WatchFlags::MOVE_SELF
| WatchFlags::MOVE
| WatchFlags::DONT_FOLLOW
| WatchFlags::EXCL_UNLINK
| WatchFlags::ONLYDIR,
)
.map_err(|err| {
if err == Errno::NOSPC {
io::Error::new(io::ErrorKind::ResourceBusy, "exhaused inotify max_user_watches, try increasing the setting or adding stricter glob filter")
} else {
err.into()
}
})?;
Ok(Watch(watch))
}
pub(super) fn event_loop(
&self,
poll: &mut Poll,
mut handle_event: impl FnMut(&OsStr, &Event /* , SystemTime */),
mut event_stream_done: impl FnMut(),
mut handle_message: impl FnMut() -> bool,
#[cfg(test)] slow: bool,
) -> io::Result<()> {
let mut buf = vec![0; BUFFERSIZE].into_boxed_slice();
let raw_fd = self.fd.as_raw_fd();
let mut fd = SourceFd(&raw_fd);
poll.registry()
.register(&mut fd, INOTIFY, Interest::READABLE)?;
let mut events = Events::with_capacity(16);
loop {
match poll.poll(&mut events, None) {
Err(ref e) if matches!(e.kind(), std::io::ErrorKind::Interrupted) => {
}
Err(e) => return Err(e),
Ok(()) => {}
}
let mut message = false;
let mut inotify = false;
for event in &events {
match event.token() {
INOTIFY => inotify = true,
MESSAGE => message = true,
_ => unreachable!(),
}
}
events.clear();
if inotify {
#[cfg(test)]
if slow {
use crate::tests::READ_DELAY;
std::thread::sleep(*READ_DELAY);
}
loop {
let buf_bytes = unsafe {
slice::from_raw_parts_mut(
buf.as_mut_ptr().cast::<u8>(),
buf.len() * size_of::<u32>(),
)
};
let size = match rustix::io::read(&self.fd, buf_bytes) {
Ok(0) | Err(Errno::AGAIN) => break,
Ok(read) => read,
Err(err) => return Err(err.into()),
};
let mut buf = &buf[..size.div_ceil(size_of::<u32>())];
while !buf.is_empty() {
let (event, name, rem) = Event::get(buf);
handle_event(name, event );
buf = rem;
}
}
event_stream_done()
}
if message && handle_message() {
break;
}
}
Ok(())
}
}
impl AsFd for Inotify {
fn as_fd(&self) -> rustix::fd::BorrowedFd<'_> {
self.fd.as_fd()
}
}
#[repr(C)]
#[derive(Debug)]
pub(super) struct Event {
pub wd: Watch,
pub flags: EventFlags,
pub cookie: u32,
len: u32,
}
const ALIGNED_SIZED: usize = {
if align_of::<u32>() != align_of::<Event>() {
panic!("invalid align");
}
if size_of::<Event>() % align_of::<Event>() != 0 {
panic!("invalid size");
}
size_of::<Event>() / align_of::<Event>()
};
const ALIGNED_MAX_EVENT_SIZE: usize = (size_of::<Event>() + 256) / align_of::<Event>();
const BUFFERSIZE: usize = 16 * 1024 * ALIGNED_MAX_EVENT_SIZE;
impl Event {
fn get(buf: &[u32]) -> (&Event, &OsStr, &[u32]) {
let (head, buf) = buf.split_at(ALIGNED_SIZED);
let event: *const Event = head.as_ptr().cast();
let event: &Event = unsafe { &*event };
let len = event.len as usize;
if len == 0 {
(event, OsStr::new(""), buf)
} else {
let (name, tail) = buf.split_at(len.div_ceil(align_of::<Event>()));
let name = unsafe { slice::from_raw_parts(name.as_ptr() as *const u8, len) };
let name = std::ffi::CStr::from_bytes_until_nul(name).unwrap();
let name = OsStr::from_bytes(name.to_bytes());
(event, name, tail)
}
}
}
bitflags! {
#[derive(Debug)]
pub struct EventFlags: u32 {
const ATTRIB = WatchFlags::ATTRIB.bits();
const CREATE = WatchFlags::CREATE.bits();
const DELETE = WatchFlags::DELETE.bits();
const DELETE_SELF = WatchFlags::DELETE_SELF.bits();
const MODIFY = WatchFlags::MODIFY.bits();
const MOVE_SELF = WatchFlags::MOVE_SELF.bits();
const MOVE_FROM = WatchFlags::MOVED_FROM.bits();
const MOVE_TO = WatchFlags::MOVED_TO.bits();
const IGNORED = 0x00008000;
const Q_OVERFLOW = 0x00004000;
const UNMOUNT = 0x00002000;
}
}