use std::borrow::Cow;
use std::collections::HashMap;
use std::ffi::{CString, OsStr, OsString};
use std::mem::replace;
use std::path::{Path, PathBuf};
use std::pin::Pin;
use std::task::{self, Poll};
use std::{fmt, io, ptr, slice};
use crate::fs::notify::{self, Events, Interest, Recursive, Watcher};
use crate::io::Read;
use crate::{AsyncFd, SubmissionQueue, syscall};
pub(crate) type Watching = HashMap<WatchFd, PathBufWithNull>;
type PathBufWithNull = CString;
type WatchFd = std::os::fd::RawFd;
const BUF_SIZE: usize = size_of::<libc::inotify_event>() + libc::NAME_MAX as usize + 1 ;
impl Watcher {
pub(crate) fn new_sys(sq: SubmissionQueue) -> io::Result<Watcher> {
let ifd = syscall!(inotify_init1(libc::IN_CLOEXEC))?;
let fd = unsafe { AsyncFd::from_raw_fd(ifd, sq) };
Ok(Watcher {
fd,
watching: HashMap::new(),
})
}
}
pub(crate) fn watch_recursive(
fd: &AsyncFd,
watching: &mut Watching,
dir: PathBuf,
interest: Interest,
recursive: Recursive,
dir_only: bool,
) -> io::Result<()> {
if let Recursive::All = recursive {
match std::fs::read_dir(&dir) {
Ok(read_dir) => {
for result in read_dir {
let entry = result?;
if entry.file_type()?.is_dir() {
let dir = entry.path();
watch_recursive(fd, watching, dir, interest, Recursive::All, true)?;
}
}
}
Err(ref err) if !dir_only && err.kind() == io::ErrorKind::NotADirectory => {
}
Err(err) => return Err(err),
}
}
let interest = Interest(interest.0 | if dir_only { libc::IN_ONLYDIR } else { 0 });
watch(fd, watching, dir, interest)
}
pub(crate) fn watch(
fd: &AsyncFd,
watching: &mut Watching,
path: PathBuf,
interest: Interest,
) -> io::Result<()> {
let path =
unsafe { PathBufWithNull::from_vec_unchecked(OsString::from(path).into_encoded_bytes()) };
let mask = interest.0
| libc::IN_DONT_FOLLOW
| libc::IN_EXCL_UNLINK
| libc::IN_MASK_ADD;
let lpath =
unsafe { Path::new(OsStr::from_encoded_bytes_unchecked(path.as_bytes())).display() };
log::trace!(path:% = lpath, mask:?; "watching path");
let wd = syscall!(inotify_add_watch(fd.fd(), path.as_ptr(), mask))?;
_ = watching.insert(wd, path);
Ok(())
}
pub(crate) const INTEREST_ALL: u32 = libc::IN_ALL_EVENTS;
pub(crate) const INTEREST_ACCESS: u32 = libc::IN_ACCESS;
pub(crate) const INTEREST_MODIFY: u32 = libc::IN_MODIFY;
pub(crate) const INTEREST_METADATA: u32 = libc::IN_ATTRIB;
pub(crate) const INTEREST_CLOSE_WRITE: u32 = libc::IN_CLOSE_WRITE;
pub(crate) const INTEREST_CLOSE_NOWRITE: u32 = libc::IN_CLOSE_NOWRITE;
pub(crate) const INTEREST_CLOSE: u32 = libc::IN_CLOSE;
pub(crate) const INTEREST_OPEN: u32 = libc::IN_OPEN;
pub(crate) const INTEREST_MOVE_FROM: u32 = libc::IN_MOVED_FROM;
pub(crate) const INTEREST_MOVE_INTO: u32 = libc::IN_MOVED_TO;
pub(crate) const INTEREST_MOVE: u32 = libc::IN_MOVE;
pub(crate) const INTEREST_CREATE: u32 = libc::IN_CREATE;
pub(crate) const INTEREST_DELETE: u32 = libc::IN_DELETE;
pub(crate) const INTEREST_DELETE_SELF: u32 = libc::IN_DELETE_SELF;
pub(crate) const INTEREST_MOVE_SELF: u32 = libc::IN_MOVE_SELF;
#[derive(Debug)]
pub(crate) enum EventsState<'w> {
Reading(Read<'w, Vec<u8>>),
Processing {
buf: Vec<u8>,
processed: usize,
fd: &'w AsyncFd,
},
Done,
}
impl<'w> EventsState<'w> {
pub(crate) fn new(fd: &'w AsyncFd) -> EventsState<'w> {
EventsState::Reading(fd.read(Vec::with_capacity(BUF_SIZE)))
}
}
impl<'w> Events<'w> {
pub(crate) fn path_for_sys<'a>(&'a self, event: &'a Event) -> Cow<'a, Path> {
let file_path = event.file_path();
let watched_path = self.watching.get(&event.event.wd).map(move |path| {
let path = unsafe { OsStr::from_encoded_bytes_unchecked(path.as_bytes()) };
Path::new(path)
});
match watched_path {
Some(path) if file_path.as_os_str().is_empty() => Cow::Borrowed(path),
Some(path) => Cow::Owned(path.join(file_path)),
None => Cow::Borrowed(file_path),
}
}
pub(crate) fn poll_sys(
mut self: Pin<&mut Self>,
ctx: &mut task::Context<'_>,
) -> Poll<Option<io::Result<&'w notify::Event>>> {
let this = &mut *self;
loop {
match &mut this.state {
EventsState::Processing { buf, processed, .. } => {
if buf.len() > *processed {
debug_assert!(buf.len() >= *processed + size_of::<libc::inotify_event>());
#[allow(clippy::cast_ptr_alignment)]
let event_ptr = unsafe {
buf.as_ptr()
.byte_add(*processed)
.cast::<libc::inotify_event>()
};
let len = unsafe { (&*event_ptr).len as usize };
*processed += size_of::<libc::inotify_event>() + len;
debug_assert!(buf.len() >= *processed);
let mask = unsafe { (&*event_ptr).mask };
if mask & libc::IN_IGNORED != 0 {
let wd = unsafe { (&*event_ptr).wd };
_ = this.watching.remove(&wd);
continue; }
if mask & libc::IN_Q_OVERFLOW != 0 {
log::warn!("inotify event queue overflowed");
continue;
}
let path = unsafe {
slice::from_raw_parts(
event_ptr
.byte_add(size_of::<libc::inotify_event>())
.cast::<u8>(),
len,
)
};
let path_len = path.iter().rposition(|b| *b != 0).map_or(len, |n| n + 1);
#[allow(clippy::cast_ptr_alignment)]
let event: &'w notify::Event = unsafe {
&*(ptr::slice_from_raw_parts(event_ptr.cast::<u8>(), path_len)
as *const notify::Event)
};
return Poll::Ready(Some(Ok(event)));
}
let (mut buf, fd) = match replace(&mut this.state, EventsState::Done) {
EventsState::Processing {
buf,
processed: _,
fd,
} => (buf, fd),
EventsState::Reading(_) | EventsState::Done => unreachable!(),
};
buf.clear();
this.state = EventsState::Reading(fd.read(buf));
}
EventsState::Reading(read) => {
match Pin::new(&mut *read).poll(ctx) {
Poll::Ready(Ok(buf)) => {
if buf.is_empty() {
this.state = EventsState::Done;
return Poll::Ready(None);
}
this.state = EventsState::Processing {
buf,
processed: 0,
fd: read.fd(),
};
}
Poll::Ready(Err(err)) => {
this.state = EventsState::Done;
return Poll::Ready(Some(Err(err)));
}
Poll::Pending => return Poll::Pending,
}
}
EventsState::Done => return Poll::Ready(None),
}
}
}
}
#[repr(C)]
pub(crate) struct Event {
event: libc::inotify_event,
path: [u8],
}
impl Event {
pub(crate) fn file_path(&self) -> &Path {
Path::new(unsafe { OsStr::from_encoded_bytes_unchecked(&self.path) })
}
pub(crate) const fn mask(&self) -> u32 {
self.event.mask
}
pub(crate) fn is_dir(&self) -> bool {
self.mask() & EVENT_IS_DIR != 0
}
pub(crate) fn modified(&self) -> bool {
self.mask() & EVENT_MODIFIED != 0
}
pub(crate) fn deleted(&self) -> bool {
self.mask() & EVENT_DELETED != 0
}
pub(crate) fn file_moved_from(&self) -> bool {
self.mask() & EVENT_FILE_MOVED_FROM != 0
}
pub(crate) fn file_moved_into(&self) -> bool {
self.mask() & EVENT_FILE_MOVED_INTO != 0
}
pub(crate) fn file_moved(&self) -> bool {
self.mask() & EVENT_FILE_MOVED != 0
}
pub(crate) fn file_created(&self) -> bool {
self.mask() & EVENT_FILE_CREATED != 0
}
pub(crate) fn file_deleted(&self) -> bool {
self.mask() & EVENT_FILE_DELETED != 0
}
pub(crate) fn fmt<'a, 'b, 'f>(
&self,
f: &'f mut fmt::DebugStruct<'a, 'b>,
) -> &'f mut fmt::DebugStruct<'a, 'b> {
f.field("wd", &self.event.wd)
.field("mask", &self.event.mask)
.field("cookie", &self.event.cookie)
}
}
pub(crate) const EVENT_IS_DIR: u32 = libc::IN_ISDIR;
pub(crate) const EVENT_ACCESSED: u32 = libc::IN_ACCESS;
pub(crate) const EVENT_MODIFIED: u32 = libc::IN_MODIFY;
pub(crate) const EVENT_METADATA_CHANGED: u32 = libc::IN_ATTRIB;
pub(crate) const EVENT_CLOSED_WRITE: u32 = libc::IN_CLOSE_WRITE;
pub(crate) const EVENT_CLOSED_NO_WRITE: u32 = libc::IN_CLOSE_NOWRITE;
pub(crate) const EVENT_CLOSED: u32 = libc::IN_CLOSE;
pub(crate) const EVENT_OPENED: u32 = libc::IN_OPEN;
pub(crate) const EVENT_DELETED: u32 = libc::IN_DELETE_SELF;
pub(crate) const EVENT_MOVED: u32 = libc::IN_MOVE_SELF;
pub(crate) const EVENT_UNMOUNTED: u32 = libc::IN_UNMOUNT;
pub(crate) const EVENT_FILE_MOVED_FROM: u32 = libc::IN_MOVED_FROM;
pub(crate) const EVENT_FILE_MOVED_INTO: u32 = libc::IN_MOVED_TO;
pub(crate) const EVENT_FILE_MOVED: u32 = libc::IN_MOVE;
pub(crate) const EVENT_FILE_CREATED: u32 = libc::IN_CREATE;
pub(crate) const EVENT_FILE_DELETED: u32 = libc::IN_DELETE;