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