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:
commit
f55e66aaed
|
@ -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")]
|
||||
|
|
|
@ -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<()> {
|
||||
|
|
Loading…
Reference in New Issue