Skip to content

std: Fix fs::read_link behavior on Windows #24198

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 13, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions src/libstd/sys/windows/c.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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")]
Expand Down
47 changes: 33 additions & 14 deletions src/libstd/sys/windows/fs2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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));;

// 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))
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)))
}

}

pub fn symlink(src: &Path, dst: &Path) -> io::Result<()> {
Expand Down