starnix_core/vfs/
inotify.rs

1// Copyright 2022 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use 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
35// InotifyFileObject represents an inotify instance created by inotify_init(2) or inotify_init1(2).
36pub struct InotifyFileObject {
37    state: Mutex<InotifyState>,
38}
39
40struct InotifyState {
41    events: InotifyEventQueue,
42
43    watches: HashMap<WdNumber, DirEntryHandle>,
44
45    // Last created WdNumber, stored as raw i32. WdNumber's are unique per inotify instance.
46    last_watch_id: i32,
47}
48
49// InotifyWatcher's attach to a FsNode.
50#[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 can contain max_queued_events inotify events, plus one optional IN_Q_OVERFLOW event
65    // if more events arrive.
66    queue: VecDeque<InotifyEvent>,
67
68    // Waiters to notify of new inotify events.
69    waiters: WaitQueue,
70
71    // Total size of InotifyEvent objects in queue, when serialized into inotify_event.
72    size_bytes: usize,
73
74    // This value is copied from /proc/sys/fs/inotify/max_queued_events on creation and is
75    // constant afterwards, even if the proc file is modified.
76    max_queued_events: usize,
77}
78
79// Serialized to inotify_event, see inotify(7).
80#[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    /// Allocate a new, empty inotify instance.
100    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    /// Adds a watch to the inotify instance.
130    ///
131    /// Attaches an InotifyWatcher to the DirEntry's FsNode.
132    /// Inotify keeps the DirEntryHandle in case it is evicted from dcache.
133    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    /// Removes a watch to the inotify instance.
155    ///
156    /// Detaches the corresponding InotifyWatcher from FsNode.
157    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        // Holds a DirEntry pending deletion to be dropped after releasing the state mutex.
182        #[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                // Linux always dequeues an available event as long as there's enough buffer space to
247                // copy it out, even if the copy below fails. Emulate this behaviour.
248                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            // If this event will overflow the queue, discard it and enqueue IN_Q_OVERFLOW instead.
353            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            // From https://man7.org/linux/man-pages/man7/inotify.7.html
362            // If successive output inotify events produced on the inotify file
363            // descriptor are identical (same wd, mask, cookie, and name), then
364            // they are coalesced into a single event if the older event has not
365            // yet been read.
366            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    // Creates a new InotifyEvent and pads name with at least 1 null-byte, aligned to DATA_SIZE.
388    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 field is zero-sized; the bytes for the name follows the struct linearly in memory.
408            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    // Checks if inotify is already part of watchers. Replaces mask if found and returns the WdNumber.
428    // Combines mask if IN_MASK_ADD is specified in mask. Returns None if no present in watchers.
429    //
430    // Errors if:
431    //  - both IN_MASK_ADD and IN_MASK_CREATE are specified in mask, or
432    //  - IN_MASK_CREATE is specified and existing entry is found.
433    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    /// Notifies all watchers that listen for the specified event mask with
476    /// struct inotify_event { event_mask, cookie, name }.
477    ///
478    /// If event_mask is IN_DELETE_SELF, all watchers are removed after this event.
479    /// cookie is used to link a pair of IN_MOVE_FROM/IN_MOVE_TO events only.
480    /// mode is used to check whether IN_ISDIR should be combined with event_mask.
481    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            // From https://man7.org/linux/man-pages/man7/inotify.7.html,
491            // cookie is only used for rename events.
492            debug_assert!(
493                event_mask.contains(InotifyMask::MOVE_FROM)
494                    || event_mask.contains(InotifyMask::MOVE_TO)
495            );
496        }
497        // Clone inotify references so that we don't hold watchers lock when notifying.
498        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            // Linux does not report IN_ISDIR with IN_DELETE_SELF or IN_MOVE_SELF for directories.
528            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        // 0 is an invalid cookie.
547        if cookie == 0 {
548            return self.next_inotify_cookie.next();
549        }
550        cookie
551    }
552}
553
554/// Corresponds to files in /proc/sys/fs/inotify/, but cannot be negative.
555#[derive(Debug)]
556pub struct InotifyLimits {
557    // This value is used when creating an inotify instance.
558    // Updating this value does not affect already-created inotify instances.
559    pub max_queued_events: AtomicI32,
560
561    // TODO(b/297439734): Make this a real user limit on inotify instances.
562    pub max_user_instances: AtomicI32,
563
564    // TODO(b/297439734): Make this a real user limit on inotify watches.
565    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        &current_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        &current_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        &current_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        // Create a name that is shorter than DATA_SIZE of 16.
646        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        // Generate 2 identical events. They should combine into 1.
685        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        // Generate 2 events, but the second event overflows the queue.
706        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        // More events cannot be added to the queue.
724        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        // Dequeue 1 event.
735        let _event = event_queue.dequeue();
736        assert_eq!(event_queue.queue.len(), 1);
737
738        // More events still cannot make it to the queue. This is because they would cause an overflow,
739        // but there is already a Q_OVERFLOW event in the queue so we do not enqueue another one.
740        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, &current_task, true);
754            let inotify =
755                file.downcast_file::<InotifyFileObject>().expect("failed to downcast to inotify");
756
757            // Use root as the watched directory.
758            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            // Generate 1 event.
767            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            // Generate another event.
777            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            // Read 1 event.
786            let mut buffer = VecOutputBuffer::new(DATA_SIZE);
787            let bytes_read =
788                file.read(locked, &current_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            // Read other event.
798            buffer.reset();
799            let bytes_read =
800                file.read(locked, &current_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, &current_task, true);
816            let inotify =
817                file.downcast_file::<InotifyFileObject>().expect("failed to downcast to inotify");
818
819            // Use root as the watched directory.
820            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, &current_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            // Use root as the watched directory.
862            let root = current_task.fs().root().entry;
863
864            // Cannot add with both MASK_ADD and MASK_CREATE.
865            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            // Replaces existing mask.
888            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            // Merges with existing mask.
898            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, &current_task, true);
918            let inotify =
919                file.downcast_file::<InotifyFileObject>().expect("failed to downcast to inotify");
920
921            // Use root as the watched directory.
922            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            // Generate 2 identical events. They should combine into 1.
931            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}