Skip to main content

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;