diff options
-rw-r--r-- | CHANGELOG.md | 4 | ||||
-rw-r--r-- | src/sys/inotify.rs | 231 | ||||
-rw-r--r-- | src/sys/mod.rs | 3 | ||||
-rw-r--r-- | test/sys/mod.rs | 2 | ||||
-rw-r--r-- | test/sys/test_inotify.rs | 65 |
5 files changed, 305 insertions, 0 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 35e32a44..81f94c24 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Added - Add IP_RECVIF & IP_RECVDSTADDR. Enable IP_PKTINFO and IP6_PKTINFO on netbsd/openbsd. ([#1002](https://github.com/nix-rust/nix/pull/1002)) + +- Added `inotify_init1`, `inotify_add_watch` and `inotify_rm_watch` wrappers for + Android and Linux. ([#1016](https://github.com/nix-rust/nix/pull/1016)) + ### Changed - `PollFd` event flags renamed to `PollFlags` ([#1024](https://github.com/nix-rust/nix/pull/1024/)) - `recvmsg` now returns an Iterator over `ControlMessageOwned` objects rather 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<OsString> +} + +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<Inotify> { + 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<P: ?Sized + NixPath>(&self, + path: &P, + mask: AddWatchFlags) + -> Result<WatchDescriptor> + { + 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<Vec<InotifyEvent>> { + let header_size = size_of::<libc::inotify_event>(); + 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; diff --git a/test/sys/mod.rs b/test/sys/mod.rs index 46f29123..60a58dd1 100644 --- a/test/sys/mod.rs +++ b/test/sys/mod.rs @@ -25,6 +25,8 @@ mod test_uio; #[cfg(target_os = "linux")] mod test_epoll; +#[cfg(target_os = "linux")] +mod test_inotify; mod test_pthread; #[cfg(any(target_os = "android", target_os = "dragonfly", diff --git a/test/sys/test_inotify.rs b/test/sys/test_inotify.rs new file mode 100644 index 00000000..a8ead46d --- /dev/null +++ b/test/sys/test_inotify.rs @@ -0,0 +1,65 @@ +use nix::sys::inotify::{AddWatchFlags,InitFlags,Inotify}; +use nix::Error; +use nix::errno::Errno; +use tempfile; +use std::ffi::OsString; +use std::fs::{rename, File}; + +#[test] +pub fn test_inotify() { + let instance = Inotify::init(InitFlags::IN_NONBLOCK) + .unwrap(); + let tempdir = tempfile::tempdir().unwrap(); + + instance.add_watch(tempdir.path(), AddWatchFlags::IN_ALL_EVENTS).unwrap(); + + let events = instance.read_events(); + assert_eq!(events.unwrap_err(), Error::Sys(Errno::EAGAIN)); + + File::create(tempdir.path().join("test")).unwrap(); + + let events = instance.read_events().unwrap(); + assert_eq!(events[0].name, Some(OsString::from("test"))); +} + +#[test] +pub fn test_inotify_multi_events() { + let instance = Inotify::init(InitFlags::IN_NONBLOCK) + .unwrap(); + let tempdir = tempfile::tempdir().unwrap(); + + instance.add_watch(tempdir.path(), AddWatchFlags::IN_ALL_EVENTS).unwrap(); + + let events = instance.read_events(); + assert_eq!(events.unwrap_err(), Error::Sys(Errno::EAGAIN)); + + File::create(tempdir.path().join("test")).unwrap(); + rename(tempdir.path().join("test"), tempdir.path().join("test2")).unwrap(); + + // Now there should be 5 events in queue: + // - IN_CREATE on test + // - IN_OPEN on test + // - IN_CLOSE_WRITE on test + // - IN_MOVED_FROM on test with a cookie + // - IN_MOVED_TO on test2 with the same cookie + + let events = instance.read_events().unwrap(); + assert_eq!(events.len(), 5); + + assert_eq!(events[0].mask, AddWatchFlags::IN_CREATE); + assert_eq!(events[0].name, Some(OsString::from("test"))); + + assert_eq!(events[1].mask, AddWatchFlags::IN_OPEN); + assert_eq!(events[1].name, Some(OsString::from("test"))); + + assert_eq!(events[2].mask, AddWatchFlags::IN_CLOSE_WRITE); + assert_eq!(events[2].name, Some(OsString::from("test"))); + + assert_eq!(events[3].mask, AddWatchFlags::IN_MOVED_FROM); + assert_eq!(events[3].name, Some(OsString::from("test"))); + + assert_eq!(events[4].mask, AddWatchFlags::IN_MOVED_TO); + assert_eq!(events[4].name, Some(OsString::from("test2"))); + + assert_eq!(events[3].cookie, events[4].cookie); +} |