windows_namespace_request_sys/watch.rs
1// Copyright (c) Mike Grier.
2
3//! The `FindFirstChangeNotificationW` entry.
4//!
5//! Entry 3 of the audited catalogue. It watches a directory for a class of
6//! changes and produces a handle that becomes signalled when one occurs.
7//!
8//! # The handle it produces is not an ordinary handle
9//!
10//! A change-notification handle is closed with `FindCloseChangeNotification`,
11//! **not** `CloseHandle`. Passing it to `CloseHandle` is a resource leak that
12//! nothing reports: the call may even appear to succeed. That is why this entry
13//! returns [`ChangeNotification`] rather than a bare `OwnedHandle` -- the type
14//! is what remembers which routine closes it, so a caller cannot forget.
15//!
16//! # What it does and does not tell you
17//!
18//! The handle signals that *something* in the watched set changed. It carries
19//! no record of what, and a burst of changes may signal once. A consumer
20//! needing the individual changes wants `ReadDirectoryChangesW`, which is a
21//! different call and therefore a different entry. This one is the cheap
22//! "something happened, go look" primitive, and the crate does not blur them.
23
24use std::ffi::c_void;
25use std::fmt;
26
27use windows_sys::Win32::Foundation::HANDLE;
28use windows_sys::Win32::Storage::FileSystem::{
29 FILE_NOTIFY_CHANGE, FILE_NOTIFY_CHANGE_ATTRIBUTES, FILE_NOTIFY_CHANGE_CREATION,
30 FILE_NOTIFY_CHANGE_DIR_NAME, FILE_NOTIFY_CHANGE_FILE_NAME, FILE_NOTIFY_CHANGE_LAST_ACCESS,
31 FILE_NOTIFY_CHANGE_LAST_WRITE, FILE_NOTIFY_CHANGE_SECURITY, FILE_NOTIFY_CHANGE_SIZE,
32 FindCloseChangeNotification, FindFirstChangeNotificationW, FindNextChangeNotification,
33};
34
35use crate::outcome::{Outcome, perform_bool, perform_handle};
36use crate::path::PreparedPath;
37
38/// An owned change-notification handle.
39///
40/// Closed with `FindCloseChangeNotification` on drop. The type exists so that
41/// the close routine travels with the handle rather than being something each
42/// call site has to remember, which is the same shape
43/// [windows-threadpool-sys](https://docs.rs/windows-threadpool-sys) already
44/// needed for wait targets.
45#[derive(Debug)]
46#[must_use = "dropping the notification stops the watch"]
47pub struct ChangeNotification {
48 /// Always a live handle produced by `FindFirstChangeNotificationW`, until
49 /// `Drop` closes it.
50 handle: HANDLE,
51}
52
53impl ChangeNotification {
54 /// The raw handle, for passing to a wait.
55 ///
56 /// Borrowed, not transferred: it stays owned by this value. Do **not** pass
57 /// it to `CloseHandle`.
58 #[must_use]
59 pub fn as_raw(&self) -> HANDLE {
60 self.handle
61 }
62
63 /// Rearms the watch after it has signalled.
64 ///
65 /// A notification handle signals once and then stays signalled until it is
66 /// rearmed, so a consumer that waits in a loop must call this between
67 /// waits.
68 ///
69 /// # Errors
70 ///
71 /// Returns the raw Win32 code, unaltered.
72 ///
73 /// # Example
74 ///
75 /// Without the rearm, the second wait returns immediately on the *first*
76 /// change forever, and a consumer's loop spins:
77 ///
78 /// ```
79 /// use std::fs;
80 ///
81 /// use windows_namespace_request_sys::prepare;
82 /// use windows_namespace_request_sys::watch::{NotifyFilter, WatchDirectory};
83 /// use windows_sys::Win32::Foundation::WAIT_OBJECT_0;
84 /// use windows_sys::Win32::System::Threading::WaitForSingleObject;
85 /// use wtf_string::Wtf16String;
86 ///
87 /// let directory = std::env::temp_dir().join(format!("wnrs-rearm-{}", std::process::id()));
88 /// let _ = fs::remove_dir_all(&directory);
89 /// fs::create_dir_all(&directory)?;
90 ///
91 /// let text = directory.to_str().expect("a temporary path is valid UTF-8");
92 /// let notification = WatchDirectory::new(prepare(&Wtf16String::from(text))?)
93 /// .with_filter(NotifyFilter::FILE_NAME)
94 /// .perform()?;
95 ///
96 /// fs::write(directory.join("first.t"), b"x")?;
97 /// // SAFETY: the handle is live for the notification's lifetime.
98 /// assert_eq!(unsafe { WaitForSingleObject(notification.as_raw(), 5_000) }, WAIT_OBJECT_0);
99 ///
100 /// // The handle stays signalled until this runs.
101 /// notification.rearm()?;
102 ///
103 /// fs::write(directory.join("second.t"), b"x")?;
104 /// // SAFETY: as above.
105 /// assert_eq!(unsafe { WaitForSingleObject(notification.as_raw(), 5_000) }, WAIT_OBJECT_0);
106 /// # drop(notification);
107 /// # let _ = fs::remove_dir_all(&directory);
108 /// # Ok::<(), Box<dyn std::error::Error>>(())
109 /// ```
110 pub fn rearm(&self) -> Outcome<()> {
111 // SAFETY: the handle is live for this value's lifetime.
112 perform_bool(|| unsafe { FindNextChangeNotification(self.handle) })
113 }
114}
115
116impl Drop for ChangeNotification {
117 fn drop(&mut self) {
118 // SAFETY: the handle came from FindFirstChangeNotificationW and has not
119 // been closed, since only this Drop closes it. The result is
120 // deliberately ignored: a close failure during drop has nowhere to go,
121 // and this crate does not panic on it.
122 unsafe {
123 FindCloseChangeNotification(self.handle);
124 }
125 }
126}
127
128// SAFETY: the value owns its handle exclusively and has no interior
129// mutability. A Windows handle is process-wide rather than thread-affine, so
130// moving it between threads and sharing a shared reference are both sound; the
131// raw pointer is what blocks the automatic derivation.
132unsafe impl Send for ChangeNotification {}
133// SAFETY: as above. `rearm` takes `&self` and is a single Win32 call that
134// Windows serialises internally.
135unsafe impl Sync for ChangeNotification {}
136
137/// Which changes a watch reports.
138///
139/// A thin newtype over `FILE_NOTIFY_CHANGE` rather than an enum, because the
140/// value is a bitmask and Windows may define bits this crate has not heard of.
141/// The named constants are provided for convenience; an unknown bit still
142/// reaches Windows unaltered.
143///
144/// # Example
145///
146/// ```
147/// use windows_namespace_request_sys::watch::NotifyFilter;
148///
149/// let combined = NotifyFilter::FILE_NAME | NotifyFilter::DIR_NAME;
150///
151/// assert!(combined.contains(NotifyFilter::FILE_NAME));
152/// assert!(!combined.contains(NotifyFilter::SIZE));
153///
154/// // A bitmask, not an enum: a bit Windows defines and this crate has never
155/// // heard of still reaches it.
156/// let unknown = NotifyFilter::from_bits(0x8000_0000);
157/// assert_eq!(unknown.bits(), 0x8000_0000);
158///
159/// // The empty filter watches nothing, and Windows -- not this crate --
160/// // rejects it at the call.
161/// assert_eq!(NotifyFilter::NONE.bits(), 0);
162/// ```
163#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
164pub struct NotifyFilter(FILE_NOTIFY_CHANGE);
165
166impl NotifyFilter {
167 /// An empty filter, which watches for nothing.
168 ///
169 /// Windows rejects this at the call, which is left to happen rather than
170 /// pre-empted here.
171 pub const NONE: Self = Self(0);
172 /// File name creation, deletion, or rename.
173 pub const FILE_NAME: Self = Self(FILE_NOTIFY_CHANGE_FILE_NAME);
174 /// Directory creation or deletion.
175 pub const DIR_NAME: Self = Self(FILE_NOTIFY_CHANGE_DIR_NAME);
176 /// Attribute changes.
177 pub const ATTRIBUTES: Self = Self(FILE_NOTIFY_CHANGE_ATTRIBUTES);
178 /// Size changes, reported when the file is flushed rather than on write.
179 pub const SIZE: Self = Self(FILE_NOTIFY_CHANGE_SIZE);
180 /// Last-write-time changes, likewise reported on flush.
181 pub const LAST_WRITE: Self = Self(FILE_NOTIFY_CHANGE_LAST_WRITE);
182 /// Last-access-time changes.
183 pub const LAST_ACCESS: Self = Self(FILE_NOTIFY_CHANGE_LAST_ACCESS);
184 /// Creation-time changes.
185 pub const CREATION: Self = Self(FILE_NOTIFY_CHANGE_CREATION);
186 /// Security-descriptor changes.
187 pub const SECURITY: Self = Self(FILE_NOTIFY_CHANGE_SECURITY);
188
189 /// Wraps a raw `FILE_NOTIFY_CHANGE` mask.
190 ///
191 /// Any bit pattern is accepted: the crate does not decide which bits
192 /// Windows understands.
193 #[must_use]
194 pub const fn from_bits(bits: FILE_NOTIFY_CHANGE) -> Self {
195 Self(bits)
196 }
197
198 /// The raw mask.
199 #[must_use]
200 pub const fn bits(self) -> FILE_NOTIFY_CHANGE {
201 self.0
202 }
203
204 /// The union of two filters.
205 #[must_use]
206 pub const fn union(self, other: Self) -> Self {
207 Self(self.0 | other.0)
208 }
209
210 /// Whether every bit of `other` is set in this filter.
211 #[must_use]
212 pub const fn contains(self, other: Self) -> bool {
213 self.0 & other.0 == other.0
214 }
215}
216
217impl std::ops::BitOr for NotifyFilter {
218 type Output = Self;
219
220 fn bitor(self, other: Self) -> Self {
221 self.union(other)
222 }
223}
224
225/// An owned, marshalable parameter set for `FindFirstChangeNotificationW`.
226///
227/// # Example
228///
229/// ```
230/// use windows_namespace_request_sys::prepare;
231/// use windows_namespace_request_sys::watch::{NotifyFilter, WatchDirectory};
232/// use wtf_string::Wtf16String;
233///
234/// let directory = std::env::temp_dir();
235/// let text = directory.to_str().expect("the temporary directory is valid UTF-8");
236///
237/// let request = WatchDirectory::new(prepare(&Wtf16String::from(text))?)
238/// .with_subtree(true)
239/// .with_filter(NotifyFilter::FILE_NAME | NotifyFilter::DIR_NAME);
240///
241/// let notification = request.perform()?;
242/// // The handle is closed with FindCloseChangeNotification, which the type
243/// // remembers on the caller's behalf.
244/// drop(notification);
245/// # Ok::<(), Box<dyn std::error::Error>>(())
246/// ```
247#[derive(Clone, Debug)]
248#[must_use = "an unperformed request watches nothing"]
249pub struct WatchDirectory {
250 path: PreparedPath,
251 subtree: bool,
252 filter: NotifyFilter,
253}
254
255impl WatchDirectory {
256 /// Begins a request to watch `path`.
257 ///
258 /// The watch starts non-recursive and watching for nothing; both are set
259 /// explicitly, as everywhere else in this crate.
260 pub fn new(path: PreparedPath) -> Self {
261 Self {
262 path,
263 subtree: false,
264 filter: NotifyFilter::NONE,
265 }
266 }
267
268 /// Sets `bWatchSubtree`.
269 pub fn with_subtree(mut self, subtree: bool) -> Self {
270 self.subtree = subtree;
271 self
272 }
273
274 /// Sets `dwNotifyFilter`.
275 pub fn with_filter(mut self, filter: NotifyFilter) -> Self {
276 self.filter = filter;
277 self
278 }
279
280 /// The prepared path this request will watch.
281 #[must_use]
282 pub fn path(&self) -> &PreparedPath {
283 &self.path
284 }
285
286 /// Whether the watch covers the whole subtree.
287 #[must_use]
288 pub fn subtree(&self) -> bool {
289 self.subtree
290 }
291
292 /// The change classes the watch reports.
293 #[must_use]
294 pub fn filter(&self) -> NotifyFilter {
295 self.filter
296 }
297
298 /// Performs the call on the calling thread.
299 ///
300 /// # Errors
301 ///
302 /// Returns the raw Win32 code, unaltered.
303 pub fn perform(&self) -> Outcome<ChangeNotification> {
304 let raw = perform_handle(|| {
305 // SAFETY: the path is NUL-terminated and outlives the call, and
306 // both remaining arguments are plain values.
307 unsafe {
308 FindFirstChangeNotificationW(
309 self.path.as_wtf16_terminated(),
310 i32::from(self.subtree),
311 self.filter.bits(),
312 )
313 }
314 })?;
315
316 Ok(ChangeNotification {
317 handle: raw.cast::<c_void>(),
318 })
319 }
320}
321
322impl fmt::Display for NotifyFilter {
323 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
324 write!(f, "FILE_NOTIFY_CHANGE({:#x})", self.0)
325 }
326}
327
328impl crate::request::Request for WatchDirectory {
329 type Error = crate::Win32Error;
330 type Output = ChangeNotification;
331
332 fn perform(&self) -> Outcome<ChangeNotification> {
333 Self::perform(self)
334 }
335}
336
337#[cfg(test)]
338mod tests;