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<()> {