Skip to content

Commit 2d0d360

Browse files
bors[bot]vdagonneau
andcommitted
Merge #1016
1016: Added inotify bindings. r=asomers a=vdagonneau Hi ! I needed inotify bindings and noticed that nix did not have any so here is a PR to add it. There was another PR from 2015 to add support for inotify that was never merged. I took some of the feedback and applied it here. Co-authored-by: Vincent Dagonneau <[email protected]>
2 parents 6e94c3c + 3966d0b commit 2d0d360

File tree

5 files changed

+305
-0
lines changed

5 files changed

+305
-0
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ This project adheres to [Semantic Versioning](http://semver.org/).
77
### Added
88
- Add IP_RECVIF & IP_RECVDSTADDR. Enable IP_PKTINFO and IP6_PKTINFO on netbsd/openbsd.
99
([#1002](https://github.com/nix-rust/nix/pull/1002))
10+
11+
- Added `inotify_init1`, `inotify_add_watch` and `inotify_rm_watch` wrappers for
12+
Android and Linux. ([#1016](https://github.com/nix-rust/nix/pull/1016))
13+
1014
### Changed
1115
- `PollFd` event flags renamed to `PollFlags` ([#1024](https://github.com/nix-rust/nix/pull/1024/))
1216
- `recvmsg` now returns an Iterator over `ControlMessageOwned` objects rather

src/sys/inotify.rs

Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
//! Monitoring API for filesystem events.
2+
//!
3+
//! Inotify is a Linux-only API to monitor filesystems events.
4+
//!
5+
//! For more documentation, please read [inotify(7)](http://man7.org/linux/man-pages/man7/inotify.7.html).
6+
//!
7+
//! # Examples
8+
//!
9+
//! Monitor all events happening in directory "test":
10+
//! ```no_run
11+
//! # use nix::sys::inotify::{AddWatchFlags,InitFlags,Inotify};
12+
//! #
13+
//! // We create a new inotify instance.
14+
//! let instance = Inotify::init(InitFlags::empty()).unwrap();
15+
//!
16+
//! // We add a new watch on directory "test" for all events.
17+
//! let wd = instance.add_watch("test", AddWatchFlags::IN_ALL_EVENTS).unwrap();
18+
//!
19+
//! loop {
20+
//! // We read from our inotify instance for events.
21+
//! let events = instance.read_events().unwrap();
22+
//! println!("Events: {:?}", events);
23+
//! }
24+
//! ```
25+
26+
use libc;
27+
use libc::{
28+
c_char,
29+
c_int,
30+
uint32_t
31+
};
32+
use std::ffi::{OsString,OsStr,CStr};
33+
use std::os::unix::ffi::OsStrExt;
34+
use std::mem::size_of;
35+
use std::os::unix::io::{RawFd,AsRawFd,FromRawFd};
36+
use unistd::read;
37+
use Result;
38+
use NixPath;
39+
use errno::Errno;
40+
41+
libc_bitflags! {
42+
/// Configuration options for [`inotify_add_watch`](fn.inotify_add_watch.html).
43+
pub struct AddWatchFlags: uint32_t {
44+
IN_ACCESS;
45+
IN_MODIFY;
46+
IN_ATTRIB;
47+
IN_CLOSE_WRITE;
48+
IN_CLOSE_NOWRITE;
49+
IN_OPEN;
50+
IN_MOVED_FROM;
51+
IN_MOVED_TO;
52+
IN_CREATE;
53+
IN_DELETE;
54+
IN_DELETE_SELF;
55+
IN_MOVE_SELF;
56+
57+
IN_UNMOUNT;
58+
IN_Q_OVERFLOW;
59+
IN_IGNORED;
60+
61+
IN_CLOSE;
62+
IN_MOVE;
63+
64+
IN_ONLYDIR;
65+
IN_DONT_FOLLOW;
66+
67+
IN_ISDIR;
68+
IN_ONESHOT;
69+
IN_ALL_EVENTS;
70+
}
71+
}
72+
73+
libc_bitflags! {
74+
/// Configuration options for [`inotify_init1`](fn.inotify_init1.html).
75+
pub struct InitFlags: c_int {
76+
IN_CLOEXEC;
77+
IN_NONBLOCK;
78+
}
79+
}
80+
81+
/// An inotify instance. This is also a file descriptor, you can feed it to
82+
/// other interfaces consuming file descriptors, epoll for example.
83+
#[derive(Debug, Clone, Copy)]
84+
pub struct Inotify {
85+
fd: RawFd
86+
}
87+
88+
/// This object is returned when you create a new watch on an inotify instance.
89+
/// It is then returned as part of an event once triggered. It allows you to
90+
/// know which watch triggered which event.
91+
#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, Ord, PartialOrd)]
92+
pub struct WatchDescriptor {
93+
wd: i32
94+
}
95+
96+
/// A single inotify event.
97+
///
98+
/// For more documentation see, [inotify(7)](http://man7.org/linux/man-pages/man7/inotify.7.html).
99+
#[derive(Debug)]
100+
pub struct InotifyEvent {
101+
/// Watch descriptor. This field corresponds to the watch descriptor you
102+
/// were issued when calling add_watch. It allows you to know which watch
103+
/// this event comes from.
104+
pub wd: WatchDescriptor,
105+
/// Event mask. This field is a bitfield describing the exact event that
106+
/// occured.
107+
pub mask: AddWatchFlags,
108+
/// This cookie is a number that allows you to connect related events. For
109+
/// now only IN_MOVED_FROM and IN_MOVED_TO can be connected.
110+
pub cookie: u32,
111+
/// Filename. This field exists only if the event was triggered for a file
112+
/// inside the watched directory.
113+
pub name: Option<OsString>
114+
}
115+
116+
impl Inotify {
117+
/// Initialize a new inotify instance.
118+
///
119+
/// Returns a Result containing an inotify instance.
120+
///
121+
/// For more information see, [inotify_init(2)](http://man7.org/linux/man-pages/man2/inotify_init.2.html).
122+
pub fn init(flags: InitFlags) -> Result<Inotify> {
123+
let res = Errno::result(unsafe {
124+
libc::inotify_init1(flags.bits())
125+
});
126+
127+
res.map(|fd| Inotify { fd })
128+
}
129+
130+
/// Adds a new watch on the target file or directory.
131+
///
132+
/// Returns a watch descriptor. This is not a File Descriptor!
133+
///
134+
/// For more information see, [inotify_add_watch(2)](http://man7.org/linux/man-pages/man2/inotify_add_watch.2.html).
135+
pub fn add_watch<P: ?Sized + NixPath>(&self,
136+
path: &P,
137+
mask: AddWatchFlags)
138+
-> Result<WatchDescriptor>
139+
{
140+
let res = path.with_nix_path(|cstr| {
141+
unsafe {
142+
libc::inotify_add_watch(self.fd, cstr.as_ptr(), mask.bits())
143+
}
144+
})?;
145+
146+
Errno::result(res).map(|wd| WatchDescriptor { wd })
147+
}
148+
149+
/// Removes an existing watch using the watch descriptor returned by
150+
/// inotify_add_watch.
151+
///
152+
/// Returns an EINVAL error if the watch descriptor is invalid.
153+
///
154+
/// For more information see, [inotify_rm_watch(2)](http://man7.org/linux/man-pages/man2/inotify_rm_watch.2.html).
155+
#[cfg(target_os = "linux")]
156+
pub fn rm_watch(&self, wd: WatchDescriptor) -> Result<()> {
157+
let res = unsafe { libc::inotify_rm_watch(self.fd, wd.wd) };
158+
159+
Errno::result(res).map(drop)
160+
}
161+
162+
#[cfg(target_os = "android")]
163+
pub fn rm_watch(&self, wd: WatchDescriptor) -> Result<()> {
164+
let res = unsafe { libc::inotify_rm_watch(self.fd, wd.wd as u32) };
165+
166+
Errno::result(res).map(drop)
167+
}
168+
169+
/// Reads a collection of events from the inotify file descriptor. This call
170+
/// can either be blocking or non blocking depending on whether IN_NONBLOCK
171+
/// was set at initialization.
172+
///
173+
/// Returns as many events as available. If the call was non blocking and no
174+
/// events could be read then the EAGAIN error is returned.
175+
pub fn read_events(&self) -> Result<Vec<InotifyEvent>> {
176+
let header_size = size_of::<libc::inotify_event>();
177+
let mut buffer = [0u8; 4096];
178+
let mut events = Vec::new();
179+
let mut offset = 0;
180+
181+
let nread = read(self.fd, &mut buffer)?;
182+
183+
while (nread - offset) >= header_size {
184+
let event = unsafe {
185+
&*(
186+
buffer
187+
.as_ptr()
188+
.offset(offset as isize) as *const libc::inotify_event
189+
)
190+
};
191+
192+
let name = match event.len {
193+
0 => None,
194+
_ => {
195+
let ptr = unsafe {
196+
buffer
197+
.as_ptr()
198+
.offset(offset as isize + header_size as isize)
199+
as *const c_char
200+
};
201+
let cstr = unsafe { CStr::from_ptr(ptr) };
202+
203+
Some(OsStr::from_bytes(cstr.to_bytes()).to_owned())
204+
}
205+
};
206+
207+
events.push(InotifyEvent {
208+
wd: WatchDescriptor { wd: event.wd },
209+
mask: AddWatchFlags::from_bits_truncate(event.mask),
210+
cookie: event.cookie,
211+
name
212+
});
213+
214+
offset += header_size + event.len as usize;
215+
}
216+
217+
Ok(events)
218+
}
219+
}
220+
221+
impl AsRawFd for Inotify {
222+
fn as_raw_fd(&self) -> RawFd {
223+
self.fd
224+
}
225+
}
226+
227+
impl FromRawFd for Inotify {
228+
unsafe fn from_raw_fd(fd: RawFd) -> Self {
229+
Inotify { fd }
230+
}
231+
}

src/sys/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,3 +88,6 @@ pub mod uio;
8888
pub mod utsname;
8989

9090
pub mod wait;
91+
92+
#[cfg(any(target_os = "android", target_os = "linux"))]
93+
pub mod inotify;

test/sys/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ mod test_uio;
2525

2626
#[cfg(target_os = "linux")]
2727
mod test_epoll;
28+
#[cfg(target_os = "linux")]
29+
mod test_inotify;
2830
mod test_pthread;
2931
#[cfg(any(target_os = "android",
3032
target_os = "dragonfly",

test/sys/test_inotify.rs

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
use nix::sys::inotify::{AddWatchFlags,InitFlags,Inotify};
2+
use nix::Error;
3+
use nix::errno::Errno;
4+
use tempfile;
5+
use std::ffi::OsString;
6+
use std::fs::{rename, File};
7+
8+
#[test]
9+
pub fn test_inotify() {
10+
let instance = Inotify::init(InitFlags::IN_NONBLOCK)
11+
.unwrap();
12+
let tempdir = tempfile::tempdir().unwrap();
13+
14+
instance.add_watch(tempdir.path(), AddWatchFlags::IN_ALL_EVENTS).unwrap();
15+
16+
let events = instance.read_events();
17+
assert_eq!(events.unwrap_err(), Error::Sys(Errno::EAGAIN));
18+
19+
File::create(tempdir.path().join("test")).unwrap();
20+
21+
let events = instance.read_events().unwrap();
22+
assert_eq!(events[0].name, Some(OsString::from("test")));
23+
}
24+
25+
#[test]
26+
pub fn test_inotify_multi_events() {
27+
let instance = Inotify::init(InitFlags::IN_NONBLOCK)
28+
.unwrap();
29+
let tempdir = tempfile::tempdir().unwrap();
30+
31+
instance.add_watch(tempdir.path(), AddWatchFlags::IN_ALL_EVENTS).unwrap();
32+
33+
let events = instance.read_events();
34+
assert_eq!(events.unwrap_err(), Error::Sys(Errno::EAGAIN));
35+
36+
File::create(tempdir.path().join("test")).unwrap();
37+
rename(tempdir.path().join("test"), tempdir.path().join("test2")).unwrap();
38+
39+
// Now there should be 5 events in queue:
40+
// - IN_CREATE on test
41+
// - IN_OPEN on test
42+
// - IN_CLOSE_WRITE on test
43+
// - IN_MOVED_FROM on test with a cookie
44+
// - IN_MOVED_TO on test2 with the same cookie
45+
46+
let events = instance.read_events().unwrap();
47+
assert_eq!(events.len(), 5);
48+
49+
assert_eq!(events[0].mask, AddWatchFlags::IN_CREATE);
50+
assert_eq!(events[0].name, Some(OsString::from("test")));
51+
52+
assert_eq!(events[1].mask, AddWatchFlags::IN_OPEN);
53+
assert_eq!(events[1].name, Some(OsString::from("test")));
54+
55+
assert_eq!(events[2].mask, AddWatchFlags::IN_CLOSE_WRITE);
56+
assert_eq!(events[2].name, Some(OsString::from("test")));
57+
58+
assert_eq!(events[3].mask, AddWatchFlags::IN_MOVED_FROM);
59+
assert_eq!(events[3].name, Some(OsString::from("test")));
60+
61+
assert_eq!(events[4].mask, AddWatchFlags::IN_MOVED_TO);
62+
assert_eq!(events[4].name, Some(OsString::from("test2")));
63+
64+
assert_eq!(events[3].cookie, events[4].cookie);
65+
}

0 commit comments

Comments
 (0)