From f3f99fb44eea1d7037f94259aef6cbc39837ebee Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 8 Apr 2015 09:50:50 -0700 Subject: [PATCH] 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] --- src/libstd/sys/windows/c.rs | 30 +++++++++++++++++++++++ src/libstd/sys/windows/fs2.rs | 45 +++++++++++++++++++++++++---------- 2 files changed, 62 insertions(+), 13 deletions(-) diff --git a/src/libstd/sys/windows/c.rs b/src/libstd/sys/windows/c.rs index 4804f650441..c8f6aca7bd3 100644 --- a/src/libstd/sys/windows/c.rs +++ b/src/libstd/sys/windows/c.rs @@ -47,6 +47,10 @@ pub const WSAESHUTDOWN: libc::c_int = 10058; pub const ERROR_NO_MORE_FILES: libc::DWORD = 18; pub const TOKEN_READ: libc::DWORD = 0x20008; +pub const FILE_FLAG_OPEN_REPARSE_POINT: libc::DWORD = 0x00200000; +pub const MAXIMUM_REPARSE_DATA_BUFFER_SIZE: usize = 16 * 1024; +pub const FSCTL_GET_REPARSE_POINT: libc::DWORD = 0x900a8; +pub const IO_REPARSE_TAG_SYMLINK: libc::DWORD = 0xa000000c; // Note that these are not actually HANDLEs, just values to pass to GetStdHandle pub const STD_INPUT_HANDLE: libc::DWORD = -10i32 as libc::DWORD; @@ -214,6 +218,24 @@ pub struct FILE_END_OF_FILE_INFO { pub EndOfFile: libc::LARGE_INTEGER, } +#[repr(C)] +pub struct REPARSE_DATA_BUFFER { + pub ReparseTag: libc::c_uint, + pub ReparseDataLength: libc::c_ushort, + pub Reserved: libc::c_ushort, + pub rest: (), +} + +#[repr(C)] +pub struct SYMBOLIC_LINK_REPARSE_BUFFER { + pub SubstituteNameOffset: libc::c_ushort, + pub SubstituteNameLength: libc::c_ushort, + pub PrintNameOffset: libc::c_ushort, + pub PrintNameLength: libc::c_ushort, + pub Flags: libc::c_ulong, + pub PathBuffer: libc::WCHAR, +} + #[link(name = "ws2_32")] extern "system" { pub fn WSAStartup(wVersionRequested: libc::WORD, @@ -433,6 +455,14 @@ extern "system" { pub fn GetCurrentProcess() -> libc::HANDLE; pub fn GetStdHandle(which: libc::DWORD) -> libc::HANDLE; pub fn ExitProcess(uExitCode: libc::c_uint) -> !; + pub fn DeviceIoControl(hDevice: libc::HANDLE, + dwIoControlCode: libc::DWORD, + lpInBuffer: libc::LPVOID, + nInBufferSize: libc::DWORD, + lpOutBuffer: libc::LPVOID, + nOutBufferSize: libc::DWORD, + lpBytesReturned: libc::LPDWORD, + lpOverlapped: libc::LPOVERLAPPED) -> libc::BOOL; } #[link(name = "userenv")] diff --git a/src/libstd/sys/windows/fs2.rs b/src/libstd/sys/windows/fs2.rs index d03e45649ed..9645c51ec0b 100644 --- a/src/libstd/sys/windows/fs2.rs +++ b/src/libstd/sys/windows/fs2.rs @@ -19,6 +19,7 @@ use libc::{self, HANDLE}; use mem; use path::{Path, PathBuf}; use ptr; +use slice; use sync::Arc; use sys::handle::Handle; use sys::{c, cvt}; @@ -364,22 +365,40 @@ pub fn rmdir(p: &Path) -> io::Result<()> { } pub fn readlink(p: &Path) -> io::Result { - use sys::c::compat::kernel32::GetFinalPathNameByHandleW; let mut opts = OpenOptions::new(); opts.read(true); - let file = try!(File::open(p, &opts));; + opts.flags_and_attributes(c::FILE_FLAG_OPEN_REPARSE_POINT as i32); + let file = try!(File::open(p, &opts)); + + let mut space = [0u8; c::MAXIMUM_REPARSE_DATA_BUFFER_SIZE]; + let mut bytes = 0; + + unsafe { + try!(cvt({ + c::DeviceIoControl(file.handle.raw(), + c::FSCTL_GET_REPARSE_POINT, + 0 as *mut _, + 0, + space.as_mut_ptr() as *mut _, + space.len() as libc::DWORD, + &mut bytes, + 0 as *mut _) + })); + let buf: *const c::REPARSE_DATA_BUFFER = space.as_ptr() as *const _; + if (*buf).ReparseTag != c::IO_REPARSE_TAG_SYMLINK { + return Err(io::Error::new(io::ErrorKind::Other, "not a symlink")) + } + let info: *const c::SYMBOLIC_LINK_REPARSE_BUFFER = + &(*buf).rest as *const _ as *const _; + let path_buffer = &(*info).PathBuffer as *const _ as *const u16; + let subst_off = (*info).SubstituteNameOffset / 2; + let subst_ptr = path_buffer.offset(subst_off as isize); + let subst_len = (*info).SubstituteNameLength / 2; + let subst = slice::from_raw_parts(subst_ptr, subst_len as usize); + + Ok(PathBuf::from(OsString::from_wide(subst))) + } - // Specify (sz - 1) because the documentation states that it's the size - // without the null pointer - // - // FIXME: I have a feeling that this reads intermediate symlinks as well. - let ret: OsString = try!(super::fill_utf16_buf_new(|buf, sz| unsafe { - GetFinalPathNameByHandleW(file.handle.raw(), - buf as *const u16, - sz - 1, - libc::VOLUME_NAME_DOS) - }, |s| OsStringExt::from_wide(s))); - Ok(PathBuf::from(&ret)) } pub fn symlink(src: &Path, dst: &Path) -> io::Result<()> {