Coverage Report

Created: 2025-02-21 07:11

/rust/registry/src/index.crates.io-6f17d22bba15001f/rustix-0.38.44/src/fs/inotify.rs
Line
Count
Source (jump to first uncovered line)
1
//! inotify support for working with inotify objects.
2
//!
3
//! # Examples
4
//!
5
//! ```
6
//! use rustix::fs::inotify;
7
//! use rustix::io;
8
//! use std::mem::MaybeUninit;
9
//!
10
//! # fn test() -> io::Result<()> {
11
//! // Create an inotify object. In this example, we use `NONBLOCK` so that the
12
//! // reader fails with `WOULDBLOCK` when no events are ready. Otherwise it
13
//! // will block until at least one event is ready.
14
//! let inotify = inotify::init(inotify::CreateFlags::NONBLOCK)?;
15
//!
16
//! // Add a directory to watch.
17
//! inotify::add_watch(
18
//!     &inotify,
19
//!     "/path/to/some/directory/to/watch",
20
//!     inotify::WatchFlags::ALL_EVENTS,
21
//! )?;
22
//!
23
//! // Generate some events in the watched directory…
24
//!
25
//! // Loop over pending events.
26
//! let mut buf = [MaybeUninit::uninit(); 512];
27
//! let mut iter = inotify::Reader::new(inotify, &mut buf);
28
//! loop {
29
//!     let entry = match iter.next() {
30
//!         // Stop iterating if there are no more events for now.
31
//!         Err(io::Errno::WOULDBLOCK) => break,
32
//!         Err(e) => return Err(e),
33
//!         Ok(entry) => entry,
34
//!     };
35
//!
36
//!     // Use `entry`…
37
//! }
38
//!
39
//! # Ok(())
40
//! # }
41
42
#![allow(unused_qualifications)]
43
44
use super::inotify;
45
pub use crate::backend::fs::inotify::{CreateFlags, ReadFlags, WatchFlags};
46
use crate::backend::fs::syscalls;
47
use crate::fd::{AsFd, OwnedFd};
48
use crate::ffi::CStr;
49
use crate::io;
50
use crate::io::{read_uninit, Errno};
51
use core::mem::{align_of, size_of, MaybeUninit};
52
use linux_raw_sys::general::inotify_event;
53
54
#[deprecated(note = "Use `inotify::add_watch`.")]
55
#[doc(hidden)]
56
pub use add_watch as inotify_add_watch;
57
#[deprecated(note = "Use `inotify::init`.")]
58
#[doc(hidden)]
59
pub use init as inotify_init;
60
#[deprecated(note = "Use `inotify::remove_watch`.")]
61
#[doc(hidden)]
62
pub use remove_watch as inotify_remove_watch;
63
64
/// `inotify_init1(flags)`—Creates a new inotify object.
65
///
66
/// Use the [`CreateFlags::CLOEXEC`] flag to prevent the resulting file
67
/// descriptor from being implicitly passed across `exec` boundaries.
68
#[doc(alias = "inotify_init1")]
69
#[inline]
70
0
pub fn init(flags: inotify::CreateFlags) -> io::Result<OwnedFd> {
71
0
    syscalls::inotify_init1(flags)
72
0
}
73
74
/// `inotify_add_watch(self, path, flags)`—Adds a watch to inotify.
75
///
76
/// This registers or updates a watch for the filesystem path `path` and
77
/// returns a watch descriptor corresponding to this watch.
78
///
79
/// Note: Due to the existence of hardlinks, providing two different paths to
80
/// this method may result in it returning the same watch descriptor. An
81
/// application should keep track of this externally to avoid logic errors.
82
#[doc(alias = "inotify_add_watch")]
83
#[inline]
84
0
pub fn add_watch<P: crate::path::Arg>(
85
0
    inot: impl AsFd,
86
0
    path: P,
87
0
    flags: inotify::WatchFlags,
88
0
) -> io::Result<i32> {
89
0
    path.into_with_c_str(|path| syscalls::inotify_add_watch(inot.as_fd(), path, flags))
90
0
}
91
92
/// `inotify_rm_watch(self, wd)`—Removes a watch from this inotify.
93
///
94
/// The watch descriptor provided should have previously been returned by
95
/// [`inotify::add_watch`] and not previously have been removed.
96
#[doc(alias = "inotify_rm_watch")]
97
#[inline]
98
0
pub fn remove_watch(inot: impl AsFd, wd: i32) -> io::Result<()> {
99
0
    syscalls::inotify_rm_watch(inot.as_fd(), wd)
100
0
}
101
102
/// An inotify event iterator implemented with the read syscall.
103
///
104
/// See the [`RawDir`] API for more details and usage examples as this API is
105
/// based on it.
106
///
107
/// [`RawDir`]: crate::fs::raw_dir::RawDir
108
pub struct Reader<'buf, Fd: AsFd> {
109
    fd: Fd,
110
    buf: &'buf mut [MaybeUninit<u8>],
111
    initialized: usize,
112
    offset: usize,
113
}
114
115
impl<'buf, Fd: AsFd> Reader<'buf, Fd> {
116
    /// Create a new iterator from the given file descriptor and buffer.
117
0
    pub fn new(fd: Fd, buf: &'buf mut [MaybeUninit<u8>]) -> Self {
118
0
        Self {
119
0
            fd,
120
0
            buf: {
121
0
                let offset = buf.as_ptr().align_offset(align_of::<inotify_event>());
122
0
                if offset < buf.len() {
123
0
                    &mut buf[offset..]
124
                } else {
125
0
                    &mut []
126
                }
127
            },
128
            initialized: 0,
129
            offset: 0,
130
        }
131
0
    }
132
}
133
134
/// An inotify event.
135
#[derive(Debug)]
136
pub struct InotifyEvent<'a> {
137
    wd: i32,
138
    events: ReadFlags,
139
    cookie: u32,
140
    file_name: Option<&'a CStr>,
141
}
142
143
impl<'a> InotifyEvent<'a> {
144
    /// Returns the watch for which this event occurs.
145
    #[inline]
146
0
    pub fn wd(&self) -> i32 {
147
0
        self.wd
148
0
    }
149
150
    /// Returns a description of the events.
151
    #[inline]
152
    #[doc(alias = "mask")]
153
0
    pub fn events(&self) -> ReadFlags {
154
0
        self.events
155
0
    }
156
157
    /// Returns the unique cookie associating related events.
158
    #[inline]
159
0
    pub fn cookie(&self) -> u32 {
160
0
        self.cookie
161
0
    }
162
163
    /// Returns the file name of this event, if any.
164
    #[inline]
165
0
    pub fn file_name(&self) -> Option<&CStr> {
166
0
        self.file_name
167
0
    }
168
}
169
170
impl<'buf, Fd: AsFd> Reader<'buf, Fd> {
171
    /// Read the next inotify event.
172
    ///
173
    /// This is similar to `[Iterator::next`] except that it doesn't return an
174
    /// `Option`, because the stream doesn't have an ending. It always returns
175
    /// events or errors.
176
    ///
177
    /// If there are no events in the buffer and none ready to be read:
178
    ///  - If the file descriptor was opened with
179
    ///    [`inotify::CreateFlags::NONBLOCK`], this will fail with
180
    ///    [`Errno::AGAIN`].
181
    ///  - Otherwise this will block until at least one event is ready or an
182
    ///    error occurs.
183
    #[allow(unsafe_code)]
184
    #[allow(clippy::should_implement_trait)]
185
0
    pub fn next(&mut self) -> io::Result<InotifyEvent<'_>> {
186
0
        if self.is_buffer_empty() {
187
0
            match read_uninit(self.fd.as_fd(), self.buf).map(|(init, _)| init.len()) {
188
0
                Ok(0) => return Err(Errno::INVAL),
189
0
                Ok(bytes_read) => {
190
0
                    self.initialized = bytes_read;
191
0
                    self.offset = 0;
192
0
                }
193
0
                Err(e) => return Err(e),
194
            }
195
0
        }
196
197
0
        let ptr = self.buf[self.offset..].as_ptr();
198
0
199
0
        // SAFETY:
200
0
        // - This data is initialized by the check above.
201
0
        //   - Assumption: the kernel will not give us partial structs.
202
0
        // - Assumption: the kernel uses proper alignment between structs.
203
0
        // - The starting pointer is aligned (performed in `Reader::new`).
204
0
        let event = unsafe { &*ptr.cast::<inotify_event>() };
205
0
206
0
        self.offset += size_of::<inotify_event>() + usize::try_from(event.len).unwrap();
207
0
208
0
        Ok(InotifyEvent {
209
0
            wd: event.wd,
210
0
            events: ReadFlags::from_bits_retain(event.mask),
211
0
            cookie: event.cookie,
212
0
            file_name: if event.len > 0 {
213
                // SAFETY: The kernel guarantees a NUL-terminated string.
214
0
                Some(unsafe { CStr::from_ptr(event.name.as_ptr().cast()) })
215
            } else {
216
0
                None
217
            },
218
        })
219
0
    }
220
221
    /// Returns true if the internal buffer is empty and will be refilled when
222
    /// calling [`next`]. This is useful to avoid further blocking reads.
223
    ///
224
    /// [`next`]: Self::next
225
0
    pub fn is_buffer_empty(&self) -> bool {
226
0
        self.offset >= self.initialized
227
0
    }
228
}