1#![allow(dead_code)]
16
17use alloc::{
18 collections::{BTreeMap, VecDeque},
19 string::String,
20 vec::Vec,
21};
22use core::sync::atomic::{AtomicI32, AtomicU64, Ordering};
23
24#[cfg(not(target_arch = "aarch64"))]
25use spin::RwLock;
26
27#[cfg(target_arch = "aarch64")]
28use super::bare_lock::RwLock;
29use crate::error::KernelError;
30
31pub const IN_ACCESS: u32 = 0x0000_0001;
37
38pub const IN_MODIFY: u32 = 0x0000_0002;
40
41pub const IN_ATTRIB: u32 = 0x0000_0004;
43
44pub const IN_CLOSE_WRITE: u32 = 0x0000_0008;
46
47pub const IN_CLOSE_NOWRITE: u32 = 0x0000_0010;
49
50pub const IN_OPEN: u32 = 0x0000_0020;
52
53pub const IN_MOVED_FROM: u32 = 0x0000_0040;
55
56pub const IN_MOVED_TO: u32 = 0x0000_0080;
58
59pub const IN_CREATE: u32 = 0x0000_0100;
61
62pub const IN_DELETE: u32 = 0x0000_0200;
64
65pub const IN_DELETE_SELF: u32 = 0x0000_0400;
67
68pub const IN_MOVE_SELF: u32 = 0x0000_0800;
70
71pub const IN_ALL_EVENTS: u32 = IN_ACCESS
73 | IN_MODIFY
74 | IN_ATTRIB
75 | IN_CLOSE_WRITE
76 | IN_CLOSE_NOWRITE
77 | IN_OPEN
78 | IN_MOVED_FROM
79 | IN_MOVED_TO
80 | IN_CREATE
81 | IN_DELETE
82 | IN_DELETE_SELF
83 | IN_MOVE_SELF;
84
85pub const IN_RECURSIVE: u32 = 0x0100_0000;
88
89pub const IN_ISDIR: u32 = 0x4000_0000;
92
93const DEFAULT_MAX_EVENTS: usize = 4096;
99
100const DEFAULT_MAX_WATCHES: usize = 8192;
102
103static NEXT_INSTANCE_ID: AtomicU64 = AtomicU64::new(1);
109
110static NEXT_WD: AtomicI32 = AtomicI32::new(1);
112
113static NEXT_COOKIE: AtomicU64 = AtomicU64::new(1);
115
116static INOTIFY_INSTANCES: RwLock<BTreeMap<u64, InotifyInstance>> = RwLock::new(BTreeMap::new());
118
119static INODE_WATCHES: RwLock<BTreeMap<u64, Vec<(u64, i32)>>> = RwLock::new(BTreeMap::new());
122
123#[derive(Debug, Clone)]
129pub struct InotifyEvent {
130 pub wd: i32,
132 pub mask: u32,
134 pub cookie: u32,
136 pub name: Option<String>,
138}
139
140impl InotifyEvent {
141 fn is_coalesceable_with(&self, other: &InotifyEvent) -> bool {
145 self.wd == other.wd
146 && self.mask == other.mask
147 && self.cookie == other.cookie
148 && self.name == other.name
149 }
150}
151
152#[derive(Debug, Clone)]
154pub struct WatchDescriptor {
155 pub wd: i32,
157 pub inode: u64,
159 pub mask: u32,
161 pub recursive: bool,
163 pub path: String,
165}
166
167pub struct InotifyInstance {
169 id: u64,
171 watches: BTreeMap<i32, WatchDescriptor>,
173 events: VecDeque<InotifyEvent>,
175 max_events: usize,
177 max_watches: usize,
179}
180
181impl InotifyInstance {
182 fn new(id: u64) -> Self {
184 Self {
185 id,
186 watches: BTreeMap::new(),
187 events: VecDeque::new(),
188 max_events: DEFAULT_MAX_EVENTS,
189 max_watches: DEFAULT_MAX_WATCHES,
190 }
191 }
192
193 fn with_limits(id: u64, max_events: usize, max_watches: usize) -> Self {
195 Self {
196 id,
197 watches: BTreeMap::new(),
198 events: VecDeque::new(),
199 max_events,
200 max_watches,
201 }
202 }
203
204 fn push_event(&mut self, event: InotifyEvent) {
209 if let Some(last) = self.events.back() {
211 if last.is_coalesceable_with(&event) {
212 return;
213 }
214 }
215
216 while self.events.len() >= self.max_events {
218 self.events.pop_front();
219 }
220
221 self.events.push_back(event);
222 }
223
224 fn read_events(&mut self, max_count: usize) -> Vec<InotifyEvent> {
227 let count = max_count.min(self.events.len());
228 let mut result = Vec::with_capacity(count);
229 for _ in 0..count {
230 if let Some(event) = self.events.pop_front() {
231 result.push(event);
232 }
233 }
234 result
235 }
236
237 fn pending_count(&self) -> usize {
239 self.events.len()
240 }
241
242 fn has_events(&self) -> bool {
244 !self.events.is_empty()
245 }
246}
247
248pub fn inotify_init() -> Result<u64, KernelError> {
256 let id = NEXT_INSTANCE_ID.fetch_add(1, Ordering::Relaxed);
257 let instance = InotifyInstance::new(id);
258
259 let mut instances = INOTIFY_INSTANCES.write();
260 instances.insert(id, instance);
261
262 Ok(id)
263}
264
265pub fn inotify_init_with_limits(max_events: usize, max_watches: usize) -> Result<u64, KernelError> {
269 if max_events == 0 {
270 return Err(KernelError::InvalidArgument {
271 name: "max_events",
272 value: "must be > 0",
273 });
274 }
275 if max_watches == 0 {
276 return Err(KernelError::InvalidArgument {
277 name: "max_watches",
278 value: "must be > 0",
279 });
280 }
281
282 let id = NEXT_INSTANCE_ID.fetch_add(1, Ordering::Relaxed);
283 let instance = InotifyInstance::with_limits(id, max_events, max_watches);
284
285 let mut instances = INOTIFY_INSTANCES.write();
286 instances.insert(id, instance);
287
288 Ok(id)
289}
290
291pub fn inotify_add_watch(
304 instance_id: u64,
305 inode: u64,
306 path: &str,
307 mask: u32,
308) -> Result<i32, KernelError> {
309 let event_mask = mask & IN_ALL_EVENTS;
310 if event_mask == 0 {
311 return Err(KernelError::InvalidArgument {
312 name: "mask",
313 value: "no valid event types specified",
314 });
315 }
316
317 let recursive = mask & IN_RECURSIVE != 0;
318
319 let mut instances = INOTIFY_INSTANCES.write();
320 let instance = instances
321 .get_mut(&instance_id)
322 .ok_or(KernelError::NotFound {
323 resource: "inotify instance",
324 id: instance_id,
325 })?;
326
327 for watch in instance.watches.values_mut() {
329 if watch.inode == inode {
330 watch.mask = event_mask;
332 watch.recursive = recursive;
333 return Ok(watch.wd);
334 }
335 }
336
337 if instance.watches.len() >= instance.max_watches {
339 return Err(KernelError::ResourceExhausted {
340 resource: "inotify watches",
341 });
342 }
343
344 let wd = NEXT_WD.fetch_add(1, Ordering::Relaxed);
346 let watch = WatchDescriptor {
347 wd,
348 inode,
349 mask: event_mask,
350 recursive,
351 path: String::from(path),
352 };
353
354 instance.watches.insert(wd, watch);
355
356 let mut inode_watches = INODE_WATCHES.write();
358 inode_watches
359 .entry(inode)
360 .or_default()
361 .push((instance_id, wd));
362
363 Ok(wd)
364}
365
366pub fn inotify_rm_watch(instance_id: u64, wd: i32) -> Result<(), KernelError> {
372 let mut instances = INOTIFY_INSTANCES.write();
373 let instance = instances
374 .get_mut(&instance_id)
375 .ok_or(KernelError::NotFound {
376 resource: "inotify instance",
377 id: instance_id,
378 })?;
379
380 let watch = instance.watches.remove(&wd).ok_or(KernelError::NotFound {
381 resource: "inotify watch",
382 id: wd as u64,
383 })?;
384
385 let mut inode_watches = INODE_WATCHES.write();
387 if let Some(watchers) = inode_watches.get_mut(&watch.inode) {
388 watchers.retain(|&(iid, w)| !(iid == instance_id && w == wd));
389 if watchers.is_empty() {
390 inode_watches.remove(&watch.inode);
391 }
392 }
393
394 Ok(())
395}
396
397pub fn inotify_close(instance_id: u64) -> Result<(), KernelError> {
402 let mut instances = INOTIFY_INSTANCES.write();
403 let instance = instances
404 .remove(&instance_id)
405 .ok_or(KernelError::NotFound {
406 resource: "inotify instance",
407 id: instance_id,
408 })?;
409
410 let mut inode_watches = INODE_WATCHES.write();
412 for watch in instance.watches.values() {
413 if let Some(watchers) = inode_watches.get_mut(&watch.inode) {
414 watchers.retain(|&(iid, _)| iid != instance_id);
415 if watchers.is_empty() {
416 inode_watches.remove(&watch.inode);
417 }
418 }
419 }
420
421 Ok(())
422}
423
424pub fn inotify_read(instance_id: u64, max_count: usize) -> Result<Vec<InotifyEvent>, KernelError> {
429 let mut instances = INOTIFY_INSTANCES.write();
430 let instance = instances
431 .get_mut(&instance_id)
432 .ok_or(KernelError::NotFound {
433 resource: "inotify instance",
434 id: instance_id,
435 })?;
436
437 Ok(instance.read_events(max_count))
438}
439
440pub fn inotify_pending(instance_id: u64) -> Result<usize, KernelError> {
442 let instances = INOTIFY_INSTANCES.read();
443 let instance = instances.get(&instance_id).ok_or(KernelError::NotFound {
444 resource: "inotify instance",
445 id: instance_id,
446 })?;
447
448 Ok(instance.pending_count())
449}
450
451pub fn generate_move_cookie() -> u32 {
453 NEXT_COOKIE.fetch_add(1, Ordering::Relaxed) as u32
455}
456
457pub fn notify_event(inode: u64, mask: u32, cookie: u32, name: Option<&str>) {
473 let watchers = {
476 let inode_watches = INODE_WATCHES.read();
477 match inode_watches.get(&inode) {
478 Some(w) => w.clone(),
479 None => return, }
481 };
482
483 let mut instances = INOTIFY_INSTANCES.write();
484
485 for (instance_id, wd) in &watchers {
486 if let Some(instance) = instances.get_mut(instance_id) {
487 if let Some(watch) = instance.watches.get(wd) {
489 if watch.mask & mask != 0 {
490 let event = InotifyEvent {
491 wd: *wd,
492 mask,
493 cookie,
494 name: name.map(String::from),
495 };
496 instance.push_event(event);
497 }
498 }
499 }
500 }
501}
502
503pub fn notify_child_event(
507 parent_inode: u64,
508 mask: u32,
509 cookie: u32,
510 child_name: &str,
511 is_dir: bool,
512) {
513 let effective_mask = if is_dir { mask | IN_ISDIR } else { mask };
514 notify_event(parent_inode, effective_mask, cookie, Some(child_name));
515}
516
517#[derive(Debug, Clone, Copy)]
523pub struct InotifyStats {
524 pub instance_count: usize,
526 pub total_watches: usize,
528 pub total_pending_events: usize,
530}
531
532pub fn get_stats() -> InotifyStats {
534 let instances = INOTIFY_INSTANCES.read();
535 let mut total_watches = 0;
536 let mut total_pending = 0;
537
538 for instance in instances.values() {
539 total_watches += instance.watches.len();
540 total_pending += instance.events.len();
541 }
542
543 InotifyStats {
544 instance_count: instances.len(),
545 total_watches,
546 total_pending_events: total_pending,
547 }
548}
549
550#[cfg(test)]
555mod tests {
556 use super::*;
557
558 #[test]
559 fn test_inotify_init() {
560 let id = inotify_init().unwrap();
561 assert!(id > 0);
562
563 inotify_close(id).unwrap();
565 }
566
567 #[test]
568 fn test_inotify_init_with_limits() {
569 let id = inotify_init_with_limits(100, 50).unwrap();
570 assert!(id > 0);
571 inotify_close(id).unwrap();
572 }
573
574 #[test]
575 fn test_inotify_init_zero_events_rejected() {
576 let result = inotify_init_with_limits(0, 50);
577 assert!(result.is_err());
578 }
579
580 #[test]
581 fn test_inotify_init_zero_watches_rejected() {
582 let result = inotify_init_with_limits(100, 0);
583 assert!(result.is_err());
584 }
585
586 #[test]
587 fn test_add_and_remove_watch() {
588 let id = inotify_init().unwrap();
589
590 let wd = inotify_add_watch(id, 42, "/tmp/test", IN_MODIFY | IN_CREATE).unwrap();
591 assert!(wd > 0);
592
593 inotify_rm_watch(id, wd).unwrap();
594 inotify_close(id).unwrap();
595 }
596
597 #[test]
598 fn test_add_watch_invalid_mask() {
599 let id = inotify_init().unwrap();
600
601 let result = inotify_add_watch(id, 42, "/tmp/test", 0);
603 assert!(result.is_err());
604
605 let result = inotify_add_watch(id, 42, "/tmp/test", IN_RECURSIVE);
607 assert!(result.is_err());
608
609 inotify_close(id).unwrap();
610 }
611
612 #[test]
613 fn test_add_watch_updates_existing() {
614 let id = inotify_init().unwrap();
615
616 let wd1 = inotify_add_watch(id, 42, "/tmp/test", IN_MODIFY).unwrap();
617 let wd2 = inotify_add_watch(id, 42, "/tmp/test", IN_CREATE).unwrap();
618
619 assert_eq!(wd1, wd2);
621
622 inotify_close(id).unwrap();
623 }
624
625 #[test]
626 fn test_rm_watch_invalid_instance() {
627 let result = inotify_rm_watch(999_999, 1);
628 assert!(result.is_err());
629 }
630
631 #[test]
632 fn test_rm_watch_invalid_wd() {
633 let id = inotify_init().unwrap();
634 let result = inotify_rm_watch(id, 999);
635 assert!(result.is_err());
636 inotify_close(id).unwrap();
637 }
638
639 #[test]
640 fn test_close_invalid_instance() {
641 let result = inotify_close(999_999);
642 assert!(result.is_err());
643 }
644
645 #[test]
646 fn test_notify_event_delivery() {
647 let id = inotify_init().unwrap();
648 let inode = 100;
649 let wd = inotify_add_watch(id, inode, "/tmp/watched", IN_MODIFY | IN_CREATE).unwrap();
650
651 notify_event(inode, IN_MODIFY, 0, None);
653
654 let events = inotify_read(id, 10).unwrap();
655 assert_eq!(events.len(), 1);
656 assert_eq!(events[0].wd, wd);
657 assert_eq!(events[0].mask, IN_MODIFY);
658 assert_eq!(events[0].cookie, 0);
659 assert!(events[0].name.is_none());
660
661 inotify_close(id).unwrap();
662 }
663
664 #[test]
665 fn test_notify_event_with_name() {
666 let id = inotify_init().unwrap();
667 let inode = 101;
668 let wd = inotify_add_watch(id, inode, "/tmp/dir", IN_CREATE).unwrap();
669
670 notify_child_event(inode, IN_CREATE, 0, "newfile.txt", false);
671
672 let events = inotify_read(id, 10).unwrap();
673 assert_eq!(events.len(), 1);
674 assert_eq!(events[0].wd, wd);
675 assert_eq!(events[0].mask, IN_CREATE);
676 assert_eq!(events[0].name.as_deref(), Some("newfile.txt"));
677
678 inotify_close(id).unwrap();
679 }
680
681 #[test]
682 fn test_notify_isdir_flag() {
683 let id = inotify_init().unwrap();
684 let inode = 102;
685 inotify_add_watch(id, inode, "/tmp/dir", IN_CREATE).unwrap();
686
687 notify_child_event(inode, IN_CREATE, 0, "subdir", true);
688
689 let events = inotify_read(id, 10).unwrap();
690 assert_eq!(events.len(), 1);
691 assert_eq!(events[0].mask, IN_CREATE | IN_ISDIR);
692
693 inotify_close(id).unwrap();
694 }
695
696 #[test]
697 fn test_event_filtering_by_mask() {
698 let id = inotify_init().unwrap();
699 let inode = 103;
700 inotify_add_watch(id, inode, "/tmp/filtered", IN_MODIFY).unwrap();
702
703 notify_event(inode, IN_CREATE, 0, Some("ignored.txt"));
705
706 notify_event(inode, IN_MODIFY, 0, None);
708
709 let events = inotify_read(id, 10).unwrap();
710 assert_eq!(events.len(), 1);
711 assert_eq!(events[0].mask, IN_MODIFY);
712
713 inotify_close(id).unwrap();
714 }
715
716 #[test]
717 fn test_event_coalescing() {
718 let id = inotify_init().unwrap();
719 let inode = 104;
720 inotify_add_watch(id, inode, "/tmp/coalesce", IN_MODIFY).unwrap();
721
722 notify_event(inode, IN_MODIFY, 0, None);
724 notify_event(inode, IN_MODIFY, 0, None);
725 notify_event(inode, IN_MODIFY, 0, None);
726
727 let events = inotify_read(id, 10).unwrap();
729 assert_eq!(events.len(), 1);
730
731 inotify_close(id).unwrap();
732 }
733
734 #[test]
735 fn test_no_coalescing_different_events() {
736 let id = inotify_init().unwrap();
737 let inode = 105;
738 inotify_add_watch(id, inode, "/tmp/nocoalesce", IN_ALL_EVENTS).unwrap();
739
740 notify_event(inode, IN_MODIFY, 0, None);
741 notify_event(inode, IN_CREATE, 0, Some("a.txt"));
742 notify_event(inode, IN_MODIFY, 0, None);
743
744 let events = inotify_read(id, 10).unwrap();
746 assert_eq!(events.len(), 3);
747
748 inotify_close(id).unwrap();
749 }
750
751 #[test]
752 fn test_event_queue_overflow() {
753 let id = inotify_init_with_limits(5, 100).unwrap();
754 let inode = 106;
755 inotify_add_watch(id, inode, "/tmp/overflow", IN_MODIFY).unwrap();
756
757 for i in 0..10 {
759 let name = if i % 2 == 0 { Some("a") } else { Some("b") };
760 notify_event(inode, IN_MODIFY, 0, name);
761 }
762
763 let events = inotify_read(id, 20).unwrap();
765 assert_eq!(events.len(), 5);
766
767 inotify_close(id).unwrap();
768 }
769
770 #[test]
771 fn test_read_events_empty() {
772 let id = inotify_init().unwrap();
773
774 let events = inotify_read(id, 10).unwrap();
775 assert!(events.is_empty());
776
777 inotify_close(id).unwrap();
778 }
779
780 #[test]
781 fn test_read_events_partial() {
782 let id = inotify_init().unwrap();
783 let inode = 107;
784 inotify_add_watch(id, inode, "/tmp/partial", IN_ALL_EVENTS).unwrap();
785
786 notify_event(inode, IN_CREATE, 0, Some("a.txt"));
787 notify_event(inode, IN_MODIFY, 0, Some("a.txt"));
788 notify_event(inode, IN_DELETE, 0, Some("a.txt"));
789
790 let events = inotify_read(id, 2).unwrap();
792 assert_eq!(events.len(), 2);
793 assert_eq!(events[0].mask, IN_CREATE);
794 assert_eq!(events[1].mask, IN_MODIFY);
795
796 let events = inotify_read(id, 10).unwrap();
798 assert_eq!(events.len(), 1);
799 assert_eq!(events[0].mask, IN_DELETE);
800
801 inotify_close(id).unwrap();
802 }
803
804 #[test]
805 fn test_inotify_pending() {
806 let id = inotify_init().unwrap();
807 let inode = 108;
808 inotify_add_watch(id, inode, "/tmp/pending", IN_MODIFY).unwrap();
809
810 assert_eq!(inotify_pending(id).unwrap(), 0);
811
812 notify_event(inode, IN_MODIFY, 0, Some("x"));
813 assert_eq!(inotify_pending(id).unwrap(), 1);
814
815 notify_event(inode, IN_MODIFY, 0, Some("y"));
816 assert_eq!(inotify_pending(id).unwrap(), 2);
817
818 inotify_close(id).unwrap();
819 }
820
821 #[test]
822 fn test_notify_unwatched_inode() {
823 notify_event(999_999, IN_MODIFY, 0, None);
825 }
826
827 #[test]
828 fn test_move_cookie() {
829 let id = inotify_init().unwrap();
830 let src_inode = 200;
831 let dst_inode = 201;
832 inotify_add_watch(id, src_inode, "/tmp/src", IN_MOVED_FROM).unwrap();
833 inotify_add_watch(id, dst_inode, "/tmp/dst", IN_MOVED_TO).unwrap();
834
835 let cookie = generate_move_cookie();
836 notify_event(src_inode, IN_MOVED_FROM, cookie, Some("file.txt"));
837 notify_event(dst_inode, IN_MOVED_TO, cookie, Some("file.txt"));
838
839 let events = inotify_read(id, 10).unwrap();
840 assert_eq!(events.len(), 2);
841 assert_eq!(events[0].mask, IN_MOVED_FROM);
842 assert_eq!(events[1].mask, IN_MOVED_TO);
843 assert_eq!(events[0].cookie, events[1].cookie);
845 assert!(events[0].cookie > 0);
846
847 inotify_close(id).unwrap();
848 }
849
850 #[test]
851 fn test_recursive_watch_flag() {
852 let id = inotify_init().unwrap();
853
854 let wd = inotify_add_watch(id, 300, "/tmp/recursive", IN_CREATE | IN_RECURSIVE).unwrap();
855
856 {
858 let instances = INOTIFY_INSTANCES.read();
859 let instance = instances.get(&id).unwrap();
860 let watch = instance.watches.get(&wd).unwrap();
861 assert!(watch.recursive);
862 }
863
864 inotify_close(id).unwrap();
865 }
866
867 #[test]
868 fn test_multiple_instances_same_inode() {
869 let id1 = inotify_init().unwrap();
870 let id2 = inotify_init().unwrap();
871 let inode = 400;
872
873 inotify_add_watch(id1, inode, "/tmp/multi", IN_MODIFY).unwrap();
874 inotify_add_watch(id2, inode, "/tmp/multi", IN_CREATE).unwrap();
875
876 notify_event(inode, IN_MODIFY, 0, None);
878 assert_eq!(inotify_pending(id1).unwrap(), 1);
879 assert_eq!(inotify_pending(id2).unwrap(), 0);
880
881 notify_event(inode, IN_CREATE, 0, Some("new.txt"));
883 assert_eq!(inotify_pending(id1).unwrap(), 1); assert_eq!(inotify_pending(id2).unwrap(), 1);
885
886 inotify_close(id1).unwrap();
887 inotify_close(id2).unwrap();
888 }
889
890 #[test]
891 fn test_close_cleans_up_inode_watches() {
892 let id = inotify_init().unwrap();
893 let inode = 500;
894 inotify_add_watch(id, inode, "/tmp/cleanup", IN_MODIFY).unwrap();
895
896 inotify_close(id).unwrap();
897
898 notify_event(inode, IN_MODIFY, 0, None);
901 }
902
903 #[test]
904 fn test_rm_watch_cleans_up_inode_mapping() {
905 let id = inotify_init().unwrap();
906 let inode = 501;
907 let wd = inotify_add_watch(id, inode, "/tmp/rmclean", IN_MODIFY).unwrap();
908
909 inotify_rm_watch(id, wd).unwrap();
910
911 notify_event(inode, IN_MODIFY, 0, None);
913 assert_eq!(inotify_pending(id).unwrap(), 0);
914
915 inotify_close(id).unwrap();
916 }
917
918 #[test]
919 fn test_watch_limit_enforcement() {
920 let id = inotify_init_with_limits(1000, 3).unwrap();
921
922 inotify_add_watch(id, 601, "/a", IN_MODIFY).unwrap();
923 inotify_add_watch(id, 602, "/b", IN_MODIFY).unwrap();
924 inotify_add_watch(id, 603, "/c", IN_MODIFY).unwrap();
925
926 let result = inotify_add_watch(id, 604, "/d", IN_MODIFY);
928 assert!(result.is_err());
929
930 inotify_close(id).unwrap();
931 }
932
933 #[test]
934 fn test_get_stats() {
935 let id = inotify_init().unwrap();
936 let inode = 700;
937 inotify_add_watch(id, inode, "/tmp/stats", IN_MODIFY).unwrap();
938 notify_event(inode, IN_MODIFY, 0, None);
939
940 let stats = get_stats();
941 assert!(stats.instance_count >= 1);
942 assert!(stats.total_watches >= 1);
943 assert!(stats.total_pending_events >= 1);
944
945 inotify_close(id).unwrap();
946 }
947
948 #[test]
949 fn test_event_coalesceable() {
950 let e1 = InotifyEvent {
951 wd: 1,
952 mask: IN_MODIFY,
953 cookie: 0,
954 name: None,
955 };
956 let e2 = InotifyEvent {
957 wd: 1,
958 mask: IN_MODIFY,
959 cookie: 0,
960 name: None,
961 };
962 let e3 = InotifyEvent {
963 wd: 1,
964 mask: IN_CREATE,
965 cookie: 0,
966 name: None,
967 };
968 let e4 = InotifyEvent {
969 wd: 2,
970 mask: IN_MODIFY,
971 cookie: 0,
972 name: None,
973 };
974 let e5 = InotifyEvent {
975 wd: 1,
976 mask: IN_MODIFY,
977 cookie: 0,
978 name: Some(String::from("file.txt")),
979 };
980
981 assert!(e1.is_coalesceable_with(&e2));
982 assert!(!e1.is_coalesceable_with(&e3)); assert!(!e1.is_coalesceable_with(&e4)); assert!(!e1.is_coalesceable_with(&e5)); }
986}