Skip to content

Commit f87218a

Browse files
committed
add symlinkat, fix(?) windows errors
1 parent eee9a89 commit f87218a

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
@@ -1786,6 +1786,15 @@ impl Dir {
17861786
) -> io::Result<()> {
17871787
self.inner.rename(from, &to_dir.inner, to)
17881788
}
1789+
1790+
/// Attempts to create a new symbolic link on the filesystem.
1791+
///
1792+
/// If `original` is a relative path, it is interpreted relative to the created link.
1793+
/// If `link` is a relative path, it is interpreted relative to `self`.
1794+
#[unstable(feature = "dirfd", issue = "120426")]
1795+
pub fn symlink<P: AsRef<Path>, Q: AsRef<Path>>(&self, original: P, link: Q) -> io::Result<()> {
1796+
self.inner.symlink(original, link)
1797+
}
17891798
}
17901799

17911800
#[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;
@@ -400,6 +400,12 @@ impl Dir {
400400
})
401401
}
402402

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

465475
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
@@ -974,15 +974,15 @@ fn run_path_with_utf16<T, P: AsRef<Path>>(
974974
impl Dir {
975975
pub fn new<P: AsRef<Path>>(path: P) -> io::Result<Self> {
976976
let opts = OpenOptions::new();
977-
Self::new_with_native(path.as_ref(), &opts).map(|handle| Self { handle })
977+
run_path_with_wcstr(path.as_ref(), &|path| Self::new_with_native(path, &opts))
978978
}
979979

980980
pub fn new_with<P: AsRef<Path>>(path: P, opts: &OpenOptions) -> io::Result<Self> {
981-
Self::new_with_native(path.as_ref(), &opts).map(|handle| Self { handle })
981+
run_path_with_wcstr(path.as_ref(), &|path| Self::new_with_native(path, &opts))
982982
}
983983

984984
pub fn new_for_traversal<P: AsRef<Path>>(path: P) -> io::Result<Self> {
985-
Self::new_native(path.as_ref()).map(|handle| Self { handle })
985+
run_path_with_wcstr(path.as_ref(), &|path| Self::new_native(path))
986986
}
987987

988988
pub fn open<P: AsRef<Path>>(&self, path: P) -> io::Result<File> {
@@ -1032,14 +1032,35 @@ impl Dir {
10321032
run_path_with_wcstr(to.as_ref(), &|to| self.rename_native(from.as_ref(), to_dir, to))
10331033
}
10341034

1035-
fn new_native(path: &Path) -> io::Result<Handle> {
1035+
pub fn symlink<P: AsRef<Path>, Q: AsRef<Path>>(&self, original: P, link: Q) -> io::Result<()> {
1036+
run_path_with_utf16(original.as_ref(), &|orig| {
1037+
self.symlink_native(orig, link.as_ref(), original.as_ref().is_relative())
1038+
})
1039+
}
1040+
1041+
fn new_native(path: &WCStr) -> io::Result<Self> {
10361042
let mut opts = OpenOptions::new();
10371043
opts.access_mode(c::FILE_TRAVERSE);
1038-
File::open(path, &opts).map(|file| file.into_inner())
1044+
Self::new_with_native(path, &opts)
10391045
}
10401046

1041-
fn new_with_native(path: &Path, opts: &OpenOptions) -> io::Result<Handle> {
1042-
File::open(path, opts).map(|file| file.into_inner())
1047+
fn new_with_native(path: &WCStr, opts: &OpenOptions) -> io::Result<Self> {
1048+
let creation = opts.get_creation_mode()?;
1049+
let handle = unsafe {
1050+
c::CreateFileW(
1051+
path.as_ptr(),
1052+
opts.get_access_mode()?,
1053+
opts.share_mode,
1054+
opts.security_attributes,
1055+
creation,
1056+
opts.get_flags_and_attributes() | c::FILE_FLAG_BACKUP_SEMANTICS,
1057+
ptr::null_mut(),
1058+
)
1059+
};
1060+
match OwnedHandle::try_from(unsafe { HandleOrInvalid::from_raw_handle(handle) }) {
1061+
Ok(handle) => Ok(Self { handle: Handle::from_inner(handle) }),
1062+
Err(_) => Err(Error::last_os_error()),
1063+
}
10431064
}
10441065

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

11701256
impl fmt::Debug for Dir {

0 commit comments

Comments
 (0)