Skip to content

Commit f55e66a

Browse files
committed
Auto merge of #24198 - alexcrichton:windows-readlink, r=aturon
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]
2 parents 588d37c + f3f99fb commit f55e66a

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)