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 wtf_string::{Wtf16Str, Wtf16String};
36
37use crate::anchored::{AnchoredPath, PathCaptureError};
38use crate::outcome::{Outcome, perform_bool, perform_handle};
39
40/// An owned change-notification handle.
41///
42/// Closed with `FindCloseChangeNotification` on drop. The type exists so that
43/// the close routine travels with the handle rather than being something each
44/// call site has to remember, which is the same shape
45/// [windows-threadpool-sys](https://docs.rs/windows-threadpool-sys) already
46/// needed for wait targets.
47#[derive(Debug)]
48#[must_use = "dropping the notification stops the watch"]
49pub struct ChangeNotification {
50    /// Always a live handle produced by `FindFirstChangeNotificationW`, until
51    /// `Drop` closes it.
52    handle: HANDLE,
53}
54
55impl ChangeNotification {
56    /// The raw handle, for passing to a wait.
57    ///
58    /// Borrowed, not transferred: it stays owned by this value. Do **not** pass
59    /// it to `CloseHandle`.
60    #[must_use]
61    pub fn as_raw(&self) -> HANDLE {
62        self.handle
63    }
64
65    /// Rearms the watch after it has signalled.
66    ///
67    /// A notification handle signals once and then stays signalled until it is
68    /// rearmed, so a consumer that waits in a loop must call this between
69    /// waits.
70    ///
71    /// # Errors
72    ///
73    /// Returns the raw Win32 code, unaltered.
74    ///
75    /// # Example
76    ///
77    /// Without the rearm, the second wait returns immediately on the *first*
78    /// change forever, and a consumer's loop spins:
79    ///
80    /// ```
81    /// use std::fs;
82    ///
83    /// use windows_namespace_request_sys::watch::{NotifyFilter, WatchDirectory};
84    /// use windows_sys::Win32::Foundation::WAIT_OBJECT_0;
85    /// use windows_sys::Win32::System::Threading::WaitForSingleObject;
86    /// use wtf_string::Wtf16String;
87    ///
88    /// let directory = std::env::temp_dir().join(format!("wnrs-rearm-{}", std::process::id()));
89    /// let _ = fs::remove_dir_all(&directory);
90    /// fs::create_dir_all(&directory)?;
91    ///
92    /// let text = directory.to_str().expect("a temporary path is valid UTF-8");
93    /// let notification = WatchDirectory::new(Wtf16String::from(text))?
94    ///     .with_filter(NotifyFilter::FILE_NAME)
95    ///     .perform()?;
96    ///
97    /// fs::write(directory.join("first.t"), b"x")?;
98    /// // SAFETY: the handle is live for the notification's lifetime.
99    /// assert_eq!(unsafe { WaitForSingleObject(notification.as_raw(), 5_000) }, WAIT_OBJECT_0);
100    ///
101    /// // The handle stays signalled until this runs.
102    /// notification.rearm()?;
103    ///
104    /// fs::write(directory.join("second.t"), b"x")?;
105    /// // SAFETY: as above.
106    /// assert_eq!(unsafe { WaitForSingleObject(notification.as_raw(), 5_000) }, WAIT_OBJECT_0);
107    /// # drop(notification);
108    /// # let _ = fs::remove_dir_all(&directory);
109    /// # Ok::<(), Box<dyn std::error::Error>>(())
110    /// ```
111    pub fn rearm(&self) -> Outcome<()> {
112        // SAFETY: the handle is live for this value's lifetime.
113        perform_bool(|| unsafe { FindNextChangeNotification(self.handle) })
114    }
115}
116
117impl Drop for ChangeNotification {
118    fn drop(&mut self) {
119        // SAFETY: the handle came from FindFirstChangeNotificationW and has not
120        // been closed, since only this Drop closes it. The result is
121        // deliberately ignored: a close failure during drop has nowhere to go,
122        // and this crate does not panic on it.
123        unsafe {
124            FindCloseChangeNotification(self.handle);
125        }
126    }
127}
128
129// SAFETY: the value owns its handle exclusively and has no interior
130// mutability. A Windows handle is process-wide rather than thread-affine, so
131// moving it between threads and sharing a shared reference are both sound; the
132// raw pointer is what blocks the automatic derivation.
133unsafe impl Send for ChangeNotification {}
134// SAFETY: as above. `rearm` takes `&self` and is a single Win32 call that
135// Windows serialises internally.
136unsafe impl Sync for ChangeNotification {}
137
138/// Which changes a watch reports.
139///
140/// A thin newtype over `FILE_NOTIFY_CHANGE` rather than an enum, because the
141/// value is a bitmask and Windows may define bits this crate has not heard of.
142/// The named constants are provided for convenience; an unknown bit still
143/// reaches Windows unaltered.
144///
145/// # Example
146///
147/// ```
148/// use windows_namespace_request_sys::watch::NotifyFilter;
149///
150/// let combined = NotifyFilter::FILE_NAME | NotifyFilter::DIR_NAME;
151///
152/// assert!(combined.contains(NotifyFilter::FILE_NAME));
153/// assert!(!combined.contains(NotifyFilter::SIZE));
154///
155/// // A bitmask, not an enum: a bit Windows defines and this crate has never
156/// // heard of still reaches it.
157/// let unknown = NotifyFilter::from_bits(0x8000_0000);
158/// assert_eq!(unknown.bits(), 0x8000_0000);
159///
160/// // The empty filter watches nothing, and Windows -- not this crate --
161/// // rejects it at the call.
162/// assert_eq!(NotifyFilter::NONE.bits(), 0);
163/// ```
164#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
165pub struct NotifyFilter(FILE_NOTIFY_CHANGE);
166
167impl NotifyFilter {
168    /// An empty filter, which watches for nothing.
169    ///
170    /// Windows rejects this at the call, which is left to happen rather than
171    /// pre-empted here.
172    pub const NONE: Self = Self(0);
173    /// File name creation, deletion, or rename.
174    pub const FILE_NAME: Self = Self(FILE_NOTIFY_CHANGE_FILE_NAME);
175    /// Directory creation or deletion.
176    pub const DIR_NAME: Self = Self(FILE_NOTIFY_CHANGE_DIR_NAME);
177    /// Attribute changes.
178    pub const ATTRIBUTES: Self = Self(FILE_NOTIFY_CHANGE_ATTRIBUTES);
179    /// Size changes, reported when the file is flushed rather than on write.
180    pub const SIZE: Self = Self(FILE_NOTIFY_CHANGE_SIZE);
181    /// Last-write-time changes, likewise reported on flush.
182    pub const LAST_WRITE: Self = Self(FILE_NOTIFY_CHANGE_LAST_WRITE);
183    /// Last-access-time changes.
184    pub const LAST_ACCESS: Self = Self(FILE_NOTIFY_CHANGE_LAST_ACCESS);
185    /// Creation-time changes.
186    pub const CREATION: Self = Self(FILE_NOTIFY_CHANGE_CREATION);
187    /// Security-descriptor changes.
188    pub const SECURITY: Self = Self(FILE_NOTIFY_CHANGE_SECURITY);
189
190    /// Wraps a raw `FILE_NOTIFY_CHANGE` mask.
191    ///
192    /// Any bit pattern is accepted: the crate does not decide which bits
193    /// Windows understands.
194    #[must_use]
195    pub const fn from_bits(bits: FILE_NOTIFY_CHANGE) -> Self {
196        Self(bits)
197    }
198
199    /// The raw mask.
200    #[must_use]
201    pub const fn bits(self) -> FILE_NOTIFY_CHANGE {
202        self.0
203    }
204
205    /// The union of two filters.
206    #[must_use]
207    pub const fn union(self, other: Self) -> Self {
208        Self(self.0 | other.0)
209    }
210
211    /// Whether every bit of `other` is set in this filter.
212    #[must_use]
213    pub const fn contains(self, other: Self) -> bool {
214        self.0 & other.0 == other.0
215    }
216}
217
218impl std::ops::BitOr for NotifyFilter {
219    type Output = Self;
220
221    fn bitor(self, other: Self) -> Self {
222        self.union(other)
223    }
224}
225
226/// An owned, marshalable parameter set for `FindFirstChangeNotificationW`.
227///
228/// # Example
229///
230/// ```
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(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: AnchoredPath,
251    subtree: bool,
252    filter: NotifyFilter,
253}
254
255impl WatchDirectory {
256    /// Begins a request to watch `path`.
257    ///
258    /// The path reaches the call as it was given, except that one Win32 would resolve against the
259    /// current directory is fixed to the current directory **as it is now**, so that it still names
260    /// the same place when the request is performed on another thread after the directory has
261    /// changed; see [`OpenFile::new`](crate::open::OpenFile::new) and `D-19` in the crate's
262    /// `DESIGN-NOTES.md`. [`path`](Self::path) is the path as given and
263    /// [`win32_path`](Self::win32_path) is the one handed to the call.
264    ///
265    /// The watch starts non-recursive and watching for nothing; both are set
266    /// explicitly, as everywhere else in this crate.
267    ///
268    /// # Errors
269    ///
270    /// [`PathCaptureError::InteriorNul`] if the path contains a NUL, which Win32 would read as its
271    /// end, and the failure of `GetCurrentDirectoryW` if it had to be read and could not be.
272    pub fn new(path: Wtf16String) -> Result<Self, PathCaptureError> {
273        Ok(Self::from_anchored(AnchoredPath::new(path)?))
274    }
275
276    /// Begins a request to watch a path the caller has already anchored.
277    ///
278    /// As [`OpenFile::from_anchored`](crate::open::OpenFile::from_anchored): the path is not anchored
279    /// again, and the request hands the call the anchored path's [`win32`](AnchoredPath::win32) form.
280    /// The watch starts as [`new`](Self::new) starts it.
281    ///
282    /// # Example
283    ///
284    /// ```
285    /// use windows_namespace_request_sys::{AnchoredPath, WatchDirectory};
286    /// use wtf_string::Wtf16String;
287    ///
288    /// let anchored = AnchoredPath::new(Wtf16String::from("a-child"))?;
289    ///
290    /// let request = WatchDirectory::from_anchored(anchored.clone());
291    /// assert_eq!(request.path(), anchored.given());
292    /// assert_eq!(request.win32_path(), anchored.win32());
293    /// # Ok::<(), Box<dyn std::error::Error>>(())
294    /// ```
295    pub fn from_anchored(path: AnchoredPath) -> Self {
296        Self {
297            path,
298            subtree: false,
299            filter: NotifyFilter::NONE,
300        }
301    }
302
303    /// Sets `bWatchSubtree`.
304    pub fn with_subtree(mut self, subtree: bool) -> Self {
305        self.subtree = subtree;
306        self
307    }
308
309    /// Sets `dwNotifyFilter`.
310    pub fn with_filter(mut self, filter: NotifyFilter) -> Self {
311        self.filter = filter;
312        self
313    }
314
315    /// The path as it was given.
316    #[must_use]
317    pub fn path(&self) -> &Wtf16Str {
318        self.path.given()
319    }
320
321    /// The path that is handed to `FindFirstChangeNotificationW`: the one given, or that one fixed
322    /// to the current directory of the moment the request was built.
323    #[must_use]
324    pub fn win32_path(&self) -> &Wtf16Str {
325        self.path.win32()
326    }
327
328    /// Whether the watch covers the whole subtree.
329    #[must_use]
330    pub fn subtree(&self) -> bool {
331        self.subtree
332    }
333
334    /// The change classes the watch reports.
335    #[must_use]
336    pub fn filter(&self) -> NotifyFilter {
337        self.filter
338    }
339
340    /// Performs the call on the calling thread.
341    ///
342    /// # Errors
343    ///
344    /// Returns the raw Win32 code, unaltered.
345    pub fn perform(&self) -> Outcome<ChangeNotification> {
346        let raw = perform_handle(|| {
347            // SAFETY: the path has no interior NUL, since the request refused one when it was
348            // built, is NUL-terminated and outlives the call, and both remaining arguments are
349            // plain values.
350            unsafe {
351                FindFirstChangeNotificationW(
352                    self.path.win32_terminated(),
353                    i32::from(self.subtree),
354                    self.filter.bits(),
355                )
356            }
357        })?;
358
359        Ok(ChangeNotification {
360            handle: raw.cast::<c_void>(),
361        })
362    }
363}
364
365impl fmt::Display for NotifyFilter {
366    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
367        write!(f, "FILE_NOTIFY_CHANGE({:#x})", self.0)
368    }
369}
370
371impl crate::request::Request for WatchDirectory {
372    type Error = crate::Win32Error;
373    type Output = ChangeNotification;
374
375    fn perform(&self) -> Outcome<ChangeNotification> {
376        Self::perform(self)
377    }
378}
379
380#[cfg(test)]
381mod tests;