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