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 crate::handle::{CapturedHandle, HandleCaptureError};
35use crate::outcome::{Outcome, perform_handle};
36use crate::path::PreparedPath;
37use crate::security::SecurityAttributes;
38
39/// An owned, marshalable parameter set for `CreateFileW`.
40///
41/// Every parameter of the underlying call is expressible, including the two no
42/// audited consumer uses. An entry that could not express two of its own call's
43/// parameters would be a *narrowed* `CreateFileW`, and narrowing a platform
44/// entry to fit the consumers currently in view is precisely the anti-pattern
45/// this workspace's platform-integrity rule names.
46///
47/// # Example
48///
49/// ```
50/// use std::fs;
51///
52/// use windows_namespace_request_sys::open::OpenFile;
53/// use windows_namespace_request_sys::prepare;
54/// use wtf_string::Wtf16String;
55/// use windows_sys::Win32::Storage::FileSystem::{
56///     FILE_GENERIC_READ, FILE_SHARE_READ, OPEN_EXISTING,
57/// };
58///
59/// let path = std::env::temp_dir().join(format!("wnrs-open-{}.tmp", std::process::id()));
60/// fs::write(&path, b"example")?;
61///
62/// // Built on this thread, where the current directory still means what the
63/// // caller thinks it means.
64/// let text = path.to_str().expect("a temporary path is valid UTF-8");
65/// let request = OpenFile::new(prepare(&Wtf16String::from(text))?)
66///     .with_desired_access(FILE_GENERIC_READ)
67///     .with_share_mode(FILE_SHARE_READ)
68///     .with_creation_disposition(OPEN_EXISTING);
69///
70/// // Performed here, but it would behave identically on any other thread.
71/// let opened = fs::File::from(request.perform()?);
72/// assert_eq!(opened.metadata()?.len(), b"example".len() as u64);
73/// # drop(opened);
74/// # fs::remove_file(&path)?;
75/// # Ok::<(), Box<dyn std::error::Error>>(())
76/// ```
77#[derive(Debug)]
78#[must_use = "an unperformed request opens nothing"]
79pub struct OpenFile {
80    path: PreparedPath,
81    desired_access: u32,
82    share_mode: FILE_SHARE_MODE,
83    security: Option<SecurityAttributes>,
84    creation_disposition: FILE_CREATION_DISPOSITION,
85    flags_and_attributes: FILE_FLAGS_AND_ATTRIBUTES,
86    template: Option<CapturedHandle>,
87}
88
89impl OpenFile {
90    /// Begins a request against `path`.
91    ///
92    /// Every other parameter starts at the value that means "the caller said
93    /// nothing": no access, no sharing, no security attributes, a zero creation
94    /// disposition, no flags, and no template. They are set explicitly rather
95    /// than defaulted to a plausible-looking open, because a plausible default
96    /// is exactly what a caller cannot see they got.
97    pub fn new(path: PreparedPath) -> Self {
98        Self {
99            path,
100            desired_access: 0,
101            share_mode: 0,
102            security: None,
103            creation_disposition: 0,
104            flags_and_attributes: 0,
105            template: None,
106        }
107    }
108
109    /// Sets `dwDesiredAccess`.
110    pub fn with_desired_access(mut self, desired_access: u32) -> Self {
111        self.desired_access = desired_access;
112        self
113    }
114
115    /// Sets `dwShareMode`.
116    pub fn with_share_mode(mut self, share_mode: FILE_SHARE_MODE) -> Self {
117        self.share_mode = share_mode;
118        self
119    }
120
121    /// Sets `lpSecurityAttributes` from an already-captured value.
122    ///
123    /// Passing `None` means a null argument: default security and a
124    /// non-inheritable handle. That is a different outcome from attributes
125    /// carrying a null descriptor, which is why the distinction survives into
126    /// this type rather than being flattened here.
127    pub fn with_security(mut self, security: Option<SecurityAttributes>) -> Self {
128        self.security = security;
129        self
130    }
131
132    /// Sets `dwCreationDisposition`.
133    pub fn with_creation_disposition(
134        mut self,
135        creation_disposition: FILE_CREATION_DISPOSITION,
136    ) -> Self {
137        self.creation_disposition = creation_disposition;
138        self
139    }
140
141    /// Sets `dwFlagsAndAttributes`.
142    ///
143    /// Carried verbatim, including `FILE_FLAG_OVERLAPPED`. Whether the opened
144    /// handle is destined for a completion port is the caller's to state and
145    /// this crate's to leave alone.
146    pub fn with_flags_and_attributes(
147        mut self,
148        flags_and_attributes: FILE_FLAGS_AND_ATTRIBUTES,
149    ) -> Self {
150        self.flags_and_attributes = flags_and_attributes;
151        self
152    }
153
154    /// Sets `hTemplateFile` from an already-captured handle.
155    ///
156    /// The request owns a duplicate, so it cannot be left naming a template the
157    /// caller has since closed.
158    pub fn with_template(mut self, template: Option<CapturedHandle>) -> Self {
159        self.template = template;
160        self
161    }
162
163    /// The prepared path this request will open.
164    #[must_use]
165    pub fn path(&self) -> &PreparedPath {
166        &self.path
167    }
168
169    /// The requested access mask.
170    #[must_use]
171    pub fn desired_access(&self) -> u32 {
172        self.desired_access
173    }
174
175    /// The requested share mode.
176    #[must_use]
177    pub fn share_mode(&self) -> FILE_SHARE_MODE {
178        self.share_mode
179    }
180
181    /// The captured security attributes, if any were supplied.
182    #[must_use]
183    pub fn security(&self) -> Option<&SecurityAttributes> {
184        self.security.as_ref()
185    }
186
187    /// The requested creation disposition.
188    #[must_use]
189    pub fn creation_disposition(&self) -> FILE_CREATION_DISPOSITION {
190        self.creation_disposition
191    }
192
193    /// The requested flags and attributes.
194    #[must_use]
195    pub fn flags_and_attributes(&self) -> FILE_FLAGS_AND_ATTRIBUTES {
196        self.flags_and_attributes
197    }
198
199    /// The captured template handle, if one was supplied.
200    #[must_use]
201    pub fn template(&self) -> Option<&CapturedHandle> {
202        self.template.as_ref()
203    }
204
205    /// Copies the request, duplicating the template handle if it has one.
206    ///
207    /// This is not `Clone` because a request may own a handle, and duplicating
208    /// a handle is fallible, as is copying the security attributes it may own.
209    /// The type inherits that from [`CapturedHandle::try_clone`] and
210    /// [`SecurityAttributes::try_clone`] rather than hiding it behind an infallible
211    /// signature that would have to panic.
212    ///
213    /// # Errors
214    ///
215    /// Returns the handle-capture failure when the template cannot be
216    /// duplicated, or [`crate::HandleCaptureFailure::CopySecurity`] when the memory for the
217    /// security attributes cannot be allocated. A request with neither cannot fail.
218    pub fn try_clone(&self) -> Result<Self, HandleCaptureError> {
219        let template = self
220            .template
221            .as_ref()
222            .map(CapturedHandle::try_clone)
223            .transpose()?;
224        let security = self
225            .security
226            .as_ref()
227            .map(SecurityAttributes::try_clone)
228            .transpose()
229            .map_err(|error| HandleCaptureError::copying_security(error.into_source()))?;
230
231        Ok(Self {
232            path: self.path.clone(),
233            desired_access: self.desired_access,
234            share_mode: self.share_mode,
235            security,
236            creation_disposition: self.creation_disposition,
237            flags_and_attributes: self.flags_and_attributes,
238            template,
239        })
240    }
241
242    /// Performs the open on the calling thread.
243    ///
244    /// The handle comes back **plain and unassociated**: nothing here binds it
245    /// to a completion port, because doing so irreversibly forecloses `IoRing`
246    /// use of it and that choice belongs to a layer that knows the handle's
247    /// destination.
248    ///
249    /// # Errors
250    ///
251    /// Returns the raw Win32 code, unaltered and snapshotted before any cleanup
252    /// can overwrite it. `ERROR_FILE_NOT_FOUND` here means a missing path and
253    /// nothing else is inferred from it.
254    pub fn perform(&self) -> Outcome<OwnedHandle> {
255        let attributes = self.security.as_ref().map(SecurityAttributes::to_raw);
256        let attributes_ptr = attributes.as_ref().map_or(ptr::null(), ptr::from_ref);
257        let template = self
258            .template
259            .as_ref()
260            .map_or(ptr::null_mut(), |handle| handle.raw());
261
262        let raw = perform_handle(|| {
263            // SAFETY: the path is NUL-terminated and outlives the call; the
264            // security attributes, when present, are a live struct pointing at
265            // a self-relative descriptor this request owns; the template, when
266            // present, is a duplicate this request owns. Every other argument
267            // is a plain value.
268            unsafe {
269                CreateFileW(
270                    self.path.as_wtf16_terminated(),
271                    self.desired_access,
272                    self.share_mode,
273                    attributes_ptr,
274                    self.creation_disposition,
275                    self.flags_and_attributes,
276                    template,
277                )
278            }
279        })?;
280
281        // SAFETY: a successful CreateFileW returns a handle this process owns
282        // exclusively and must release with CloseHandle, which OwnedHandle
283        // does.
284        Ok(unsafe { OwnedHandle::from_raw_handle(raw.cast::<c_void>()) })
285    }
286}
287
288impl crate::request::Request for OpenFile {
289    type Error = crate::Win32Error;
290    type Output = OwnedHandle;
291
292    fn perform(&self) -> Outcome<OwnedHandle> {
293        Self::perform(self)
294    }
295}
296
297#[cfg(test)]
298mod tests;