Skip to main content

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