From cdb1df749e848c2d0e0a87fa6140aa7c74220b80 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Tue, 2 Feb 2016 07:04:55 +0100 Subject: Enable more fs tests on Windows --- src/libstd/sys/windows/fs.rs | 172 +++++++++++++++---------------------------- 1 file changed, 58 insertions(+), 114 deletions(-) (limited to 'src/libstd/sys/windows') diff --git a/src/libstd/sys/windows/fs.rs b/src/libstd/sys/windows/fs.rs index a8b82ef5f29..17d9bf329df 100644 --- a/src/libstd/sys/windows/fs.rs +++ b/src/libstd/sys/windows/fs.rs @@ -517,6 +517,25 @@ pub fn rmdir(p: &Path) -> io::Result<()> { Ok(()) } +pub fn remove_dir_all(path: &Path) -> io::Result<()> { + for child in try!(readdir(path)) { + let child = try!(child).path(); + let stat = try!(lstat(&*child)); + if stat.data.dwFileAttributes & c::FILE_ATTRIBUTE_DIRECTORY != 0 { + if stat.data.dwFileAttributes & c::FILE_ATTRIBUTE_REPARSE_POINT != 0 { + // remove junctions and directory symlinks with rmdir + try!(rmdir(&*child)); + } else { + try!(remove_dir_all(&*child)); + } + } else { + // remove files and file symlinks + try!(unlink(&*child)); + } + } + rmdir(path) +} + pub fn readlink(p: &Path) -> io::Result { let file = try!(File::open_reparse_point(p, false)); file.readlink() @@ -635,124 +654,49 @@ pub fn copy(from: &Path, to: &Path) -> io::Result { Ok(size as u64) } -#[test] -fn directory_junctions_are_directories() { - use ffi::OsStr; - use env; - use rand::{self, Rng}; - use vec::Vec; - - macro_rules! t { - ($e:expr) => (match $e { - Ok(e) => e, - Err(e) => panic!("{} failed with: {}", stringify!($e), e), - }) - } +pub fn symlink_junction, Q: AsRef>(src: P, dst: Q) -> io::Result<()> { + symlink_junction_inner(src.as_ref(), dst.as_ref()) +} +// Creating a directory junction on windows involves dealing with reparse +// points and the DeviceIoControl function, and this code is a skeleton of +// what can be found here: +// +// http://www.flexhex.com/docs/articles/hard-links.phtml +fn symlink_junction_inner(target: &Path, junction: &Path) -> io::Result<()> { let d = DirBuilder::new(); - let p = env::temp_dir(); - let mut r = rand::thread_rng(); - let ret = p.join(&format!("rust-{}", r.next_u32())); - let foo = ret.join("foo"); - let bar = ret.join("bar"); - t!(d.mkdir(&ret)); - t!(d.mkdir(&foo)); - t!(d.mkdir(&bar)); - - t!(create_junction(&bar, &foo)); - let metadata = stat(&bar); - t!(delete_junction(&bar)); - - t!(rmdir(&foo)); - t!(rmdir(&bar)); - t!(rmdir(&ret)); - - let metadata = t!(metadata); - assert!(metadata.file_type().is_dir()); - - // Creating a directory junction on windows involves dealing with reparse - // points and the DeviceIoControl function, and this code is a skeleton of - // what can be found here: - // - // http://www.flexhex.com/docs/articles/hard-links.phtml - fn create_junction(src: &Path, dst: &Path) -> io::Result<()> { - let f = try!(opendir(src, true)); - let h = f.handle().raw(); + try!(d.mkdir(&junction)); + let f = try!(File::open_reparse_point(junction, true)); + let h = f.handle().raw(); - unsafe { - let mut data = [0u8; c::MAXIMUM_REPARSE_DATA_BUFFER_SIZE]; - let mut db = data.as_mut_ptr() - as *mut c::REPARSE_MOUNTPOINT_DATA_BUFFER; - let buf = &mut (*db).ReparseTarget as *mut _; - let mut i = 0; - let v = br"\??\"; - let v = v.iter().map(|x| *x as u16); - for c in v.chain(dst.as_os_str().encode_wide()) { - *buf.offset(i) = c; - i += 1; - } - *buf.offset(i) = 0; + unsafe { + let mut data = [0u8; c::MAXIMUM_REPARSE_DATA_BUFFER_SIZE]; + let mut db = data.as_mut_ptr() + as *mut c::REPARSE_MOUNTPOINT_DATA_BUFFER; + let buf = &mut (*db).ReparseTarget as *mut _; + let mut i = 0; + // FIXME: this conversion is very hacky + let v = br"\??\"; + let v = v.iter().map(|x| *x as u16); + for c in v.chain(target.as_os_str().encode_wide()) { + *buf.offset(i) = c; i += 1; - (*db).ReparseTag = c::IO_REPARSE_TAG_MOUNT_POINT; - (*db).ReparseTargetMaximumLength = (i * 2) as c::WORD; - (*db).ReparseTargetLength = ((i - 1) * 2) as c::WORD; - (*db).ReparseDataLength = - (*db).ReparseTargetLength as c::DWORD + 12; - - let mut ret = 0; - cvt(c::DeviceIoControl(h as *mut _, - c::FSCTL_SET_REPARSE_POINT, - data.as_ptr() as *mut _, - (*db).ReparseDataLength + 8, - ptr::null_mut(), 0, - &mut ret, - ptr::null_mut())).map(|_| ()) - } - } - - fn opendir(p: &Path, write: bool) -> io::Result { - unsafe { - let mut token = ptr::null_mut(); - let mut tp: c::TOKEN_PRIVILEGES = mem::zeroed(); - try!(cvt(c::OpenProcessToken(c::GetCurrentProcess(), - c::TOKEN_ADJUST_PRIVILEGES, - &mut token))); - let name: &OsStr = if write { - "SeRestorePrivilege".as_ref() - } else { - "SeBackupPrivilege".as_ref() - }; - let name = name.encode_wide().chain(Some(0)).collect::>(); - try!(cvt(c::LookupPrivilegeValueW(ptr::null(), - name.as_ptr(), - &mut tp.Privileges[0].Luid))); - tp.PrivilegeCount = 1; - tp.Privileges[0].Attributes = c::SE_PRIVILEGE_ENABLED; - let size = mem::size_of::() as c::DWORD; - try!(cvt(c::AdjustTokenPrivileges(token, c::FALSE, &mut tp, size, - ptr::null_mut(), ptr::null_mut()))); - try!(cvt(c::CloseHandle(token))); - - File::open_reparse_point(p, write) - } - } - - fn delete_junction(p: &Path) -> io::Result<()> { - unsafe { - let f = try!(opendir(p, true)); - let h = f.handle().raw(); - let mut data = [0u8; c::MAXIMUM_REPARSE_DATA_BUFFER_SIZE]; - let mut db = data.as_mut_ptr() - as *mut c::REPARSE_MOUNTPOINT_DATA_BUFFER; - (*db).ReparseTag = c::IO_REPARSE_TAG_MOUNT_POINT; - let mut bytes = 0; - cvt(c::DeviceIoControl(h as *mut _, - c::FSCTL_DELETE_REPARSE_POINT, - data.as_ptr() as *mut _, - (*db).ReparseDataLength + 8, - ptr::null_mut(), 0, - &mut bytes, - ptr::null_mut())).map(|_| ()) } + *buf.offset(i) = 0; + i += 1; + (*db).ReparseTag = c::IO_REPARSE_TAG_MOUNT_POINT; + (*db).ReparseTargetMaximumLength = (i * 2) as c::WORD; + (*db).ReparseTargetLength = ((i - 1) * 2) as c::WORD; + (*db).ReparseDataLength = + (*db).ReparseTargetLength as c::DWORD + 12; + + let mut ret = 0; + cvt(c::DeviceIoControl(h as *mut _, + c::FSCTL_SET_REPARSE_POINT, + data.as_ptr() as *mut _, + (*db).ReparseDataLength + 8, + ptr::null_mut(), 0, + &mut ret, + ptr::null_mut())).map(|_| ()) } } -- cgit 1.4.1-3-g733a5 From 7f6f4581946f9ae8e2c23cd7e09eb4f03d6c3cb1 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Wed, 3 Feb 2016 18:23:33 +0100 Subject: Adress comments --- src/libstd/fs.rs | 34 +++++++++++++++++++++++++++++- src/libstd/sys/unix/fs.rs | 9 ++++---- src/libstd/sys/windows/fs.rs | 49 +++++++++++++++++++++++--------------------- 3 files changed, 63 insertions(+), 29 deletions(-) (limited to 'src/libstd/sys/windows') diff --git a/src/libstd/fs.rs b/src/libstd/fs.rs index 63980f39c48..23672d0cc13 100644 --- a/src/libstd/fs.rs +++ b/src/libstd/fs.rs @@ -1475,7 +1475,9 @@ mod tests { use str; #[cfg(windows)] - use os::windows::fs::{symlink_dir, symlink_file, symlink_junction}; + use os::windows::fs::{symlink_dir, symlink_file}; + #[cfg(windows)] + use sys::fs::symlink_junction; #[cfg(unix)] use os::unix::fs::symlink as symlink_dir; #[cfg(unix)] @@ -1533,6 +1535,7 @@ mod tests { // `SeCreateSymbolicLinkPrivilege`). Instead of disabling these test on Windows, use this // function to test whether we have permission, and return otherwise. This way, we still don't // run these tests most of the time, but at least we do if the user has the right permissions. + #[cfg(windows)] pub fn got_symlink_permission(tmpdir: &TempDir) -> bool { let link = tmpdir.join("some_hopefully_unique_link_name"); @@ -1546,6 +1549,9 @@ mod tests { } } } + #[cfg(not(windows))] + #[allow(unused_variables)] + pub fn got_symlink_permission(tmpdir: &TempDir) -> bool { true } #[test] fn file_test_io_smoke_test() { @@ -2401,4 +2407,30 @@ mod tests { let res = fs::read_dir("/path/that/does/not/exist"); assert_eq!(res.err().unwrap().kind(), ErrorKind::NotFound); } + + #[test] + fn create_dir_all_with_junctions() { + let tmpdir = tmpdir(); + let target = tmpdir.join("target"); + + let junction = tmpdir.join("junction"); + let b = junction.join("a/b"); + + let link = tmpdir.join("link"); + let d = link.join("c/d"); + + fs::create_dir(&target).unwrap(); + + check!(symlink_junction(&target, &junction)); + check!(fs::create_dir_all(&b)); + // the junction itself is not a directory, but `is_dir()` on a Path follows links + assert!(junction.is_dir()); + assert!(b.exists()); + + if !got_symlink_permission(&tmpdir) { return }; + check!(symlink_dir(&target, &link)); + check!(fs::create_dir_all(&d)); + assert!(link.is_dir()); + assert!(d.exists()); + } } diff --git a/src/libstd/sys/unix/fs.rs b/src/libstd/sys/unix/fs.rs index f7989d35710..a2e6f467b17 100644 --- a/src/libstd/sys/unix/fs.rs +++ b/src/libstd/sys/unix/fs.rs @@ -512,12 +512,11 @@ pub fn rmdir(p: &Path) -> io::Result<()> { pub fn remove_dir_all(path: &Path) -> io::Result<()> { for child in try!(readdir(path)) { - let child = try!(child).path(); - let stat = try!(lstat(&*child)); - if stat.file_type().is_dir() { - try!(remove_dir_all(&*child)); + let child = try!(child); + if try!(child.file_type()).is_dir() { + try!(remove_dir_all(&child.path())); } else { - try!(unlink(&*child)); + try!(unlink(&child.path())); } } rmdir(path) diff --git a/src/libstd/sys/windows/fs.rs b/src/libstd/sys/windows/fs.rs index 17d9bf329df..7ace48fe561 100644 --- a/src/libstd/sys/windows/fs.rs +++ b/src/libstd/sys/windows/fs.rs @@ -35,7 +35,7 @@ pub struct FileAttr { #[derive(Copy, Clone, PartialEq, Eq, Hash)] pub enum FileType { - Dir, File, Symlink, ReparsePoint, MountPoint, + Dir, File, SymlinkFile, SymlinkDir, ReparsePoint, MountPoint, } pub struct ReadDir { @@ -444,23 +444,30 @@ impl FilePermissions { impl FileType { fn new(attrs: c::DWORD, reparse_tag: c::DWORD) -> FileType { - if attrs & c::FILE_ATTRIBUTE_REPARSE_POINT != 0 { - match reparse_tag { - c::IO_REPARSE_TAG_SYMLINK => FileType::Symlink, - c::IO_REPARSE_TAG_MOUNT_POINT => FileType::MountPoint, - _ => FileType::ReparsePoint, - } - } else if attrs & c::FILE_ATTRIBUTE_DIRECTORY != 0 { - FileType::Dir - } else { - FileType::File + match (attrs & c::FILE_ATTRIBUTE_DIRECTORY != 0, + attrs & c::FILE_ATTRIBUTE_REPARSE_POINT != 0, + reparse_tag) { + (false, false, _) => FileType::File, + (true, false, _) => FileType::Dir, + (false, true, c::IO_REPARSE_TAG_SYMLINK) => FileType::SymlinkFile, + (true, true, c::IO_REPARSE_TAG_SYMLINK) => FileType::SymlinkDir, + (true, true, c::IO_REPARSE_TAG_MOUNT_POINT) => FileType::MountPoint, + (_, true, _) => FileType::ReparsePoint, + // Note: if a _file_ has a reparse tag of the type IO_REPARSE_TAG_MOUNT_POINT it is + // invalid, as junctions always have to be dirs. We set the filetype to ReparsePoint + // to indicate it is something symlink-like, but not something you can follow. } } pub fn is_dir(&self) -> bool { *self == FileType::Dir } pub fn is_file(&self) -> bool { *self == FileType::File } pub fn is_symlink(&self) -> bool { - *self == FileType::Symlink || *self == FileType::MountPoint + *self == FileType::SymlinkFile || + *self == FileType::SymlinkDir || + *self == FileType::MountPoint + } + pub fn is_symlink_dir(&self) -> bool { + *self == FileType::SymlinkDir || *self == FileType::MountPoint } } @@ -519,18 +526,14 @@ pub fn rmdir(p: &Path) -> io::Result<()> { pub fn remove_dir_all(path: &Path) -> io::Result<()> { for child in try!(readdir(path)) { - let child = try!(child).path(); - let stat = try!(lstat(&*child)); - if stat.data.dwFileAttributes & c::FILE_ATTRIBUTE_DIRECTORY != 0 { - if stat.data.dwFileAttributes & c::FILE_ATTRIBUTE_REPARSE_POINT != 0 { - // remove junctions and directory symlinks with rmdir - try!(rmdir(&*child)); - } else { - try!(remove_dir_all(&*child)); - } + let child = try!(child); + let child_type = try!(child.file_type()); + if child_type.is_dir() { + try!(remove_dir_all(&child.path())); + } else if child_type.is_symlink_dir() { + try!(rmdir(&child.path())); } else { - // remove files and file symlinks - try!(unlink(&*child)); + try!(unlink(&child.path())); } } rmdir(path) -- cgit 1.4.1-3-g733a5 From fb172b676e5ab951e58b98cede795ab1a7557a58 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Thu, 4 Feb 2016 16:29:55 +0100 Subject: Allow dead code for `symlink_junction()` --- src/libstd/sys/windows/fs.rs | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src/libstd/sys/windows') diff --git a/src/libstd/sys/windows/fs.rs b/src/libstd/sys/windows/fs.rs index 7ace48fe561..9f9290b751d 100644 --- a/src/libstd/sys/windows/fs.rs +++ b/src/libstd/sys/windows/fs.rs @@ -657,6 +657,7 @@ pub fn copy(from: &Path, to: &Path) -> io::Result { Ok(size as u64) } +#[allow(dead_code)] pub fn symlink_junction, Q: AsRef>(src: P, dst: Q) -> io::Result<()> { symlink_junction_inner(src.as_ref(), dst.as_ref()) } @@ -666,6 +667,7 @@ pub fn symlink_junction, Q: AsRef>(src: P, dst: Q) -> io::R // what can be found here: // // http://www.flexhex.com/docs/articles/hard-links.phtml +#[allow(dead_code)] fn symlink_junction_inner(target: &Path, junction: &Path) -> io::Result<()> { let d = DirBuilder::new(); try!(d.mkdir(&junction)); -- cgit 1.4.1-3-g733a5