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