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:
@@ -47,6 +47,10 @@ pub const WSAESHUTDOWN: libc::c_int = 10058;
|
|||||||
|
|
||||||
pub const ERROR_NO_MORE_FILES: libc::DWORD = 18;
|
pub const ERROR_NO_MORE_FILES: libc::DWORD = 18;
|
||||||
pub const TOKEN_READ: libc::DWORD = 0x20008;
|
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
|
// Note that these are not actually HANDLEs, just values to pass to GetStdHandle
|
||||||
pub const STD_INPUT_HANDLE: libc::DWORD = -10i32 as libc::DWORD;
|
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,
|
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")]
|
#[link(name = "ws2_32")]
|
||||||
extern "system" {
|
extern "system" {
|
||||||
pub fn WSAStartup(wVersionRequested: libc::WORD,
|
pub fn WSAStartup(wVersionRequested: libc::WORD,
|
||||||
@@ -433,6 +455,14 @@ extern "system" {
|
|||||||
pub fn GetCurrentProcess() -> libc::HANDLE;
|
pub fn GetCurrentProcess() -> libc::HANDLE;
|
||||||
pub fn GetStdHandle(which: libc::DWORD) -> libc::HANDLE;
|
pub fn GetStdHandle(which: libc::DWORD) -> libc::HANDLE;
|
||||||
pub fn ExitProcess(uExitCode: libc::c_uint) -> !;
|
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")]
|
#[link(name = "userenv")]
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ use libc::{self, HANDLE};
|
|||||||
use mem;
|
use mem;
|
||||||
use path::{Path, PathBuf};
|
use path::{Path, PathBuf};
|
||||||
use ptr;
|
use ptr;
|
||||||
|
use slice;
|
||||||
use sync::Arc;
|
use sync::Arc;
|
||||||
use sys::handle::Handle;
|
use sys::handle::Handle;
|
||||||
use sys::{c, cvt};
|
use sys::{c, cvt};
|
||||||
@@ -364,22 +365,40 @@ pub fn rmdir(p: &Path) -> io::Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn readlink(p: &Path) -> io::Result<PathBuf> {
|
pub fn readlink(p: &Path) -> io::Result<PathBuf> {
|
||||||
use sys::c::compat::kernel32::GetFinalPathNameByHandleW;
|
|
||||||
let mut opts = OpenOptions::new();
|
let mut opts = OpenOptions::new();
|
||||||
opts.read(true);
|
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<()> {
|
pub fn symlink(src: &Path, dst: &Path) -> io::Result<()> {
|
||||||
|
|||||||
Reference in New Issue
Block a user