1 | use std::{ |
2 | ffi::{ |
3 | OsStr, |
4 | OsString, |
5 | }, |
6 | mem, |
7 | os::unix::ffi::OsStrExt, |
8 | sync::Weak, |
9 | }; |
10 | |
11 | use inotify_sys as ffi; |
12 | |
13 | use crate::fd_guard::FdGuard; |
14 | use crate::watches::WatchDescriptor; |
15 | use crate::util::align_buffer; |
16 | |
17 | |
18 | /// Iterator over inotify events |
19 | /// |
20 | /// Allows for iteration over the events returned by |
21 | /// [`Inotify::read_events_blocking`] or [`Inotify::read_events`]. |
22 | /// |
23 | /// [`Inotify::read_events_blocking`]: struct.Inotify.html#method.read_events_blocking |
24 | /// [`Inotify::read_events`]: struct.Inotify.html#method.read_events |
25 | #[derive (Debug)] |
26 | pub struct Events<'a> { |
27 | fd : Weak<FdGuard>, |
28 | buffer : &'a [u8], |
29 | num_bytes: usize, |
30 | pos : usize, |
31 | } |
32 | |
33 | impl<'a> Events<'a> { |
34 | pub(crate) fn new(fd: Weak<FdGuard>, buffer: &'a [u8], num_bytes: usize) |
35 | -> Self |
36 | { |
37 | Events { |
38 | fd : fd, |
39 | buffer : buffer, |
40 | num_bytes: num_bytes, |
41 | pos : 0, |
42 | } |
43 | } |
44 | } |
45 | |
46 | impl<'a> Iterator for Events<'a> { |
47 | type Item = Event<&'a OsStr>; |
48 | |
49 | fn next(&mut self) -> Option<Self::Item> { |
50 | if self.pos < self.num_bytes { |
51 | let (step: usize, event: Event<&OsStr>) = Event::from_buffer(self.fd.clone(), &self.buffer[self.pos..]); |
52 | self.pos += step; |
53 | |
54 | Some(event) |
55 | } |
56 | else { |
57 | None |
58 | } |
59 | } |
60 | } |
61 | |
62 | |
63 | /// An inotify event |
64 | /// |
65 | /// A file system event that describes a change that the user previously |
66 | /// registered interest in. To watch for events, call [`Inotify::add_watch`]. To |
67 | /// retrieve events, call [`Inotify::read_events_blocking`] or |
68 | /// [`Inotify::read_events`]. |
69 | /// |
70 | /// [`Inotify::add_watch`]: struct.Inotify.html#method.add_watch |
71 | /// [`Inotify::read_events_blocking`]: struct.Inotify.html#method.read_events_blocking |
72 | /// [`Inotify::read_events`]: struct.Inotify.html#method.read_events |
73 | #[derive (Clone, Debug)] |
74 | pub struct Event<S> { |
75 | /// Identifies the watch this event originates from |
76 | /// |
77 | /// This [`WatchDescriptor`] is equal to the one that [`Inotify::add_watch`] |
78 | /// returned when interest for this event was registered. The |
79 | /// [`WatchDescriptor`] can be used to remove the watch using |
80 | /// [`Inotify::rm_watch`], thereby preventing future events of this type |
81 | /// from being created. |
82 | /// |
83 | /// [`WatchDescriptor`]: struct.WatchDescriptor.html |
84 | /// [`Inotify::add_watch`]: struct.Inotify.html#method.add_watch |
85 | /// [`Inotify::rm_watch`]: struct.Inotify.html#method.rm_watch |
86 | pub wd: WatchDescriptor, |
87 | |
88 | /// Indicates what kind of event this is |
89 | pub mask: EventMask, |
90 | |
91 | /// Connects related events to each other |
92 | /// |
93 | /// When a file is renamed, this results two events: [`MOVED_FROM`] and |
94 | /// [`MOVED_TO`]. The `cookie` field will be the same for both of them, |
95 | /// thereby making is possible to connect the event pair. |
96 | /// |
97 | /// [`MOVED_FROM`]: event_mask/constant.MOVED_FROM.html |
98 | /// [`MOVED_TO`]: event_mask/constant.MOVED_TO.html |
99 | pub cookie: u32, |
100 | |
101 | /// The name of the file the event originates from |
102 | /// |
103 | /// This field is set only if the subject of the event is a file or directory in a |
104 | /// watched directory. If the event concerns a file or directory that is |
105 | /// watched directly, `name` will be `None`. |
106 | pub name: Option<S>, |
107 | } |
108 | |
109 | impl<'a> Event<&'a OsStr> { |
110 | fn new(fd: Weak<FdGuard>, event: &ffi::inotify_event, name: &'a OsStr) |
111 | -> Self |
112 | { |
113 | let mask = EventMask::from_bits(event.mask) |
114 | .expect("Failed to convert event mask. This indicates a bug." ); |
115 | |
116 | let wd = crate::WatchDescriptor { |
117 | id: event.wd, |
118 | fd, |
119 | }; |
120 | |
121 | let name = if name == "" { |
122 | None |
123 | } |
124 | else { |
125 | Some(name) |
126 | }; |
127 | |
128 | Event { |
129 | wd, |
130 | mask, |
131 | cookie: event.cookie, |
132 | name, |
133 | } |
134 | } |
135 | |
136 | /// Create an `Event` from a buffer |
137 | /// |
138 | /// Assumes that a full `inotify_event` plus its name is located at the |
139 | /// beginning of `buffer`. |
140 | /// |
141 | /// Returns the number of bytes used from the buffer, and the event. |
142 | /// |
143 | /// # Panics |
144 | /// |
145 | /// Panics if the buffer does not contain a full event, including its name. |
146 | pub(crate) fn from_buffer( |
147 | fd : Weak<FdGuard>, |
148 | buffer: &'a [u8], |
149 | ) |
150 | -> (usize, Self) |
151 | { |
152 | let event_size = mem::size_of::<ffi::inotify_event>(); |
153 | let event_align = mem::align_of::<ffi::inotify_event>(); |
154 | |
155 | // Make sure that the buffer can satisfy the alignment requirements for `inotify_event` |
156 | assert!(buffer.len() >= event_align); |
157 | |
158 | // Discard the unaligned portion, if any, of the supplied buffer |
159 | let buffer = align_buffer(buffer); |
160 | |
161 | // Make sure that the aligned buffer is big enough to contain an event, without |
162 | // the name. Otherwise we can't safely convert it to an `inotify_event`. |
163 | assert!(buffer.len() >= event_size); |
164 | |
165 | |
166 | let event = buffer.as_ptr() as *const ffi::inotify_event; |
167 | |
168 | // We have a pointer to an `inotify_event`, pointing to the beginning of |
169 | // `buffer`. Since we know, as per the assertion above, that there are |
170 | // enough bytes in the buffer for at least one event, we can safely |
171 | // convert that pointer into a reference. |
172 | let event = unsafe { &*event }; |
173 | |
174 | // The name's length is given by `event.len`. There should always be |
175 | // enough bytes left in the buffer to fit the name. Let's make sure that |
176 | // is the case. |
177 | let bytes_left_in_buffer = buffer.len() - event_size; |
178 | assert!(bytes_left_in_buffer >= event.len as usize); |
179 | |
180 | // Directly after the event struct should be a name, if there's one |
181 | // associated with the event. Let's make a new slice that starts with |
182 | // that name. If there's no name, this slice might have a length of `0`. |
183 | let bytes_consumed = event_size + event.len as usize; |
184 | let name = &buffer[event_size..bytes_consumed]; |
185 | |
186 | // Remove trailing '\0' bytes |
187 | // |
188 | // The events in the buffer are aligned, and `name` is filled up |
189 | // with '\0' up to the alignment boundary. Here we remove those |
190 | // additional bytes. |
191 | // |
192 | // The `unwrap` here is safe, because `splitn` always returns at |
193 | // least one result, even if the original slice contains no '\0'. |
194 | let name = name |
195 | .splitn(2, |b| b == &0u8) |
196 | .next() |
197 | .unwrap(); |
198 | |
199 | let event = Event::new( |
200 | fd, |
201 | event, |
202 | OsStr::from_bytes(name), |
203 | ); |
204 | |
205 | (bytes_consumed, event) |
206 | } |
207 | |
208 | /// Returns an owned copy of the event. |
209 | #[must_use = "cloning is often expensive and is not expected to have side effects" ] |
210 | pub fn into_owned(&self) -> EventOwned { |
211 | Event { |
212 | wd: self.wd.clone(), |
213 | mask: self.mask, |
214 | cookie: self.cookie, |
215 | name: self.name.map(OsStr::to_os_string), |
216 | } |
217 | } |
218 | } |
219 | |
220 | |
221 | /// An owned version of `Event` |
222 | pub type EventOwned = Event<OsString>; |
223 | |
224 | |
225 | bitflags! { |
226 | /// Indicates the type of an event |
227 | /// |
228 | /// This struct can be retrieved from an [`Event`] via its `mask` field. |
229 | /// You can determine the [`Event`]'s type by comparing the `EventMask` to |
230 | /// its associated constants. |
231 | /// |
232 | /// Please refer to the documentation of [`Event`] for a usage example. |
233 | /// |
234 | /// [`Event`]: struct.Event.html |
235 | pub struct EventMask: u32 { |
236 | /// File was accessed |
237 | /// |
238 | /// When watching a directory, this event is only triggered for objects |
239 | /// inside the directory, not the directory itself. |
240 | /// |
241 | /// See [`inotify_sys::IN_ACCESS`]. |
242 | /// |
243 | /// [`inotify_sys::IN_ACCESS`]: ../inotify_sys/constant.IN_ACCESS.html |
244 | const ACCESS = ffi::IN_ACCESS; |
245 | |
246 | /// Metadata (permissions, timestamps, ...) changed |
247 | /// |
248 | /// When watching a directory, this event can be triggered for the |
249 | /// directory itself, as well as objects inside the directory. |
250 | /// |
251 | /// See [`inotify_sys::IN_ATTRIB`]. |
252 | /// |
253 | /// [`inotify_sys::IN_ATTRIB`]: ../inotify_sys/constant.IN_ATTRIB.html |
254 | const ATTRIB = ffi::IN_ATTRIB; |
255 | |
256 | /// File opened for writing was closed |
257 | /// |
258 | /// When watching a directory, this event is only triggered for objects |
259 | /// inside the directory, not the directory itself. |
260 | /// |
261 | /// See [`inotify_sys::IN_CLOSE_WRITE`]. |
262 | /// |
263 | /// [`inotify_sys::IN_CLOSE_WRITE`]: ../inotify_sys/constant.IN_CLOSE_WRITE.html |
264 | const CLOSE_WRITE = ffi::IN_CLOSE_WRITE; |
265 | |
266 | /// File or directory not opened for writing was closed |
267 | /// |
268 | /// When watching a directory, this event can be triggered for the |
269 | /// directory itself, as well as objects inside the directory. |
270 | /// |
271 | /// See [`inotify_sys::IN_CLOSE_NOWRITE`]. |
272 | /// |
273 | /// [`inotify_sys::IN_CLOSE_NOWRITE`]: ../inotify_sys/constant.IN_CLOSE_NOWRITE.html |
274 | const CLOSE_NOWRITE = ffi::IN_CLOSE_NOWRITE; |
275 | |
276 | /// File/directory created in watched directory |
277 | /// |
278 | /// When watching a directory, this event is only triggered for objects |
279 | /// inside the directory, not the directory itself. |
280 | /// |
281 | /// See [`inotify_sys::IN_CREATE`]. |
282 | /// |
283 | /// [`inotify_sys::IN_CREATE`]: ../inotify_sys/constant.IN_CREATE.html |
284 | const CREATE = ffi::IN_CREATE; |
285 | |
286 | /// File/directory deleted from watched directory |
287 | /// |
288 | /// When watching a directory, this event is only triggered for objects |
289 | /// inside the directory, not the directory itself. |
290 | /// |
291 | /// See [`inotify_sys::IN_DELETE`]. |
292 | /// |
293 | /// [`inotify_sys::IN_DELETE`]: ../inotify_sys/constant.IN_DELETE.html |
294 | const DELETE = ffi::IN_DELETE; |
295 | |
296 | /// Watched file/directory was deleted |
297 | /// |
298 | /// See [`inotify_sys::IN_DELETE_SELF`]. |
299 | /// |
300 | /// [`inotify_sys::IN_DELETE_SELF`]: ../inotify_sys/constant.IN_DELETE_SELF.html |
301 | const DELETE_SELF = ffi::IN_DELETE_SELF; |
302 | |
303 | /// File was modified |
304 | /// |
305 | /// When watching a directory, this event is only triggered for objects |
306 | /// inside the directory, not the directory itself. |
307 | /// |
308 | /// See [`inotify_sys::IN_MODIFY`]. |
309 | /// |
310 | /// [`inotify_sys::IN_MODIFY`]: ../inotify_sys/constant.IN_MODIFY.html |
311 | const MODIFY = ffi::IN_MODIFY; |
312 | |
313 | /// Watched file/directory was moved |
314 | /// |
315 | /// See [`inotify_sys::IN_MOVE_SELF`]. |
316 | /// |
317 | /// [`inotify_sys::IN_MOVE_SELF`]: ../inotify_sys/constant.IN_MOVE_SELF.html |
318 | const MOVE_SELF = ffi::IN_MOVE_SELF; |
319 | |
320 | /// File was renamed/moved; watched directory contained old name |
321 | /// |
322 | /// When watching a directory, this event is only triggered for objects |
323 | /// inside the directory, not the directory itself. |
324 | /// |
325 | /// See [`inotify_sys::IN_MOVED_FROM`]. |
326 | /// |
327 | /// [`inotify_sys::IN_MOVED_FROM`]: ../inotify_sys/constant.IN_MOVED_FROM.html |
328 | const MOVED_FROM = ffi::IN_MOVED_FROM; |
329 | |
330 | /// File was renamed/moved; watched directory contains new name |
331 | /// |
332 | /// When watching a directory, this event is only triggered for objects |
333 | /// inside the directory, not the directory itself. |
334 | /// |
335 | /// See [`inotify_sys::IN_MOVED_TO`]. |
336 | /// |
337 | /// [`inotify_sys::IN_MOVED_TO`]: ../inotify_sys/constant.IN_MOVED_TO.html |
338 | const MOVED_TO = ffi::IN_MOVED_TO; |
339 | |
340 | /// File or directory was opened |
341 | /// |
342 | /// When watching a directory, this event can be triggered for the |
343 | /// directory itself, as well as objects inside the directory. |
344 | /// |
345 | /// See [`inotify_sys::IN_OPEN`]. |
346 | /// |
347 | /// [`inotify_sys::IN_OPEN`]: ../inotify_sys/constant.IN_OPEN.html |
348 | const OPEN = ffi::IN_OPEN; |
349 | |
350 | /// Watch was removed |
351 | /// |
352 | /// This event will be generated, if the watch was removed explicitly |
353 | /// (via [`Inotify::rm_watch`]), or automatically (because the file was |
354 | /// deleted or the file system was unmounted). |
355 | /// |
356 | /// See [`inotify_sys::IN_IGNORED`]. |
357 | /// |
358 | /// [`inotify_sys::IN_IGNORED`]: ../inotify_sys/constant.IN_IGNORED.html |
359 | const IGNORED = ffi::IN_IGNORED; |
360 | |
361 | /// Event related to a directory |
362 | /// |
363 | /// The subject of the event is a directory. |
364 | /// |
365 | /// See [`inotify_sys::IN_ISDIR`]. |
366 | /// |
367 | /// [`inotify_sys::IN_ISDIR`]: ../inotify_sys/constant.IN_ISDIR.html |
368 | const ISDIR = ffi::IN_ISDIR; |
369 | |
370 | /// Event queue overflowed |
371 | /// |
372 | /// The event queue has overflowed and events have presumably been lost. |
373 | /// |
374 | /// See [`inotify_sys::IN_Q_OVERFLOW`]. |
375 | /// |
376 | /// [`inotify_sys::IN_Q_OVERFLOW`]: ../inotify_sys/constant.IN_Q_OVERFLOW.html |
377 | const Q_OVERFLOW = ffi::IN_Q_OVERFLOW; |
378 | |
379 | /// File system containing watched object was unmounted. |
380 | /// File system was unmounted |
381 | /// |
382 | /// The file system that contained the watched object has been |
383 | /// unmounted. An event with [`WatchMask::IGNORED`] will subsequently be |
384 | /// generated for the same watch descriptor. |
385 | /// |
386 | /// See [`inotify_sys::IN_UNMOUNT`]. |
387 | /// |
388 | /// [`WatchMask::IGNORED`]: #associatedconstant.IGNORED |
389 | /// [`inotify_sys::IN_UNMOUNT`]: ../inotify_sys/constant.IN_UNMOUNT.html |
390 | const UNMOUNT = ffi::IN_UNMOUNT; |
391 | } |
392 | } |
393 | |
394 | |
395 | #[cfg (test)] |
396 | mod tests { |
397 | use std::{ |
398 | io::prelude::*, |
399 | mem, |
400 | slice, |
401 | sync, |
402 | }; |
403 | |
404 | use crate::util; |
405 | |
406 | use inotify_sys as ffi; |
407 | |
408 | use super::Event; |
409 | |
410 | |
411 | #[test ] |
412 | fn from_buffer_should_not_mistake_next_event_for_name_of_previous_event() { |
413 | let mut buffer = [0u8; 1024]; |
414 | |
415 | // Make sure the buffer is properly aligned before writing raw events into it |
416 | let buffer = util::align_buffer_mut(&mut buffer); |
417 | |
418 | // First, put a normal event into the buffer |
419 | let event = ffi::inotify_event { |
420 | wd: 0, |
421 | mask: 0, |
422 | cookie: 0, |
423 | len: 0, // no name following after event |
424 | }; |
425 | let event = unsafe { |
426 | slice::from_raw_parts( |
427 | &event as *const _ as *const u8, |
428 | mem::size_of_val(&event), |
429 | ) |
430 | }; |
431 | (&mut buffer[..]).write(event) |
432 | .expect("Failed to write into buffer" ); |
433 | |
434 | // After that event, simulate an event that starts with a non-zero byte. |
435 | buffer[mem::size_of_val(&event)] = 1; |
436 | |
437 | // Now create the event and verify that the name is actually `None`, as |
438 | // dictated by the value `len` above. |
439 | let (_, event) = Event::from_buffer( |
440 | sync::Weak::new(), |
441 | &buffer, |
442 | ); |
443 | assert_eq!(event.name, None); |
444 | } |
445 | } |
446 | |