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