]> git.lizzy.rs Git - rust.git/commitdiff
Fixes #28528
authorPaul Dicker <pitdicker@gmail.com>
Sat, 13 Feb 2016 19:44:37 +0000 (20:44 +0100)
committerPaul Dicker <pitdicker@gmail.com>
Sat, 13 Feb 2016 19:44:37 +0000 (20:44 +0100)
Fix `read_link` to also be able to read the target of junctions on Windows.
Also the path returned should not include a NT namespace, and there were
some problems with permissions.

src/libstd/fs.rs
src/libstd/sys/windows/c.rs
src/libstd/sys/windows/fs.rs

index d42b948918049ba2291048438e8eb82dad97252a..badbba21d55cc9ddfbf7c55f266a7be4760550ea 100644 (file)
@@ -2151,6 +2151,26 @@ fn symlink_noexist() {
                    "foo");
     }
 
+    #[test]
+    fn read_link() {
+        if cfg!(windows) {
+            // directory symlink
+            assert_eq!(check!(fs::read_link(r"C:\Users\All Users")).to_str().unwrap(),
+                       r"C:\ProgramData");
+            // junction
+            assert_eq!(check!(fs::read_link(r"C:\Users\Default User")).to_str().unwrap(),
+                       r"C:\Users\Default");
+            // junction with special permissions
+            assert_eq!(check!(fs::read_link(r"C:\Documents and Settings\")).to_str().unwrap(),
+                       r"C:\Users");
+        }
+        let tmpdir = tmpdir();
+        let link = tmpdir.join("link");
+        if !got_symlink_permission(&tmpdir) { return };
+        check!(symlink_file(&"foo", &link));
+        assert_eq!(check!(fs::read_link(&link)).to_str().unwrap(), "foo");
+    }
+
     #[test]
     fn readlink_not_symlink() {
         let tmpdir = tmpdir();
index 9fdeb0aef14c8317527531f07ee8b12ee8ecb584..d25d8e0b8048d9ba6f9b7ef03759312d2df8f376 100644 (file)
@@ -240,6 +240,7 @@ fn clone(&self) -> Self { *self }
 pub const FSCTL_GET_REPARSE_POINT: DWORD = 0x900a8;
 pub const IO_REPARSE_TAG_SYMLINK: DWORD = 0xa000000c;
 pub const IO_REPARSE_TAG_MOUNT_POINT: DWORD = 0xa0000003;
+pub const SYMLINK_FLAG_RELATIVE: DWORD = 0x00000001;
 pub const FSCTL_SET_REPARSE_POINT: DWORD = 0x900a4;
 pub const FSCTL_DELETE_REPARSE_POINT: DWORD = 0x900ac;
 
@@ -533,6 +534,15 @@ pub struct SYMBOLIC_LINK_REPARSE_BUFFER {
     pub PathBuffer: WCHAR,
 }
 
+#[repr(C)]
+pub struct MOUNT_POINT_REPARSE_BUFFER {
+    pub SubstituteNameOffset: c_ushort,
+    pub SubstituteNameLength: c_ushort,
+    pub PrintNameOffset: c_ushort,
+    pub PrintNameLength: c_ushort,
+    pub PathBuffer: WCHAR,
+}
+
 pub type LPPROGRESS_ROUTINE = ::option::Option<unsafe extern "system" fn(
     TotalFileSize: LARGE_INTEGER,
     TotalBytesTransferred: LARGE_INTEGER,
index 3062d38f8c259ae4245378151a3b818d0b3b9ccc..d4d95a12f81c42a00be3d76007f6ff2e6990df4e 100644 (file)
@@ -150,7 +150,12 @@ pub fn metadata(&self) -> io::Result<FileAttr> {
                 nFileSizeHigh: self.data.nFileSizeHigh,
                 nFileSizeLow: self.data.nFileSizeLow,
             },
-            reparse_tag: self.data.dwReserved0,
+            reparse_tag: if self.data.dwFileAttributes & c::FILE_ATTRIBUTE_REPARSE_POINT != 0 {
+                    // reserved unless this is a reparse point
+                    self.data.dwReserved0
+                } else {
+                    0
+                },
         })
     }
 }
@@ -240,15 +245,6 @@ fn get_flags_and_attributes(&self) -> c::DWORD {
 }
 
 impl File {
-    fn open_reparse_point(path: &Path, write: bool) -> io::Result<File> {
-        let mut opts = OpenOptions::new();
-        opts.read(!write);
-        opts.write(write);
-        opts.custom_flags(c::FILE_FLAG_OPEN_REPARSE_POINT |
-                          c::FILE_FLAG_BACKUP_SEMANTICS);
-        File::open(path, &opts)
-    }
-
     pub fn open(path: &Path, opts: &OpenOptions) -> io::Result<File> {
         let path = try!(to_u16s(path));
         let handle = unsafe {
@@ -371,19 +367,34 @@ fn reparse_point<'a>(&self,
     fn readlink(&self) -> io::Result<PathBuf> {
         let mut space = [0u8; c::MAXIMUM_REPARSE_DATA_BUFFER_SIZE];
         let (_bytes, buf) = try!(self.reparse_point(&mut space));
-        if buf.ReparseTag != c::IO_REPARSE_TAG_SYMLINK {
-            return Err(io::Error::new(io::ErrorKind::Other, "not a symlink"))
-        }
-
         unsafe {
-            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 (path_buffer, subst_off, subst_len, relative) = match buf.ReparseTag {
+                c::IO_REPARSE_TAG_SYMLINK => {
+                    let info: *const c::SYMBOLIC_LINK_REPARSE_BUFFER =
+                        &buf.rest as *const _ as *const _;
+                    (&(*info).PathBuffer as *const _ as *const u16,
+                     (*info).SubstituteNameOffset / 2,
+                     (*info).SubstituteNameLength / 2,
+                     (*info).Flags & c::SYMLINK_FLAG_RELATIVE != 0)
+                },
+                c::IO_REPARSE_TAG_MOUNT_POINT => {
+                    let info: *const c::MOUNT_POINT_REPARSE_BUFFER =
+                        &buf.rest as *const _ as *const _;
+                    (&(*info).PathBuffer as *const _ as *const u16,
+                     (*info).SubstituteNameOffset / 2,
+                     (*info).SubstituteNameLength / 2,
+                     false)
+                },
+                _ => return Err(io::Error::new(io::ErrorKind::Other,
+                                               "Unsupported reparse point type"))
+            };
             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);
-
+            let mut subst = slice::from_raw_parts(subst_ptr, subst_len as usize);
+            // Absolute paths start with an NT internal namespace prefix `\??\`
+            // We should not let it leak through.
+            if !relative && subst.starts_with(&[92u16, 63u16, 63u16, 92u16]) {
+                subst = &subst[4..];
+            }
             Ok(PathBuf::from(OsString::from_wide(subst)))
         }
     }
@@ -577,8 +588,15 @@ fn remove_dir_all_recursive(path: &Path) -> io::Result<()> {
     rmdir(path)
 }
 
-pub fn readlink(p: &Path) -> io::Result<PathBuf> {
-    let file = try!(File::open_reparse_point(p, false));
+pub fn readlink(path: &Path) -> io::Result<PathBuf> {
+    // Open the link with no access mode, instead of generic read.
+    // By default FILE_LIST_DIRECTORY is denied for the junction "C:\Documents and Settings", so
+    // this is needed for a common case.
+    let mut opts = OpenOptions::new();
+    opts.access_mode(0);
+    opts.custom_flags(c::FILE_FLAG_OPEN_REPARSE_POINT |
+                      c::FILE_FLAG_BACKUP_SEMANTICS);
+    let file = try!(File::open(&path, &opts));
     file.readlink()
 }
 
@@ -605,42 +623,23 @@ pub fn link(src: &Path, dst: &Path) -> io::Result<()> {
     Ok(())
 }
 
-pub fn stat(p: &Path) -> io::Result<FileAttr> {
-    let attr = try!(lstat(p));
-
-    // If this is a reparse point, then we need to reopen the file to get the
-    // actual destination. We also pass the FILE_FLAG_BACKUP_SEMANTICS flag to
-    // ensure that we can open directories (this path may be a directory
-    // junction). Once the file is opened we ask the opened handle what its
-    // metadata information is.
-    if attr.is_reparse_point() {
-        let mut opts = OpenOptions::new();
-        // No read or write permissions are necessary
-        opts.access_mode(0);
-        // This flag is so we can open directories too
-        opts.custom_flags(c::FILE_FLAG_BACKUP_SEMANTICS);
-        let file = try!(File::open(p, &opts));
-        file.file_attr()
-    } else {
-        Ok(attr)
-    }
+pub fn stat(path: &Path) -> io::Result<FileAttr> {
+    let mut opts = OpenOptions::new();
+    // No read or write permissions are necessary
+    opts.access_mode(0);
+    // This flag is so we can open directories too
+    opts.custom_flags(c::FILE_FLAG_BACKUP_SEMANTICS);
+    let file = try!(File::open(path, &opts));
+    file.file_attr()
 }
 
-pub fn lstat(p: &Path) -> io::Result<FileAttr> {
-    let u16s = try!(to_u16s(p));
-    unsafe {
-        let mut attr: FileAttr = mem::zeroed();
-        try!(cvt(c::GetFileAttributesExW(u16s.as_ptr(),
-                                         c::GetFileExInfoStandard,
-                                         &mut attr.data as *mut _ as *mut _)));
-        if attr.is_reparse_point() {
-            attr.reparse_tag = File::open_reparse_point(p, false).and_then(|f| {
-                let mut b = [0; c::MAXIMUM_REPARSE_DATA_BUFFER_SIZE];
-                f.reparse_point(&mut b).map(|(_, b)| b.ReparseTag)
-            }).unwrap_or(0);
-        }
-        Ok(attr)
-    }
+pub fn lstat(path: &Path) -> io::Result<FileAttr> {
+    let mut opts = OpenOptions::new();
+    // No read or write permissions are necessary
+    opts.access_mode(0);
+    opts.custom_flags(c::FILE_FLAG_BACKUP_SEMANTICS | c::FILE_FLAG_OPEN_REPARSE_POINT);
+    let file = try!(File::open(path, &opts));
+    file.file_attr()
 }
 
 pub fn set_perm(p: &Path, perm: FilePermissions) -> io::Result<()> {
@@ -709,7 +708,12 @@ pub fn symlink_junction<P: AsRef<Path>, Q: AsRef<Path>>(src: P, dst: Q) -> io::R
 fn symlink_junction_inner(target: &Path, junction: &Path) -> io::Result<()> {
     let d = DirBuilder::new();
     try!(d.mkdir(&junction));
-    let f = try!(File::open_reparse_point(junction, true));
+
+    let mut opts = OpenOptions::new();
+    opts.write(true);
+    opts.custom_flags(c::FILE_FLAG_OPEN_REPARSE_POINT |
+                      c::FILE_FLAG_BACKUP_SEMANTICS);
+    let f = try!(File::open(junction, &opts));
     let h = f.handle().raw();
 
     unsafe {