use std::ffi;
use std::io;
use std::os::unix::ffi::OsStrExt;
use std::os::unix::ffi::OsStringExt;
use std::os::unix::io::{AsRawFd, RawFd};
use std::path::Path;
use bitflags::bitflags;
use crate::constants;
bitflags! {
pub struct Events: u32 {
const OPEN = libc::IN_OPEN;
const ATTRIB = libc::IN_ATTRIB;
const ACCESS = libc::IN_ACCESS;
const MODIFY = libc::IN_MODIFY;
const CLOSE_WRITE = libc::IN_CLOSE_WRITE;
const CLOSE_NOWRITE = libc::IN_CLOSE_NOWRITE;
const CREATE = libc::IN_CREATE;
const DELETE = libc::IN_DELETE;
const DELETE_SELF = libc::IN_DELETE_SELF;
const MOVE_SELF = libc::IN_MOVE_SELF;
const MOVED_FROM = libc::IN_MOVED_FROM;
const MOVED_TO = libc::IN_MOVED_TO;
const MOVE = libc::IN_MOVE;
const CLOSE = libc::IN_CLOSE;
const ALL_EVENTS = libc::IN_ALL_EVENTS;
}
}
bitflags! {
pub struct WatchFlags: u32 {
const DONT_FOLLOW = libc::IN_DONT_FOLLOW;
const ONESHOT = libc::IN_ONESHOT;
const ONLYDIR = libc::IN_ONLYDIR;
const EXCL_UNLINK = constants::IN_EXCL_UNLINK;
}
}
bitflags! {
pub struct EventFlags: u32 {
const IGNORED = libc::IN_IGNORED;
const ISDIR = libc::IN_ISDIR;
const Q_OVERFLOW = libc::IN_Q_OVERFLOW;
const UNMOUNT = libc::IN_UNMOUNT;
}
}
#[derive(Clone, Debug)]
pub struct Event {
pub watch: Watch,
pub events: Events,
pub flags: EventFlags,
pub cookie: u32,
pub name: ffi::OsString,
}
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
pub struct Watch {
wd: i32,
}
bitflags! {
#[derive(Default)]
pub struct InotifyFlags: i32 {
const NONBLOCK = libc::IN_NONBLOCK;
const CLOEXEC = libc::IN_CLOEXEC;
}
}
#[derive(Debug)]
pub struct Inotify {
fd: i32,
}
const RAW_EVENT_SIZE: usize = std::mem::size_of::<libc::inotify_event>();
impl Inotify {
pub fn new(flags: InotifyFlags) -> io::Result<Self> {
let fd = crate::error::convert_neg_ret(unsafe { libc::inotify_init1(flags.bits) })?;
Ok(Self { fd })
}
fn add_watch_impl<P: AsRef<Path>>(&mut self, path: P, flags: u32) -> io::Result<Watch> {
let c_path = ffi::CString::new(path.as_ref().as_os_str().as_bytes())?;
let wd = crate::error::convert_neg_ret(unsafe {
libc::inotify_add_watch(self.fd, c_path.as_ptr(), flags)
})?;
Ok(Watch { wd })
}
#[inline]
pub fn add_watch<P: AsRef<Path>>(
&mut self,
path: P,
events: Events,
flags: WatchFlags,
) -> io::Result<Watch> {
self.add_watch_impl(path, events.bits | flags.bits)
}
#[inline]
pub fn extend_watch<P: AsRef<Path>>(
&mut self,
path: P,
events: Events,
flags: WatchFlags,
) -> io::Result<Watch> {
self.add_watch_impl(path, events.bits | flags.bits | constants::IN_MASK_ADD)
}
#[inline]
pub fn create_watch<P: AsRef<Path>>(
&mut self,
path: P,
events: Events,
flags: WatchFlags,
) -> io::Result<Watch> {
self.add_watch_impl(path, events.bits | flags.bits | constants::IN_MASK_CREATE)
}
pub fn rm_watch(&mut self, watch: Watch) -> io::Result<()> {
crate::error::convert_nzero_ret(unsafe { libc::inotify_rm_watch(self.fd, watch.wd) })
}
pub fn read_nowait(&mut self) -> io::Result<Vec<Event>> {
let mut nbytes: i32 = 0;
crate::error::convert_nzero_ret(unsafe {
libc::ioctl(self.fd, libc::FIONREAD, &mut nbytes)
})?;
if nbytes == 0 {
return Ok(Vec::new());
}
let mut buf: Vec<u8> = Vec::new();
buf.resize(nbytes as usize, 0);
let nbytes: isize = crate::error::convert_neg_ret(unsafe {
libc::read(
self.fd,
buf.as_mut_ptr() as *mut libc::c_void,
nbytes as usize,
)
})?;
buf.resize(nbytes as usize, 0);
Ok(Self::parse_multi(&buf))
}
pub fn read_wait(&mut self) -> io::Result<Vec<Event>> {
let mut buf: Vec<u8> = Vec::new();
buf.resize(4096, 0);
let mut i = 0;
loop {
match crate::error::convert_neg_ret(unsafe {
libc::read(self.fd, buf.as_mut_ptr() as *mut libc::c_void, buf.len())
}) {
Ok(nbytes) => {
buf.resize(nbytes as usize, 0);
return Ok(Self::parse_multi(&buf));
}
Err(e) => {
if i < 10 && crate::error::is_einval(&e) {
buf.resize(buf.len() * 2, 0);
} else {
return Err(e);
}
}
}
i += 1;
}
}
fn parse_multi(data: &[u8]) -> Vec<Event> {
let mut events: Vec<Event> = Vec::new();
let mut offset: usize = 0;
while offset < data.len() {
let (event, inc) = Self::parse_one(&data[offset..]);
events.push(event);
offset += inc;
}
events
}
fn parse_one(data: &[u8]) -> (Event, usize) {
debug_assert!(data.len() >= RAW_EVENT_SIZE);
#[allow(clippy::transmute_ptr_to_ref)]
let raw_event =
unsafe { std::mem::transmute::<*const u8, &libc::inotify_event>(data.as_ptr()) };
let name = ffi::OsString::from_vec(
data.iter()
.skip(RAW_EVENT_SIZE)
.take(raw_event.len as usize)
.take_while(|x| **x != 0)
.cloned()
.collect(),
);
(
Event {
watch: Watch { wd: raw_event.wd },
events: Events::from_bits_truncate(raw_event.mask),
flags: EventFlags::from_bits_truncate(raw_event.mask),
cookie: raw_event.cookie,
name,
},
RAW_EVENT_SIZE + raw_event.len as usize,
)
}
}
impl AsRawFd for Inotify {
#[inline]
fn as_raw_fd(&self) -> RawFd {
self.fd
}
}
impl Drop for Inotify {
#[inline]
fn drop(&mut self) {
unsafe {
libc::close(self.fd);
}
}
}