Skip to main content

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