Skip to content

Commit 7b25da5

Browse files
committed
add symlinkat, fix(?) windows errors
1 parent df85023 commit 7b25da5

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
@@ -1781,6 +1781,15 @@ impl Dir {
17811781
) -> io::Result<()> {
17821782
self.inner.rename(from, &to_dir.inner, to)
17831783
}
1784+
1785+
/// Attempts to create a new symbolic link on the filesystem.
1786+
///
1787+
/// If `original` is a relative path, it is interpreted relative to the created link.
1788+
/// If `link` is a relative path, it is interpreted relative to `self`.
1789+
#[unstable(feature = "dirfd", issue = "120426")]
1790+
pub fn symlink<P: AsRef<Path>, Q: AsRef<Path>>(&self, original: P, link: Q) -> io::Result<()> {
1791+
self.inner.symlink(original, link)
1792+
}
17841793
}
17851794

17861795
#[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
@@ -969,15 +969,15 @@ fn run_path_with_utf16<T, P: AsRef<Path>>(
969969
impl Dir {
970970
pub fn new<P: AsRef<Path>>(path: P) -> io::Result<Self> {
971971
let opts = OpenOptions::new();
972-
Self::new_with_native(path.as_ref(), &opts).map(|handle| Self { handle })
972+
run_path_with_wcstr(path.as_ref(), &|path| Self::new_with_native(path, &opts))
973973
}
974974

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

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

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

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

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

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

11651251
impl fmt::Debug for Dir {

0 commit comments

Comments
 (0)