Skip to content

Commit f3f99fb

Browse files
committed
std: Fix fs::read_link behavior on Windows
The current implementation of using GetFinalPathNameByHandle actually reads all intermediate links instead of just looking at the current link. This commit alters the behavior of the function to use a different API which correctly reads only one level of the soft link. [breaking-change]
1 parent 926f38e commit f3f99fb

File tree

2 files changed

+63
-14
lines changed

2 files changed

+63
-14
lines changed

src/libstd/sys/windows/c.rs

+30
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@ pub const WSAESHUTDOWN: libc::c_int = 10058;
4747

4848
pub const ERROR_NO_MORE_FILES: libc::DWORD = 18;
4949
pub const TOKEN_READ: libc::DWORD = 0x20008;
50+
pub const FILE_FLAG_OPEN_REPARSE_POINT: libc::DWORD = 0x00200000;
51+
pub const MAXIMUM_REPARSE_DATA_BUFFER_SIZE: usize = 16 * 1024;
52+
pub const FSCTL_GET_REPARSE_POINT: libc::DWORD = 0x900a8;
53+
pub const IO_REPARSE_TAG_SYMLINK: libc::DWORD = 0xa000000c;
5054

5155
// Note that these are not actually HANDLEs, just values to pass to GetStdHandle
5256
pub const STD_INPUT_HANDLE: libc::DWORD = -10i32 as libc::DWORD;
@@ -214,6 +218,24 @@ pub struct FILE_END_OF_FILE_INFO {
214218
pub EndOfFile: libc::LARGE_INTEGER,
215219
}
216220

221+
#[repr(C)]
222+
pub struct REPARSE_DATA_BUFFER {
223+
pub ReparseTag: libc::c_uint,
224+
pub ReparseDataLength: libc::c_ushort,
225+
pub Reserved: libc::c_ushort,
226+
pub rest: (),
227+
}
228+
229+
#[repr(C)]
230+
pub struct SYMBOLIC_LINK_REPARSE_BUFFER {
231+
pub SubstituteNameOffset: libc::c_ushort,
232+
pub SubstituteNameLength: libc::c_ushort,
233+
pub PrintNameOffset: libc::c_ushort,
234+
pub PrintNameLength: libc::c_ushort,
235+
pub Flags: libc::c_ulong,
236+
pub PathBuffer: libc::WCHAR,
237+
}
238+
217239
#[link(name = "ws2_32")]
218240
extern "system" {
219241
pub fn WSAStartup(wVersionRequested: libc::WORD,
@@ -433,6 +455,14 @@ extern "system" {
433455
pub fn GetCurrentProcess() -> libc::HANDLE;
434456
pub fn GetStdHandle(which: libc::DWORD) -> libc::HANDLE;
435457
pub fn ExitProcess(uExitCode: libc::c_uint) -> !;
458+
pub fn DeviceIoControl(hDevice: libc::HANDLE,
459+
dwIoControlCode: libc::DWORD,
460+
lpInBuffer: libc::LPVOID,
461+
nInBufferSize: libc::DWORD,
462+
lpOutBuffer: libc::LPVOID,
463+
nOutBufferSize: libc::DWORD,
464+
lpBytesReturned: libc::LPDWORD,
465+
lpOverlapped: libc::LPOVERLAPPED) -> libc::BOOL;
436466
}
437467

438468
#[link(name = "userenv")]

src/libstd/sys/windows/fs2.rs

+33-14
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ use libc::{self, HANDLE};
1919
use mem;
2020
use path::{Path, PathBuf};
2121
use ptr;
22+
use slice;
2223
use sync::Arc;
2324
use sys::handle::Handle;
2425
use sys::{c, cvt};
@@ -364,22 +365,40 @@ pub fn rmdir(p: &Path) -> io::Result<()> {
364365
}
365366

366367
pub fn readlink(p: &Path) -> io::Result<PathBuf> {
367-
use sys::c::compat::kernel32::GetFinalPathNameByHandleW;
368368
let mut opts = OpenOptions::new();
369369
opts.read(true);
370-
let file = try!(File::open(p, &opts));;
371-
372-
// Specify (sz - 1) because the documentation states that it's the size
373-
// without the null pointer
374-
//
375-
// FIXME: I have a feeling that this reads intermediate symlinks as well.
376-
let ret: OsString = try!(super::fill_utf16_buf_new(|buf, sz| unsafe {
377-
GetFinalPathNameByHandleW(file.handle.raw(),
378-
buf as *const u16,
379-
sz - 1,
380-
libc::VOLUME_NAME_DOS)
381-
}, |s| OsStringExt::from_wide(s)));
382-
Ok(PathBuf::from(&ret))
370+
opts.flags_and_attributes(c::FILE_FLAG_OPEN_REPARSE_POINT as i32);
371+
let file = try!(File::open(p, &opts));
372+
373+
let mut space = [0u8; c::MAXIMUM_REPARSE_DATA_BUFFER_SIZE];
374+
let mut bytes = 0;
375+
376+
unsafe {
377+
try!(cvt({
378+
c::DeviceIoControl(file.handle.raw(),
379+
c::FSCTL_GET_REPARSE_POINT,
380+
0 as *mut _,
381+
0,
382+
space.as_mut_ptr() as *mut _,
383+
space.len() as libc::DWORD,
384+
&mut bytes,
385+
0 as *mut _)
386+
}));
387+
let buf: *const c::REPARSE_DATA_BUFFER = space.as_ptr() as *const _;
388+
if (*buf).ReparseTag != c::IO_REPARSE_TAG_SYMLINK {
389+
return Err(io::Error::new(io::ErrorKind::Other, "not a symlink"))
390+
}
391+
let info: *const c::SYMBOLIC_LINK_REPARSE_BUFFER =
392+
&(*buf).rest as *const _ as *const _;
393+
let path_buffer = &(*info).PathBuffer as *const _ as *const u16;
394+
let subst_off = (*info).SubstituteNameOffset / 2;
395+
let subst_ptr = path_buffer.offset(subst_off as isize);
396+
let subst_len = (*info).SubstituteNameLength / 2;
397+
let subst = slice::from_raw_parts(subst_ptr, subst_len as usize);
398+
399+
Ok(PathBuf::from(OsString::from_wide(subst)))
400+
}
401+
383402
}
384403

385404
pub fn symlink(src: &Path, dst: &Path) -> io::Result<()> {

0 commit comments

Comments
 (0)