summary refs log tree commit diff
path: root/src/libstd/sys/windows/fs2.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/libstd/sys/windows/fs2.rs')
-rw-r--r--src/libstd/sys/windows/fs2.rs47
1 files changed, 33 insertions, 14 deletions
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<()> {