Skip to content

Commit b613e97

Browse files
committed
Use with_native_path for Windows
Also add a WCStr type
1 parent 2205455 commit b613e97

File tree

3 files changed

+86
-47
lines changed

3 files changed

+86
-47
lines changed

library/std/src/sys/fs/mod.rs

+22-8
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ cfg_if::cfg_if! {
2020
mod windows;
2121
use windows as imp;
2222
pub use windows::{symlink_inner, junction_point};
23+
use crate::sys::path::with_native_path;
2324
} else if #[cfg(target_os = "hermit")] {
2425
mod hermit;
2526
use hermit as imp;
@@ -39,7 +40,7 @@ cfg_if::cfg_if! {
3940
}
4041

4142
// FIXME: Replace this with platform-specific path conversion functions.
42-
#[cfg(not(target_family = "unix"))]
43+
#[cfg(not(any(target_family = "unix", target_os = "windows")))]
4344
#[inline]
4445
pub fn with_native_path<T>(path: &Path, f: &dyn Fn(&Path) -> io::Result<T>) -> io::Result<T> {
4546
f(path)
@@ -51,7 +52,7 @@ pub use imp::{
5152
};
5253

5354
pub fn read_dir(path: &Path) -> io::Result<ReadDir> {
54-
// FIXME: use with_native_path
55+
// FIXME: use with_native_path on all platforms
5556
imp::readdir(path)
5657
}
5758

@@ -68,15 +69,22 @@ pub fn remove_dir(path: &Path) -> io::Result<()> {
6869
}
6970

7071
pub fn remove_dir_all(path: &Path) -> io::Result<()> {
71-
// FIXME: use with_native_path
72-
imp::remove_dir_all(path)
72+
// FIXME: use with_native_path on all platforms
73+
#[cfg(not(windows))]
74+
return imp::remove_dir_all(path);
75+
#[cfg(windows)]
76+
with_native_path(path, &imp::remove_dir_all)
7377
}
7478

7579
pub fn read_link(path: &Path) -> io::Result<PathBuf> {
7680
with_native_path(path, &imp::readlink)
7781
}
7882

7983
pub fn symlink(original: &Path, link: &Path) -> io::Result<()> {
84+
// FIXME: use with_native_path on all platforms
85+
#[cfg(windows)]
86+
return imp::symlink(original, link);
87+
#[cfg(not(windows))]
8088
with_native_path(original, &|original| {
8189
with_native_path(link, &|link| imp::symlink(original, link))
8290
})
@@ -105,11 +113,17 @@ pub fn canonicalize(path: &Path) -> io::Result<PathBuf> {
105113
}
106114

107115
pub fn copy(from: &Path, to: &Path) -> io::Result<u64> {
108-
// FIXME: use with_native_path
109-
imp::copy(from, to)
116+
// FIXME: use with_native_path on all platforms
117+
#[cfg(not(windows))]
118+
return imp::copy(from, to);
119+
#[cfg(windows)]
120+
with_native_path(from, &|from| with_native_path(to, &|to| imp::copy(from, to)))
110121
}
111122

112123
pub fn exists(path: &Path) -> io::Result<bool> {
113-
// FIXME: use with_native_path
114-
imp::exists(path)
124+
// FIXME: use with_native_path on all platforms
125+
#[cfg(not(windows))]
126+
return imp::exists(path);
127+
#[cfg(windows)]
128+
with_native_path(path, &imp::exists)
115129
}

library/std/src/sys/fs/windows.rs

+30-39
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use crate::sync::Arc;
1212
use crate::sys::handle::Handle;
1313
use crate::sys::pal::api::{self, WinError, set_file_information_by_handle};
1414
use crate::sys::pal::{IoResult, fill_utf16_buf, to_u16s, truncate_utf16_at_nul};
15-
use crate::sys::path::maybe_verbatim;
15+
use crate::sys::path::{WCStr, maybe_verbatim};
1616
use crate::sys::time::SystemTime;
1717
use crate::sys::{Align8, c, cvt};
1818
use crate::sys_common::{AsInner, FromInner, IntoInner};
@@ -298,10 +298,12 @@ impl OpenOptions {
298298
impl File {
299299
pub fn open(path: &Path, opts: &OpenOptions) -> io::Result<File> {
300300
let path = maybe_verbatim(path)?;
301+
// SAFETY: maybe_verbatim returns null-terminated strings
302+
let path = unsafe { WCStr::from_wchars_with_null_unchecked(&path) };
301303
Self::open_native(&path, opts)
302304
}
303305

304-
fn open_native(path: &[u16], opts: &OpenOptions) -> io::Result<File> {
306+
fn open_native(path: &WCStr, opts: &OpenOptions) -> io::Result<File> {
305307
let creation = opts.get_creation_mode()?;
306308
let handle = unsafe {
307309
c::CreateFileW(
@@ -1212,9 +1214,8 @@ pub fn readdir(p: &Path) -> io::Result<ReadDir> {
12121214
}
12131215
}
12141216

1215-
pub fn unlink(p: &Path) -> io::Result<()> {
1216-
let p_u16s = maybe_verbatim(p)?;
1217-
if unsafe { c::DeleteFileW(p_u16s.as_ptr()) } == 0 {
1217+
pub fn unlink(path: &WCStr) -> io::Result<()> {
1218+
if unsafe { c::DeleteFileW(path.as_ptr()) } == 0 {
12181219
let err = api::get_last_error();
12191220
// if `DeleteFileW` fails with ERROR_ACCESS_DENIED then try to remove
12201221
// the file while ignoring the readonly attribute.
@@ -1223,7 +1224,7 @@ pub fn unlink(p: &Path) -> io::Result<()> {
12231224
let mut opts = OpenOptions::new();
12241225
opts.access_mode(c::DELETE);
12251226
opts.custom_flags(c::FILE_FLAG_OPEN_REPARSE_POINT);
1226-
if let Ok(f) = File::open_native(&p_u16s, &opts) {
1227+
if let Ok(f) = File::open_native(&path, &opts) {
12271228
if f.posix_delete().is_ok() {
12281229
return Ok(());
12291230
}
@@ -1236,10 +1237,7 @@ pub fn unlink(p: &Path) -> io::Result<()> {
12361237
}
12371238
}
12381239

1239-
pub fn rename(old: &Path, new: &Path) -> io::Result<()> {
1240-
let old = maybe_verbatim(old)?;
1241-
let new = maybe_verbatim(new)?;
1242-
1240+
pub fn rename(old: &WCStr, new: &WCStr) -> io::Result<()> {
12431241
if unsafe { c::MoveFileExW(old.as_ptr(), new.as_ptr(), c::MOVEFILE_REPLACE_EXISTING) } == 0 {
12441242
let err = api::get_last_error();
12451243
// if `MoveFileExW` fails with ERROR_ACCESS_DENIED then try to move
@@ -1253,7 +1251,8 @@ pub fn rename(old: &Path, new: &Path) -> io::Result<()> {
12531251

12541252
// Calculate the layout of the `FILE_RENAME_INFO` we pass to `SetFileInformation`
12551253
// This is a dynamically sized struct so we need to get the position of the last field to calculate the actual size.
1256-
let Ok(new_len_without_nul_in_bytes): Result<u32, _> = ((new.len() - 1) * 2).try_into()
1254+
let Ok(new_len_without_nul_in_bytes): Result<u32, _> =
1255+
((new.count_bytes() - 1) * 2).try_into()
12571256
else {
12581257
return Err(err).io_result();
12591258
};
@@ -1282,7 +1281,7 @@ pub fn rename(old: &Path, new: &Path) -> io::Result<()> {
12821281

12831282
new.as_ptr().copy_to_nonoverlapping(
12841283
(&raw mut (*file_rename_info).FileName).cast::<u16>(),
1285-
new.len(),
1284+
new.count_bytes(),
12861285
);
12871286
}
12881287

@@ -1309,20 +1308,19 @@ pub fn rename(old: &Path, new: &Path) -> io::Result<()> {
13091308
Ok(())
13101309
}
13111310

1312-
pub fn rmdir(p: &Path) -> io::Result<()> {
1313-
let p = maybe_verbatim(p)?;
1311+
pub fn rmdir(p: &WCStr) -> io::Result<()> {
13141312
cvt(unsafe { c::RemoveDirectoryW(p.as_ptr()) })?;
13151313
Ok(())
13161314
}
13171315

1318-
pub fn remove_dir_all(path: &Path) -> io::Result<()> {
1316+
pub fn remove_dir_all(path: &WCStr) -> io::Result<()> {
13191317
// Open a file or directory without following symlinks.
13201318
let mut opts = OpenOptions::new();
13211319
opts.access_mode(c::FILE_LIST_DIRECTORY);
13221320
// `FILE_FLAG_BACKUP_SEMANTICS` allows opening directories.
13231321
// `FILE_FLAG_OPEN_REPARSE_POINT` opens a link instead of its target.
13241322
opts.custom_flags(c::FILE_FLAG_BACKUP_SEMANTICS | c::FILE_FLAG_OPEN_REPARSE_POINT);
1325-
let file = File::open(path, &opts)?;
1323+
let file = File::open_native(path, &opts)?;
13261324

13271325
// Test if the file is not a directory or a symlink to a directory.
13281326
if (file.basic_info()?.FileAttributes & c::FILE_ATTRIBUTE_DIRECTORY) == 0 {
@@ -1333,14 +1331,14 @@ pub fn remove_dir_all(path: &Path) -> io::Result<()> {
13331331
remove_dir_all_iterative(file).io_result()
13341332
}
13351333

1336-
pub fn readlink(path: &Path) -> io::Result<PathBuf> {
1334+
pub fn readlink(path: &WCStr) -> io::Result<PathBuf> {
13371335
// Open the link with no access mode, instead of generic read.
13381336
// By default FILE_LIST_DIRECTORY is denied for the junction "C:\Documents and Settings", so
13391337
// this is needed for a common case.
13401338
let mut opts = OpenOptions::new();
13411339
opts.access_mode(0);
13421340
opts.custom_flags(c::FILE_FLAG_OPEN_REPARSE_POINT | c::FILE_FLAG_BACKUP_SEMANTICS);
1343-
let file = File::open(path, &opts)?;
1341+
let file = File::open_native(&path, &opts)?;
13441342
file.readlink()
13451343
}
13461344

@@ -1378,19 +1376,17 @@ pub fn symlink_inner(original: &Path, link: &Path, dir: bool) -> io::Result<()>
13781376
}
13791377

13801378
#[cfg(not(target_vendor = "uwp"))]
1381-
pub fn link(original: &Path, link: &Path) -> io::Result<()> {
1382-
let original = maybe_verbatim(original)?;
1383-
let link = maybe_verbatim(link)?;
1379+
pub fn link(original: &WCStr, link: &WCStr) -> io::Result<()> {
13841380
cvt(unsafe { c::CreateHardLinkW(link.as_ptr(), original.as_ptr(), ptr::null_mut()) })?;
13851381
Ok(())
13861382
}
13871383

13881384
#[cfg(target_vendor = "uwp")]
1389-
pub fn link(_original: &Path, _link: &Path) -> io::Result<()> {
1385+
pub fn link(_original: &WCStr, _link: &WCStr) -> io::Result<()> {
13901386
return Err(io::const_error!(io::ErrorKind::Unsupported, "hard link are not supported on UWP"));
13911387
}
13921388

1393-
pub fn stat(path: &Path) -> io::Result<FileAttr> {
1389+
pub fn stat(path: &WCStr) -> io::Result<FileAttr> {
13941390
match metadata(path, ReparsePoint::Follow) {
13951391
Err(err) if err.raw_os_error() == Some(c::ERROR_CANT_ACCESS_FILE as i32) => {
13961392
if let Ok(attrs) = lstat(path) {
@@ -1404,7 +1400,7 @@ pub fn stat(path: &Path) -> io::Result<FileAttr> {
14041400
}
14051401
}
14061402

1407-
pub fn lstat(path: &Path) -> io::Result<FileAttr> {
1403+
pub fn lstat(path: &WCStr) -> io::Result<FileAttr> {
14081404
metadata(path, ReparsePoint::Open)
14091405
}
14101406

@@ -1420,7 +1416,7 @@ impl ReparsePoint {
14201416
}
14211417
}
14221418

1423-
fn metadata(path: &Path, reparse: ReparsePoint) -> io::Result<FileAttr> {
1419+
fn metadata(path: &WCStr, reparse: ReparsePoint) -> io::Result<FileAttr> {
14241420
let mut opts = OpenOptions::new();
14251421
// No read or write permissions are necessary
14261422
opts.access_mode(0);
@@ -1429,7 +1425,7 @@ fn metadata(path: &Path, reparse: ReparsePoint) -> io::Result<FileAttr> {
14291425
// Attempt to open the file normally.
14301426
// If that fails with `ERROR_SHARING_VIOLATION` then retry using `FindFirstFileExW`.
14311427
// If the fallback fails for any reason we return the original error.
1432-
match File::open(path, &opts) {
1428+
match File::open_native(&path, &opts) {
14331429
Ok(file) => file.file_attr(),
14341430
Err(e)
14351431
if [Some(c::ERROR_SHARING_VIOLATION as _), Some(c::ERROR_ACCESS_DENIED as _)]
@@ -1442,8 +1438,6 @@ fn metadata(path: &Path, reparse: ReparsePoint) -> io::Result<FileAttr> {
14421438
// However, there are special system files, such as
14431439
// `C:\hiberfil.sys`, that are locked in a way that denies even that.
14441440
unsafe {
1445-
let path = maybe_verbatim(path)?;
1446-
14471441
// `FindFirstFileExW` accepts wildcard file names.
14481442
// Fortunately wildcards are not valid file names and
14491443
// `ERROR_SHARING_VIOLATION` means the file exists (but is locked)
@@ -1482,8 +1476,7 @@ fn metadata(path: &Path, reparse: ReparsePoint) -> io::Result<FileAttr> {
14821476
}
14831477
}
14841478

1485-
pub fn set_perm(p: &Path, perm: FilePermissions) -> io::Result<()> {
1486-
let p = maybe_verbatim(p)?;
1479+
pub fn set_perm(p: &WCStr, perm: FilePermissions) -> io::Result<()> {
14871480
unsafe {
14881481
cvt(c::SetFileAttributesW(p.as_ptr(), perm.attrs))?;
14891482
Ok(())
@@ -1499,17 +1492,17 @@ fn get_path(f: &File) -> io::Result<PathBuf> {
14991492
)
15001493
}
15011494

1502-
pub fn canonicalize(p: &Path) -> io::Result<PathBuf> {
1495+
pub fn canonicalize(p: &WCStr) -> io::Result<PathBuf> {
15031496
let mut opts = OpenOptions::new();
15041497
// No read or write permissions are necessary
15051498
opts.access_mode(0);
15061499
// This flag is so we can open directories too
15071500
opts.custom_flags(c::FILE_FLAG_BACKUP_SEMANTICS);
1508-
let f = File::open(p, &opts)?;
1501+
let f = File::open_native(p, &opts)?;
15091502
get_path(&f)
15101503
}
15111504

1512-
pub fn copy(from: &Path, to: &Path) -> io::Result<u64> {
1505+
pub fn copy(from: &WCStr, to: &WCStr) -> io::Result<u64> {
15131506
unsafe extern "system" fn callback(
15141507
_TotalFileSize: i64,
15151508
_TotalBytesTransferred: i64,
@@ -1528,13 +1521,11 @@ pub fn copy(from: &Path, to: &Path) -> io::Result<u64> {
15281521
c::PROGRESS_CONTINUE
15291522
}
15301523
}
1531-
let pfrom = maybe_verbatim(from)?;
1532-
let pto = maybe_verbatim(to)?;
15331524
let mut size = 0i64;
15341525
cvt(unsafe {
15351526
c::CopyFileExW(
1536-
pfrom.as_ptr(),
1537-
pto.as_ptr(),
1527+
from.as_ptr(),
1528+
to.as_ptr(),
15381529
Some(callback),
15391530
(&raw mut size) as *mut _,
15401531
ptr::null_mut(),
@@ -1624,14 +1615,14 @@ pub fn junction_point(original: &Path, link: &Path) -> io::Result<()> {
16241615
}
16251616

16261617
// Try to see if a file exists but, unlike `exists`, report I/O errors.
1627-
pub fn exists(path: &Path) -> io::Result<bool> {
1618+
pub fn exists(path: &WCStr) -> io::Result<bool> {
16281619
// Open the file to ensure any symlinks are followed to their target.
16291620
let mut opts = OpenOptions::new();
16301621
// No read, write, etc access rights are needed.
16311622
opts.access_mode(0);
16321623
// Backup semantics enables opening directories as well as files.
16331624
opts.custom_flags(c::FILE_FLAG_BACKUP_SEMANTICS);
1634-
match File::open(path, &opts) {
1625+
match File::open_native(path, &opts) {
16351626
Err(e) => match e.kind() {
16361627
// The file definitely does not exist
16371628
io::ErrorKind::NotFound => Ok(false),

library/std/src/sys/path/windows.rs

+34
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,40 @@ mod tests;
1010
pub const MAIN_SEP_STR: &str = "\\";
1111
pub const MAIN_SEP: char = '\\';
1212

13+
/// A null terminated wide string.
14+
#[repr(transparent)]
15+
pub struct WCStr([u16]);
16+
17+
impl WCStr {
18+
/// Convert a slice to a WCStr without checks.
19+
///
20+
/// Though it is memory safe, the slice should also not contain interior nulls
21+
/// as this may lead to unwanted truncation.
22+
///
23+
/// # Safety
24+
///
25+
/// The slice must end in a null.
26+
pub unsafe fn from_wchars_with_null_unchecked(s: &[u16]) -> &Self {
27+
unsafe { &*(s as *const [u16] as *const Self) }
28+
}
29+
30+
pub fn as_ptr(&self) -> *const u16 {
31+
self.0.as_ptr()
32+
}
33+
34+
pub fn count_bytes(&self) -> usize {
35+
self.0.len()
36+
}
37+
}
38+
39+
#[inline]
40+
pub fn with_native_path<T>(path: &Path, f: &dyn Fn(&WCStr) -> io::Result<T>) -> io::Result<T> {
41+
let path = maybe_verbatim(path)?;
42+
// SAFETY: maybe_verbatim returns null-terminated strings
43+
let path = unsafe { WCStr::from_wchars_with_null_unchecked(&path) };
44+
f(path)
45+
}
46+
1347
#[inline]
1448
pub fn is_sep_byte(b: u8) -> bool {
1549
b == b'/' || b == b'\\'

0 commit comments

Comments
 (0)