about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/libstd/sys/windows/c.rs30
-rw-r--r--src/libstd/sys/windows/fs2.rs47
2 files changed, 63 insertions, 14 deletions
diff --git a/src/libstd/sys/windows/c.rs b/src/libstd/sys/windows/c.rs
index 4804f650441..c8f6aca7bd3 100644
--- a/src/libstd/sys/windows/c.rs
+++ b/src/libstd/sys/windows/c.rs
@@ -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")]
diff --git a/src/libstd/sys/windows/fs2.rs b/src/libstd/sys/windows/fs2.rs
index d03e45649ed..9645c51ec0b 100644
--- a/src/libstd/sys/windows/fs2.rs
+++ b/src/libstd/sys/windows/fs2.rs
@@ -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));;
-
-    // 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<()> {