Skip to content

Commit b2445aa

Browse files
committed
Use NtCreateFile to implement open_unchecked on Windows.
Windows' `NtCreateFile` has an ability to take a directory and a relative path, so use that to implement `open_unchecked` instead of using path concatenation. We still use concatenation for other functions, but this is the first step to rewriting those to avoid it. Fixes #226.
1 parent 0ad2154 commit b2445aa

17 files changed

+647
-78
lines changed

Diff for: Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ rustix = { version = "0.36.0", features = ["fs"] }
4040
nt_version = "0.1.3"
4141

4242
[target.'cfg(windows)'.dependencies.windows-sys]
43-
version = "0.42.0"
43+
version = "0.45.0"
4444
features = [
4545
"Win32_Storage_FileSystem",
4646
"Win32_Foundation",

Diff for: cap-directories/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ directories-next = "2.0.0"
2020
rustix = { version = "0.36.0" }
2121

2222
[target.'cfg(windows)'.dependencies.windows-sys]
23-
version = "0.42.0"
23+
version = "0.45.0"
2424
features = [
2525
"Win32_Foundation",
2626
]

Diff for: cap-fs-ext/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ std = ["cap-std"]
3333
#async_std_arf_strings = ["cap-async-std/arf_strings", "async_std_fs_utf8", "arf-strings"]
3434

3535
[target.'cfg(windows)'.dependencies.windows-sys]
36-
version = "0.42.0"
36+
version = "0.45.0"
3737
features = [
3838
"Win32_Storage_FileSystem",
3939
]

Diff for: cap-primitives/Cargo.toml

+6-3
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,15 @@ cap-tempfile = { path = "../cap-tempfile" }
2828
rustix = { version = "0.36.0", features = ["fs", "process", "procfs", "termios", "time"] }
2929

3030
[target.'cfg(windows)'.dependencies]
31-
winx = "0.34.0"
31+
winx = "0.35.0"
3232

3333
[target.'cfg(windows)'.dependencies.windows-sys]
34-
version = "0.42.0"
34+
version = "0.45.0"
3535
features = [
36-
"Win32_Storage_FileSystem",
3736
"Win32_Foundation",
37+
"Win32_Security",
38+
"Win32_Storage_FileSystem",
39+
"Win32_System_Kernel",
3840
"Win32_System_SystemServices",
41+
"Win32_System_WindowsProgramming",
3942
]

Diff for: cap-primitives/src/fs/manually/canonicalize.rs

+28-3
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,40 @@ pub(crate) fn canonicalize_with(
2424
let mut canonical_path = PathBuf::new();
2525
let start = MaybeOwnedFile::borrowed(start);
2626

27-
if let Err(e) = internal_open(
27+
match internal_open(
2828
start,
2929
path,
3030
canonicalize_options().follow(follow),
3131
&mut symlink_count,
3232
Some(&mut canonical_path),
3333
) {
34-
if canonical_path.as_os_str().is_empty() {
35-
return Err(e);
34+
// If the open succeeded, we got our path.
35+
Ok(_) => (),
36+
37+
// If it failed due to an invalid argument or filename, report it.
38+
Err(err) if err.kind() == io::ErrorKind::InvalidInput => {
39+
return Err(err);
40+
}
41+
#[cfg(io_error_more)]
42+
Err(err) if err.kind() == io::ErrorKind::InvalidFilename => {
43+
return Err(err);
44+
}
45+
#[cfg(windows)]
46+
Err(err)
47+
if err.raw_os_error()
48+
== Some(windows_sys::Win32::Foundation::ERROR_INVALID_NAME as _)
49+
|| err.raw_os_error()
50+
== Some(windows_sys::Win32::Foundation::ERROR_DIRECTORY as _) =>
51+
{
52+
return Err(err);
53+
}
54+
55+
// For any other error, like permission denied, it's ok as long as
56+
// we got our path.
57+
Err(err) => {
58+
if canonical_path.as_os_str().is_empty() {
59+
return Err(err);
60+
}
3661
}
3762
}
3863

Diff for: cap-primitives/src/fs/maybe_owned_file.rs

+1
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ impl<'borrow> MaybeOwnedFile<'borrow> {
106106

107107
/// Produce an owned `File`. This uses `open` on "." if needed to convert a
108108
/// borrowed `File` to an owned one.
109+
#[cfg_attr(windows, allow(dead_code))]
109110
pub(super) fn into_file(self, options: &OpenOptions) -> io::Result<fs::File> {
110111
match self.inner {
111112
MaybeOwned::Owned(file) => Ok(file),

Diff for: cap-primitives/src/windows/fs/create_file_at_w.rs

+270
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,270 @@
1+
#![allow(unsafe_code)]
2+
3+
use std::convert::TryInto;
4+
use std::mem;
5+
use std::os::windows::io::HandleOrInvalid;
6+
use std::ptr::null_mut;
7+
use windows_sys::Win32::Foundation::{
8+
RtlNtStatusToDosError, SetLastError, ERROR_ALREADY_EXISTS, ERROR_FILE_EXISTS,
9+
ERROR_INVALID_NAME, ERROR_INVALID_PARAMETER, ERROR_NOT_SUPPORTED, HANDLE, INVALID_HANDLE_VALUE,
10+
STATUS_OBJECT_NAME_COLLISION, STATUS_PENDING, STATUS_SUCCESS, SUCCESS, UNICODE_STRING,
11+
};
12+
use windows_sys::Win32::Security::{
13+
SECURITY_ATTRIBUTES, SECURITY_DYNAMIC_TRACKING, SECURITY_QUALITY_OF_SERVICE,
14+
SECURITY_STATIC_TRACKING,
15+
};
16+
use windows_sys::Win32::Storage::FileSystem::{
17+
NtCreateFile, CREATE_ALWAYS, CREATE_NEW, DELETE, FILE_ACCESS_FLAGS, FILE_ATTRIBUTE_ARCHIVE,
18+
FILE_ATTRIBUTE_COMPRESSED, FILE_ATTRIBUTE_DEVICE, FILE_ATTRIBUTE_DIRECTORY, FILE_ATTRIBUTE_EA,
19+
FILE_ATTRIBUTE_ENCRYPTED, FILE_ATTRIBUTE_HIDDEN, FILE_ATTRIBUTE_INTEGRITY_STREAM,
20+
FILE_ATTRIBUTE_NORMAL, FILE_ATTRIBUTE_NOT_CONTENT_INDEXED, FILE_ATTRIBUTE_NO_SCRUB_DATA,
21+
FILE_ATTRIBUTE_OFFLINE, FILE_ATTRIBUTE_PINNED, FILE_ATTRIBUTE_READONLY,
22+
FILE_ATTRIBUTE_RECALL_ON_DATA_ACCESS, FILE_ATTRIBUTE_RECALL_ON_OPEN,
23+
FILE_ATTRIBUTE_REPARSE_POINT, FILE_ATTRIBUTE_SPARSE_FILE, FILE_ATTRIBUTE_SYSTEM,
24+
FILE_ATTRIBUTE_TEMPORARY, FILE_ATTRIBUTE_UNPINNED, FILE_ATTRIBUTE_VIRTUAL, FILE_CREATE,
25+
FILE_CREATION_DISPOSITION, FILE_FLAGS_AND_ATTRIBUTES, FILE_FLAG_BACKUP_SEMANTICS,
26+
FILE_FLAG_DELETE_ON_CLOSE, FILE_FLAG_NO_BUFFERING, FILE_FLAG_OPEN_NO_RECALL,
27+
FILE_FLAG_OPEN_REPARSE_POINT, FILE_FLAG_OVERLAPPED, FILE_FLAG_POSIX_SEMANTICS,
28+
FILE_FLAG_RANDOM_ACCESS, FILE_FLAG_SEQUENTIAL_SCAN, FILE_FLAG_SESSION_AWARE,
29+
FILE_FLAG_WRITE_THROUGH, FILE_OPEN, FILE_OPEN_IF, FILE_OVERWRITE, FILE_OVERWRITE_IF,
30+
FILE_READ_ATTRIBUTES, FILE_SHARE_MODE, OPEN_ALWAYS, OPEN_EXISTING, SECURITY_CONTEXT_TRACKING,
31+
SECURITY_EFFECTIVE_ONLY, SECURITY_SQOS_PRESENT, SYNCHRONIZE, TRUNCATE_EXISTING,
32+
};
33+
use windows_sys::Win32::System::Kernel::{OBJ_CASE_INSENSITIVE, OBJ_INHERIT};
34+
use windows_sys::Win32::System::SystemServices::{GENERIC_ALL, GENERIC_READ, GENERIC_WRITE};
35+
use windows_sys::Win32::System::WindowsProgramming::{
36+
FILE_DELETE_ON_CLOSE, FILE_NON_DIRECTORY_FILE, FILE_NO_INTERMEDIATE_BUFFERING, FILE_OPENED,
37+
FILE_OPEN_FOR_BACKUP_INTENT, FILE_OPEN_NO_RECALL, FILE_OPEN_REMOTE_INSTANCE,
38+
FILE_OPEN_REPARSE_POINT, FILE_OVERWRITTEN, FILE_RANDOM_ACCESS, FILE_SEQUENTIAL_ONLY,
39+
FILE_SYNCHRONOUS_IO_NONALERT, FILE_WRITE_THROUGH, IO_STATUS_BLOCK, OBJECT_ATTRIBUTES,
40+
};
41+
42+
// All currently known `FILE_ATTRIBUTE_*` constants, according to
43+
// windows-sys' documentation.
44+
const FILE_ATTRIBUTE_VALID_FLAGS: FILE_FLAGS_AND_ATTRIBUTES = FILE_ATTRIBUTE_EA
45+
| FILE_ATTRIBUTE_DEVICE
46+
| FILE_ATTRIBUTE_HIDDEN
47+
| FILE_ATTRIBUTE_NORMAL
48+
| FILE_ATTRIBUTE_PINNED
49+
| FILE_ATTRIBUTE_SYSTEM
50+
| FILE_ATTRIBUTE_ARCHIVE
51+
| FILE_ATTRIBUTE_OFFLINE
52+
| FILE_ATTRIBUTE_VIRTUAL
53+
| FILE_ATTRIBUTE_READONLY
54+
| FILE_ATTRIBUTE_UNPINNED
55+
| FILE_ATTRIBUTE_DIRECTORY
56+
| FILE_ATTRIBUTE_ENCRYPTED
57+
| FILE_ATTRIBUTE_TEMPORARY
58+
| FILE_ATTRIBUTE_COMPRESSED
59+
| FILE_ATTRIBUTE_SPARSE_FILE
60+
| FILE_ATTRIBUTE_NO_SCRUB_DATA
61+
| FILE_ATTRIBUTE_REPARSE_POINT
62+
| FILE_ATTRIBUTE_RECALL_ON_OPEN
63+
| FILE_ATTRIBUTE_INTEGRITY_STREAM
64+
| FILE_ATTRIBUTE_NOT_CONTENT_INDEXED
65+
| FILE_ATTRIBUTE_RECALL_ON_DATA_ACCESS;
66+
67+
/// Like Windows' `CreateFileW`, but takes a `dir` argument to use as the
68+
/// root directory.
69+
#[allow(non_snake_case)]
70+
pub unsafe fn CreateFileAtW(
71+
dir: HANDLE,
72+
lpfilename: &[u16],
73+
dwdesiredaccess: FILE_ACCESS_FLAGS,
74+
dwsharemode: FILE_SHARE_MODE,
75+
lpsecurityattributes: *const SECURITY_ATTRIBUTES,
76+
dwcreationdisposition: FILE_CREATION_DISPOSITION,
77+
dwflagsandattributes: FILE_FLAGS_AND_ATTRIBUTES,
78+
htemplatefile: HANDLE,
79+
) -> HandleOrInvalid {
80+
// Absolute paths are not yet implemented here.
81+
//
82+
// It seems like `NtCreatePath` needs the apparently NT-internal `\??\`
83+
// prefix prepended to absolute paths. It's possible it needs other
84+
// path transforms as well. `RtlDosPathNameToNtPathName_U` may be a
85+
// function that does these things, though it's not available in
86+
// windows-sys and not documented, though one can find
87+
// [unofficial blog posts], though even they say things like "I`m
88+
// sorry that I cannot give more details on these functions".
89+
//
90+
// [unofficial blog posts]: https://mecanik.dev/en/posts/convert-dos-and-nt-paths-using-rtl-functions/
91+
assert!(dir != 0);
92+
93+
// Extended attributes are not implemented yet.
94+
if htemplatefile != 0 {
95+
SetLastError(ERROR_NOT_SUPPORTED);
96+
return HandleOrInvalid::from_raw_handle(INVALID_HANDLE_VALUE as _);
97+
}
98+
99+
// Convert `dwcreationdisposition` to the `createdisposition` argument
100+
// to `NtCreateFile`. Do this before converting `lpfilename` so that
101+
// we can return early on failure.
102+
let createdisposition = match dwcreationdisposition {
103+
CREATE_NEW => FILE_CREATE,
104+
CREATE_ALWAYS => FILE_OVERWRITE_IF,
105+
OPEN_EXISTING => FILE_OPEN,
106+
OPEN_ALWAYS => FILE_OPEN_IF,
107+
TRUNCATE_EXISTING => FILE_OVERWRITE,
108+
_ => {
109+
SetLastError(ERROR_INVALID_PARAMETER);
110+
return HandleOrInvalid::from_raw_handle(INVALID_HANDLE_VALUE as _);
111+
}
112+
};
113+
114+
// Convert `lpfilename` to a `UNICODE_STRING`.
115+
let byte_length = lpfilename.len() * mem::size_of::<u16>();
116+
let length: u16 = match byte_length.try_into() {
117+
Ok(length) => length,
118+
Err(_) => {
119+
SetLastError(ERROR_INVALID_NAME);
120+
return HandleOrInvalid::from_raw_handle(INVALID_HANDLE_VALUE as _);
121+
}
122+
};
123+
let mut unicode_string = UNICODE_STRING {
124+
Buffer: lpfilename.as_ptr() as *mut u16,
125+
Length: length,
126+
MaximumLength: length,
127+
};
128+
129+
let mut handle = INVALID_HANDLE_VALUE;
130+
131+
// Convert `dwdesiredaccess` and `dwflagsandattributes` to the
132+
// `desiredaccess` argument to `NtCreateFile`.
133+
let mut desiredaccess = dwdesiredaccess | SYNCHRONIZE | FILE_READ_ATTRIBUTES;
134+
if dwflagsandattributes & FILE_FLAG_DELETE_ON_CLOSE != 0 {
135+
desiredaccess |= DELETE;
136+
}
137+
138+
// Compute `objectattributes`' `Attributes` field. Case-insensitive is
139+
// the expected behavior on Windows.
140+
let mut attributes = 0;
141+
if dwflagsandattributes & FILE_FLAG_POSIX_SEMANTICS != 0 {
142+
attributes |= OBJ_CASE_INSENSITIVE as u32;
143+
};
144+
if !lpsecurityattributes.is_null() && (*lpsecurityattributes).bInheritHandle != 0 {
145+
attributes |= OBJ_INHERIT as u32;
146+
}
147+
148+
// Compute the `objectattributes` argument to `NtCreateFile`.
149+
let mut objectattributes = mem::zeroed::<OBJECT_ATTRIBUTES>();
150+
objectattributes.Length = mem::size_of::<OBJECT_ATTRIBUTES>() as _;
151+
objectattributes.RootDirectory = dir;
152+
objectattributes.ObjectName = &mut unicode_string;
153+
objectattributes.Attributes = attributes;
154+
if !lpsecurityattributes.is_null() {
155+
objectattributes.SecurityDescriptor = (*lpsecurityattributes).lpSecurityDescriptor;
156+
}
157+
158+
// If needed, set `objectattributes`' `SecurityQualityOfService` field.
159+
let mut qos;
160+
if dwflagsandattributes & SECURITY_SQOS_PRESENT != 0 {
161+
qos = mem::zeroed::<SECURITY_QUALITY_OF_SERVICE>();
162+
qos.Length = mem::size_of::<SECURITY_QUALITY_OF_SERVICE>() as _;
163+
qos.ImpersonationLevel = ((dwflagsandattributes >> 16) & 0x3) as _;
164+
qos.ContextTrackingMode = if dwflagsandattributes & SECURITY_CONTEXT_TRACKING != 0 {
165+
SECURITY_DYNAMIC_TRACKING
166+
} else {
167+
SECURITY_STATIC_TRACKING
168+
};
169+
qos.EffectiveOnly = ((dwflagsandattributes & SECURITY_EFFECTIVE_ONLY) != 0) as _;
170+
171+
objectattributes.SecurityQualityOfService =
172+
(&mut qos as *mut SECURITY_QUALITY_OF_SERVICE).cast();
173+
}
174+
175+
let mut iostatusblock = mem::zeroed::<IO_STATUS_BLOCK>();
176+
iostatusblock.Anonymous.Status = STATUS_PENDING;
177+
178+
// Compute the `fileattributes` argument to `NtCreateFile`. Mask off
179+
// unrecognized flags.
180+
let mut fileattributes = dwflagsandattributes & FILE_ATTRIBUTE_VALID_FLAGS;
181+
if fileattributes == 0 {
182+
fileattributes = FILE_ATTRIBUTE_NORMAL;
183+
}
184+
185+
// Compute the `createoptions` argument to `NtCreateFile`.
186+
let mut createoptions = 0;
187+
if dwflagsandattributes & FILE_FLAG_BACKUP_SEMANTICS == 0 {
188+
createoptions |= FILE_NON_DIRECTORY_FILE;
189+
} else {
190+
if dwdesiredaccess & GENERIC_ALL != 0 {
191+
createoptions |= FILE_OPEN_FOR_BACKUP_INTENT | FILE_OPEN_REMOTE_INSTANCE;
192+
} else {
193+
if dwdesiredaccess & GENERIC_READ != 0 {
194+
createoptions |= FILE_OPEN_FOR_BACKUP_INTENT;
195+
}
196+
if dwdesiredaccess & GENERIC_WRITE != 0 {
197+
createoptions |= FILE_OPEN_REMOTE_INSTANCE;
198+
}
199+
}
200+
}
201+
if dwflagsandattributes & FILE_FLAG_DELETE_ON_CLOSE != 0 {
202+
createoptions |= FILE_DELETE_ON_CLOSE;
203+
}
204+
if dwflagsandattributes & FILE_FLAG_NO_BUFFERING != 0 {
205+
createoptions |= FILE_NO_INTERMEDIATE_BUFFERING;
206+
}
207+
if dwflagsandattributes & FILE_FLAG_OPEN_NO_RECALL != 0 {
208+
createoptions |= FILE_OPEN_NO_RECALL;
209+
}
210+
if dwflagsandattributes & FILE_FLAG_OPEN_REPARSE_POINT != 0 {
211+
createoptions |= FILE_OPEN_REPARSE_POINT;
212+
}
213+
if dwflagsandattributes & FILE_FLAG_OVERLAPPED == 0 {
214+
createoptions |= FILE_SYNCHRONOUS_IO_NONALERT;
215+
}
216+
// FILE_FLAG_POSIX_SEMANTICS is handled above.
217+
if dwflagsandattributes & FILE_FLAG_RANDOM_ACCESS != 0 {
218+
createoptions |= FILE_RANDOM_ACCESS;
219+
}
220+
if dwflagsandattributes & FILE_FLAG_SESSION_AWARE != 0 {
221+
// TODO: How should we handle FILE_FLAG_SESSION_AWARE?
222+
SetLastError(ERROR_NOT_SUPPORTED);
223+
return HandleOrInvalid::from_raw_handle(INVALID_HANDLE_VALUE as _);
224+
}
225+
if dwflagsandattributes & FILE_FLAG_SEQUENTIAL_SCAN != 0 {
226+
createoptions |= FILE_SEQUENTIAL_ONLY;
227+
}
228+
if dwflagsandattributes & FILE_FLAG_WRITE_THROUGH != 0 {
229+
createoptions |= FILE_WRITE_THROUGH;
230+
}
231+
232+
// Ok, we have what we need to call `NtCreateFile` now!
233+
let status = NtCreateFile(
234+
&mut handle,
235+
desiredaccess,
236+
&mut objectattributes,
237+
&mut iostatusblock,
238+
null_mut(),
239+
fileattributes,
240+
dwsharemode,
241+
createdisposition,
242+
createoptions,
243+
null_mut(),
244+
0,
245+
);
246+
247+
// Check for errors.
248+
if status != STATUS_SUCCESS {
249+
handle = INVALID_HANDLE_VALUE;
250+
if status == STATUS_OBJECT_NAME_COLLISION {
251+
SetLastError(ERROR_FILE_EXISTS);
252+
} else {
253+
SetLastError(RtlNtStatusToDosError(status));
254+
}
255+
} else if (dwcreationdisposition == CREATE_ALWAYS
256+
&& iostatusblock.Information == FILE_OVERWRITTEN as _)
257+
|| (dwcreationdisposition == OPEN_ALWAYS && iostatusblock.Information == FILE_OPENED as _)
258+
{
259+
// Set `ERROR_ALREADY_EXISTS` according to the table for
260+
// `dwCreationDisposition` in the [`CreateFileW` docs].
261+
//
262+
// [`CreateFileW` docs]: https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew
263+
SetLastError(ERROR_ALREADY_EXISTS);
264+
} else {
265+
// Otherwise indicate that we succeeded.
266+
SetLastError(SUCCESS);
267+
}
268+
269+
HandleOrInvalid::from_raw_handle(handle as _)
270+
}

Diff for: cap-primitives/src/windows/fs/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
mod copy;
22
mod create_dir_unchecked;
3+
mod create_file_at_w;
34
mod dir_entry_inner;
45
mod dir_options_ext;
56
mod dir_utils;
@@ -74,7 +75,6 @@ pub(crate) use symlink_unchecked::*;
7475
// <https://docs.microsoft.com/en-us/windows/win32/fileio/reparse-points>
7576
pub(crate) const MAX_SYMLINK_EXPANSIONS: u8 = 63;
7677

77-
#[cfg(any(test, racy_asserts))]
7878
pub(crate) fn file_path(file: &std::fs::File) -> Option<std::path::PathBuf> {
7979
get_path::get_path(file).ok()
8080
}

0 commit comments

Comments
 (0)