Skip to main content

windows_namespace_request_sys/
open.rs

1// Copyright (c) Mike Grier.
2
3//! The `CreateFileW` entry.
4//!
5//! Entry 1 of the audited catalogue, and the only one all three audited
6//! consumers use. It captures the complete parameter set on the calling thread
7//! and performs the open faithfully wherever it is executed.
8//!
9//! # The overlapped split is a field, not a policy
10//!
11//! Two of the audited consumers open without `FILE_FLAG_OVERLAPPED` and one
12//! opens with it, because the watcher's handle is destined for a completion
13//! port and the other two are not. That difference belongs to the caller: this
14//! entry carries whatever flags it was given and never adds, removes, or
15//! second-guesses one. Deciding it here would be the delivery-model choice the
16//! crate refuses to make -- an opened handle comes back plain and unassociated
17//! either way, and associating it is a later layer's call.
18//!
19//! # Nothing is defaulted on the caller's behalf
20//!
21//! `FILE_FLAG_BACKUP_SEMANTICS` is mandatory to open a *directory* at all, and
22//! every audited consumer passes it. It is still not implied here. An entry
23//! that quietly added a flag would be deciding what the caller meant, and the
24//! same field is what a caller opening a plain file must be able to leave out.
25
26use std::ffi::c_void;
27use std::os::windows::io::{FromRawHandle, OwnedHandle};
28use std::ptr;
29
30use windows_sys::Win32::Storage::FileSystem::{
31    CreateFileW, FILE_CREATION_DISPOSITION, FILE_FLAGS_AND_ATTRIBUTES, FILE_SHARE_MODE,
32};
33
34use wtf_string::{Wtf16Str, Wtf16String};
35
36use crate::anchored::{AnchoredPath, PathCaptureError};
37use crate::handle::{CapturedHandle, HandleCaptureError};
38use crate::outcome::{Outcome, perform_handle};
39use crate::security::SecurityAttributes;
40
41/// An owned, marshalable parameter set for `CreateFileW`.
42///
43/// Every parameter of the underlying call is expressible, including the two no
44/// audited consumer uses. An entry that could not express two of its own call's
45/// parameters would be a *narrowed* `CreateFileW`, and narrowing a platform
46/// entry to fit the consumers currently in view is precisely the anti-pattern
47/// this workspace's platform-integrity rule names.
48///
49/// # Example
50///
51/// ```
52/// use std::fs;
53///
54/// use windows_namespace_request_sys::open::OpenFile;
55/// use windows_sys::Win32::Storage::FileSystem::{
56///     FILE_GENERIC_READ, FILE_SHARE_READ, OPEN_EXISTING,
57/// };
58/// use wtf_string::Wtf16String;
59///
60/// let path = std::env::temp_dir().join(format!("wnrs-open-{}.tmp", std::process::id()));
61/// fs::write(&path, b"example")?;
62///
63/// // Built on this thread, where the current directory still means what the
64/// // caller thinks it means: a relative path would be fixed to it here.
65/// let text = path.to_str().expect("a temporary path is valid UTF-8");
66/// let request = OpenFile::new(Wtf16String::from(text))?
67///     .with_desired_access(FILE_GENERIC_READ)
68///     .with_share_mode(FILE_SHARE_READ)
69///     .with_creation_disposition(OPEN_EXISTING);
70///
71/// // Performed here, but it would behave identically on any other thread.
72/// let opened = fs::File::from(request.perform()?);
73/// assert_eq!(opened.metadata()?.len(), b"example".len() as u64);
74/// # drop(opened);
75/// # fs::remove_file(&path)?;
76/// # Ok::<(), Box<dyn std::error::Error>>(())
77/// ```
78#[derive(Debug)]
79#[must_use = "an unperformed request opens nothing"]
80pub struct OpenFile {
81    path: AnchoredPath,
82    desired_access: u32,
83    share_mode: FILE_SHARE_MODE,
84    security: Option<SecurityAttributes>,
85    creation_disposition: FILE_CREATION_DISPOSITION,
86    flags_and_attributes: FILE_FLAGS_AND_ATTRIBUTES,
87    template: Option<CapturedHandle>,
88}
89
90impl OpenFile {
91    /// Begins a request against `path`.
92    ///
93    /// The path reaches `CreateFileW` as it was given, with no length limit and no check of this
94    /// crate's own, with one exception: a path that Win32 would resolve against the current
95    /// directory is fixed to the current directory **as it is now**, by concatenation, so that it
96    /// still names the same place when the request is performed on another thread after the
97    /// directory has changed. A path that is not clearly a plain relative one is left exactly as
98    /// given, and Win32 resolves it when the call is made; the rules, and what they leave out, are
99    /// those of `D-19` in the crate's `DESIGN-NOTES.md`. [`path`](Self::path) is the path as given
100    /// and [`win32_path`](Self::win32_path) is the one handed to the call.
101    ///
102    /// Every other parameter starts at the value that means "the caller said
103    /// nothing": no access, no sharing, no security attributes, a zero creation
104    /// disposition, no flags, and no template. They are set explicitly rather
105    /// than defaulted to a plausible-looking open, because a plausible default
106    /// is exactly what a caller cannot see they got.
107    ///
108    /// # Errors
109    ///
110    /// [`PathCaptureError::InteriorNul`] if the path contains a NUL, which Win32 would read as its
111    /// end, and the failure of `GetCurrentDirectoryW` if it had to be read and could not be.
112    pub fn new(path: Wtf16String) -> Result<Self, PathCaptureError> {
113        Ok(Self {
114            path: AnchoredPath::new(path)?,
115            desired_access: 0,
116            share_mode: 0,
117            security: None,
118            creation_disposition: 0,
119            flags_and_attributes: 0,
120            template: None,
121        })
122    }
123
124    /// Sets `dwDesiredAccess`.
125    pub fn with_desired_access(mut self, desired_access: u32) -> Self {
126        self.desired_access = desired_access;
127        self
128    }
129
130    /// Sets `dwShareMode`.
131    pub fn with_share_mode(mut self, share_mode: FILE_SHARE_MODE) -> Self {
132        self.share_mode = share_mode;
133        self
134    }
135
136    /// Sets `lpSecurityAttributes` from an already-captured value.
137    ///
138    /// Passing `None` means a null argument: default security and a
139    /// non-inheritable handle. That is a different outcome from attributes
140    /// carrying a null descriptor, which is why the distinction survives into
141    /// this type rather than being flattened here.
142    pub fn with_security(mut self, security: Option<SecurityAttributes>) -> Self {
143        self.security = security;
144        self
145    }
146
147    /// Sets `dwCreationDisposition`.
148    pub fn with_creation_disposition(
149        mut self,
150        creation_disposition: FILE_CREATION_DISPOSITION,
151    ) -> Self {
152        self.creation_disposition = creation_disposition;
153        self
154    }
155
156    /// Sets `dwFlagsAndAttributes`.
157    ///
158    /// Carried verbatim, including `FILE_FLAG_OVERLAPPED`. Whether the opened
159    /// handle is destined for a completion port is the caller's to state and
160    /// this crate's to leave alone.
161    pub fn with_flags_and_attributes(
162        mut self,
163        flags_and_attributes: FILE_FLAGS_AND_ATTRIBUTES,
164    ) -> Self {
165        self.flags_and_attributes = flags_and_attributes;
166        self
167    }
168
169    /// Sets `hTemplateFile` from an already-captured handle.
170    ///
171    /// The request owns a duplicate, so it cannot be left naming a template the
172    /// caller has since closed.
173    pub fn with_template(mut self, template: Option<CapturedHandle>) -> Self {
174        self.template = template;
175        self
176    }
177
178    /// The path as it was given.
179    #[must_use]
180    pub fn path(&self) -> &Wtf16Str {
181        self.path.given()
182    }
183
184    /// The path that is handed to `CreateFileW`: the one given, or that one fixed to the current
185    /// directory of the moment the request was built.
186    #[must_use]
187    pub fn win32_path(&self) -> &Wtf16Str {
188        self.path.win32()
189    }
190
191    /// The requested access mask.
192    #[must_use]
193    pub fn desired_access(&self) -> u32 {
194        self.desired_access
195    }
196
197    /// The requested share mode.
198    #[must_use]
199    pub fn share_mode(&self) -> FILE_SHARE_MODE {
200        self.share_mode
201    }
202
203    /// The captured security attributes, if any were supplied.
204    #[must_use]
205    pub fn security(&self) -> Option<&SecurityAttributes> {
206        self.security.as_ref()
207    }
208
209    /// The requested creation disposition.
210    #[must_use]
211    pub fn creation_disposition(&self) -> FILE_CREATION_DISPOSITION {
212        self.creation_disposition
213    }
214
215    /// The requested flags and attributes.
216    #[must_use]
217    pub fn flags_and_attributes(&self) -> FILE_FLAGS_AND_ATTRIBUTES {
218        self.flags_and_attributes
219    }
220
221    /// The captured template handle, if one was supplied.
222    #[must_use]
223    pub fn template(&self) -> Option<&CapturedHandle> {
224        self.template.as_ref()
225    }
226
227    /// Copies the request, duplicating the template handle if it has one.
228    ///
229    /// This is not `Clone` because a request may own a handle, and duplicating
230    /// a handle is fallible, as is copying the security attributes it may own.
231    /// The type inherits that from [`CapturedHandle::try_clone`] and
232    /// [`SecurityAttributes::try_clone`] rather than hiding it behind an infallible
233    /// signature that would have to panic.
234    ///
235    /// # Errors
236    ///
237    /// Returns the handle-capture failure when the template cannot be
238    /// duplicated, or [`crate::HandleCaptureFailure::CopySecurity`] when the memory for the
239    /// security attributes cannot be allocated. A request with neither cannot fail.
240    pub fn try_clone(&self) -> Result<Self, HandleCaptureError> {
241        let template = self
242            .template
243            .as_ref()
244            .map(CapturedHandle::try_clone)
245            .transpose()?;
246        let security = self
247            .security
248            .as_ref()
249            .map(SecurityAttributes::try_clone)
250            .transpose()
251            .map_err(|error| HandleCaptureError::copying_security(error.into_source()))?;
252
253        Ok(Self {
254            path: self.path.clone(),
255            desired_access: self.desired_access,
256            share_mode: self.share_mode,
257            security,
258            creation_disposition: self.creation_disposition,
259            flags_and_attributes: self.flags_and_attributes,
260            template,
261        })
262    }
263
264    /// Performs the open on the calling thread.
265    ///
266    /// The handle comes back **plain and unassociated**: nothing here binds it
267    /// to a completion port, because doing so irreversibly forecloses `IoRing`
268    /// use of it and that choice belongs to a layer that knows the handle's
269    /// destination.
270    ///
271    /// # Errors
272    ///
273    /// Returns the raw Win32 code, unaltered and snapshotted before any cleanup
274    /// can overwrite it. `ERROR_FILE_NOT_FOUND` here means a missing path and
275    /// nothing else is inferred from it.
276    pub fn perform(&self) -> Outcome<OwnedHandle> {
277        let attributes = self.security.as_ref().map(SecurityAttributes::to_raw);
278        let attributes_ptr = attributes.as_ref().map_or(ptr::null(), ptr::from_ref);
279        let template = self
280            .template
281            .as_ref()
282            .map_or(ptr::null_mut(), |handle| handle.raw());
283
284        let raw = perform_handle(|| {
285            // SAFETY: the path has no interior NUL, since the request refused one when it was
286            // built, is NUL-terminated and outlives the call; the
287            // security attributes, when present, are a live struct pointing at
288            // a self-relative descriptor this request owns; the template, when
289            // present, is a duplicate this request owns. Every other argument
290            // is a plain value.
291            unsafe {
292                CreateFileW(
293                    self.path.win32_terminated(),
294                    self.desired_access,
295                    self.share_mode,
296                    attributes_ptr,
297                    self.creation_disposition,
298                    self.flags_and_attributes,
299                    template,
300                )
301            }
302        })?;
303
304        // SAFETY: a successful CreateFileW returns a handle this process owns
305        // exclusively and must release with CloseHandle, which OwnedHandle
306        // does.
307        Ok(unsafe { OwnedHandle::from_raw_handle(raw.cast::<c_void>()) })
308    }
309}
310
311impl crate::request::Request for OpenFile {
312    type Error = crate::Win32Error;
313    type Output = OwnedHandle;
314
315    fn perform(&self) -> Outcome<OwnedHandle> {
316        Self::perform(self)
317    }
318}
319
320#[cfg(test)]
321mod tests;