1use starnix_core::mm::MemoryAccessorExt;
6use starnix_core::task::{CurrentTask, EventHandler, Kernel, WaitCanceler, WaitQueue, Waiter};
7use starnix_core::vfs::buffers::{InputBuffer, OutputBuffer};
8use starnix_core::vfs::{
9 Anon, DirEntryHandle, FileHandle, FileObject, FileObjectState, FileOps, FsStr, FsString,
10 WdNumber, fileops_impl_nonseekable, fileops_impl_noop_sync,
11};
12use starnix_sync::{InotifyStateLock, LockDepMutex};
13use starnix_syscalls::{SUCCESS, SyscallArg, SyscallResult};
14use starnix_uapi::arc_key::WeakKey;
15use starnix_uapi::errors::Errno;
16use starnix_uapi::file_mode::FileMode;
17use starnix_uapi::inotify_mask::InotifyMask;
18use starnix_uapi::math::round_up_to_increment;
19use starnix_uapi::open_flags::OpenFlags;
20use starnix_uapi::user_address::{UserAddress, UserRef};
21use starnix_uapi::vfs::FdEvents;
22use starnix_uapi::{FIONREAD, errno, error, inotify_event};
23use std::collections::{HashMap, VecDeque};
24use std::mem::size_of;
25use std::sync::Arc;
26use std::sync::atomic::Ordering;
27use zerocopy::IntoBytes;
28
29const DATA_SIZE: usize = size_of::<inotify_event>();
30
31pub struct InotifyFileObject {
33 state: LockDepMutex<InotifyState, InotifyStateLock>,
34}
35
36struct InotifyState {
37 events: InotifyEventQueue,
38
39 watches: HashMap<WdNumber, DirEntryHandle>,
40
41 last_watch_id: i32,
43}
44
45#[derive(Default)]
46struct InotifyEventQueue {
47 queue: VecDeque<InotifyEvent>,
50
51 waiters: WaitQueue,
53
54 size_bytes: usize,
56
57 max_queued_events: usize,
60}
61
62#[derive(Debug, PartialEq, Eq)]
64struct InotifyEvent {
65 watch_id: WdNumber,
66
67 mask: InotifyMask,
68
69 cookie: u32,
70
71 name: FsString,
72}
73
74impl InotifyState {
75 fn next_watch_id(&mut self) -> WdNumber {
76 self.last_watch_id += 1;
77 WdNumber::from_raw(self.last_watch_id)
78 }
79}
80
81impl InotifyFileObject {
82 pub fn new_file(current_task: &CurrentTask, non_blocking: bool) -> FileHandle {
84 let flags =
85 OpenFlags::RDONLY | if non_blocking { OpenFlags::NONBLOCK } else { OpenFlags::empty() };
86 let max_queued_events =
87 current_task.kernel().system_limits.inotify.max_queued_events.load(Ordering::Relaxed);
88 assert!(max_queued_events >= 0);
89 Anon::new_private_file(
90 current_task,
91 Box::new(InotifyFileObject {
92 state: InotifyState {
93 events: InotifyEventQueue::new_with_max(max_queued_events as usize),
94 watches: Default::default(),
95 last_watch_id: 0,
96 }
97 .into(),
98 }),
99 flags,
100 "inotify",
101 )
102 }
103
104 pub fn add_watch(
109 &self,
110 dir_entry: DirEntryHandle,
111 mask: InotifyMask,
112 inotify_file: &FileHandle,
113 ) -> Result<WdNumber, Errno> {
114 let weak_key = WeakKey::from(inotify_file);
115 if let Some(watch_id) = dir_entry.node.ensure_watchers().maybe_update(mask, &weak_key)? {
116 return Ok(watch_id);
117 }
118
119 let watch_id;
120 {
121 let mut state = self.state.lock();
122 watch_id = state.next_watch_id();
123 state.watches.insert(watch_id, dir_entry.clone());
124 }
125 dir_entry.node.ensure_watchers().add(mask, watch_id, weak_key);
126 Ok(watch_id)
127 }
128
129 pub fn remove_watch(&self, watch_id: WdNumber, file: &FileHandle) -> Result<(), Errno> {
133 let dir_entry;
134 {
135 let mut state = self.state.lock();
136 dir_entry = state.watches.remove(&watch_id).ok_or_else(|| errno!(EINVAL))?;
137 state.events.enqueue(InotifyEvent::new(
138 watch_id,
139 InotifyMask::IGNORED,
140 0,
141 FsString::default(),
142 ));
143 }
144 dir_entry.node.ensure_watchers().remove(&WeakKey::from(file));
145 Ok(())
146 }
147
148 fn notify(
149 &self,
150 watch_id: WdNumber,
151 event_mask: Option<InotifyMask>,
152 cookie: u32,
153 name: &FsStr,
154 remove_watcher_after_notify: bool,
155 ) {
156 #[allow(clippy::collection_is_never_read)]
158 let _dir_entry: Option<DirEntryHandle>;
159 {
160 let mut state = self.state.lock();
161 if let Some(event_mask) = event_mask {
162 state.events.enqueue(InotifyEvent::new(
163 watch_id,
164 event_mask,
165 cookie,
166 name.to_owned(),
167 ));
168 }
169 if remove_watcher_after_notify {
170 _dir_entry = state.watches.remove(&watch_id);
171 state.events.enqueue(InotifyEvent::new(
172 watch_id,
173 InotifyMask::IGNORED,
174 0,
175 FsString::default(),
176 ));
177 }
178 }
179 }
180
181 fn available(&self) -> usize {
182 let state = self.state.lock();
183 state.events.size_bytes
184 }
185}
186
187impl FileOps for InotifyFileObject {
188 fileops_impl_nonseekable!();
189 fileops_impl_noop_sync!();
190
191 fn write(
192 &self,
193 _file: &FileObject,
194 _current_task: &CurrentTask,
195 offset: usize,
196 _data: &mut dyn InputBuffer,
197 ) -> Result<usize, Errno> {
198 debug_assert!(offset == 0);
199 error!(EINVAL)
200 }
201
202 fn read(
203 &self,
204 file: &FileObject,
205 current_task: &CurrentTask,
206 offset: usize,
207 data: &mut dyn OutputBuffer,
208 ) -> Result<usize, Errno> {
209 debug_assert!(offset == 0);
210 file.blocking_op(current_task, FdEvents::POLLIN | FdEvents::POLLHUP, None, || {
211 let mut state = self.state.lock();
212 if let Some(front) = state.events.front() {
213 if data.available() < front.size() {
214 return error!(EINVAL);
215 }
216 } else {
217 return error!(EAGAIN);
218 }
219
220 let mut bytes_read: usize = 0;
221 while let Some(front) = state.events.front() {
222 if data.available() < front.size() {
223 break;
224 }
225 bytes_read += state.events.dequeue().unwrap().write_to(data)?;
228 }
229 Ok(bytes_read)
230 })
231 }
232
233 fn ioctl(
234 &self,
235 _file: &FileObject,
236 current_task: &CurrentTask,
237 request: u32,
238 arg: SyscallArg,
239 ) -> Result<SyscallResult, Errno> {
240 let user_addr = UserAddress::from(arg);
241 match request {
242 FIONREAD => {
243 let addr = UserRef::<i32>::new(user_addr);
244 let size = i32::try_from(self.available()).unwrap_or(i32::MAX);
245 current_task.write_object(addr, &size).map(|_| SUCCESS)
246 }
247 _ => error!(ENOTTY),
248 }
249 }
250
251 fn wait_async(
252 &self,
253 _file: &FileObject,
254 _current_task: &CurrentTask,
255 waiter: &Waiter,
256 events: FdEvents,
257 handler: EventHandler,
258 ) -> Option<WaitCanceler> {
259 Some(self.state.lock().events.waiters.wait_async_fd_events(waiter, events, handler))
260 }
261
262 fn query_events(
263 &self,
264 _file: &FileObject,
265 _current_task: &CurrentTask,
266 ) -> Result<FdEvents, Errno> {
267 if self.available() > 0 { Ok(FdEvents::POLLIN) } else { Ok(FdEvents::empty()) }
268 }
269
270 fn close(self: Box<Self>, file: &FileObjectState, _current_task: &CurrentTask) {
271 let dir_entries = {
272 let mut state = self.state.lock();
273 state.watches.drain().map(|(_key, value)| value).collect::<Vec<_>>()
274 };
275
276 for dir_entry in dir_entries {
277 dir_entry.node.ensure_watchers().remove_by_ref(&file.weak_handle);
278 }
279 }
280
281 fn extra_fdinfo(&self, file: &FileHandle, _current_task: &CurrentTask) -> Option<FsString> {
282 let state = self.state.lock();
283 let mut info = String::new();
284 for dir_entry in state.watches.values() {
285 let ino = dir_entry.node.ino;
286 let sdev = dir_entry.node.fs().dev_id;
287 if let Some(watcher) = dir_entry.node.ensure_watchers().get(&WeakKey::from(file)) {
288 let wd = watcher.watch_id;
289 let mask = watcher.mask;
290 info.push_str(&format!(
291 "inotify wd:{} ino:{:x} sdev:{:x} mask:{:x}\n",
292 wd.raw(),
293 ino,
294 sdev.bits(),
295 mask.bits()
296 ));
297 }
298 }
299 Some(info.into())
300 }
301}
302
303impl InotifyEventQueue {
304 fn new_with_max(max_queued_events: usize) -> Self {
305 InotifyEventQueue {
306 queue: Default::default(),
307 waiters: Default::default(),
308 size_bytes: 0,
309 max_queued_events,
310 }
311 }
312
313 fn enqueue(&mut self, mut event: InotifyEvent) {
314 if self.queue.len() > self.max_queued_events {
315 return;
316 }
317 if self.queue.len() == self.max_queued_events {
318 event = InotifyEvent::new(
320 WdNumber::from_raw(-1),
321 InotifyMask::Q_OVERFLOW,
322 0,
323 FsString::default(),
324 );
325 }
326 if Some(&event) == self.queue.back() {
327 return;
333 }
334 self.size_bytes += event.size();
335 self.queue.push_back(event);
336 self.waiters.notify_fd_events(FdEvents::POLLIN);
337 }
338
339 fn front(&self) -> Option<&InotifyEvent> {
340 self.queue.front()
341 }
342
343 fn dequeue(&mut self) -> Option<InotifyEvent> {
344 let maybe_event = self.queue.pop_front();
345 if let Some(event) = maybe_event.as_ref() {
346 self.size_bytes -= event.size();
347 }
348 maybe_event
349 }
350}
351
352impl InotifyEvent {
353 fn new(watch_id: WdNumber, mask: InotifyMask, cookie: u32, mut name: FsString) -> Self {
355 if !name.is_empty() {
356 let len = round_up_to_increment(name.len() + 1, DATA_SIZE)
357 .expect("padded name should not overflow");
358 name.resize(len, 0);
359 }
360 InotifyEvent { watch_id, mask, cookie, name }
361 }
362
363 fn size(&self) -> usize {
364 DATA_SIZE + self.name.len()
365 }
366
367 fn write_to(&self, data: &mut dyn OutputBuffer) -> Result<usize, Errno> {
368 let event = inotify_event {
369 wd: self.watch_id.raw(),
370 mask: self.mask.bits(),
371 cookie: self.cookie,
372 len: self.name.len().try_into().map_err(|_| errno!(EINVAL))?,
373 name: Default::default(),
375 };
376
377 let mut bytes_written = data.write(event.as_bytes())?;
378 if !self.name.is_empty() {
379 bytes_written += data.write(self.name.as_bytes())?;
380 }
381
382 debug_assert!(bytes_written == self.size());
383 Ok(bytes_written)
384 }
385}
386
387struct InotifyImpl {
388 next_cookie: std::sync::atomic::AtomicU32,
389}
390
391impl starnix_core::vfs::inotify_hook::NotifyHook for InotifyImpl {
392 fn notify(
393 &self,
394 watchers: &starnix_core::vfs::inotify_hook::InotifyWatchers,
395 mut event_mask: InotifyMask,
396 cookie: u32,
397 name: &FsStr,
398 mode: FileMode,
399 is_dead: bool,
400 ) {
401 if cookie != 0 {
402 debug_assert!(
405 event_mask.contains(InotifyMask::MOVE_FROM)
406 || event_mask.contains(InotifyMask::MOVE_TO)
407 );
408 }
409 struct InotifyWatch {
411 watch_id: WdNumber,
412 file: FileHandle,
413 should_send_event: bool,
414 should_remove: bool,
415 }
416 let mut watches: Vec<InotifyWatch> = vec![];
417 {
418 let mut watchers = watchers.watchers.lock();
419 watchers.retain(|inotify, watcher| {
420 let mut should_remove = event_mask.contains(InotifyMask::DELETE_SELF);
421 let should_send_event = watcher.mask.contains(event_mask)
422 && !(is_dead && watcher.mask.contains(InotifyMask::EXCL_UNLINK));
423 if should_send_event {
424 should_remove = should_remove || watcher.mask.contains(InotifyMask::ONESHOT);
425 }
426 if should_send_event || should_remove {
427 if let Some(file) = inotify.0.upgrade() {
428 watches.push(InotifyWatch {
429 watch_id: watcher.watch_id,
430 file,
431 should_send_event,
432 should_remove,
433 });
434 } else {
435 should_remove = true;
436 }
437 }
438 !should_remove
439 });
440 }
441
442 if mode.is_dir() {
443 if event_mask != InotifyMask::DELETE_SELF && event_mask != InotifyMask::MOVE_SELF {
445 event_mask |= InotifyMask::ISDIR;
446 }
447 }
448
449 for watch in watches {
450 let inotify = watch
451 .file
452 .downcast_file::<InotifyFileObject>()
453 .expect("failed to downcast to inotify");
454 let mask = watch.should_send_event.then_some(event_mask);
455 inotify.notify(watch.watch_id, mask, cookie, name, watch.should_remove);
456 }
457 }
458
459 fn get_next_cookie(&self) -> u32 {
460 let mut cookie = self.next_cookie.fetch_add(1, Ordering::Relaxed);
461 while cookie == 0 {
462 cookie = self.next_cookie.fetch_add(1, Ordering::Relaxed);
463 }
464 cookie
465 }
466}
467
468pub fn inotify_init(kernel: &Kernel) {
469 kernel.expando.get_or_init(|| {
470 Arc::new(InotifyImpl { next_cookie: std::sync::atomic::AtomicU32::new(1) })
471 as Arc<dyn starnix_core::vfs::inotify_hook::NotifyHook>
472 });
473}
474
475#[cfg(test)]
476mod tests {
477 use super::*;
478 use starnix_core::testing::spawn_kernel_and_run_with_pkgfs;
479 use starnix_core::vfs::buffers::VecOutputBuffer;
480
481 #[::fuchsia::test]
482 fn inotify_event() {
483 let event = InotifyEvent::new(WdNumber::from_raw(1), InotifyMask::ACCESS, 0, "".into());
484 let mut buffer = VecOutputBuffer::new(DATA_SIZE + 100);
485 let bytes_written = event.write_to(&mut buffer).expect("write_to buffer");
486
487 assert_eq!(bytes_written, DATA_SIZE);
488 assert_eq!(buffer.bytes_written(), DATA_SIZE);
489 }
490
491 #[::fuchsia::test]
492 fn inotify_event_with_name() {
493 let name = "file1";
495 let event = InotifyEvent::new(WdNumber::from_raw(1), InotifyMask::ACCESS, 0, name.into());
496 let mut buffer = VecOutputBuffer::new(DATA_SIZE + 100);
497 let bytes_written = event.write_to(&mut buffer).expect("write_to buffer");
498
499 assert!(bytes_written > DATA_SIZE);
500 assert_eq!(bytes_written % DATA_SIZE, 0);
501 assert_eq!(buffer.bytes_written(), bytes_written);
502 }
503
504 #[::fuchsia::test]
505 fn inotify_event_queue() {
506 let mut event_queue = InotifyEventQueue::new_with_max(10);
507
508 event_queue.enqueue(InotifyEvent::new(
509 WdNumber::from_raw(1),
510 InotifyMask::ACCESS,
511 0,
512 "".into(),
513 ));
514
515 assert_eq!(event_queue.queue.len(), 1);
516 assert_eq!(event_queue.size_bytes, DATA_SIZE);
517
518 let event = event_queue.dequeue();
519
520 assert_eq!(
521 event,
522 Some(InotifyEvent::new(WdNumber::from_raw(1), InotifyMask::ACCESS, 0, "".into()))
523 );
524 assert_eq!(event_queue.queue.len(), 0);
525 assert_eq!(event_queue.size_bytes, 0);
526 }
527
528 #[::fuchsia::test]
529 fn inotify_event_queue_coalesce_events() {
530 let mut event_queue = InotifyEventQueue::new_with_max(10);
531
532 event_queue.enqueue(InotifyEvent::new(
534 WdNumber::from_raw(1),
535 InotifyMask::ACCESS,
536 0,
537 "".into(),
538 ));
539 event_queue.enqueue(InotifyEvent::new(
540 WdNumber::from_raw(1),
541 InotifyMask::ACCESS,
542 0,
543 "".into(),
544 ));
545
546 assert_eq!(event_queue.queue.len(), 1);
547 }
548
549 #[::fuchsia::test]
550 fn inotify_event_queue_max_queued_events() {
551 let mut event_queue = InotifyEventQueue::new_with_max(1);
552
553 event_queue.enqueue(InotifyEvent::new(
555 WdNumber::from_raw(1),
556 InotifyMask::ACCESS,
557 0,
558 "".into(),
559 ));
560 event_queue.enqueue(InotifyEvent::new(
561 WdNumber::from_raw(1),
562 InotifyMask::MODIFY,
563 0,
564 "".into(),
565 ));
566
567 assert_eq!(event_queue.queue.len(), 2);
568 assert_eq!(event_queue.queue.get(0).unwrap().mask, InotifyMask::ACCESS);
569 assert_eq!(event_queue.queue.get(1).unwrap().mask, InotifyMask::Q_OVERFLOW);
570
571 event_queue.enqueue(InotifyEvent::new(
573 WdNumber::from_raw(1),
574 InotifyMask::ATTRIB,
575 0,
576 "".into(),
577 ));
578 assert_eq!(event_queue.queue.len(), 2);
579 assert_eq!(event_queue.queue.get(0).unwrap().mask, InotifyMask::ACCESS);
580 assert_eq!(event_queue.queue.get(1).unwrap().mask, InotifyMask::Q_OVERFLOW);
581
582 let _event = event_queue.dequeue();
584 assert_eq!(event_queue.queue.len(), 1);
585
586 event_queue.enqueue(InotifyEvent::new(
589 WdNumber::from_raw(1),
590 InotifyMask::DELETE,
591 0,
592 "".into(),
593 ));
594 assert_eq!(event_queue.queue.len(), 1);
595 assert_eq!(event_queue.queue.get(0).unwrap().mask, InotifyMask::Q_OVERFLOW);
596 }
597
598 #[::fuchsia::test]
599 async fn notify_from_watchers() {
600 spawn_kernel_and_run_with_pkgfs(async |current_task| {
601 inotify_init(current_task.kernel());
602 let file = InotifyFileObject::new_file(¤t_task, true);
603 let inotify =
604 file.downcast_file::<InotifyFileObject>().expect("failed to downcast to inotify");
605
606 let root = current_task.fs().root().entry;
608 assert!(inotify.add_watch(root.clone(), InotifyMask::ALL_EVENTS, &file).is_ok());
609
610 {
611 let watchers = root.node.ensure_watchers().watchers.lock();
612 assert_eq!(watchers.len(), 1);
613 }
614
615 root.node.notify(InotifyMask::ACCESS, 0, Default::default(), FileMode::IFREG, false);
617
618 assert_eq!(inotify.available(), DATA_SIZE);
619 {
620 let state = inotify.state.lock();
621 assert_eq!(state.watches.len(), 1);
622 assert_eq!(state.events.queue.len(), 1);
623 }
624
625 root.node.notify(InotifyMask::ATTRIB, 0, Default::default(), FileMode::IFREG, false);
627
628 assert_eq!(inotify.available(), DATA_SIZE * 2);
629 {
630 let state = inotify.state.lock();
631 assert_eq!(state.events.queue.len(), 2);
632 }
633
634 let mut buffer = VecOutputBuffer::new(DATA_SIZE);
636 let bytes_read = file.read(¤t_task, &mut buffer).expect("read into buffer");
637
638 assert_eq!(bytes_read, DATA_SIZE);
639 assert_eq!(inotify.available(), DATA_SIZE);
640 {
641 let state = inotify.state.lock();
642 assert_eq!(state.events.queue.len(), 1);
643 }
644
645 buffer.reset();
647 let bytes_read = file.read(¤t_task, &mut buffer).expect("read into buffer");
648
649 assert_eq!(bytes_read, DATA_SIZE);
650 assert_eq!(inotify.available(), 0);
651 {
652 let state = inotify.state.lock();
653 assert_eq!(state.events.queue.len(), 0);
654 }
655 })
656 .await;
657 }
658
659 #[::fuchsia::test]
660 async fn notify_deletion_from_watchers() {
661 spawn_kernel_and_run_with_pkgfs(async |current_task| {
662 inotify_init(current_task.kernel());
663 let file = InotifyFileObject::new_file(¤t_task, true);
664 let inotify =
665 file.downcast_file::<InotifyFileObject>().expect("failed to downcast to inotify");
666
667 let root = current_task.fs().root().entry;
669 assert!(inotify.add_watch(root.clone(), InotifyMask::ALL_EVENTS, &file).is_ok());
670
671 {
672 let watchers = root.node.ensure_watchers().watchers.lock();
673 assert_eq!(watchers.len(), 1);
674 }
675
676 root.node.notify(
677 InotifyMask::DELETE_SELF,
678 0,
679 Default::default(),
680 FileMode::IFREG,
681 false,
682 );
683
684 {
685 let watchers = root.node.ensure_watchers().watchers.lock();
686 assert_eq!(watchers.len(), 0);
687 }
688
689 {
690 let state = inotify.state.lock();
691 assert_eq!(state.watches.len(), 0);
692 assert_eq!(state.events.queue.len(), 2);
693
694 assert_eq!(state.events.queue.get(0).unwrap().mask, InotifyMask::DELETE_SELF);
695 assert_eq!(state.events.queue.get(1).unwrap().mask, InotifyMask::IGNORED);
696 }
697 })
698 .await;
699 }
700
701 #[::fuchsia::test]
702 async fn notify_deletion_without_delete_self_mask() {
703 spawn_kernel_and_run_with_pkgfs(async |current_task| {
704 inotify_init(current_task.kernel());
705 let file = InotifyFileObject::new_file(¤t_task, true);
706 let inotify =
707 file.downcast_file::<InotifyFileObject>().expect("failed to downcast to inotify");
708
709 let root = current_task.fs().root().entry;
710 assert!(
712 inotify
713 .add_watch(
714 root.clone(),
715 InotifyMask::CREATE | InotifyMask::DELETE | InotifyMask::MODIFY,
716 &file
717 )
718 .is_ok()
719 );
720
721 root.node.notify(
722 InotifyMask::DELETE_SELF,
723 0,
724 Default::default(),
725 FileMode::IFDIR,
726 false,
727 );
728
729 {
730 let watchers = root.node.ensure_watchers().watchers.lock();
731 assert_eq!(watchers.len(), 0);
732 }
733
734 {
735 let state = inotify.state.lock();
736 assert_eq!(state.watches.len(), 0);
737 assert_eq!(state.events.queue.len(), 1);
738 assert_eq!(state.events.queue.get(0).unwrap().mask, InotifyMask::IGNORED);
739 }
740 })
741 .await;
742 }
743
744 #[::fuchsia::test]
745 async fn inotify_on_same_file() {
746 spawn_kernel_and_run_with_pkgfs(async |current_task| {
747 inotify_init(current_task.kernel());
748 let file = InotifyFileObject::new_file(¤t_task, true);
749 let file_key = WeakKey::from(&file);
750 let inotify =
751 file.downcast_file::<InotifyFileObject>().expect("failed to downcast to inotify");
752
753 let root = current_task.fs().root().entry;
755
756 assert!(
758 inotify
759 .add_watch(
760 root.clone(),
761 InotifyMask::MODIFY | InotifyMask::MASK_ADD | InotifyMask::MASK_CREATE,
762 &file
763 )
764 .is_err()
765 );
766
767 assert!(
768 inotify
769 .add_watch(root.clone(), InotifyMask::MODIFY | InotifyMask::MASK_CREATE, &file)
770 .is_ok()
771 );
772
773 {
774 let watchers = root.node.ensure_watchers().watchers.lock();
775 assert_eq!(watchers.len(), 1);
776 assert!(watchers.get(&file_key).unwrap().mask.contains(InotifyMask::MODIFY));
777 }
778
779 assert!(inotify.add_watch(root.clone(), InotifyMask::ACCESS, &file).is_ok());
781
782 {
783 let watchers = root.node.ensure_watchers().watchers.lock();
784 assert_eq!(watchers.len(), 1);
785 assert!(watchers.get(&file_key).unwrap().mask.contains(InotifyMask::ACCESS));
786 assert!(!watchers.get(&file_key).unwrap().mask.contains(InotifyMask::MODIFY));
787 }
788
789 assert!(
791 inotify
792 .add_watch(root.clone(), InotifyMask::MODIFY | InotifyMask::MASK_ADD, &file)
793 .is_ok()
794 );
795
796 {
797 let watchers = root.node.ensure_watchers().watchers.lock();
798 assert_eq!(watchers.len(), 1);
799 assert!(watchers.get(&file_key).unwrap().mask.contains(InotifyMask::ACCESS));
800 assert!(watchers.get(&file_key).unwrap().mask.contains(InotifyMask::MODIFY));
801 }
802 })
803 .await;
804 }
805
806 #[::fuchsia::test]
807 async fn coalesce_events() {
808 spawn_kernel_and_run_with_pkgfs(async |current_task| {
809 inotify_init(current_task.kernel());
810 let file = InotifyFileObject::new_file(¤t_task, true);
811 let inotify =
812 file.downcast_file::<InotifyFileObject>().expect("failed to downcast to inotify");
813
814 let root = current_task.fs().root().entry;
816 assert!(inotify.add_watch(root.clone(), InotifyMask::ALL_EVENTS, &file).is_ok());
817
818 {
819 let watchers = root.node.ensure_watchers().watchers.lock();
820 assert_eq!(watchers.len(), 1);
821 }
822
823 root.node.notify(InotifyMask::ACCESS, 0, Default::default(), FileMode::IFREG, false);
825 root.node.notify(InotifyMask::ACCESS, 0, Default::default(), FileMode::IFREG, false);
826
827 assert_eq!(inotify.available(), DATA_SIZE);
828 {
829 let state = inotify.state.lock();
830 assert_eq!(state.watches.len(), 1);
831 assert_eq!(state.events.queue.len(), 1);
832 }
833 })
834 .await;
835 }
836}