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]
This commit is contained in:
bors 2015-04-13 17:39:14 +00:00
commit f55e66aaed
2 changed files with 62 additions and 13 deletions

View File

@ -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")]

View File

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