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