diff --git a/src/libstd/fs.rs b/src/libstd/fs.rs index d42b9489180..badbba21d55 100644 --- a/src/libstd/fs.rs +++ b/src/libstd/fs.rs @@ -2151,6 +2151,26 @@ mod tests { "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(); diff --git a/src/libstd/sys/windows/c.rs b/src/libstd/sys/windows/c.rs index 9fdeb0aef14..d25d8e0b804 100644 --- a/src/libstd/sys/windows/c.rs +++ b/src/libstd/sys/windows/c.rs @@ -240,6 +240,7 @@ pub const MAXIMUM_REPARSE_DATA_BUFFER_SIZE: usize = 16 * 1024; 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 io::Result { - 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 { let path = try!(to_u16s(path)); let handle = unsafe { @@ -371,19 +367,34 @@ impl File { fn readlink(&self) -> io::Result { 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 { - let file = try!(File::open_reparse_point(p, false)); +pub fn readlink(path: &Path) -> io::Result { + // 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 { - 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 { + 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 { - 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 { + 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, Q: AsRef>(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 {