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]
This commit is contained in:
parent
926f38e588
commit
f3f99fb44e
2 changed files with 62 additions and 13 deletions
|
@ -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…
Add table
Add a link
Reference in a new issue