Auto merge of #31630 - pitdicker:read_link, r=alexcrichton
This commit is contained in:
commit
9b367d9d8b
@ -2151,6 +2151,26 @@ mod tests {
|
|||||||
"foo");
|
"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]
|
#[test]
|
||||||
fn readlink_not_symlink() {
|
fn readlink_not_symlink() {
|
||||||
let tmpdir = tmpdir();
|
let tmpdir = tmpdir();
|
||||||
|
@ -240,6 +240,7 @@ pub const MAXIMUM_REPARSE_DATA_BUFFER_SIZE: usize = 16 * 1024;
|
|||||||
pub const FSCTL_GET_REPARSE_POINT: DWORD = 0x900a8;
|
pub const FSCTL_GET_REPARSE_POINT: DWORD = 0x900a8;
|
||||||
pub const IO_REPARSE_TAG_SYMLINK: DWORD = 0xa000000c;
|
pub const IO_REPARSE_TAG_SYMLINK: DWORD = 0xa000000c;
|
||||||
pub const IO_REPARSE_TAG_MOUNT_POINT: DWORD = 0xa0000003;
|
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_SET_REPARSE_POINT: DWORD = 0x900a4;
|
||||||
pub const FSCTL_DELETE_REPARSE_POINT: DWORD = 0x900ac;
|
pub const FSCTL_DELETE_REPARSE_POINT: DWORD = 0x900ac;
|
||||||
|
|
||||||
@ -533,6 +534,15 @@ pub struct SYMBOLIC_LINK_REPARSE_BUFFER {
|
|||||||
pub PathBuffer: WCHAR,
|
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(
|
pub type LPPROGRESS_ROUTINE = ::option::Option<unsafe extern "system" fn(
|
||||||
TotalFileSize: LARGE_INTEGER,
|
TotalFileSize: LARGE_INTEGER,
|
||||||
TotalBytesTransferred: LARGE_INTEGER,
|
TotalBytesTransferred: LARGE_INTEGER,
|
||||||
|
@ -150,7 +150,12 @@ impl DirEntry {
|
|||||||
nFileSizeHigh: self.data.nFileSizeHigh,
|
nFileSizeHigh: self.data.nFileSizeHigh,
|
||||||
nFileSizeLow: self.data.nFileSizeLow,
|
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 @@ impl OpenOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl File {
|
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> {
|
pub fn open(path: &Path, opts: &OpenOptions) -> io::Result<File> {
|
||||||
let path = try!(to_u16s(path));
|
let path = try!(to_u16s(path));
|
||||||
let handle = unsafe {
|
let handle = unsafe {
|
||||||
@ -371,19 +367,34 @@ impl File {
|
|||||||
fn readlink(&self) -> io::Result<PathBuf> {
|
fn readlink(&self) -> io::Result<PathBuf> {
|
||||||
let mut space = [0u8; c::MAXIMUM_REPARSE_DATA_BUFFER_SIZE];
|
let mut space = [0u8; c::MAXIMUM_REPARSE_DATA_BUFFER_SIZE];
|
||||||
let (_bytes, buf) = try!(self.reparse_point(&mut space));
|
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 {
|
unsafe {
|
||||||
let info: *const c::SYMBOLIC_LINK_REPARSE_BUFFER =
|
let (path_buffer, subst_off, subst_len, relative) = match buf.ReparseTag {
|
||||||
&buf.rest as *const _ as *const _;
|
c::IO_REPARSE_TAG_SYMLINK => {
|
||||||
let path_buffer = &(*info).PathBuffer as *const _ as *const u16;
|
let info: *const c::SYMBOLIC_LINK_REPARSE_BUFFER =
|
||||||
let subst_off = (*info).SubstituteNameOffset / 2;
|
&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_ptr = path_buffer.offset(subst_off as isize);
|
||||||
let subst_len = (*info).SubstituteNameLength / 2;
|
let mut subst = slice::from_raw_parts(subst_ptr, subst_len as usize);
|
||||||
let 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)))
|
Ok(PathBuf::from(OsString::from_wide(subst)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -577,8 +588,15 @@ fn remove_dir_all_recursive(path: &Path) -> io::Result<()> {
|
|||||||
rmdir(path)
|
rmdir(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn readlink(p: &Path) -> io::Result<PathBuf> {
|
pub fn readlink(path: &Path) -> io::Result<PathBuf> {
|
||||||
let file = try!(File::open_reparse_point(p, false));
|
// 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()
|
file.readlink()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -605,42 +623,23 @@ pub fn link(src: &Path, dst: &Path) -> io::Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn stat(p: &Path) -> io::Result<FileAttr> {
|
pub fn stat(path: &Path) -> io::Result<FileAttr> {
|
||||||
let attr = try!(lstat(p));
|
let mut opts = OpenOptions::new();
|
||||||
|
// No read or write permissions are necessary
|
||||||
// If this is a reparse point, then we need to reopen the file to get the
|
opts.access_mode(0);
|
||||||
// actual destination. We also pass the FILE_FLAG_BACKUP_SEMANTICS flag to
|
// This flag is so we can open directories too
|
||||||
// ensure that we can open directories (this path may be a directory
|
opts.custom_flags(c::FILE_FLAG_BACKUP_SEMANTICS);
|
||||||
// junction). Once the file is opened we ask the opened handle what its
|
let file = try!(File::open(path, &opts));
|
||||||
// metadata information is.
|
file.file_attr()
|
||||||
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 lstat(p: &Path) -> io::Result<FileAttr> {
|
pub fn lstat(path: &Path) -> io::Result<FileAttr> {
|
||||||
let u16s = try!(to_u16s(p));
|
let mut opts = OpenOptions::new();
|
||||||
unsafe {
|
// No read or write permissions are necessary
|
||||||
let mut attr: FileAttr = mem::zeroed();
|
opts.access_mode(0);
|
||||||
try!(cvt(c::GetFileAttributesExW(u16s.as_ptr(),
|
opts.custom_flags(c::FILE_FLAG_BACKUP_SEMANTICS | c::FILE_FLAG_OPEN_REPARSE_POINT);
|
||||||
c::GetFileExInfoStandard,
|
let file = try!(File::open(path, &opts));
|
||||||
&mut attr.data as *mut _ as *mut _)));
|
file.file_attr()
|
||||||
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 set_perm(p: &Path, perm: FilePermissions) -> io::Result<()> {
|
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<()> {
|
fn symlink_junction_inner(target: &Path, junction: &Path) -> io::Result<()> {
|
||||||
let d = DirBuilder::new();
|
let d = DirBuilder::new();
|
||||||
try!(d.mkdir(&junction));
|
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();
|
let h = f.handle().raw();
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
|
Loading…
Reference in New Issue
Block a user