From 3966d0bd195580e8262084a99faa2d940817d219 Mon Sep 17 00:00:00 2001 From: Vincent Dagonneau Date: Sun, 20 Jan 2019 09:11:15 +0100 Subject: Added inotify bindings for Linux and Android. --- src/sys/inotify.rs | 231 +++++++++++++++++++++++++++++++++++++++++++++++++++++ src/sys/mod.rs | 3 + 2 files changed, 234 insertions(+) create mode 100644 src/sys/inotify.rs (limited to 'src') diff --git a/src/sys/inotify.rs b/src/sys/inotify.rs new file mode 100644 index 00000000..91cc48d3 --- /dev/null +++ b/src/sys/inotify.rs @@ -0,0 +1,231 @@ +//! Monitoring API for filesystem events. +//! +//! Inotify is a Linux-only API to monitor filesystems events. +//! +//! For more documentation, please read [inotify(7)](http://man7.org/linux/man-pages/man7/inotify.7.html). +//! +//! # Examples +//! +//! Monitor all events happening in directory "test": +//! ```no_run +//! # use nix::sys::inotify::{AddWatchFlags,InitFlags,Inotify}; +//! # +//! // We create a new inotify instance. +//! let instance = Inotify::init(InitFlags::empty()).unwrap(); +//! +//! // We add a new watch on directory "test" for all events. +//! let wd = instance.add_watch("test", AddWatchFlags::IN_ALL_EVENTS).unwrap(); +//! +//! loop { +//! // We read from our inotify instance for events. +//! let events = instance.read_events().unwrap(); +//! println!("Events: {:?}", events); +//! } +//! ``` + +use libc; +use libc::{ + c_char, + c_int, + uint32_t +}; +use std::ffi::{OsString,OsStr,CStr}; +use std::os::unix::ffi::OsStrExt; +use std::mem::size_of; +use std::os::unix::io::{RawFd,AsRawFd,FromRawFd}; +use unistd::read; +use Result; +use NixPath; +use errno::Errno; + +libc_bitflags! { + /// Configuration options for [`inotify_add_watch`](fn.inotify_add_watch.html). + pub struct AddWatchFlags: uint32_t { + 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! { + /// Configuration options for [`inotify_init1`](fn.inotify_init1.html). + pub struct InitFlags: c_int { + IN_CLOEXEC; + IN_NONBLOCK; + } +} + +/// An inotify instance. This is also a file descriptor, you can feed it to +/// other interfaces consuming file descriptors, epoll for example. +#[derive(Debug, Clone, Copy)] +pub struct Inotify { + fd: RawFd +} + +/// This object is returned when you create a new watch on an inotify instance. +/// It is then returned as part of an event once triggered. It allows you to +/// know which watch triggered which event. +#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, Ord, PartialOrd)] +pub struct WatchDescriptor { + wd: i32 +} + +/// A single inotify event. +/// +/// For more documentation see, [inotify(7)](http://man7.org/linux/man-pages/man7/inotify.7.html). +#[derive(Debug)] +pub struct InotifyEvent { + /// Watch descriptor. This field corresponds to the watch descriptor you + /// were issued when calling add_watch. It allows you to know which watch + /// this event comes from. + pub wd: WatchDescriptor, + /// Event mask. This field is a bitfield describing the exact event that + /// occured. + pub mask: AddWatchFlags, + /// This cookie is a number that allows you to connect related events. For + /// now only IN_MOVED_FROM and IN_MOVED_TO can be connected. + pub cookie: u32, + /// Filename. This field exists only if the event was triggered for a file + /// inside the watched directory. + pub name: Option +} + +impl Inotify { + /// Initialize a new inotify instance. + /// + /// Returns a Result containing an inotify instance. + /// + /// For more information see, [inotify_init(2)](http://man7.org/linux/man-pages/man2/inotify_init.2.html). + pub fn init(flags: InitFlags) -> Result { + let res = Errno::result(unsafe { + libc::inotify_init1(flags.bits()) + }); + + res.map(|fd| Inotify { fd }) + } + + /// Adds a new watch on the target file or directory. + /// + /// Returns a watch descriptor. This is not a File Descriptor! + /// + /// For more information see, [inotify_add_watch(2)](http://man7.org/linux/man-pages/man2/inotify_add_watch.2.html). + pub fn add_watch(&self, + path: &P, + mask: AddWatchFlags) + -> Result + { + let res = path.with_nix_path(|cstr| { + unsafe { + libc::inotify_add_watch(self.fd, cstr.as_ptr(), mask.bits()) + } + })?; + + Errno::result(res).map(|wd| WatchDescriptor { wd }) + } + + /// Removes an existing watch using the watch descriptor returned by + /// inotify_add_watch. + /// + /// Returns an EINVAL error if the watch descriptor is invalid. + /// + /// For more information see, [inotify_rm_watch(2)](http://man7.org/linux/man-pages/man2/inotify_rm_watch.2.html). + #[cfg(target_os = "linux")] + pub fn rm_watch(&self, wd: WatchDescriptor) -> Result<()> { + let res = unsafe { libc::inotify_rm_watch(self.fd, wd.wd) }; + + Errno::result(res).map(drop) + } + + #[cfg(target_os = "android")] + pub fn rm_watch(&self, wd: WatchDescriptor) -> Result<()> { + let res = unsafe { libc::inotify_rm_watch(self.fd, wd.wd as u32) }; + + Errno::result(res).map(drop) + } + + /// Reads a collection of events from the inotify file descriptor. This call + /// can either be blocking or non blocking depending on whether IN_NONBLOCK + /// was set at initialization. + /// + /// Returns as many events as available. If the call was non blocking and no + /// events could be read then the EAGAIN error is returned. + pub fn read_events(&self) -> Result> { + let header_size = size_of::(); + let mut buffer = [0u8; 4096]; + let mut events = Vec::new(); + let mut offset = 0; + + let nread = read(self.fd, &mut buffer)?; + + while (nread - offset) >= header_size { + let event = unsafe { + &*( + buffer + .as_ptr() + .offset(offset as isize) as *const libc::inotify_event + ) + }; + + let name = match event.len { + 0 => None, + _ => { + let ptr = unsafe { + buffer + .as_ptr() + .offset(offset as isize + header_size as isize) + 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) + } +} + +impl AsRawFd for Inotify { + fn as_raw_fd(&self) -> RawFd { + self.fd + } +} + +impl FromRawFd for Inotify { + unsafe fn from_raw_fd(fd: RawFd) -> Self { + Inotify { fd } + } +} diff --git a/src/sys/mod.rs b/src/sys/mod.rs index 72d59649..88c7251d 100644 --- a/src/sys/mod.rs +++ b/src/sys/mod.rs @@ -88,3 +88,6 @@ pub mod uio; pub mod utsname; pub mod wait; + +#[cfg(any(target_os = "android", target_os = "linux"))] +pub mod inotify; -- cgit v1.2.3