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, spawn_kernel_and_run_with_pkgfs};
479 use starnix_core::vfs::buffers::VecOutputBuffer;
480 use starnix_core::vfs::{DirectoryMode, UnlinkKind};
481
482 #[::fuchsia::test]
483 fn inotify_event() {
484 let event = InotifyEvent::new(WdNumber::from_raw(1), InotifyMask::ACCESS, 0, "".into());
485 let mut buffer = VecOutputBuffer::new(DATA_SIZE + 100);
486 let bytes_written = event.write_to(&mut buffer).expect("write_to buffer");
487
488 assert_eq!(bytes_written, DATA_SIZE);
489 assert_eq!(buffer.bytes_written(), DATA_SIZE);
490 }
491
492 #[::fuchsia::test]
493 fn inotify_event_with_name() {
494 let name = "file1";
496 let event = InotifyEvent::new(WdNumber::from_raw(1), InotifyMask::ACCESS, 0, name.into());
497 let mut buffer = VecOutputBuffer::new(DATA_SIZE + 100);
498 let bytes_written = event.write_to(&mut buffer).expect("write_to buffer");
499
500 assert!(bytes_written > DATA_SIZE);
501 assert_eq!(bytes_written % DATA_SIZE, 0);
502 assert_eq!(buffer.bytes_written(), bytes_written);
503 }
504
505 #[::fuchsia::test]
506 fn inotify_event_queue() {
507 let mut event_queue = InotifyEventQueue::new_with_max(10);
508
509 event_queue.enqueue(InotifyEvent::new(
510 WdNumber::from_raw(1),
511 InotifyMask::ACCESS,
512 0,
513 "".into(),
514 ));
515
516 assert_eq!(event_queue.queue.len(), 1);
517 assert_eq!(event_queue.size_bytes, DATA_SIZE);
518
519 let event = event_queue.dequeue();
520
521 assert_eq!(
522 event,
523 Some(InotifyEvent::new(WdNumber::from_raw(1), InotifyMask::ACCESS, 0, "".into()))
524 );
525 assert_eq!(event_queue.queue.len(), 0);
526 assert_eq!(event_queue.size_bytes, 0);
527 }
528
529 #[::fuchsia::test]
530 fn inotify_event_queue_coalesce_events() {
531 let mut event_queue = InotifyEventQueue::new_with_max(10);
532
533 event_queue.enqueue(InotifyEvent::new(
535 WdNumber::from_raw(1),
536 InotifyMask::ACCESS,
537 0,
538 "".into(),
539 ));
540 event_queue.enqueue(InotifyEvent::new(
541 WdNumber::from_raw(1),
542 InotifyMask::ACCESS,
543 0,
544 "".into(),
545 ));
546
547 assert_eq!(event_queue.queue.len(), 1);
548 }
549
550 #[::fuchsia::test]
551 fn inotify_event_queue_max_queued_events() {
552 let mut event_queue = InotifyEventQueue::new_with_max(1);
553
554 event_queue.enqueue(InotifyEvent::new(
556 WdNumber::from_raw(1),
557 InotifyMask::ACCESS,
558 0,
559 "".into(),
560 ));
561 event_queue.enqueue(InotifyEvent::new(
562 WdNumber::from_raw(1),
563 InotifyMask::MODIFY,
564 0,
565 "".into(),
566 ));
567
568 assert_eq!(event_queue.queue.len(), 2);
569 assert_eq!(event_queue.queue.get(0).unwrap().mask, InotifyMask::ACCESS);
570 assert_eq!(event_queue.queue.get(1).unwrap().mask, InotifyMask::Q_OVERFLOW);
571
572 event_queue.enqueue(InotifyEvent::new(
574 WdNumber::from_raw(1),
575 InotifyMask::ATTRIB,
576 0,
577 "".into(),
578 ));
579 assert_eq!(event_queue.queue.len(), 2);
580 assert_eq!(event_queue.queue.get(0).unwrap().mask, InotifyMask::ACCESS);
581 assert_eq!(event_queue.queue.get(1).unwrap().mask, InotifyMask::Q_OVERFLOW);
582
583 let _event = event_queue.dequeue();
585 assert_eq!(event_queue.queue.len(), 1);
586
587 event_queue.enqueue(InotifyEvent::new(
590 WdNumber::from_raw(1),
591 InotifyMask::DELETE,
592 0,
593 "".into(),
594 ));
595 assert_eq!(event_queue.queue.len(), 1);
596 assert_eq!(event_queue.queue.get(0).unwrap().mask, InotifyMask::Q_OVERFLOW);
597 }
598
599 #[::fuchsia::test]
600 async fn notify_from_watchers() {
601 spawn_kernel_and_run_with_pkgfs(async |current_task| {
602 inotify_init(current_task.kernel());
603 let file = InotifyFileObject::new_file(¤t_task, true);
604 let inotify =
605 file.downcast_file::<InotifyFileObject>().expect("failed to downcast to inotify");
606
607 let root = current_task.fs().root().entry;
609 assert!(inotify.add_watch(root.clone(), InotifyMask::ALL_EVENTS, &file).is_ok());
610
611 {
612 let watchers = root.node.ensure_watchers().watchers.lock();
613 assert_eq!(watchers.len(), 1);
614 }
615
616 root.node.notify(InotifyMask::ACCESS, 0, Default::default(), FileMode::IFREG, false);
618
619 assert_eq!(inotify.available(), DATA_SIZE);
620 {
621 let state = inotify.state.lock();
622 assert_eq!(state.watches.len(), 1);
623 assert_eq!(state.events.queue.len(), 1);
624 }
625
626 root.node.notify(InotifyMask::ATTRIB, 0, Default::default(), FileMode::IFREG, false);
628
629 assert_eq!(inotify.available(), DATA_SIZE * 2);
630 {
631 let state = inotify.state.lock();
632 assert_eq!(state.events.queue.len(), 2);
633 }
634
635 let mut buffer = VecOutputBuffer::new(DATA_SIZE);
637 let bytes_read = file.read(¤t_task, &mut buffer).expect("read into buffer");
638
639 assert_eq!(bytes_read, DATA_SIZE);
640 assert_eq!(inotify.available(), DATA_SIZE);
641 {
642 let state = inotify.state.lock();
643 assert_eq!(state.events.queue.len(), 1);
644 }
645
646 buffer.reset();
648 let bytes_read = file.read(¤t_task, &mut buffer).expect("read into buffer");
649
650 assert_eq!(bytes_read, DATA_SIZE);
651 assert_eq!(inotify.available(), 0);
652 {
653 let state = inotify.state.lock();
654 assert_eq!(state.events.queue.len(), 0);
655 }
656 })
657 .await;
658 }
659
660 #[::fuchsia::test]
661 async fn notify_deletion_from_watchers() {
662 spawn_kernel_and_run_with_pkgfs(async |current_task| {
663 inotify_init(current_task.kernel());
664 let file = InotifyFileObject::new_file(¤t_task, true);
665 let inotify =
666 file.downcast_file::<InotifyFileObject>().expect("failed to downcast to inotify");
667
668 let root = current_task.fs().root().entry;
670 assert!(inotify.add_watch(root.clone(), InotifyMask::ALL_EVENTS, &file).is_ok());
671
672 {
673 let watchers = root.node.ensure_watchers().watchers.lock();
674 assert_eq!(watchers.len(), 1);
675 }
676
677 root.node.notify(
678 InotifyMask::DELETE_SELF,
679 0,
680 Default::default(),
681 FileMode::IFREG,
682 false,
683 );
684
685 {
686 let watchers = root.node.ensure_watchers().watchers.lock();
687 assert_eq!(watchers.len(), 0);
688 }
689
690 {
691 let state = inotify.state.lock();
692 assert_eq!(state.watches.len(), 0);
693 assert_eq!(state.events.queue.len(), 2);
694
695 assert_eq!(state.events.queue.get(0).unwrap().mask, InotifyMask::DELETE_SELF);
696 assert_eq!(state.events.queue.get(1).unwrap().mask, InotifyMask::IGNORED);
697 }
698 })
699 .await;
700 }
701
702 #[::fuchsia::test]
703 async fn notify_deletion_without_delete_self_mask() {
704 spawn_kernel_and_run_with_pkgfs(async |current_task| {
705 inotify_init(current_task.kernel());
706 let file = InotifyFileObject::new_file(¤t_task, true);
707 let inotify =
708 file.downcast_file::<InotifyFileObject>().expect("failed to downcast to inotify");
709
710 let root = current_task.fs().root().entry;
711 assert!(
713 inotify
714 .add_watch(
715 root.clone(),
716 InotifyMask::CREATE | InotifyMask::DELETE | InotifyMask::MODIFY,
717 &file
718 )
719 .is_ok()
720 );
721
722 root.node.notify(
723 InotifyMask::DELETE_SELF,
724 0,
725 Default::default(),
726 FileMode::IFDIR,
727 false,
728 );
729
730 {
731 let watchers = root.node.ensure_watchers().watchers.lock();
732 assert_eq!(watchers.len(), 0);
733 }
734
735 {
736 let state = inotify.state.lock();
737 assert_eq!(state.watches.len(), 0);
738 assert_eq!(state.events.queue.len(), 1);
739 assert_eq!(state.events.queue.get(0).unwrap().mask, InotifyMask::IGNORED);
740 }
741 })
742 .await;
743 }
744
745 #[::fuchsia::test]
746 async fn vfs_unlink_directory_emits_delete_self_and_ignored() {
747 spawn_kernel_and_run(async |current_task| {
749 inotify_init(current_task.kernel());
750 let file = InotifyFileObject::new_file(¤t_task, true);
751 let inotify =
752 file.downcast_file::<InotifyFileObject>().expect("failed to downcast to inotify");
753
754 let root = current_task.fs().root();
755 let subdir = root
756 .create_node(
757 ¤t_task,
758 "watched_dir".into(),
759 FileMode::IFDIR | FileMode::from_bits(0o755),
760 starnix_uapi::device_id::DeviceId::NONE,
761 )
762 .expect("create_node");
763
764 assert!(
765 inotify
766 .add_watch(
767 subdir.entry.clone(),
768 InotifyMask::CREATE | InotifyMask::DELETE_SELF,
769 &file
770 )
771 .is_ok()
772 );
773
774 root.unlink(
775 ¤t_task,
776 "watched_dir".into(),
777 UnlinkKind::Directory,
778 DirectoryMode::MustBeDirectory,
779 )
780 .expect("unlink dir");
781
782 assert_eq!(subdir.entry.node.ensure_watchers().watchers.lock().len(), 0);
783 let state = inotify.state.lock();
784 assert_eq!(state.watches.len(), 0);
785 assert_eq!(state.events.queue.len(), 2);
786 assert_eq!(state.events.queue.get(0).unwrap().mask, InotifyMask::DELETE_SELF);
787 assert_eq!(state.events.queue.get(1).unwrap().mask, InotifyMask::IGNORED);
788 })
789 .await;
790 }
791
792 #[::fuchsia::test]
793 async fn inotify_on_same_file() {
794 spawn_kernel_and_run_with_pkgfs(async |current_task| {
795 inotify_init(current_task.kernel());
796 let file = InotifyFileObject::new_file(¤t_task, true);
797 let file_key = WeakKey::from(&file);
798 let inotify =
799 file.downcast_file::<InotifyFileObject>().expect("failed to downcast to inotify");
800
801 let root = current_task.fs().root().entry;
803
804 assert!(
806 inotify
807 .add_watch(
808 root.clone(),
809 InotifyMask::MODIFY | InotifyMask::MASK_ADD | InotifyMask::MASK_CREATE,
810 &file
811 )
812 .is_err()
813 );
814
815 assert!(
816 inotify
817 .add_watch(root.clone(), InotifyMask::MODIFY | InotifyMask::MASK_CREATE, &file)
818 .is_ok()
819 );
820
821 {
822 let watchers = root.node.ensure_watchers().watchers.lock();
823 assert_eq!(watchers.len(), 1);
824 assert!(watchers.get(&file_key).unwrap().mask.contains(InotifyMask::MODIFY));
825 }
826
827 assert!(inotify.add_watch(root.clone(), InotifyMask::ACCESS, &file).is_ok());
829
830 {
831 let watchers = root.node.ensure_watchers().watchers.lock();
832 assert_eq!(watchers.len(), 1);
833 assert!(watchers.get(&file_key).unwrap().mask.contains(InotifyMask::ACCESS));
834 assert!(!watchers.get(&file_key).unwrap().mask.contains(InotifyMask::MODIFY));
835 }
836
837 assert!(
839 inotify
840 .add_watch(root.clone(), InotifyMask::MODIFY | InotifyMask::MASK_ADD, &file)
841 .is_ok()
842 );
843
844 {
845 let watchers = root.node.ensure_watchers().watchers.lock();
846 assert_eq!(watchers.len(), 1);
847 assert!(watchers.get(&file_key).unwrap().mask.contains(InotifyMask::ACCESS));
848 assert!(watchers.get(&file_key).unwrap().mask.contains(InotifyMask::MODIFY));
849 }
850 })
851 .await;
852 }
853
854 #[::fuchsia::test]
855 async fn coalesce_events() {
856 spawn_kernel_and_run_with_pkgfs(async |current_task| {
857 inotify_init(current_task.kernel());
858 let file = InotifyFileObject::new_file(¤t_task, true);
859 let inotify =
860 file.downcast_file::<InotifyFileObject>().expect("failed to downcast to inotify");
861
862 let root = current_task.fs().root().entry;
864 assert!(inotify.add_watch(root.clone(), InotifyMask::ALL_EVENTS, &file).is_ok());
865
866 {
867 let watchers = root.node.ensure_watchers().watchers.lock();
868 assert_eq!(watchers.len(), 1);
869 }
870
871 root.node.notify(InotifyMask::ACCESS, 0, Default::default(), FileMode::IFREG, false);
873 root.node.notify(InotifyMask::ACCESS, 0, Default::default(), FileMode::IFREG, false);
874
875 assert_eq!(inotify.available(), DATA_SIZE);
876 {
877 let state = inotify.state.lock();
878 assert_eq!(state.watches.len(), 1);
879 assert_eq!(state.events.queue.len(), 1);
880 }
881 })
882 .await;
883 }
884}