summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVincent Dagonneau <vincentdagonneau@gmail.com>2019-01-20 09:11:15 +0100
committerVincent Dagonneau <vincentdagonneau@gmail.com>2019-02-21 09:23:23 +0100
commit3966d0bd195580e8262084a99faa2d940817d219 (patch)
tree191e7e2306ec4747c320f89398796cf33d87a04f
parenta2fa2828e0663de420ce02a063c9ea66e7f00c36 (diff)
downloadnix-3966d0bd195580e8262084a99faa2d940817d219.zip
Added inotify bindings for Linux and Android.
-rw-r--r--CHANGELOG.md4
-rw-r--r--src/sys/inotify.rs231
-rw-r--r--src/sys/mod.rs3
-rw-r--r--test/sys/mod.rs2
-rw-r--r--test/sys/test_inotify.rs65
5 files changed, 305 insertions, 0 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ec74eb27..5b895298 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);
+}