1use crate::mm::MemoryAccessorExt;
6use crate::security;
7use crate::task::{CurrentTask, EventHandler, Kernel, WaitCanceler, WaitQueue, Waiter};
8use crate::vfs::buffers::{InputBuffer, OutputBuffer};
9use crate::vfs::pseudo::simple_file::{BytesFile, BytesFileOps};
10use crate::vfs::{
11 Anon, DirEntryHandle, FileHandle, FileHandleKey, FileObject, FileObjectState, FileOps,
12 FsNodeOps, FsStr, FsString, WdNumber, WeakFileHandle, default_ioctl, fileops_impl_nonseekable,
13 fileops_impl_noop_sync, fs_args, inotify,
14};
15use starnix_sync::{FileOpsCore, LockEqualOrBefore, Locked, Mutex, Unlocked};
16use starnix_syscalls::{SUCCESS, SyscallArg, SyscallResult};
17use starnix_uapi::arc_key::WeakKey;
18use starnix_uapi::auth::CAP_SYS_ADMIN;
19use starnix_uapi::errors::Errno;
20use starnix_uapi::file_mode::FileMode;
21use starnix_uapi::inotify_mask::InotifyMask;
22use starnix_uapi::math::round_up_to_increment;
23use starnix_uapi::open_flags::OpenFlags;
24use starnix_uapi::user_address::{UserAddress, UserRef};
25use starnix_uapi::vfs::FdEvents;
26use starnix_uapi::{FIONREAD, errno, error, inotify_event};
27use std::borrow::Cow;
28use std::collections::{BTreeMap, HashMap, VecDeque};
29use std::mem::size_of;
30use std::sync::atomic::{AtomicI32, Ordering};
31use zerocopy::IntoBytes;
32
33const DATA_SIZE: usize = size_of::<inotify_event>();
34
35pub struct InotifyFileObject {
37 state: Mutex<InotifyState>,
38}
39
40struct InotifyState {
41 events: InotifyEventQueue,
42
43 watches: HashMap<WdNumber, DirEntryHandle>,
44
45 last_watch_id: i32,
47}
48
49#[derive(Clone)]
51pub struct InotifyWatcher {
52 pub watch_id: WdNumber,
53
54 pub mask: InotifyMask,
55}
56
57#[derive(Default)]
58pub struct InotifyWatchers {
59 watchers: Mutex<BTreeMap<FileHandleKey, InotifyWatcher>>,
60}
61
62#[derive(Default)]
63struct InotifyEventQueue {
64 queue: VecDeque<InotifyEvent>,
67
68 waiters: WaitQueue,
70
71 size_bytes: usize,
73
74 max_queued_events: usize,
77}
78
79#[derive(Debug, PartialEq, Eq)]
81struct InotifyEvent {
82 watch_id: WdNumber,
83
84 mask: InotifyMask,
85
86 cookie: u32,
87
88 name: FsString,
89}
90
91impl InotifyState {
92 fn next_watch_id(&mut self) -> WdNumber {
93 self.last_watch_id += 1;
94 WdNumber::from_raw(self.last_watch_id)
95 }
96}
97
98impl InotifyFileObject {
99 pub fn new_file<L>(
101 locked: &mut Locked<L>,
102 current_task: &CurrentTask,
103 non_blocking: bool,
104 ) -> FileHandle
105 where
106 L: LockEqualOrBefore<FileOpsCore>,
107 {
108 let flags =
109 OpenFlags::RDONLY | if non_blocking { OpenFlags::NONBLOCK } else { OpenFlags::empty() };
110 let max_queued_events =
111 current_task.kernel().system_limits.inotify.max_queued_events.load(Ordering::Relaxed);
112 assert!(max_queued_events >= 0);
113 Anon::new_private_file(
114 locked,
115 current_task,
116 Box::new(InotifyFileObject {
117 state: InotifyState {
118 events: InotifyEventQueue::new_with_max(max_queued_events as usize),
119 watches: Default::default(),
120 last_watch_id: 0,
121 }
122 .into(),
123 }),
124 flags,
125 "inotify",
126 )
127 }
128
129 pub fn add_watch(
134 &self,
135 dir_entry: DirEntryHandle,
136 mask: InotifyMask,
137 inotify_file: &FileHandle,
138 ) -> Result<WdNumber, Errno> {
139 let weak_key = WeakKey::from(inotify_file);
140 if let Some(watch_id) = dir_entry.node.ensure_watchers().maybe_update(mask, &weak_key)? {
141 return Ok(watch_id);
142 }
143
144 let watch_id;
145 {
146 let mut state = self.state.lock();
147 watch_id = state.next_watch_id();
148 state.watches.insert(watch_id, dir_entry.clone());
149 }
150 dir_entry.node.ensure_watchers().add(mask, watch_id, weak_key);
151 Ok(watch_id)
152 }
153
154 pub fn remove_watch(&self, watch_id: WdNumber, file: &FileHandle) -> Result<(), Errno> {
158 let dir_entry;
159 {
160 let mut state = self.state.lock();
161 dir_entry = state.watches.remove(&watch_id).ok_or_else(|| errno!(EINVAL))?;
162 state.events.enqueue(InotifyEvent::new(
163 watch_id,
164 InotifyMask::IGNORED,
165 0,
166 FsString::default(),
167 ));
168 }
169 dir_entry.node.ensure_watchers().remove(&WeakKey::from(file));
170 Ok(())
171 }
172
173 fn notify(
174 &self,
175 watch_id: WdNumber,
176 event_mask: InotifyMask,
177 cookie: u32,
178 name: &FsStr,
179 remove_watcher_after_notify: bool,
180 ) {
181 #[allow(clippy::collection_is_never_read)]
183 let _dir_entry: Option<DirEntryHandle>;
184 {
185 let mut state = self.state.lock();
186 state.events.enqueue(InotifyEvent::new(watch_id, event_mask, cookie, name.to_owned()));
187 if remove_watcher_after_notify {
188 _dir_entry = state.watches.remove(&watch_id);
189 state.events.enqueue(InotifyEvent::new(
190 watch_id,
191 InotifyMask::IGNORED,
192 0,
193 FsString::default(),
194 ));
195 }
196 }
197 }
198
199 fn available(&self) -> usize {
200 let state = self.state.lock();
201 state.events.size_bytes
202 }
203}
204
205impl FileOps for InotifyFileObject {
206 fileops_impl_nonseekable!();
207 fileops_impl_noop_sync!();
208
209 fn write(
210 &self,
211 _locked: &mut Locked<FileOpsCore>,
212 _file: &FileObject,
213 _current_task: &CurrentTask,
214 offset: usize,
215 _data: &mut dyn InputBuffer,
216 ) -> Result<usize, Errno> {
217 debug_assert!(offset == 0);
218 error!(EINVAL)
219 }
220
221 fn read(
222 &self,
223 locked: &mut Locked<FileOpsCore>,
224 file: &FileObject,
225 current_task: &CurrentTask,
226 offset: usize,
227 data: &mut dyn OutputBuffer,
228 ) -> Result<usize, Errno> {
229 debug_assert!(offset == 0);
230
231 file.blocking_op(locked, current_task, FdEvents::POLLIN | FdEvents::POLLHUP, None, |_| {
232 let mut state = self.state.lock();
233 if let Some(front) = state.events.front() {
234 if data.available() < front.size() {
235 return error!(EINVAL);
236 }
237 } else {
238 return error!(EAGAIN);
239 }
240
241 let mut bytes_read: usize = 0;
242 while let Some(front) = state.events.front() {
243 if data.available() < front.size() {
244 break;
245 }
246 bytes_read += state.events.dequeue().unwrap().write_to(data)?;
249 }
250 Ok(bytes_read)
251 })
252 }
253
254 fn ioctl(
255 &self,
256 locked: &mut Locked<Unlocked>,
257 file: &FileObject,
258 current_task: &CurrentTask,
259 request: u32,
260 arg: SyscallArg,
261 ) -> Result<SyscallResult, Errno> {
262 let user_addr = UserAddress::from(arg);
263 match request {
264 FIONREAD => {
265 let addr = UserRef::<i32>::new(user_addr);
266 let size = i32::try_from(self.available()).unwrap_or(i32::MAX);
267 current_task.write_object(addr, &size).map(|_| SUCCESS)
268 }
269 _ => default_ioctl(file, locked, current_task, request, arg),
270 }
271 }
272
273 fn wait_async(
274 &self,
275 _locked: &mut Locked<FileOpsCore>,
276 _file: &FileObject,
277 _current_task: &CurrentTask,
278 waiter: &Waiter,
279 events: FdEvents,
280 handler: EventHandler,
281 ) -> Option<WaitCanceler> {
282 Some(self.state.lock().events.waiters.wait_async_fd_events(waiter, events, handler))
283 }
284
285 fn query_events(
286 &self,
287 _locked: &mut Locked<FileOpsCore>,
288 _file: &FileObject,
289 _current_task: &CurrentTask,
290 ) -> Result<FdEvents, Errno> {
291 if self.available() > 0 { Ok(FdEvents::POLLIN) } else { Ok(FdEvents::empty()) }
292 }
293
294 fn close(
295 self: Box<Self>,
296 _locked: &mut Locked<FileOpsCore>,
297 file: &FileObjectState,
298 _current_task: &CurrentTask,
299 ) {
300 let dir_entries = {
301 let mut state = self.state.lock();
302 state.watches.drain().map(|(_key, value)| value).collect::<Vec<_>>()
303 };
304
305 for dir_entry in dir_entries {
306 dir_entry.node.ensure_watchers().remove_by_ref(&file.weak_handle);
307 }
308 }
309
310 fn extra_fdinfo(
311 &self,
312 _locked: &mut Locked<FileOpsCore>,
313 file: &FileHandle,
314 _current_task: &CurrentTask,
315 ) -> Option<FsString> {
316 let state = self.state.lock();
317 let mut info = String::new();
318 for dir_entry in state.watches.values() {
319 let ino = dir_entry.node.ino;
320 let sdev = dir_entry.node.fs().dev_id;
321 if let Some(watcher) = dir_entry.node.ensure_watchers().get(&WeakKey::from(file)) {
322 let wd = watcher.watch_id;
323 let mask = watcher.mask;
324 info.push_str(&format!(
325 "inotify wd:{} ino:{:x} sdev:{:x} mask:{:x}\n",
326 wd.raw(),
327 ino,
328 sdev.bits(),
329 mask.bits()
330 ));
331 }
332 }
333 Some(info.into())
334 }
335}
336
337impl InotifyEventQueue {
338 fn new_with_max(max_queued_events: usize) -> Self {
339 InotifyEventQueue {
340 queue: Default::default(),
341 waiters: Default::default(),
342 size_bytes: 0,
343 max_queued_events,
344 }
345 }
346
347 fn enqueue(&mut self, mut event: InotifyEvent) {
348 if self.queue.len() > self.max_queued_events {
349 return;
350 }
351 if self.queue.len() == self.max_queued_events {
352 event = InotifyEvent::new(
354 WdNumber::from_raw(-1),
355 InotifyMask::Q_OVERFLOW,
356 0,
357 FsString::default(),
358 );
359 }
360 if Some(&event) == self.queue.back() {
361 return;
367 }
368 self.size_bytes += event.size();
369 self.queue.push_back(event);
370 self.waiters.notify_fd_events(FdEvents::POLLIN);
371 }
372
373 fn front(&self) -> Option<&InotifyEvent> {
374 self.queue.front()
375 }
376
377 fn dequeue(&mut self) -> Option<InotifyEvent> {
378 let maybe_event = self.queue.pop_front();
379 if let Some(event) = maybe_event.as_ref() {
380 self.size_bytes -= event.size();
381 }
382 maybe_event
383 }
384}
385
386impl InotifyEvent {
387 fn new(watch_id: WdNumber, mask: InotifyMask, cookie: u32, mut name: FsString) -> Self {
389 if !name.is_empty() {
390 let len = round_up_to_increment(name.len() + 1, DATA_SIZE)
391 .expect("padded name should not overflow");
392 name.resize(len, 0);
393 }
394 InotifyEvent { watch_id, mask, cookie, name }
395 }
396
397 fn size(&self) -> usize {
398 DATA_SIZE + self.name.len()
399 }
400
401 fn write_to(&self, data: &mut dyn OutputBuffer) -> Result<usize, Errno> {
402 let event = inotify_event {
403 wd: self.watch_id.raw(),
404 mask: self.mask.bits(),
405 cookie: self.cookie,
406 len: self.name.len().try_into().map_err(|_| errno!(EINVAL))?,
407 name: Default::default(),
409 };
410
411 let mut bytes_written = data.write(event.as_bytes())?;
412 if !self.name.is_empty() {
413 bytes_written += data.write(self.name.as_bytes())?;
414 }
415
416 debug_assert!(bytes_written == self.size());
417 Ok(bytes_written)
418 }
419}
420
421impl InotifyWatchers {
422 fn add(&self, mask: InotifyMask, watch_id: WdNumber, inotify: FileHandleKey) {
423 let mut watchers = self.watchers.lock();
424 watchers.insert(inotify, inotify::InotifyWatcher { watch_id, mask });
425 }
426
427 fn maybe_update(
434 &self,
435 mask: InotifyMask,
436 inotify: &FileHandleKey,
437 ) -> Result<Option<WdNumber>, Errno> {
438 let combine_existing = mask.contains(InotifyMask::MASK_ADD);
439 let create_new = mask.contains(InotifyMask::MASK_CREATE);
440 if combine_existing && create_new {
441 return error!(EINVAL);
442 }
443
444 let mut watchers = self.watchers.lock();
445 if let Some(watcher) = watchers.get_mut(inotify) {
446 if create_new {
447 return error!(EEXIST);
448 }
449
450 if combine_existing {
451 watcher.mask.insert(mask);
452 } else {
453 watcher.mask = mask;
454 }
455 Ok(Some(watcher.watch_id))
456 } else {
457 Ok(None)
458 }
459 }
460
461 fn get(&self, inotify: &FileHandleKey) -> Option<InotifyWatcher> {
462 self.watchers.lock().get(inotify).cloned()
463 }
464
465 fn remove(&self, inotify: &FileHandleKey) {
466 let mut watchers = self.watchers.lock();
467 watchers.remove(inotify);
468 }
469
470 fn remove_by_ref(&self, inotify: &WeakFileHandle) {
471 let mut watchers = self.watchers.lock();
472 watchers.retain(|weak_key, _| weak_key.0.strong_count() > 0 && weak_key != inotify)
473 }
474
475 pub fn notify(
482 &self,
483 mut event_mask: InotifyMask,
484 cookie: u32,
485 name: &FsStr,
486 mode: FileMode,
487 is_dead: bool,
488 ) {
489 if cookie != 0 {
490 debug_assert!(
493 event_mask.contains(InotifyMask::MOVE_FROM)
494 || event_mask.contains(InotifyMask::MOVE_TO)
495 );
496 }
497 struct InotifyWatch {
499 watch_id: WdNumber,
500 file: FileHandle,
501 should_remove: bool,
502 }
503 let mut watches: Vec<InotifyWatch> = vec![];
504 {
505 let mut watchers = self.watchers.lock();
506 watchers.retain(|inotify, watcher| {
507 let mut should_remove = event_mask == InotifyMask::DELETE_SELF;
508 if watcher.mask.contains(event_mask)
509 && !(is_dead && watcher.mask.contains(InotifyMask::EXCL_UNLINK))
510 {
511 should_remove = should_remove || watcher.mask.contains(InotifyMask::ONESHOT);
512 if let Some(file) = inotify.0.upgrade() {
513 watches.push(InotifyWatch {
514 watch_id: watcher.watch_id,
515 file,
516 should_remove,
517 });
518 } else {
519 should_remove = true;
520 }
521 }
522 !should_remove
523 });
524 }
525
526 if mode.is_dir() {
527 if event_mask != InotifyMask::DELETE_SELF && event_mask != InotifyMask::MOVE_SELF {
529 event_mask |= InotifyMask::ISDIR;
530 }
531 }
532
533 for watch in watches {
534 let inotify = watch
535 .file
536 .downcast_file::<InotifyFileObject>()
537 .expect("failed to downcast to inotify");
538 inotify.notify(watch.watch_id, event_mask, cookie, name, watch.should_remove);
539 }
540 }
541}
542
543impl Kernel {
544 pub fn get_next_inotify_cookie(&self) -> u32 {
545 let cookie = self.next_inotify_cookie.next();
546 if cookie == 0 {
548 return self.next_inotify_cookie.next();
549 }
550 cookie
551 }
552}
553
554#[derive(Debug)]
556pub struct InotifyLimits {
557 pub max_queued_events: AtomicI32,
560
561 pub max_user_instances: AtomicI32,
563
564 pub max_user_watches: AtomicI32,
566}
567
568pub trait AtomicGetter {
569 fn get_atomic<'a>(current_task: &'a CurrentTask) -> &'a AtomicI32;
570}
571
572pub struct MaxQueuedEventsGetter;
573impl AtomicGetter for MaxQueuedEventsGetter {
574 fn get_atomic<'a>(current_task: &'a CurrentTask) -> &'a AtomicI32 {
575 ¤t_task.kernel().system_limits.inotify.max_queued_events
576 }
577}
578
579pub struct MaxUserInstancesGetter;
580impl AtomicGetter for MaxUserInstancesGetter {
581 fn get_atomic<'a>(current_task: &'a CurrentTask) -> &'a AtomicI32 {
582 ¤t_task.kernel().system_limits.inotify.max_user_instances
583 }
584}
585
586pub struct MaxUserWatchesGetter;
587impl AtomicGetter for MaxUserWatchesGetter {
588 fn get_atomic<'a>(current_task: &'a CurrentTask) -> &'a AtomicI32 {
589 ¤t_task.kernel().system_limits.inotify.max_user_watches
590 }
591}
592
593pub struct InotifyLimitProcFile<G: AtomicGetter + Send + Sync + 'static> {
594 marker: std::marker::PhantomData<G>,
595}
596
597impl<G: AtomicGetter + Send + Sync + 'static> InotifyLimitProcFile<G> {
598 pub fn new_node() -> impl FsNodeOps {
599 BytesFile::new_node(Self { marker: Default::default() })
600 }
601}
602
603impl<G: AtomicGetter + Send + Sync + 'static> BytesFileOps for InotifyLimitProcFile<G> {
604 fn write(&self, current_task: &CurrentTask, data: Vec<u8>) -> Result<(), Errno> {
605 security::check_task_capable(current_task, CAP_SYS_ADMIN)?;
606 let value = fs_args::parse::<i32>(data.as_slice().into())?;
607 if value < 0 {
608 return error!(EINVAL);
609 }
610 G::get_atomic(current_task).store(value, Ordering::Relaxed);
611 Ok(())
612 }
613
614 fn read(&self, current_task: &CurrentTask) -> Result<Cow<'_, [u8]>, Errno> {
615 Ok(G::get_atomic(current_task).load(Ordering::Relaxed).to_string().into_bytes().into())
616 }
617}
618
619pub type InotifyMaxQueuedEvents = InotifyLimitProcFile<MaxQueuedEventsGetter>;
620pub type InotifyMaxUserInstances = InotifyLimitProcFile<MaxUserInstancesGetter>;
621pub type InotifyMaxUserWatches = InotifyLimitProcFile<MaxUserWatchesGetter>;
622
623#[cfg(test)]
624mod tests {
625 use super::{DATA_SIZE, InotifyEvent, InotifyEventQueue, InotifyFileObject};
626 use crate::testing::spawn_kernel_and_run_with_pkgfs;
627 use crate::vfs::buffers::VecOutputBuffer;
628 use crate::vfs::{OutputBuffer, WdNumber};
629 use starnix_uapi::arc_key::WeakKey;
630 use starnix_uapi::file_mode::FileMode;
631 use starnix_uapi::inotify_mask::InotifyMask;
632
633 #[::fuchsia::test]
634 fn inotify_event() {
635 let event = InotifyEvent::new(WdNumber::from_raw(1), InotifyMask::ACCESS, 0, "".into());
636 let mut buffer = VecOutputBuffer::new(DATA_SIZE + 100);
637 let bytes_written = event.write_to(&mut buffer).expect("write_to buffer");
638
639 assert_eq!(bytes_written, DATA_SIZE);
640 assert_eq!(buffer.bytes_written(), DATA_SIZE);
641 }
642
643 #[::fuchsia::test]
644 fn inotify_event_with_name() {
645 let name = "file1";
647 let event = InotifyEvent::new(WdNumber::from_raw(1), InotifyMask::ACCESS, 0, name.into());
648 let mut buffer = VecOutputBuffer::new(DATA_SIZE + 100);
649 let bytes_written = event.write_to(&mut buffer).expect("write_to buffer");
650
651 assert!(bytes_written > DATA_SIZE);
652 assert_eq!(bytes_written % DATA_SIZE, 0);
653 assert_eq!(buffer.bytes_written(), bytes_written);
654 }
655
656 #[::fuchsia::test]
657 fn inotify_event_queue() {
658 let mut event_queue = InotifyEventQueue::new_with_max(10);
659
660 event_queue.enqueue(InotifyEvent::new(
661 WdNumber::from_raw(1),
662 InotifyMask::ACCESS,
663 0,
664 "".into(),
665 ));
666
667 assert_eq!(event_queue.queue.len(), 1);
668 assert_eq!(event_queue.size_bytes, DATA_SIZE);
669
670 let event = event_queue.dequeue();
671
672 assert_eq!(
673 event,
674 Some(InotifyEvent::new(WdNumber::from_raw(1), InotifyMask::ACCESS, 0, "".into()))
675 );
676 assert_eq!(event_queue.queue.len(), 0);
677 assert_eq!(event_queue.size_bytes, 0);
678 }
679
680 #[::fuchsia::test]
681 fn inotify_event_queue_coalesce_events() {
682 let mut event_queue = InotifyEventQueue::new_with_max(10);
683
684 event_queue.enqueue(InotifyEvent::new(
686 WdNumber::from_raw(1),
687 InotifyMask::ACCESS,
688 0,
689 "".into(),
690 ));
691 event_queue.enqueue(InotifyEvent::new(
692 WdNumber::from_raw(1),
693 InotifyMask::ACCESS,
694 0,
695 "".into(),
696 ));
697
698 assert_eq!(event_queue.queue.len(), 1);
699 }
700
701 #[::fuchsia::test]
702 fn inotify_event_queue_max_queued_events() {
703 let mut event_queue = InotifyEventQueue::new_with_max(1);
704
705 event_queue.enqueue(InotifyEvent::new(
707 WdNumber::from_raw(1),
708 InotifyMask::ACCESS,
709 0,
710 "".into(),
711 ));
712 event_queue.enqueue(InotifyEvent::new(
713 WdNumber::from_raw(1),
714 InotifyMask::MODIFY,
715 0,
716 "".into(),
717 ));
718
719 assert_eq!(event_queue.queue.len(), 2);
720 assert_eq!(event_queue.queue.get(0).unwrap().mask, InotifyMask::ACCESS);
721 assert_eq!(event_queue.queue.get(1).unwrap().mask, InotifyMask::Q_OVERFLOW);
722
723 event_queue.enqueue(InotifyEvent::new(
725 WdNumber::from_raw(1),
726 InotifyMask::ATTRIB,
727 0,
728 "".into(),
729 ));
730 assert_eq!(event_queue.queue.len(), 2);
731 assert_eq!(event_queue.queue.get(0).unwrap().mask, InotifyMask::ACCESS);
732 assert_eq!(event_queue.queue.get(1).unwrap().mask, InotifyMask::Q_OVERFLOW);
733
734 let _event = event_queue.dequeue();
736 assert_eq!(event_queue.queue.len(), 1);
737
738 event_queue.enqueue(InotifyEvent::new(
741 WdNumber::from_raw(1),
742 InotifyMask::DELETE,
743 0,
744 "".into(),
745 ));
746 assert_eq!(event_queue.queue.len(), 1);
747 assert_eq!(event_queue.queue.get(0).unwrap().mask, InotifyMask::Q_OVERFLOW);
748 }
749
750 #[::fuchsia::test]
751 async fn notify_from_watchers() {
752 spawn_kernel_and_run_with_pkgfs(async |locked, current_task| {
753 let file = InotifyFileObject::new_file(locked, ¤t_task, true);
754 let inotify =
755 file.downcast_file::<InotifyFileObject>().expect("failed to downcast to inotify");
756
757 let root = current_task.fs().root().entry;
759 assert!(inotify.add_watch(root.clone(), InotifyMask::ALL_EVENTS, &file).is_ok());
760
761 {
762 let watchers = root.node.ensure_watchers().watchers.lock();
763 assert_eq!(watchers.len(), 1);
764 }
765
766 root.node.notify(InotifyMask::ACCESS, 0, Default::default(), FileMode::IFREG, false);
768
769 assert_eq!(inotify.available(), DATA_SIZE);
770 {
771 let state = inotify.state.lock();
772 assert_eq!(state.watches.len(), 1);
773 assert_eq!(state.events.queue.len(), 1);
774 }
775
776 root.node.notify(InotifyMask::ATTRIB, 0, Default::default(), FileMode::IFREG, false);
778
779 assert_eq!(inotify.available(), DATA_SIZE * 2);
780 {
781 let state = inotify.state.lock();
782 assert_eq!(state.events.queue.len(), 2);
783 }
784
785 let mut buffer = VecOutputBuffer::new(DATA_SIZE);
787 let bytes_read =
788 file.read(locked, ¤t_task, &mut buffer).expect("read into buffer");
789
790 assert_eq!(bytes_read, DATA_SIZE);
791 assert_eq!(inotify.available(), DATA_SIZE);
792 {
793 let state = inotify.state.lock();
794 assert_eq!(state.events.queue.len(), 1);
795 }
796
797 buffer.reset();
799 let bytes_read =
800 file.read(locked, ¤t_task, &mut buffer).expect("read into buffer");
801
802 assert_eq!(bytes_read, DATA_SIZE);
803 assert_eq!(inotify.available(), 0);
804 {
805 let state = inotify.state.lock();
806 assert_eq!(state.events.queue.len(), 0);
807 }
808 })
809 .await;
810 }
811
812 #[::fuchsia::test]
813 async fn notify_deletion_from_watchers() {
814 spawn_kernel_and_run_with_pkgfs(async |locked, current_task| {
815 let file = InotifyFileObject::new_file(locked, ¤t_task, true);
816 let inotify =
817 file.downcast_file::<InotifyFileObject>().expect("failed to downcast to inotify");
818
819 let root = current_task.fs().root().entry;
821 assert!(inotify.add_watch(root.clone(), InotifyMask::ALL_EVENTS, &file).is_ok());
822
823 {
824 let watchers = root.node.ensure_watchers().watchers.lock();
825 assert_eq!(watchers.len(), 1);
826 }
827
828 root.node.notify(
829 InotifyMask::DELETE_SELF,
830 0,
831 Default::default(),
832 FileMode::IFREG,
833 false,
834 );
835
836 {
837 let watchers = root.node.ensure_watchers().watchers.lock();
838 assert_eq!(watchers.len(), 0);
839 }
840
841 {
842 let state = inotify.state.lock();
843 assert_eq!(state.watches.len(), 0);
844 assert_eq!(state.events.queue.len(), 2);
845
846 assert_eq!(state.events.queue.get(0).unwrap().mask, InotifyMask::DELETE_SELF);
847 assert_eq!(state.events.queue.get(1).unwrap().mask, InotifyMask::IGNORED);
848 }
849 })
850 .await;
851 }
852
853 #[::fuchsia::test]
854 async fn inotify_on_same_file() {
855 spawn_kernel_and_run_with_pkgfs(async |locked, current_task| {
856 let file = InotifyFileObject::new_file(locked, ¤t_task, true);
857 let file_key = WeakKey::from(&file);
858 let inotify =
859 file.downcast_file::<InotifyFileObject>().expect("failed to downcast to inotify");
860
861 let root = current_task.fs().root().entry;
863
864 assert!(
866 inotify
867 .add_watch(
868 root.clone(),
869 InotifyMask::MODIFY | InotifyMask::MASK_ADD | InotifyMask::MASK_CREATE,
870 &file
871 )
872 .is_err()
873 );
874
875 assert!(
876 inotify
877 .add_watch(root.clone(), InotifyMask::MODIFY | InotifyMask::MASK_CREATE, &file)
878 .is_ok()
879 );
880
881 {
882 let watchers = root.node.ensure_watchers().watchers.lock();
883 assert_eq!(watchers.len(), 1);
884 assert!(watchers.get(&file_key).unwrap().mask.contains(InotifyMask::MODIFY));
885 }
886
887 assert!(inotify.add_watch(root.clone(), InotifyMask::ACCESS, &file).is_ok());
889
890 {
891 let watchers = root.node.ensure_watchers().watchers.lock();
892 assert_eq!(watchers.len(), 1);
893 assert!(watchers.get(&file_key).unwrap().mask.contains(InotifyMask::ACCESS));
894 assert!(!watchers.get(&file_key).unwrap().mask.contains(InotifyMask::MODIFY));
895 }
896
897 assert!(
899 inotify
900 .add_watch(root.clone(), InotifyMask::MODIFY | InotifyMask::MASK_ADD, &file)
901 .is_ok()
902 );
903
904 {
905 let watchers = root.node.ensure_watchers().watchers.lock();
906 assert_eq!(watchers.len(), 1);
907 assert!(watchers.get(&file_key).unwrap().mask.contains(InotifyMask::ACCESS));
908 assert!(watchers.get(&file_key).unwrap().mask.contains(InotifyMask::MODIFY));
909 }
910 })
911 .await;
912 }
913
914 #[::fuchsia::test]
915 async fn coalesce_events() {
916 spawn_kernel_and_run_with_pkgfs(async |locked, current_task| {
917 let file = InotifyFileObject::new_file(locked, ¤t_task, true);
918 let inotify =
919 file.downcast_file::<InotifyFileObject>().expect("failed to downcast to inotify");
920
921 let root = current_task.fs().root().entry;
923 assert!(inotify.add_watch(root.clone(), InotifyMask::ALL_EVENTS, &file).is_ok());
924
925 {
926 let watchers = root.node.ensure_watchers().watchers.lock();
927 assert_eq!(watchers.len(), 1);
928 }
929
930 root.node.notify(InotifyMask::ACCESS, 0, Default::default(), FileMode::IFREG, false);
932 root.node.notify(InotifyMask::ACCESS, 0, Default::default(), FileMode::IFREG, false);
933
934 assert_eq!(inotify.available(), DATA_SIZE);
935 {
936 let state = inotify.state.lock();
937 assert_eq!(state.watches.len(), 1);
938 assert_eq!(state.events.queue.len(), 1);
939 }
940 })
941 .await;
942 }
943}