1 #![deny(warnings)]
2 
3 
4 // This test suite is incomplete and doesn't cover all available functionality.
5 // Contributions to improve test coverage would be highly appreciated!
6 
7 use inotify::{
8     Inotify,
9     WatchMask
10 };
11 use std::fs::File;
12 use std::io::{
13     Write,
14     ErrorKind,
15 };
16 use std::os::unix::io::{
17     AsRawFd,
18     FromRawFd,
19     IntoRawFd,
20 };
21 use std::path::PathBuf;
22 use tempfile::TempDir;
23 
24 #[cfg(feature = "stream")]
25 use maplit::hashmap;
26 #[cfg(feature = "stream")]
27 use inotify::EventMask;
28 #[cfg(feature = "stream")]
29 use rand::{thread_rng, prelude::SliceRandom};
30 #[cfg(feature = "stream")]
31 use std::sync::{Mutex, Arc};
32 #[cfg(feature = "stream")]
33 use futures_util::StreamExt;
34 
35 
36 #[test]
it_should_watch_a_file()37 fn it_should_watch_a_file() {
38     let mut testdir = TestDir::new();
39     let (path, mut file) = testdir.new_file();
40 
41     let mut inotify = Inotify::init().unwrap();
42     let watch = inotify.watches().add(&path, WatchMask::MODIFY).unwrap();
43 
44     write_to(&mut file);
45 
46     let mut buffer = [0; 1024];
47     let events = inotify.read_events_blocking(&mut buffer).unwrap();
48 
49     let mut num_events = 0;
50     for event in events {
51         assert_eq!(watch, event.wd);
52         num_events += 1;
53     }
54     assert!(num_events > 0);
55 }
56 
57 #[cfg(feature = "stream")]
58 #[tokio::test]
it_should_watch_a_file_async()59 async fn it_should_watch_a_file_async() {
60     let mut testdir = TestDir::new();
61     let (path, mut file) = testdir.new_file();
62 
63     let inotify = Inotify::init().unwrap();
64 
65     // Hold ownership of `watches` for this test, so that the underlying file descriptor has
66     // at least one reference to keep it alive, and we can inspect the WatchDescriptors below.
67     // Otherwise the `Weak<FdGuard>` contained in the WatchDescriptors will be invalidated
68     // when `inotify` is consumed by `into_event_stream()` and the EventStream is dropped
69     // during `await`.
70     let mut watches = inotify.watches();
71 
72     let watch = watches.add(&path, WatchMask::MODIFY).unwrap();
73 
74     write_to(&mut file);
75 
76     let mut buffer = [0; 1024];
77 
78     use futures_util::StreamExt;
79     let events = inotify
80         .into_event_stream(&mut buffer[..])
81         .unwrap()
82         .take(1)
83         .collect::<Vec<_>>()
84         .await;
85 
86     let mut num_events = 0;
87     for event in events {
88         if let Ok(event) = event {
89             assert_eq!(watch, event.wd);
90             num_events += 1;
91         }
92     }
93     assert!(num_events > 0);
94 }
95 
96 #[cfg(feature = "stream")]
97 #[tokio::test]
it_should_watch_a_file_from_eventstream_watches()98 async fn it_should_watch_a_file_from_eventstream_watches() {
99     let mut testdir = TestDir::new();
100     let (path, mut file) = testdir.new_file();
101 
102     let inotify = Inotify::init().unwrap();
103 
104     let mut buffer = [0; 1024];
105 
106     use futures_util::StreamExt;
107     let stream = inotify.into_event_stream(&mut buffer[..]).unwrap();
108 
109     // Hold ownership of `watches` for this test, so that the underlying file descriptor has
110     // at least one reference to keep it alive, and we can inspect the WatchDescriptors below.
111     // Otherwise the `Weak<FdGuard>` contained in the WatchDescriptors will be invalidated
112     // when `stream` is dropped during `await`.
113     let mut watches = stream.watches();
114 
115     let watch = watches.add(&path, WatchMask::MODIFY).unwrap();
116     write_to(&mut file);
117 
118     let events = stream
119         .take(1)
120         .collect::<Vec<_>>()
121         .await;
122 
123     let mut num_events = 0;
124     for event in events {
125         if let Ok(event) = event {
126             assert_eq!(watch, event.wd);
127             num_events += 1;
128         }
129     }
130     assert!(num_events > 0);
131 }
132 
133 #[cfg(feature = "stream")]
134 #[tokio::test]
it_should_watch_a_file_after_converting_back_from_eventstream()135 async fn it_should_watch_a_file_after_converting_back_from_eventstream() {
136     let mut testdir = TestDir::new();
137     let (path, mut file) = testdir.new_file();
138 
139     let inotify = Inotify::init().unwrap();
140 
141     let mut buffer = [0; 1024];
142     let stream = inotify.into_event_stream(&mut buffer[..]).unwrap();
143     let mut inotify = stream.into_inotify();
144 
145     let watch = inotify.watches().add(&path, WatchMask::MODIFY).unwrap();
146 
147     write_to(&mut file);
148 
149     let events = inotify.read_events_blocking(&mut buffer).unwrap();
150 
151     let mut num_events = 0;
152     for event in events {
153         assert_eq!(watch, event.wd);
154         num_events += 1;
155     }
156     assert!(num_events > 0);
157 }
158 
159 #[test]
it_should_return_immediately_if_no_events_are_available()160 fn it_should_return_immediately_if_no_events_are_available() {
161     let mut inotify = Inotify::init().unwrap();
162 
163     let mut buffer = [0; 1024];
164     assert_eq!(inotify.read_events(&mut buffer).unwrap_err().kind(), ErrorKind::WouldBlock);
165 }
166 
167 #[test]
it_should_convert_the_name_into_an_os_str()168 fn it_should_convert_the_name_into_an_os_str() {
169     let mut testdir = TestDir::new();
170     let (path, mut file) = testdir.new_file();
171 
172     let mut inotify = Inotify::init().unwrap();
173     inotify.watches().add(&path.parent().unwrap(), WatchMask::MODIFY).unwrap();
174 
175     write_to(&mut file);
176 
177     let mut buffer = [0; 1024];
178     let mut events = inotify.read_events_blocking(&mut buffer).unwrap();
179 
180     if let Some(event) = events.next() {
181         assert_eq!(path.file_name(), event.name);
182     }
183     else {
184         panic!("Expected inotify event");
185     }
186 }
187 
188 #[test]
it_should_set_name_to_none_if_it_is_empty()189 fn it_should_set_name_to_none_if_it_is_empty() {
190     let mut testdir = TestDir::new();
191     let (path, mut file) = testdir.new_file();
192 
193     let mut inotify = Inotify::init().unwrap();
194     inotify.watches().add(&path, WatchMask::MODIFY).unwrap();
195 
196     write_to(&mut file);
197 
198     let mut buffer = [0; 1024];
199     let mut events = inotify.read_events_blocking(&mut buffer).unwrap();
200 
201     if let Some(event) = events.next() {
202         assert_eq!(event.name, None);
203     }
204     else {
205         panic!("Expected inotify event");
206     }
207 }
208 
209 #[test]
it_should_not_accept_watchdescriptors_from_other_instances()210 fn it_should_not_accept_watchdescriptors_from_other_instances() {
211     let mut testdir = TestDir::new();
212     let (path, _) = testdir.new_file();
213 
214     let inotify = Inotify::init().unwrap();
215     let _ = inotify.watches().add(&path, WatchMask::ACCESS).unwrap();
216 
217     let second_inotify = Inotify::init().unwrap();
218     let wd2 = second_inotify.watches().add(&path, WatchMask::ACCESS).unwrap();
219 
220     assert_eq!(inotify.watches().remove(wd2).unwrap_err().kind(), ErrorKind::InvalidInput);
221 }
222 
223 #[test]
watch_descriptors_from_different_inotify_instances_should_not_be_equal()224 fn watch_descriptors_from_different_inotify_instances_should_not_be_equal() {
225     let mut testdir = TestDir::new();
226     let (path, _) = testdir.new_file();
227 
228     let inotify_1 = Inotify::init()
229         .unwrap();
230     let inotify_2 = Inotify::init()
231         .unwrap();
232 
233     let wd_1 = inotify_1
234         .watches()
235         .add(&path, WatchMask::ACCESS)
236         .unwrap();
237     let wd_2 = inotify_2
238         .watches()
239         .add(&path, WatchMask::ACCESS)
240         .unwrap();
241 
242     // As far as inotify is concerned, watch descriptors are just integers that
243     // are scoped per inotify instance. This means that multiple instances will
244     // produce the same watch descriptor number, a case we want inotify-rs to
245     // detect.
246     assert!(wd_1 != wd_2);
247 }
248 
249 #[test]
watch_descriptor_equality_should_not_be_confused_by_reused_fds()250 fn watch_descriptor_equality_should_not_be_confused_by_reused_fds() {
251     let mut testdir = TestDir::new();
252     let (path, _) = testdir.new_file();
253 
254     // When a new inotify instance is created directly after closing another
255     // one, it is possible that the file descriptor is reused immediately, and
256     // we end up with a new instance that has the same file descriptor as the
257     // old one.
258     // This is quite likely, but it doesn't happen every time. Therefore we may
259     // need a few tries until we find two instances where that is the case.
260     let (wd_1, inotify_2) = loop {
261         let inotify_1 = Inotify::init()
262             .unwrap();
263 
264         let wd_1 = inotify_1
265             .watches()
266             .add(&path, WatchMask::ACCESS)
267             .unwrap();
268         let fd_1 = inotify_1.as_raw_fd();
269 
270         inotify_1
271             .close()
272             .unwrap();
273         let inotify_2 = Inotify::init()
274             .unwrap();
275 
276         if fd_1 == inotify_2.as_raw_fd() {
277             break (wd_1, inotify_2);
278         }
279     };
280 
281     let wd_2 = inotify_2
282         .watches()
283         .add(&path, WatchMask::ACCESS)
284         .unwrap();
285 
286     // The way we engineered this situation, both `WatchDescriptor` instances
287     // have the same fields. They still come from different inotify instances
288     // though, so they shouldn't be equal.
289     assert!(wd_1 != wd_2);
290 
291     inotify_2
292         .close()
293         .unwrap();
294 
295     // A little extra gotcha: If both inotify instances are closed, and the `Eq`
296     // implementation naively compares the weak pointers, both will be `None`,
297     // making them equal. Let's make sure this isn't the case.
298     assert!(wd_1 != wd_2);
299 }
300 
301 #[test]
it_should_implement_raw_fd_traits_correctly()302 fn it_should_implement_raw_fd_traits_correctly() {
303     let fd = Inotify::init()
304         .expect("Failed to initialize inotify instance")
305         .into_raw_fd();
306 
307     // If `IntoRawFd` has been implemented naively, `Inotify`'s `Drop`
308     // implementation will have closed the inotify instance at this point. Let's
309     // make sure this didn't happen.
310     let mut inotify = unsafe { <Inotify as FromRawFd>::from_raw_fd(fd) };
311 
312     let mut buffer = [0; 1024];
313     if let Err(error) = inotify.read_events(&mut buffer) {
314         if error.kind() != ErrorKind::WouldBlock {
315             panic!("Failed to add watch: {}", error);
316         }
317     }
318 }
319 
320 #[test]
it_should_watch_correctly_with_a_watches_clone()321 fn it_should_watch_correctly_with_a_watches_clone() {
322     let mut testdir = TestDir::new();
323     let (path, mut file) = testdir.new_file();
324 
325     let mut inotify = Inotify::init().unwrap();
326     let mut watches1 = inotify.watches();
327     let mut watches2 = watches1.clone();
328     let watch1 = watches1.add(&path, WatchMask::MODIFY).unwrap();
329     let watch2 = watches2.add(&path, WatchMask::MODIFY).unwrap();
330 
331     // same path and same Inotify should return same descriptor
332     assert_eq!(watch1, watch2);
333 
334     write_to(&mut file);
335 
336     let mut buffer = [0; 1024];
337     let events = inotify.read_events_blocking(&mut buffer).unwrap();
338 
339     let mut num_events = 0;
340     for event in events {
341         assert_eq!(watch2, event.wd);
342         num_events += 1;
343     }
344     assert!(num_events > 0);
345 }
346 
347 #[cfg(feature = "stream")]
348 #[tokio::test]
349 /// Testing if two files with the same name but different directories
350 /// (e.g. "file_a" and "another_dir/file_a") are distinguished when _randomly_
351 /// triggering a DELETE_SELF for the two files.
it_should_distinguish_event_for_files_with_same_name()352 async fn it_should_distinguish_event_for_files_with_same_name() {
353     let mut testdir = TestDir::new();
354     let testdir_path = testdir.dir.path().to_owned();
355     let file_order = Arc::new(Mutex::new(vec!["file_a", "another_dir/file_a"]));
356     file_order.lock().unwrap().shuffle(&mut thread_rng());
357     let file_order_clone = file_order.clone();
358 
359     let inotify = Inotify::init().expect("Failed to initialize inotify instance");
360 
361     // creating file_a inside `TestDir.dir`
362     let (path_1, _) = testdir.new_file_with_name("file_a");
363     // creating a directory inside `TestDir.dir`
364     testdir.new_directory_with_name("another_dir");
365     // creating a file inside `TestDir.dir/another_dir`
366     let (path_2, _) = testdir.new_file_in_directory_with_name("another_dir", "file_a");
367 
368     // watching both files for `DELETE_SELF`
369     let wd_1 = inotify.watches().add(&path_1, WatchMask::DELETE_SELF).unwrap();
370     let wd_2 = inotify.watches().add(&path_2, WatchMask::DELETE_SELF).unwrap();
371 
372     let expected_ids = hashmap! {
373         wd_1.get_watch_descriptor_id() => "file_a",
374         wd_2.get_watch_descriptor_id() => "another_dir/file_a"
375     };
376     let mut buffer = [0; 1024];
377 
378     let file_removal_handler = tokio::spawn(async move {
379         for file in file_order.lock().unwrap().iter() {
380             testdir.delete_file(file);
381         }
382     });
383 
384     let event_handle = tokio::spawn(async move {
385         let mut events = inotify.into_event_stream(&mut buffer).unwrap();
386         while let Some(Ok(event)) = events.next().await {
387             if event.mask == EventMask::DELETE_SELF {
388                 let id = event.wd.get_watch_descriptor_id();
389                 let file = expected_ids.get(&id).unwrap();
390                 let full_path = testdir_path.join(*file);
391                 println!("file {:?} was deleted", full_path);
392                 file_order_clone.lock().unwrap().retain(|&x| x != *file);
393 
394                 if file_order_clone.lock().unwrap().is_empty() {
395                     break;
396                 }
397             }
398         }
399     });
400 
401     let () = event_handle.await.unwrap();
402     let () = file_removal_handler.await.unwrap();
403 }
404 
405 struct TestDir {
406     dir: TempDir,
407     counter: u32,
408 }
409 
410 impl TestDir {
new() -> TestDir411     fn new() -> TestDir {
412         TestDir {
413             dir: TempDir::new().unwrap(),
414             counter: 0,
415         }
416     }
417 
418     #[cfg(feature = "stream")]
new_file_with_name(&mut self, file_name: &str) -> (PathBuf, File)419     fn new_file_with_name(&mut self, file_name: &str) -> (PathBuf, File) {
420         self.counter += 1;
421 
422         let path = self.dir.path().join(file_name);
423         let file = File::create(&path)
424             .unwrap_or_else(|error| panic!("Failed to create temporary file: {}", error));
425 
426         (path, file)
427     }
428 
429     #[cfg(feature = "stream")]
delete_file(&mut self, relative_path_to_file: &str)430     fn delete_file(&mut self, relative_path_to_file: &str) {
431         let path = &self.dir.path().join(relative_path_to_file);
432         std::fs::remove_file(path).unwrap();
433     }
434 
435     #[cfg(feature = "stream")]
new_file_in_directory_with_name( &mut self, dir_name: &str, file_name: &str, ) -> (PathBuf, File)436     fn new_file_in_directory_with_name(
437         &mut self,
438         dir_name: &str,
439         file_name: &str,
440     ) -> (PathBuf, File) {
441         self.counter += 1;
442 
443         let path = self.dir.path().join(dir_name).join(file_name);
444         let file = File::create(&path)
445             .unwrap_or_else(|error| panic!("Failed to create temporary file: {}", error));
446 
447         (path, file)
448     }
449 
450     #[cfg(feature = "stream")]
new_directory_with_name(&mut self, dir_name: &str) -> PathBuf451     fn new_directory_with_name(&mut self, dir_name: &str) -> PathBuf {
452         let path = self.dir.path().join(dir_name);
453         let () = std::fs::create_dir(&path).unwrap();
454         path.to_path_buf()
455     }
456 
new_file(&mut self) -> (PathBuf, File)457     fn new_file(&mut self) -> (PathBuf, File) {
458         let id = self.counter;
459         self.counter += 1;
460 
461         let path = self.dir.path().join("file-".to_string() + &id.to_string());
462         let file = File::create(&path)
463             .unwrap_or_else(|error| panic!("Failed to create temporary file: {}", error));
464 
465         (path, file)
466     }
467 }
468 
write_to(file: &mut File)469 fn write_to(file: &mut File) {
470     file
471         .write(b"This should trigger an inotify event.")
472         .unwrap_or_else(|error|
473             panic!("Failed to write to file: {}", error)
474         );
475 }
476