use std::{
fs::{File, OpenOptions},
io,
os::{
fd::{AsRawFd, FromRawFd, OwnedFd},
unix::fs::OpenOptionsExt,
},
path::Path,
};
pub(super) struct Watch {
queue: OwnedFd,
_directories: Vec<File>,
}
impl Watch {
pub(super) fn new(parent: &Path) -> io::Result<Self> {
let descriptor = unsafe { libc::kqueue() };
if descriptor < 0 {
return Err(io::Error::last_os_error());
}
let queue = unsafe { OwnedFd::from_raw_fd(descriptor) };
if unsafe { libc::fcntl(queue.as_raw_fd(), libc::F_SETFD, libc::FD_CLOEXEC) } < 0 {
return Err(io::Error::last_os_error());
}
register(&queue, 0, libc::EVFILT_FS, 0x0008 | 0x0010 | 0x0100)?;
let mut directories = Vec::new();
for path in parent.ancestors().collect::<Vec<_>>().into_iter().rev() {
if !path.symlink_metadata()?.file_type().is_dir() {
return Err(super::unsupported());
}
let directory = OpenOptions::new()
.read(true)
.custom_flags(libc::O_EVTONLY | libc::O_NOFOLLOW | libc::O_CLOEXEC)
.open(path)?;
let mut filesystem = std::mem::MaybeUninit::<libc::statfs>::uninit();
if unsafe { libc::fstatfs(directory.as_raw_fd(), filesystem.as_mut_ptr()) } != 0 {
return Err(io::Error::last_os_error());
}
let filesystem = unsafe { filesystem.assume_init() };
let name: Vec<_> = filesystem
.f_fstypename
.iter()
.take_while(|byte| **byte != 0)
.map(|byte| *byte as u8)
.collect();
if filesystem.f_flags & libc::MNT_LOCAL as u32 == 0
|| !matches!(name.as_slice(), b"apfs" | b"hfs")
{
return Err(super::unsupported());
}
let flags = libc::NOTE_RENAME
| libc::NOTE_DELETE
| libc::NOTE_REVOKE
| libc::NOTE_ATTRIB
| if path == parent {
libc::NOTE_WRITE | libc::NOTE_LINK
} else {
0
};
register(
&queue,
directory.as_raw_fd() as usize,
libc::EVFILT_VNODE,
flags,
)?;
directories.push(directory);
}
Ok(Self {
queue,
_directories: directories,
})
}
pub(super) fn changed(&self) -> io::Result<bool> {
let mut event = std::mem::MaybeUninit::<libc::kevent>::uninit();
let timeout = libc::timespec {
tv_sec: 0,
tv_nsec: 0,
};
let count = unsafe {
libc::kevent(
self.queue.as_raw_fd(),
std::ptr::null(),
0,
event.as_mut_ptr(),
1,
&raw const timeout,
)
};
if count < 0 {
Err(io::Error::last_os_error())
} else {
Ok(count != 0)
}
}
}
fn register(queue: &OwnedFd, identity: usize, filter: i16, flags: u32) -> io::Result<()> {
let event = libc::kevent {
ident: identity,
filter,
flags: libc::EV_ADD | libc::EV_CLEAR,
fflags: flags,
data: 0,
udata: std::ptr::null_mut(),
};
let result = unsafe {
libc::kevent(
queue.as_raw_fd(),
&raw const event,
1,
std::ptr::null_mut(),
0,
std::ptr::null(),
)
};
if result < 0 {
Err(io::Error::last_os_error())
} else {
Ok(())
}
}