-
Notifications
You must be signed in to change notification settings - Fork 667
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added inotify bindings for Linux and Android.
- Loading branch information
1 parent
a2fa282
commit 3966d0b
Showing
5 changed files
with
305 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 } | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
} |