Skip to content

Commit d0ef740

Browse files
committed
Auto merge of #31360 - pitdicker:fs_tests_cleanup, r=alexcrichton
- use `symlink_file` and `symlink_dir` instead of the old `soft_link` - create a junction instead of a directory symlink for testing recursive_rmdir (as it causes the same troubles, but can be created by users without `SeCreateSymbolicLinkPrivilege`) - `remove_dir_all` was unable to remove directory symlinks and junctions - only run tests that create symlinks if we have the right permissions. - rename `Path2` to `Path` - remove the global `#[allow(deprecated)]` and outdated comments - After factoring out `create_junction()` from the test `directory_junctions_are_directories` and removing needlessly complex code, what I was left with was: ``` #[test] #[cfg(windows)] fn directory_junctions_are_directories() { use sys::fs::create_junction; let tmpdir = tmpdir(); let foo = tmpdir.join("foo"); let bar = tmpdir.join("bar"); fs::create_dir(&foo).unwrap(); check!(create_junction(&foo, &bar)); assert!(bar.metadata().unwrap().is_dir()); } ``` It test whether a junction is a directory instead of a reparse point. But it actually test the target of the junction (which is a directory if it exists) instead of the junction itself, which should always be a symlink. So this test is invalid, and I expect it only exists because the author was suprised by it. So I removed it. Some things that do not yet work right: - relative symlinks do not accept forward slashes - the conversion of paths for `create_junction` is hacky - `remove_dir_all` now messes with the internal data of `FileAttr` to be able to remove symlinks. We should add some method like `is_symlink_dir()` to it, so code outside the standard library can see the difference between file and directory symlinks too.
2 parents f01b85b + fb172b6 commit d0ef740

File tree

3 files changed

+192
-183
lines changed

3 files changed

+192
-183
lines changed

Diff for: src/libstd/fs.rs

+105-57
Original file line numberDiff line numberDiff line change
@@ -1265,20 +1265,7 @@ pub fn remove_dir<P: AsRef<Path>>(path: P) -> io::Result<()> {
12651265
/// ```
12661266
#[stable(feature = "rust1", since = "1.0.0")]
12671267
pub fn remove_dir_all<P: AsRef<Path>>(path: P) -> io::Result<()> {
1268-
_remove_dir_all(path.as_ref())
1269-
}
1270-
1271-
fn _remove_dir_all(path: &Path) -> io::Result<()> {
1272-
for child in try!(read_dir(path)) {
1273-
let child = try!(child).path();
1274-
let stat = try!(symlink_metadata(&*child));
1275-
if stat.is_dir() {
1276-
try!(remove_dir_all(&*child));
1277-
} else {
1278-
try!(remove_file(&*child));
1279-
}
1280-
}
1281-
remove_dir(path)
1268+
fs_imp::remove_dir_all(path.as_ref())
12821269
}
12831270

12841271
/// Returns an iterator over the entries within a directory.
@@ -1489,19 +1476,27 @@ impl AsInnerMut<fs_imp::DirBuilder> for DirBuilder {
14891476

14901477
#[cfg(test)]
14911478
mod tests {
1492-
#![allow(deprecated)] //rand
1493-
14941479
use prelude::v1::*;
14951480
use io::prelude::*;
14961481

14971482
use env;
14981483
use fs::{self, File, OpenOptions};
14991484
use io::{ErrorKind, SeekFrom};
1500-
use path::PathBuf;
1501-
use path::Path as Path2;
1485+
use path::{Path, PathBuf};
15021486
use rand::{self, StdRng, Rng};
15031487
use str;
15041488

1489+
#[cfg(windows)]
1490+
use os::windows::fs::{symlink_dir, symlink_file};
1491+
#[cfg(windows)]
1492+
use sys::fs::symlink_junction;
1493+
#[cfg(unix)]
1494+
use os::unix::fs::symlink as symlink_dir;
1495+
#[cfg(unix)]
1496+
use os::unix::fs::symlink as symlink_file;
1497+
#[cfg(unix)]
1498+
use os::unix::fs::symlink as symlink_junction;
1499+
15051500
macro_rules! check { ($e:expr) => (
15061501
match $e {
15071502
Ok(t) => t,
@@ -1525,7 +1520,7 @@ mod tests {
15251520
p.join(path)
15261521
}
15271522

1528-
fn path<'a>(&'a self) -> &'a Path2 {
1523+
fn path<'a>(&'a self) -> &'a Path {
15291524
let TempDir(ref p) = *self;
15301525
p
15311526
}
@@ -1548,6 +1543,27 @@ mod tests {
15481543
TempDir(ret)
15491544
}
15501545

1546+
// Several test fail on windows if the user does not have permission to
1547+
// create symlinks (the `SeCreateSymbolicLinkPrivilege`). Instead of
1548+
// disabling these test on Windows, use this function to test whether we
1549+
// have permission, and return otherwise. This way, we still don't run these
1550+
// tests most of the time, but at least we do if the user has the right
1551+
// permissions.
1552+
pub fn got_symlink_permission(tmpdir: &TempDir) -> bool {
1553+
if cfg!(unix) { return true }
1554+
let link = tmpdir.join("some_hopefully_unique_link_name");
1555+
1556+
match symlink_file(r"nonexisting_target", link) {
1557+
Ok(_) => true,
1558+
Err(ref err) =>
1559+
if err.to_string().contains("A required privilege is not held by the client.") {
1560+
false
1561+
} else {
1562+
true
1563+
}
1564+
}
1565+
}
1566+
15511567
#[test]
15521568
fn file_test_io_smoke_test() {
15531569
let message = "it's alright. have a good time";
@@ -1578,8 +1594,9 @@ mod tests {
15781594
if cfg!(unix) {
15791595
error!(result, "o such file or directory");
15801596
}
1581-
// error!(result, "couldn't open path as file");
1582-
// error!(result, format!("path={}; mode=open; access=read", filename.display()));
1597+
if cfg!(windows) {
1598+
error!(result, "The system cannot find the file specified");
1599+
}
15831600
}
15841601

15851602
#[test]
@@ -1592,8 +1609,9 @@ mod tests {
15921609
if cfg!(unix) {
15931610
error!(result, "o such file or directory");
15941611
}
1595-
// error!(result, "couldn't unlink path");
1596-
// error!(result, format!("path={}", filename.display()));
1612+
if cfg!(windows) {
1613+
error!(result, "The system cannot find the file specified");
1614+
}
15971615
}
15981616

15991617
#[test]
@@ -1799,6 +1817,7 @@ mod tests {
17991817
}
18001818

18011819
#[test]
1820+
#[allow(deprecated)]
18021821
fn file_test_walk_dir() {
18031822
let tmpdir = tmpdir();
18041823
let dir = &tmpdir.join("walk_dir");
@@ -1855,19 +1874,13 @@ mod tests {
18551874
let result = fs::create_dir_all(&file);
18561875

18571876
assert!(result.is_err());
1858-
// error!(result, "couldn't recursively mkdir");
1859-
// error!(result, "couldn't create directory");
1860-
// error!(result, "mode=0700");
1861-
// error!(result, format!("path={}", file.display()));
18621877
}
18631878

18641879
#[test]
18651880
fn recursive_mkdir_slash() {
1866-
check!(fs::create_dir_all(&Path2::new("/")));
1881+
check!(fs::create_dir_all(&Path::new("/")));
18671882
}
18681883

1869-
// FIXME(#12795) depends on lstat to work on windows
1870-
#[cfg(not(windows))]
18711884
#[test]
18721885
fn recursive_rmdir() {
18731886
let tmpdir = tmpdir();
@@ -1879,7 +1892,7 @@ mod tests {
18791892
check!(fs::create_dir_all(&dtt));
18801893
check!(fs::create_dir_all(&d2));
18811894
check!(check!(File::create(&canary)).write(b"foo"));
1882-
check!(fs::soft_link(&d2, &dt.join("d2")));
1895+
check!(symlink_junction(&d2, &dt.join("d2")));
18831896
check!(fs::remove_dir_all(&d1));
18841897

18851898
assert!(!d1.is_dir());
@@ -1888,8 +1901,8 @@ mod tests {
18881901

18891902
#[test]
18901903
fn unicode_path_is_dir() {
1891-
assert!(Path2::new(".").is_dir());
1892-
assert!(!Path2::new("test/stdtest/fs.rs").is_dir());
1904+
assert!(Path::new(".").is_dir());
1905+
assert!(!Path::new("test/stdtest/fs.rs").is_dir());
18931906

18941907
let tmpdir = tmpdir();
18951908

@@ -1907,21 +1920,21 @@ mod tests {
19071920

19081921
#[test]
19091922
fn unicode_path_exists() {
1910-
assert!(Path2::new(".").exists());
1911-
assert!(!Path2::new("test/nonexistent-bogus-path").exists());
1923+
assert!(Path::new(".").exists());
1924+
assert!(!Path::new("test/nonexistent-bogus-path").exists());
19121925

19131926
let tmpdir = tmpdir();
19141927
let unicode = tmpdir.path();
19151928
let unicode = unicode.join(&format!("test-각丁ー再见"));
19161929
check!(fs::create_dir(&unicode));
19171930
assert!(unicode.exists());
1918-
assert!(!Path2::new("test/unicode-bogus-path-각丁ー再见").exists());
1931+
assert!(!Path::new("test/unicode-bogus-path-각丁ー再见").exists());
19191932
}
19201933

19211934
#[test]
19221935
fn copy_file_does_not_exist() {
1923-
let from = Path2::new("test/nonexistent-bogus-path");
1924-
let to = Path2::new("test/other-bogus-path");
1936+
let from = Path::new("test/nonexistent-bogus-path");
1937+
let to = Path::new("test/other-bogus-path");
19251938

19261939
match fs::copy(&from, &to) {
19271940
Ok(..) => panic!(),
@@ -1935,7 +1948,7 @@ mod tests {
19351948
#[test]
19361949
fn copy_src_does_not_exist() {
19371950
let tmpdir = tmpdir();
1938-
let from = Path2::new("test/nonexistent-bogus-path");
1951+
let from = Path::new("test/nonexistent-bogus-path");
19391952
let to = tmpdir.join("out.txt");
19401953
check!(check!(File::create(&to)).write(b"hello"));
19411954
assert!(fs::copy(&from, &to).is_err());
@@ -2026,34 +2039,35 @@ mod tests {
20262039
assert_eq!(v, b"carrot".to_vec());
20272040
}
20282041

2029-
#[cfg(not(windows))] // FIXME(#10264) operation not permitted?
20302042
#[test]
20312043
fn symlinks_work() {
20322044
let tmpdir = tmpdir();
2045+
if !got_symlink_permission(&tmpdir) { return };
2046+
20332047
let input = tmpdir.join("in.txt");
20342048
let out = tmpdir.join("out.txt");
20352049

20362050
check!(check!(File::create(&input)).write("foobar".as_bytes()));
2037-
check!(fs::soft_link(&input, &out));
2038-
// if cfg!(not(windows)) {
2039-
// assert_eq!(check!(lstat(&out)).kind, FileType::Symlink);
2040-
// assert_eq!(check!(out.lstat()).kind, FileType::Symlink);
2041-
// }
2051+
check!(symlink_file(&input, &out));
2052+
assert!(check!(out.symlink_metadata()).file_type().is_symlink());
20422053
assert_eq!(check!(fs::metadata(&out)).len(),
20432054
check!(fs::metadata(&input)).len());
20442055
let mut v = Vec::new();
20452056
check!(check!(File::open(&out)).read_to_end(&mut v));
20462057
assert_eq!(v, b"foobar".to_vec());
20472058
}
20482059

2049-
#[cfg(not(windows))] // apparently windows doesn't like symlinks
20502060
#[test]
20512061
fn symlink_noexist() {
2062+
// Symlinks can point to things that don't exist
20522063
let tmpdir = tmpdir();
2053-
// symlinks can point to things that don't exist
2054-
check!(fs::soft_link(&tmpdir.join("foo"), &tmpdir.join("bar")));
2055-
assert_eq!(check!(fs::read_link(&tmpdir.join("bar"))),
2056-
tmpdir.join("foo"));
2064+
if !got_symlink_permission(&tmpdir) { return };
2065+
2066+
// Use a relative path for testing. Symlinks get normalized by Windows,
2067+
// so we may not get the same path back for absolute paths
2068+
check!(symlink_file(&"foo", &tmpdir.join("bar")));
2069+
assert_eq!(check!(fs::read_link(&tmpdir.join("bar"))).to_str().unwrap(),
2070+
"foo");
20572071
}
20582072

20592073
#[test]
@@ -2346,9 +2360,10 @@ mod tests {
23462360
}
23472361

23482362
#[test]
2349-
#[cfg(not(windows))]
23502363
fn realpath_works() {
23512364
let tmpdir = tmpdir();
2365+
if !got_symlink_permission(&tmpdir) { return };
2366+
23522367
let tmpdir = fs::canonicalize(tmpdir.path()).unwrap();
23532368
let file = tmpdir.join("test");
23542369
let dir = tmpdir.join("test2");
@@ -2357,8 +2372,8 @@ mod tests {
23572372

23582373
File::create(&file).unwrap();
23592374
fs::create_dir(&dir).unwrap();
2360-
fs::soft_link(&file, &link).unwrap();
2361-
fs::soft_link(&dir, &linkdir).unwrap();
2375+
symlink_file(&file, &link).unwrap();
2376+
symlink_dir(&dir, &linkdir).unwrap();
23622377

23632378
assert!(link.symlink_metadata().unwrap().file_type().is_symlink());
23642379

@@ -2370,11 +2385,11 @@ mod tests {
23702385
}
23712386

23722387
#[test]
2373-
#[cfg(not(windows))]
23742388
fn realpath_works_tricky() {
23752389
let tmpdir = tmpdir();
2376-
let tmpdir = fs::canonicalize(tmpdir.path()).unwrap();
2390+
if !got_symlink_permission(&tmpdir) { return };
23772391

2392+
let tmpdir = fs::canonicalize(tmpdir.path()).unwrap();
23782393
let a = tmpdir.join("a");
23792394
let b = a.join("b");
23802395
let c = b.join("c");
@@ -2385,8 +2400,14 @@ mod tests {
23852400
fs::create_dir_all(&b).unwrap();
23862401
fs::create_dir_all(&d).unwrap();
23872402
File::create(&f).unwrap();
2388-
fs::soft_link("../d/e", &c).unwrap();
2389-
fs::soft_link("../f", &e).unwrap();
2403+
if cfg!(not(windows)) {
2404+
symlink_dir("../d/e", &c).unwrap();
2405+
symlink_file("../f", &e).unwrap();
2406+
}
2407+
if cfg!(windows) {
2408+
symlink_dir(r"..\d\e", &c).unwrap();
2409+
symlink_file(r"..\f", &e).unwrap();
2410+
}
23902411

23912412
assert_eq!(fs::canonicalize(&c).unwrap(), f);
23922413
assert_eq!(fs::canonicalize(&e).unwrap(), f);
@@ -2420,4 +2441,31 @@ mod tests {
24202441
let res = fs::read_dir("/path/that/does/not/exist");
24212442
assert_eq!(res.err().unwrap().kind(), ErrorKind::NotFound);
24222443
}
2444+
2445+
#[test]
2446+
fn create_dir_all_with_junctions() {
2447+
let tmpdir = tmpdir();
2448+
let target = tmpdir.join("target");
2449+
2450+
let junction = tmpdir.join("junction");
2451+
let b = junction.join("a/b");
2452+
2453+
let link = tmpdir.join("link");
2454+
let d = link.join("c/d");
2455+
2456+
fs::create_dir(&target).unwrap();
2457+
2458+
check!(symlink_junction(&target, &junction));
2459+
check!(fs::create_dir_all(&b));
2460+
// the junction itself is not a directory, but `is_dir()` on a Path
2461+
// follows links
2462+
assert!(junction.is_dir());
2463+
assert!(b.exists());
2464+
2465+
if !got_symlink_permission(&tmpdir) { return };
2466+
check!(symlink_dir(&target, &link));
2467+
check!(fs::create_dir_all(&d));
2468+
assert!(link.is_dir());
2469+
assert!(d.exists());
2470+
}
24232471
}

Diff for: src/libstd/sys/unix/fs.rs

+12
Original file line numberDiff line numberDiff line change
@@ -554,6 +554,18 @@ pub fn rmdir(p: &Path) -> io::Result<()> {
554554
Ok(())
555555
}
556556

557+
pub fn remove_dir_all(path: &Path) -> io::Result<()> {
558+
for child in try!(readdir(path)) {
559+
let child = try!(child);
560+
if try!(child.file_type()).is_dir() {
561+
try!(remove_dir_all(&child.path()));
562+
} else {
563+
try!(unlink(&child.path()));
564+
}
565+
}
566+
rmdir(path)
567+
}
568+
557569
pub fn readlink(p: &Path) -> io::Result<PathBuf> {
558570
let c_path = try!(cstr(p));
559571
let p = c_path.as_ptr();

0 commit comments

Comments
 (0)