Skip to content

Commit 4ca11c7

Browse files
committed
add symlinkat, fix(?) windows errors
1 parent 299559d commit 4ca11c7

File tree

6 files changed

+135
-10
lines changed

6 files changed

+135
-10
lines changed

library/std/src/fs.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1765,6 +1765,15 @@ impl Dir {
17651765
) -> io::Result<()> {
17661766
self.inner.rename(from, &to_dir.inner, to)
17671767
}
1768+
1769+
/// Attempts to create a new symbolic link on the filesystem.
1770+
///
1771+
/// If `original` is a relative path, it is interpreted relative to the created link.
1772+
/// If `link` is a relative path, it is interpreted relative to `self`.
1773+
#[unstable(feature = "dirfd", issue = "120426")]
1774+
pub fn symlink<P: AsRef<Path>, Q: AsRef<Path>>(&self, original: P, link: Q) -> io::Result<()> {
1775+
self.inner.symlink(original, link)
1776+
}
17681777
}
17691778

17701779
#[unstable(feature = "dirfd", issue = "120426")]

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use crate::fmt;
44
use crate::fs::{self, create_dir, remove_dir, remove_file, rename};
55
use crate::io::{self, Error, ErrorKind};
66
use crate::path::{Path, PathBuf};
7-
use crate::sys::fs::{File, OpenOptions};
7+
use crate::sys::fs::{File, OpenOptions, symlink};
88
use crate::sys_common::ignore_notfound;
99

1010
pub(crate) const NOT_FILE_ERROR: Error = io::const_error!(
@@ -116,6 +116,10 @@ impl Dir {
116116
) -> io::Result<()> {
117117
rename(self.path.join(from), to_dir.path.join(to))
118118
}
119+
120+
pub fn symlink<P: AsRef<Path>, Q: AsRef<Path>>(&self, original: P, link: Q) -> io::Result<()> {
121+
symlink(original.as_ref(), link.as_ref())
122+
}
119123
}
120124

121125
impl fmt::Debug for Dir {

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,14 @@ impl Dir {
166166
) -> io::Result<()> {
167167
self.0
168168
}
169+
170+
pub fn symlink<P: AsRef<Path>, Q: AsRef<Path>>(
171+
&self,
172+
_original: P,
173+
_link: Q,
174+
) -> io::Result<()> {
175+
self.0
176+
}
169177
}
170178

171179
impl fmt::Debug for Dir {

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

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ use libc::{dirent64, fstat64, ftruncate64, lseek64, lstat64, off64_t, open64, st
7979
target_os = "solaris",
8080
target_vendor = "apple",
8181
))]
82-
use libc::{mkdirat, openat as openat64, renameat, unlinkat};
82+
use libc::{mkdirat, openat as openat64, renameat, symlinkat, unlinkat};
8383
#[cfg(not(any(
8484
target_os = "redox",
8585
target_os = "espidf",
@@ -94,7 +94,7 @@ use libc::{mkdirat, openat as openat64, renameat, unlinkat};
9494
target_vendor = "apple",
9595
miri
9696
)))]
97-
use libc::{mkdirat, openat64, renameat, unlinkat};
97+
use libc::{mkdirat, openat64, renameat, symlinkat, unlinkat};
9898

9999
#[cfg(any(target_os = "freebsd", target_os = "aix"))]
100100
const TRAVERSE_DIRECTORY: i32 = libc::O_EXEC;
@@ -399,6 +399,12 @@ impl Dir {
399399
})
400400
}
401401

402+
pub fn symlink<P: AsRef<Path>, Q: AsRef<Path>>(&self, original: P, link: Q) -> io::Result<()> {
403+
run_path_with_cstr(original.as_ref(), &|original| {
404+
run_path_with_cstr(link.as_ref(), &|link| self.symlink_c(original, link))
405+
})
406+
}
407+
402408
pub fn open_c(&self, path: &CStr, opts: &OpenOptions) -> io::Result<File> {
403409
let flags = libc::O_CLOEXEC
404410
| opts.get_access_mode()?
@@ -459,6 +465,10 @@ impl Dir {
459465
})
460466
.map(|_| ())
461467
}
468+
469+
pub fn symlink_c(&self, original: &CStr, link: &CStr) -> io::Result<()> {
470+
cvt(unsafe { symlinkat(original.as_ptr(), self.0.as_raw_fd(), link.as_ptr()) }).map(|_| ())
471+
}
462472
}
463473

464474
fn get_path_from_fd(fd: c_int) -> Option<PathBuf> {

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,14 @@ impl Dir {
202202
) -> io::Result<()> {
203203
self.0
204204
}
205+
206+
pub fn symlink<P: AsRef<Path>, Q: AsRef<Path>>(
207+
&self,
208+
_original: P,
209+
_link: Q,
210+
) -> io::Result<()> {
211+
self.0
212+
}
205213
}
206214

207215
impl fmt::Debug for Dir {

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

Lines changed: 93 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -968,15 +968,15 @@ fn run_path_with_utf16<T, P: AsRef<Path>>(
968968
impl Dir {
969969
pub fn new<P: AsRef<Path>>(path: P) -> io::Result<Self> {
970970
let opts = OpenOptions::new();
971-
Self::new_with_native(path.as_ref(), &opts).map(|handle| Self { handle })
971+
run_path_with_wcstr(path.as_ref(), &|path| Self::new_with_native(path, &opts))
972972
}
973973

974974
pub fn new_with<P: AsRef<Path>>(path: P, opts: &OpenOptions) -> io::Result<Self> {
975-
Self::new_with_native(path.as_ref(), &opts).map(|handle| Self { handle })
975+
run_path_with_wcstr(path.as_ref(), &|path| Self::new_with_native(path, &opts))
976976
}
977977

978978
pub fn new_for_traversal<P: AsRef<Path>>(path: P) -> io::Result<Self> {
979-
Self::new_native(path.as_ref()).map(|handle| Self { handle })
979+
run_path_with_wcstr(path.as_ref(), &|path| Self::new_native(path))
980980
}
981981

982982
pub fn open<P: AsRef<Path>>(&self, path: P) -> io::Result<File> {
@@ -1026,14 +1026,35 @@ impl Dir {
10261026
run_path_with_wcstr(to.as_ref(), &|to| self.rename_native(from.as_ref(), to_dir, to))
10271027
}
10281028

1029-
fn new_native(path: &Path) -> io::Result<Handle> {
1029+
pub fn symlink<P: AsRef<Path>, Q: AsRef<Path>>(&self, original: P, link: Q) -> io::Result<()> {
1030+
run_path_with_utf16(original.as_ref(), &|orig| {
1031+
self.symlink_native(orig, link.as_ref(), original.as_ref().is_relative())
1032+
})
1033+
}
1034+
1035+
fn new_native(path: &WCStr) -> io::Result<Self> {
10301036
let mut opts = OpenOptions::new();
10311037
opts.access_mode(c::FILE_TRAVERSE);
1032-
File::open(path, &opts).map(|file| file.into_inner())
1038+
Self::new_with_native(path, &opts)
10331039
}
10341040

1035-
fn new_with_native(path: &Path, opts: &OpenOptions) -> io::Result<Handle> {
1036-
File::open(path, opts).map(|file| file.into_inner())
1041+
fn new_with_native(path: &WCStr, opts: &OpenOptions) -> io::Result<Self> {
1042+
let creation = opts.get_creation_mode()?;
1043+
let handle = unsafe {
1044+
c::CreateFileW(
1045+
path.as_ptr(),
1046+
opts.get_access_mode()?,
1047+
opts.share_mode,
1048+
opts.security_attributes,
1049+
creation,
1050+
opts.get_flags_and_attributes() | c::FILE_FLAG_BACKUP_SEMANTICS,
1051+
ptr::null_mut(),
1052+
)
1053+
};
1054+
match unsafe { HandleOrInvalid::from_raw_handle(handle) } {
1055+
Ok(handle) => Self { handle: Handle::from_inner(handle) },
1056+
Err(_) => Err(Error::last_os_error()),
1057+
}
10371058
}
10381059

10391060
fn open_native(&self, path: &[u16], opts: &OpenOptions) -> io::Result<Handle> {
@@ -1159,6 +1180,71 @@ impl Dir {
11591180
unsafe { dealloc(file_rename_info.cast::<u8>(), layout) };
11601181
if result == 0 { Err(api::get_last_error()).io_result() } else { Ok(()) }
11611182
}
1183+
1184+
fn symlink_native(&self, original: &[u16], link: &Path, relative: bool) -> io::Result<()> {
1185+
const TOO_LONG_ERR: io::Error =
1186+
io::const_error!(io::ErrorKind::InvalidFilename, "File name is too long");
1187+
let mut opts = OpenOptions::new();
1188+
opts.write(true);
1189+
let linkfile = File::open(link, &opts)?;
1190+
let utf16: Vec<u16> = original.iter().chain(original).copied().collect();
1191+
let file_name_len = u16::try_from(original.len()).or(Err(TOO_LONG_ERR))?;
1192+
let sym_buffer = c::SYMBOLIC_LINK_REPARSE_BUFFER {
1193+
SubstituteNameOffset: 0,
1194+
SubstituteNameLength: file_name_len,
1195+
PrintNameOffset: file_name_len,
1196+
PrintNameLength: file_name_len,
1197+
Flags: if relative { c::SYMLINK_FLAG_RELATIVE } else { 0 },
1198+
PathBuffer: 0,
1199+
};
1200+
let layout = Layout::new::<c::REPARSE_DATA_BUFFER>();
1201+
let layout = layout
1202+
.extend(Layout::new::<c::SYMBOLIC_LINK_REPARSE_BUFFER>())
1203+
.or(Err(TOO_LONG_ERR))?
1204+
.0;
1205+
let layout = Layout::array::<u16>(original.len() * 2)
1206+
.and_then(|arr| layout.extend(arr))
1207+
.or(Err(TOO_LONG_ERR))?
1208+
.0;
1209+
let buffer = unsafe { alloc(layout) }.cast::<c::REPARSE_DATA_BUFFER>();
1210+
unsafe {
1211+
buffer.write(c::REPARSE_DATA_BUFFER {
1212+
ReparseTag: c::IO_REPARSE_TAG_SYMLINK,
1213+
ReparseDataLength: u16::try_from(size_of_val(&sym_buffer)).or(Err(TOO_LONG_ERR))?,
1214+
Reserved: 0,
1215+
rest: (),
1216+
});
1217+
buffer
1218+
.add(offset_of!(c::REPARSE_DATA_BUFFER, rest))
1219+
.cast::<c::SYMBOLIC_LINK_REPARSE_BUFFER>()
1220+
.write(sym_buffer);
1221+
ptr::copy_nonoverlapping(
1222+
utf16.as_ptr(),
1223+
buffer
1224+
.add(offset_of!(c::REPARSE_DATA_BUFFER, rest))
1225+
.add(offset_of!(c::SYMBOLIC_LINK_REPARSE_BUFFER, PathBuffer))
1226+
.cast::<u16>(),
1227+
original.len() * 2,
1228+
);
1229+
};
1230+
let result = unsafe {
1231+
c::DeviceIoControl(
1232+
linkfile.handle.as_raw_handle(),
1233+
c::FSCTL_SET_REPARSE_POINT,
1234+
&raw const buffer as *const c_void,
1235+
u32::try_from(size_of_val(&buffer)).or(Err(TOO_LONG_ERR))?,
1236+
ptr::null_mut(),
1237+
0,
1238+
ptr::null_mut(),
1239+
ptr::null_mut(),
1240+
)
1241+
};
1242+
unsafe {
1243+
dealloc(buffer.cast(), layout);
1244+
}
1245+
1246+
if result == 0 { Err(api::get_last_error()).io_result() } else { Ok(()) }
1247+
}
11621248
}
11631249

11641250
impl fmt::Debug for Dir {

0 commit comments

Comments
 (0)