use crate::io::prelude::*; use crate::env; use crate::fs::{self, File, OpenOptions}; use crate::io::{ErrorKind, SeekFrom}; use crate::path::Path; use crate::str; use crate::sync::Arc; use crate::sys_common::io::test::{tmpdir, TempDir}; use crate::thread; use crate::time::{Duration, Instant}; use rand::RngCore; #[cfg(target_os = "macos")] use crate::ffi::{c_char, c_int}; #[cfg(unix)] use crate::os::unix::fs::symlink as symlink_dir; #[cfg(unix)] use crate::os::unix::fs::symlink as symlink_file; #[cfg(unix)] use crate::os::unix::fs::symlink as symlink_junction; #[cfg(windows)] use crate::os::windows::fs::{symlink_dir, symlink_file}; #[cfg(windows)] use crate::sys::fs::symlink_junction; #[cfg(target_os = "macos")] use crate::sys::weak::weak; macro_rules! check { ($e:expr) => { match $e { Ok(t) => t, Err(e) => panic!("{} failed with: {e}", stringify!($e)), } }; } #[cfg(windows)] macro_rules! error { ($e:expr, $s:expr) => { match $e { Ok(_) => panic!("Unexpected success. Should've been: {:?}", $s), Err(ref err) => { assert!(err.raw_os_error() == Some($s), "`{}` did not have a code of `{}`", err, $s) } } }; } #[cfg(unix)] macro_rules! error { ($e:expr, $s:expr) => { error_contains!($e, $s) }; } macro_rules! error_contains { ($e:expr, $s:expr) => { match $e { Ok(_) => panic!("Unexpected success. Should've been: {:?}", $s), Err(ref err) => { assert!(err.to_string().contains($s), "`{}` did not contain `{}`", err, $s) } } }; } // Several test fail on windows if the user does not have permission to // create symlinks (the `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. pub fn got_symlink_permission(tmpdir: &TempDir) -> bool { if cfg!(unix) { return true; } let link = tmpdir.join("some_hopefully_unique_link_name"); match symlink_file(r"nonexisting_target", link) { // ERROR_PRIVILEGE_NOT_HELD = 1314 Err(ref err) if err.raw_os_error() == Some(1314) => false, Ok(_) | Err(_) => true, } } #[cfg(target_os = "macos")] fn able_to_not_follow_symlinks_while_hard_linking() -> bool { weak!(fn linkat(c_int, *const c_char, c_int, *const c_char, c_int) -> c_int); linkat.get().is_some() } #[cfg(not(target_os = "macos"))] fn able_to_not_follow_symlinks_while_hard_linking() -> bool { return true; } #[test] fn file_test_io_smoke_test() { let message = "it's alright. have a good time"; let tmpdir = tmpdir(); let filename = &tmpdir.join("file_rt_io_file_test.txt"); { let mut write_stream = check!(File::create(filename)); check!(write_stream.write(message.as_bytes())); } { let mut read_stream = check!(File::open(filename)); let mut read_buf = [0; 1028]; let read_str = match check!(read_stream.read(&mut read_buf)) { 0 => panic!("shouldn't happen"), n => str::from_utf8(&read_buf[..n]).unwrap().to_string(), }; assert_eq!(read_str, message); } check!(fs::remove_file(filename)); } #[test] fn invalid_path_raises() { let tmpdir = tmpdir(); let filename = &tmpdir.join("file_that_does_not_exist.txt"); let result = File::open(filename); #[cfg(all(unix, not(target_os = "vxworks")))] error!(result, "No such file or directory"); #[cfg(target_os = "vxworks")] error!(result, "no such file or directory"); #[cfg(windows)] error!(result, 2); // ERROR_FILE_NOT_FOUND } #[test] fn file_test_iounlinking_invalid_path_should_raise_condition() { let tmpdir = tmpdir(); let filename = &tmpdir.join("file_another_file_that_does_not_exist.txt"); let result = fs::remove_file(filename); #[cfg(all(unix, not(target_os = "vxworks")))] error!(result, "No such file or directory"); #[cfg(target_os = "vxworks")] error!(result, "no such file or directory"); #[cfg(windows)] error!(result, 2); // ERROR_FILE_NOT_FOUND } #[test] fn file_test_io_non_positional_read() { let message: &str = "ten-four"; let mut read_mem = [0; 8]; let tmpdir = tmpdir(); let filename = &tmpdir.join("file_rt_io_file_test_positional.txt"); { let mut rw_stream = check!(File::create(filename)); check!(rw_stream.write(message.as_bytes())); } { let mut read_stream = check!(File::open(filename)); { let read_buf = &mut read_mem[0..4]; check!(read_stream.read(read_buf)); } { let read_buf = &mut read_mem[4..8]; check!(read_stream.read(read_buf)); } } check!(fs::remove_file(filename)); let read_str = str::from_utf8(&read_mem).unwrap(); assert_eq!(read_str, message); } #[test] fn file_test_io_seek_and_tell_smoke_test() { let message = "ten-four"; let mut read_mem = [0; 4]; let set_cursor = 4 as u64; let tell_pos_pre_read; let tell_pos_post_read; let tmpdir = tmpdir(); let filename = &tmpdir.join("file_rt_io_file_test_seeking.txt"); { let mut rw_stream = check!(File::create(filename)); check!(rw_stream.write(message.as_bytes())); } { let mut read_stream = check!(File::open(filename)); check!(read_stream.seek(SeekFrom::Start(set_cursor))); tell_pos_pre_read = check!(read_stream.seek(SeekFrom::Current(0))); check!(read_stream.read(&mut read_mem)); tell_pos_post_read = check!(read_stream.seek(SeekFrom::Current(0))); } check!(fs::remove_file(filename)); let read_str = str::from_utf8(&read_mem).unwrap(); assert_eq!(read_str, &message[4..8]); assert_eq!(tell_pos_pre_read, set_cursor); assert_eq!(tell_pos_post_read, message.len() as u64); } #[test] fn file_test_io_seek_and_write() { let initial_msg = "food-is-yummy"; let overwrite_msg = "-the-bar!!"; let final_msg = "foo-the-bar!!"; let seek_idx = 3; let mut read_mem = [0; 13]; let tmpdir = tmpdir(); let filename = &tmpdir.join("file_rt_io_file_test_seek_and_write.txt"); { let mut rw_stream = check!(File::create(filename)); check!(rw_stream.write(initial_msg.as_bytes())); check!(rw_stream.seek(SeekFrom::Start(seek_idx))); check!(rw_stream.write(overwrite_msg.as_bytes())); } { let mut read_stream = check!(File::open(filename)); check!(read_stream.read(&mut read_mem)); } check!(fs::remove_file(filename)); let read_str = str::from_utf8(&read_mem).unwrap(); assert!(read_str == final_msg); } #[test] fn file_test_io_seek_shakedown() { // 01234567890123 let initial_msg = "qwer-asdf-zxcv"; let chunk_one: &str = "qwer"; let chunk_two: &str = "asdf"; let chunk_three: &str = "zxcv"; let mut read_mem = [0; 4]; let tmpdir = tmpdir(); let filename = &tmpdir.join("file_rt_io_file_test_seek_shakedown.txt"); { let mut rw_stream = check!(File::create(filename)); check!(rw_stream.write(initial_msg.as_bytes())); } { let mut read_stream = check!(File::open(filename)); check!(read_stream.seek(SeekFrom::End(-4))); check!(read_stream.read(&mut read_mem)); assert_eq!(str::from_utf8(&read_mem).unwrap(), chunk_three); check!(read_stream.seek(SeekFrom::Current(-9))); check!(read_stream.read(&mut read_mem)); assert_eq!(str::from_utf8(&read_mem).unwrap(), chunk_two); check!(read_stream.seek(SeekFrom::Start(0))); check!(read_stream.read(&mut read_mem)); assert_eq!(str::from_utf8(&read_mem).unwrap(), chunk_one); } check!(fs::remove_file(filename)); } #[test] fn file_test_io_eof() { let tmpdir = tmpdir(); let filename = tmpdir.join("file_rt_io_file_test_eof.txt"); let mut buf = [0; 256]; { let oo = OpenOptions::new().create_new(true).write(true).read(true).clone(); let mut rw = check!(oo.open(&filename)); assert_eq!(check!(rw.read(&mut buf)), 0); assert_eq!(check!(rw.read(&mut buf)), 0); } check!(fs::remove_file(&filename)); } #[test] #[cfg(unix)] fn file_test_io_read_write_at() { use crate::os::unix::fs::FileExt; let tmpdir = tmpdir(); let filename = tmpdir.join("file_rt_io_file_test_read_write_at.txt"); let mut buf = [0; 256]; let write1 = "asdf"; let write2 = "qwer-"; let write3 = "-zxcv"; let content = "qwer-asdf-zxcv"; { let oo = OpenOptions::new().create_new(true).write(true).read(true).clone(); let mut rw = check!(oo.open(&filename)); assert_eq!(check!(rw.write_at(write1.as_bytes(), 5)), write1.len()); assert_eq!(check!(rw.seek(SeekFrom::Current(0))), 0); assert_eq!(check!(rw.read_at(&mut buf, 5)), write1.len()); assert_eq!(str::from_utf8(&buf[..write1.len()]), Ok(write1)); assert_eq!(check!(rw.seek(SeekFrom::Current(0))), 0); assert_eq!(check!(rw.read_at(&mut buf[..write2.len()], 0)), write2.len()); assert_eq!(str::from_utf8(&buf[..write2.len()]), Ok("\0\0\0\0\0")); assert_eq!(check!(rw.seek(SeekFrom::Current(0))), 0); assert_eq!(check!(rw.write(write2.as_bytes())), write2.len()); assert_eq!(check!(rw.seek(SeekFrom::Current(0))), 5); assert_eq!(check!(rw.read(&mut buf)), write1.len()); assert_eq!(str::from_utf8(&buf[..write1.len()]), Ok(write1)); assert_eq!(check!(rw.seek(SeekFrom::Current(0))), 9); assert_eq!(check!(rw.read_at(&mut buf[..write2.len()], 0)), write2.len()); assert_eq!(str::from_utf8(&buf[..write2.len()]), Ok(write2)); assert_eq!(check!(rw.seek(SeekFrom::Current(0))), 9); assert_eq!(check!(rw.write_at(write3.as_bytes(), 9)), write3.len()); assert_eq!(check!(rw.seek(SeekFrom::Current(0))), 9); } { let mut read = check!(File::open(&filename)); assert_eq!(check!(read.read_at(&mut buf, 0)), content.len()); assert_eq!(str::from_utf8(&buf[..content.len()]), Ok(content)); assert_eq!(check!(read.seek(SeekFrom::Current(0))), 0); assert_eq!(check!(read.seek(SeekFrom::End(-5))), 9); assert_eq!(check!(read.read_at(&mut buf, 0)), content.len()); assert_eq!(str::from_utf8(&buf[..content.len()]), Ok(content)); assert_eq!(check!(read.seek(SeekFrom::Current(0))), 9); assert_eq!(check!(read.read(&mut buf)), write3.len()); assert_eq!(str::from_utf8(&buf[..write3.len()]), Ok(write3)); assert_eq!(check!(read.seek(SeekFrom::Current(0))), 14); assert_eq!(check!(read.read_at(&mut buf, 0)), content.len()); assert_eq!(str::from_utf8(&buf[..content.len()]), Ok(content)); assert_eq!(check!(read.seek(SeekFrom::Current(0))), 14); assert_eq!(check!(read.read_at(&mut buf, 14)), 0); assert_eq!(check!(read.read_at(&mut buf, 15)), 0); assert_eq!(check!(read.seek(SeekFrom::Current(0))), 14); } check!(fs::remove_file(&filename)); } #[test] #[cfg(unix)] fn set_get_unix_permissions() { use crate::os::unix::fs::PermissionsExt; let tmpdir = tmpdir(); let filename = &tmpdir.join("set_get_unix_permissions"); check!(fs::create_dir(filename)); let mask = 0o7777; check!(fs::set_permissions(filename, fs::Permissions::from_mode(0))); let metadata0 = check!(fs::metadata(filename)); assert_eq!(mask & metadata0.permissions().mode(), 0); check!(fs::set_permissions(filename, fs::Permissions::from_mode(0o1777))); let metadata1 = check!(fs::metadata(filename)); #[cfg(all(unix, not(target_os = "vxworks")))] assert_eq!(mask & metadata1.permissions().mode(), 0o1777); #[cfg(target_os = "vxworks")] assert_eq!(mask & metadata1.permissions().mode(), 0o0777); } #[test] #[cfg(windows)] fn file_test_io_seek_read_write() { use crate::os::windows::fs::FileExt; let tmpdir = tmpdir(); let filename = tmpdir.join("file_rt_io_file_test_seek_read_write.txt"); let mut buf = [0; 256]; let write1 = "asdf"; let write2 = "qwer-"; let write3 = "-zxcv"; let content = "qwer-asdf-zxcv"; { let oo = OpenOptions::new().create_new(true).write(true).read(true).clone(); let mut rw = check!(oo.open(&filename)); assert_eq!(check!(rw.seek_write(write1.as_bytes(), 5)), write1.len()); assert_eq!(check!(rw.seek(SeekFrom::Current(0))), 9); assert_eq!(check!(rw.seek_read(&mut buf, 5)), write1.len()); assert_eq!(str::from_utf8(&buf[..write1.len()]), Ok(write1)); assert_eq!(check!(rw.seek(SeekFrom::Current(0))), 9); assert_eq!(check!(rw.seek(SeekFrom::Start(0))), 0); assert_eq!(check!(rw.write(write2.as_bytes())), write2.len()); assert_eq!(check!(rw.seek(SeekFrom::Current(0))), 5); assert_eq!(check!(rw.read(&mut buf)), write1.len()); assert_eq!(str::from_utf8(&buf[..write1.len()]), Ok(write1)); assert_eq!(check!(rw.seek(SeekFrom::Current(0))), 9); assert_eq!(check!(rw.seek_read(&mut buf[..write2.len()], 0)), write2.len()); assert_eq!(str::from_utf8(&buf[..write2.len()]), Ok(write2)); assert_eq!(check!(rw.seek(SeekFrom::Current(0))), 5); assert_eq!(check!(rw.seek_write(write3.as_bytes(), 9)), write3.len()); assert_eq!(check!(rw.seek(SeekFrom::Current(0))), 14); } { let mut read = check!(File::open(&filename)); assert_eq!(check!(read.seek_read(&mut buf, 0)), content.len()); assert_eq!(str::from_utf8(&buf[..content.len()]), Ok(content)); assert_eq!(check!(read.seek(SeekFrom::Current(0))), 14); assert_eq!(check!(read.seek(SeekFrom::End(-5))), 9); assert_eq!(check!(read.seek_read(&mut buf, 0)), content.len()); assert_eq!(str::from_utf8(&buf[..content.len()]), Ok(content)); assert_eq!(check!(read.seek(SeekFrom::Current(0))), 14); assert_eq!(check!(read.seek(SeekFrom::End(-5))), 9); assert_eq!(check!(read.read(&mut buf)), write3.len()); assert_eq!(str::from_utf8(&buf[..write3.len()]), Ok(write3)); assert_eq!(check!(read.seek(SeekFrom::Current(0))), 14); assert_eq!(check!(read.seek_read(&mut buf, 0)), content.len()); assert_eq!(str::from_utf8(&buf[..content.len()]), Ok(content)); assert_eq!(check!(read.seek(SeekFrom::Current(0))), 14); assert_eq!(check!(read.seek_read(&mut buf, 14)), 0); assert_eq!(check!(read.seek_read(&mut buf, 15)), 0); } check!(fs::remove_file(&filename)); } #[test] fn file_test_stat_is_correct_on_is_file() { let tmpdir = tmpdir(); let filename = &tmpdir.join("file_stat_correct_on_is_file.txt"); { let mut opts = OpenOptions::new(); let mut fs = check!(opts.read(true).write(true).create(true).open(filename)); let msg = "hw"; fs.write(msg.as_bytes()).unwrap(); let fstat_res = check!(fs.metadata()); assert!(fstat_res.is_file()); } let stat_res_fn = check!(fs::metadata(filename)); assert!(stat_res_fn.is_file()); let stat_res_meth = check!(filename.metadata()); assert!(stat_res_meth.is_file()); check!(fs::remove_file(filename)); } #[test] fn file_test_stat_is_correct_on_is_dir() { let tmpdir = tmpdir(); let filename = &tmpdir.join("file_stat_correct_on_is_dir"); check!(fs::create_dir(filename)); let stat_res_fn = check!(fs::metadata(filename)); assert!(stat_res_fn.is_dir()); let stat_res_meth = check!(filename.metadata()); assert!(stat_res_meth.is_dir()); check!(fs::remove_dir(filename)); } #[test] fn file_test_fileinfo_false_when_checking_is_file_on_a_directory() { let tmpdir = tmpdir(); let dir = &tmpdir.join("fileinfo_false_on_dir"); check!(fs::create_dir(dir)); assert!(!dir.is_file()); check!(fs::remove_dir(dir)); } #[test] fn file_test_fileinfo_check_exists_before_and_after_file_creation() { let tmpdir = tmpdir(); let file = &tmpdir.join("fileinfo_check_exists_b_and_a.txt"); check!(check!(File::create(file)).write(b"foo")); assert!(file.exists()); check!(fs::remove_file(file)); assert!(!file.exists()); } #[test] fn file_test_directoryinfo_check_exists_before_and_after_mkdir() { let tmpdir = tmpdir(); let dir = &tmpdir.join("before_and_after_dir"); assert!(!dir.exists()); check!(fs::create_dir(dir)); assert!(dir.exists()); assert!(dir.is_dir()); check!(fs::remove_dir(dir)); assert!(!dir.exists()); } #[test] fn file_test_directoryinfo_readdir() { let tmpdir = tmpdir(); let dir = &tmpdir.join("di_readdir"); check!(fs::create_dir(dir)); let prefix = "foo"; for n in 0..3 { let f = dir.join(&format!("{n}.txt")); let mut w = check!(File::create(&f)); let msg_str = format!("{}{}", prefix, n.to_string()); let msg = msg_str.as_bytes(); check!(w.write(msg)); } let files = check!(fs::read_dir(dir)); let mut mem = [0; 4]; for f in files { let f = f.unwrap().path(); { let n = f.file_stem().unwrap(); check!(check!(File::open(&f)).read(&mut mem)); let read_str = str::from_utf8(&mem).unwrap(); let expected = format!("{}{}", prefix, n.to_str().unwrap()); assert_eq!(expected, read_str); } check!(fs::remove_file(&f)); } check!(fs::remove_dir(dir)); } #[test] fn file_create_new_already_exists_error() { let tmpdir = tmpdir(); let file = &tmpdir.join("file_create_new_error_exists"); check!(fs::File::create(file)); let e = fs::OpenOptions::new().write(true).create_new(true).open(file).unwrap_err(); assert_eq!(e.kind(), ErrorKind::AlreadyExists); } #[test] fn mkdir_path_already_exists_error() { let tmpdir = tmpdir(); let dir = &tmpdir.join("mkdir_error_twice"); check!(fs::create_dir(dir)); let e = fs::create_dir(dir).unwrap_err(); assert_eq!(e.kind(), ErrorKind::AlreadyExists); } #[test] fn recursive_mkdir() { let tmpdir = tmpdir(); let dir = tmpdir.join("d1/d2"); check!(fs::create_dir_all(&dir)); assert!(dir.is_dir()) } #[test] fn recursive_mkdir_failure() { let tmpdir = tmpdir(); let dir = tmpdir.join("d1"); let file = dir.join("f1"); check!(fs::create_dir_all(&dir)); check!(File::create(&file)); let result = fs::create_dir_all(&file); assert!(result.is_err()); } #[test] fn concurrent_recursive_mkdir() { for _ in 0..100 { let dir = tmpdir(); let mut dir = dir.join("a"); for _ in 0..40 { dir = dir.join("a"); } let mut join = vec![]; for _ in 0..8 { let dir = dir.clone(); join.push(thread::spawn(move || { check!(fs::create_dir_all(&dir)); })) } // No `Display` on result of `join()` join.drain(..).map(|join| join.join().unwrap()).count(); } } #[test] fn recursive_mkdir_slash() { check!(fs::create_dir_all(Path::new("/"))); } #[test] fn recursive_mkdir_dot() { check!(fs::create_dir_all(Path::new("."))); } #[test] fn recursive_mkdir_empty() { check!(fs::create_dir_all(Path::new(""))); } #[test] fn recursive_rmdir() { let tmpdir = tmpdir(); let d1 = tmpdir.join("d1"); let dt = d1.join("t"); let dtt = dt.join("t"); let d2 = tmpdir.join("d2"); let canary = d2.join("do_not_delete"); check!(fs::create_dir_all(&dtt)); check!(fs::create_dir_all(&d2)); check!(check!(File::create(&canary)).write(b"foo")); check!(symlink_junction(&d2, &dt.join("d2"))); let _ = symlink_file(&canary, &d1.join("canary")); check!(fs::remove_dir_all(&d1)); assert!(!d1.is_dir()); assert!(canary.exists()); } #[test] fn recursive_rmdir_of_symlink() { // test we do not recursively delete a symlink but only dirs. let tmpdir = tmpdir(); let link = tmpdir.join("d1"); let dir = tmpdir.join("d2"); let canary = dir.join("do_not_delete"); check!(fs::create_dir_all(&dir)); check!(check!(File::create(&canary)).write(b"foo")); check!(symlink_junction(&dir, &link)); check!(fs::remove_dir_all(&link)); assert!(!link.is_dir()); assert!(canary.exists()); } #[test] fn recursive_rmdir_of_file_fails() { // test we do not delete a directly specified file. let tmpdir = tmpdir(); let canary = tmpdir.join("do_not_delete"); check!(check!(File::create(&canary)).write(b"foo")); let result = fs::remove_dir_all(&canary); #[cfg(unix)] error!(result, "Not a directory"); #[cfg(windows)] error!(result, 267); // ERROR_DIRECTORY - The directory name is invalid. assert!(result.is_err()); assert!(canary.exists()); } #[test] // only Windows makes a distinction between file and directory symlinks. #[cfg(windows)] fn recursive_rmdir_of_file_symlink() { let tmpdir = tmpdir(); if !got_symlink_permission(&tmpdir) { return; }; let f1 = tmpdir.join("f1"); let f2 = tmpdir.join("f2"); check!(check!(File::create(&f1)).write(b"foo")); check!(symlink_file(&f1, &f2)); match fs::remove_dir_all(&f2) { Ok(..) => panic!("wanted a failure"), Err(..) => {} } } #[test] #[ignore] // takes too much time fn recursive_rmdir_toctou() { // Test for time-of-check to time-of-use issues. // // Scenario: // The attacker wants to get directory contents deleted, to which they do not have access. // They have a way to get a privileged Rust binary call `std::fs::remove_dir_all()` on a // directory they control, e.g. in their home directory. // // The POC sets up the `attack_dest/attack_file` which the attacker wants to have deleted. // The attacker repeatedly creates a directory and replaces it with a symlink from // `victim_del` to `attack_dest` while the victim code calls `std::fs::remove_dir_all()` // on `victim_del`. After a few seconds the attack has succeeded and // `attack_dest/attack_file` is deleted. let tmpdir = tmpdir(); let victim_del_path = tmpdir.join("victim_del"); let victim_del_path_clone = victim_del_path.clone(); // setup dest let attack_dest_dir = tmpdir.join("attack_dest"); let attack_dest_dir = attack_dest_dir.as_path(); fs::create_dir(attack_dest_dir).unwrap(); let attack_dest_file = tmpdir.join("attack_dest/attack_file"); File::create(&attack_dest_file).unwrap(); let drop_canary_arc = Arc::new(()); let drop_canary_weak = Arc::downgrade(&drop_canary_arc); eprintln!("x: {:?}", &victim_del_path); // victim just continuously removes `victim_del` thread::spawn(move || { while drop_canary_weak.upgrade().is_some() { let _ = fs::remove_dir_all(&victim_del_path_clone); } }); // attacker (could of course be in a separate process) let start_time = Instant::now(); while Instant::now().duration_since(start_time) < Duration::from_secs(1000) { if !attack_dest_file.exists() { panic!( "Victim deleted symlinked file outside of victim_del. Attack succeeded in {:?}.", Instant::now().duration_since(start_time) ); } let _ = fs::create_dir(&victim_del_path); let _ = fs::remove_dir(&victim_del_path); let _ = symlink_dir(attack_dest_dir, &victim_del_path); } } #[test] fn unicode_path_is_dir() { assert!(Path::new(".").is_dir()); assert!(!Path::new("test/stdtest/fs.rs").is_dir()); let tmpdir = tmpdir(); let mut dirpath = tmpdir.path().to_path_buf(); dirpath.push("test-가一ー你好"); check!(fs::create_dir(&dirpath)); assert!(dirpath.is_dir()); let mut filepath = dirpath; filepath.push("unicode-file-\u{ac00}\u{4e00}\u{30fc}\u{4f60}\u{597d}.rs"); check!(File::create(&filepath)); // ignore return; touch only assert!(!filepath.is_dir()); assert!(filepath.exists()); } #[test] fn unicode_path_exists() { assert!(Path::new(".").exists()); assert!(!Path::new("test/nonexistent-bogus-path").exists()); let tmpdir = tmpdir(); let unicode = tmpdir.path(); let unicode = unicode.join("test-각丁ー再见"); check!(fs::create_dir(&unicode)); assert!(unicode.exists()); assert!(!Path::new("test/unicode-bogus-path-각丁ー再见").exists()); } #[test] fn copy_file_does_not_exist() { let from = Path::new("test/nonexistent-bogus-path"); let to = Path::new("test/other-bogus-path"); match fs::copy(&from, &to) { Ok(..) => panic!(), Err(..) => { assert!(!from.exists()); assert!(!to.exists()); } } } #[test] fn copy_src_does_not_exist() { let tmpdir = tmpdir(); let from = Path::new("test/nonexistent-bogus-path"); let to = tmpdir.join("out.txt"); check!(check!(File::create(&to)).write(b"hello")); assert!(fs::copy(&from, &to).is_err()); assert!(!from.exists()); let mut v = Vec::new(); check!(check!(File::open(&to)).read_to_end(&mut v)); assert_eq!(v, b"hello"); } #[test] fn copy_file_ok() { let tmpdir = tmpdir(); let input = tmpdir.join("in.txt"); let out = tmpdir.join("out.txt"); check!(check!(File::create(&input)).write(b"hello")); check!(fs::copy(&input, &out)); let mut v = Vec::new(); check!(check!(File::open(&out)).read_to_end(&mut v)); assert_eq!(v, b"hello"); assert_eq!(check!(input.metadata()).permissions(), check!(out.metadata()).permissions()); } #[test] fn copy_file_dst_dir() { let tmpdir = tmpdir(); let out = tmpdir.join("out"); check!(File::create(&out)); match fs::copy(&*out, tmpdir.path()) { Ok(..) => panic!(), Err(..) => {} } } #[test] fn copy_file_dst_exists() { let tmpdir = tmpdir(); let input = tmpdir.join("in"); let output = tmpdir.join("out"); check!(check!(File::create(&input)).write("foo".as_bytes())); check!(check!(File::create(&output)).write("bar".as_bytes())); check!(fs::copy(&input, &output)); let mut v = Vec::new(); check!(check!(File::open(&output)).read_to_end(&mut v)); assert_eq!(v, b"foo".to_vec()); } #[test] fn copy_file_src_dir() { let tmpdir = tmpdir(); let out = tmpdir.join("out"); match fs::copy(tmpdir.path(), &out) { Ok(..) => panic!(), Err(..) => {} } assert!(!out.exists()); } #[test] fn copy_file_preserves_perm_bits() { let tmpdir = tmpdir(); let input = tmpdir.join("in.txt"); let out = tmpdir.join("out.txt"); let attr = check!(check!(File::create(&input)).metadata()); let mut p = attr.permissions(); p.set_readonly(true); check!(fs::set_permissions(&input, p)); check!(fs::copy(&input, &out)); assert!(check!(out.metadata()).permissions().readonly()); check!(fs::set_permissions(&input, attr.permissions())); check!(fs::set_permissions(&out, attr.permissions())); } #[test] #[cfg(windows)] fn copy_file_preserves_streams() { let tmp = tmpdir(); check!(check!(File::create(tmp.join("in.txt:bunny"))).write("carrot".as_bytes())); assert_eq!(check!(fs::copy(tmp.join("in.txt"), tmp.join("out.txt"))), 0); assert_eq!(check!(tmp.join("out.txt").metadata()).len(), 0); let mut v = Vec::new(); check!(check!(File::open(tmp.join("out.txt:bunny"))).read_to_end(&mut v)); assert_eq!(v, b"carrot".to_vec()); } #[test] fn copy_file_returns_metadata_len() { let tmp = tmpdir(); let in_path = tmp.join("in.txt"); let out_path = tmp.join("out.txt"); check!(check!(File::create(&in_path)).write(b"lettuce")); #[cfg(windows)] check!(check!(File::create(tmp.join("in.txt:bunny"))).write(b"carrot")); let copied_len = check!(fs::copy(&in_path, &out_path)); assert_eq!(check!(out_path.metadata()).len(), copied_len); } #[test] fn copy_file_follows_dst_symlink() { let tmp = tmpdir(); if !got_symlink_permission(&tmp) { return; }; let in_path = tmp.join("in.txt"); let out_path = tmp.join("out.txt"); let out_path_symlink = tmp.join("out_symlink.txt"); check!(fs::write(&in_path, "foo")); check!(fs::write(&out_path, "bar")); check!(symlink_file(&out_path, &out_path_symlink)); check!(fs::copy(&in_path, &out_path_symlink)); assert!(check!(out_path_symlink.symlink_metadata()).file_type().is_symlink()); assert_eq!(check!(fs::read(&out_path_symlink)), b"foo".to_vec()); assert_eq!(check!(fs::read(&out_path)), b"foo".to_vec()); } #[test] fn symlinks_work() { let tmpdir = tmpdir(); if !got_symlink_permission(&tmpdir) { return; }; let input = tmpdir.join("in.txt"); let out = tmpdir.join("out.txt"); check!(check!(File::create(&input)).write("foobar".as_bytes())); check!(symlink_file(&input, &out)); assert!(check!(out.symlink_metadata()).file_type().is_symlink()); assert_eq!(check!(fs::metadata(&out)).len(), check!(fs::metadata(&input)).len()); let mut v = Vec::new(); check!(check!(File::open(&out)).read_to_end(&mut v)); assert_eq!(v, b"foobar".to_vec()); } #[test] fn symlink_noexist() { // Symlinks can point to things that don't exist let tmpdir = tmpdir(); if !got_symlink_permission(&tmpdir) { return; }; // Use a relative path for testing. Symlinks get normalized by Windows, // so we might not get the same path back for absolute paths check!(symlink_file(&"foo", &tmpdir.join("bar"))); assert_eq!(check!(fs::read_link(&tmpdir.join("bar"))).to_str().unwrap(), "foo"); } #[test] fn read_link() { if cfg!(windows) { // directory symlink assert_eq!(check!(fs::read_link(r"C:\Users\All Users")), Path::new(r"C:\ProgramData")); // junction assert_eq!(check!(fs::read_link(r"C:\Users\Default User")), Path::new(r"C:\Users\Default")); // junction with special permissions // Since not all localized windows versions contain the folder "Documents and Settings" in english, // we will briefly check, if it exists and otherwise skip the test. Except during CI we will always execute the test. if Path::new(r"C:\Documents and Settings\").exists() || env::var_os("CI").is_some() { assert_eq!( check!(fs::read_link(r"C:\Documents and Settings\")), Path::new(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(); match fs::read_link(tmpdir.path()) { Ok(..) => panic!("wanted a failure"), Err(..) => {} } } #[test] fn links_work() { let tmpdir = tmpdir(); let input = tmpdir.join("in.txt"); let out = tmpdir.join("out.txt"); check!(check!(File::create(&input)).write("foobar".as_bytes())); check!(fs::hard_link(&input, &out)); assert_eq!(check!(fs::metadata(&out)).len(), check!(fs::metadata(&input)).len()); assert_eq!(check!(fs::metadata(&out)).len(), check!(input.metadata()).len()); let mut v = Vec::new(); check!(check!(File::open(&out)).read_to_end(&mut v)); assert_eq!(v, b"foobar".to_vec()); // can't link to yourself match fs::hard_link(&input, &input) { Ok(..) => panic!("wanted a failure"), Err(..) => {} } // can't link to something that doesn't exist match fs::hard_link(&tmpdir.join("foo"), &tmpdir.join("bar")) { Ok(..) => panic!("wanted a failure"), Err(..) => {} } } #[test] fn chmod_works() { let tmpdir = tmpdir(); let file = tmpdir.join("in.txt"); check!(File::create(&file)); let attr = check!(fs::metadata(&file)); assert!(!attr.permissions().readonly()); let mut p = attr.permissions(); p.set_readonly(true); check!(fs::set_permissions(&file, p.clone())); let attr = check!(fs::metadata(&file)); assert!(attr.permissions().readonly()); match fs::set_permissions(&tmpdir.join("foo"), p.clone()) { Ok(..) => panic!("wanted an error"), Err(..) => {} } p.set_readonly(false); check!(fs::set_permissions(&file, p)); } #[test] fn fchmod_works() { let tmpdir = tmpdir(); let path = tmpdir.join("in.txt"); let file = check!(File::create(&path)); let attr = check!(fs::metadata(&path)); assert!(!attr.permissions().readonly()); let mut p = attr.permissions(); p.set_readonly(true); check!(file.set_permissions(p.clone())); let attr = check!(fs::metadata(&path)); assert!(attr.permissions().readonly()); p.set_readonly(false); check!(file.set_permissions(p)); } #[test] fn sync_doesnt_kill_anything() { let tmpdir = tmpdir(); let path = tmpdir.join("in.txt"); let mut file = check!(File::create(&path)); check!(file.sync_all()); check!(file.sync_data()); check!(file.write(b"foo")); check!(file.sync_all()); check!(file.sync_data()); } #[test] fn truncate_works() { let tmpdir = tmpdir(); let path = tmpdir.join("in.txt"); let mut file = check!(File::create(&path)); check!(file.write(b"foo")); check!(file.sync_all()); // Do some simple things with truncation assert_eq!(check!(file.metadata()).len(), 3); check!(file.set_len(10)); assert_eq!(check!(file.metadata()).len(), 10); check!(file.write(b"bar")); check!(file.sync_all()); assert_eq!(check!(file.metadata()).len(), 10); let mut v = Vec::new(); check!(check!(File::open(&path)).read_to_end(&mut v)); assert_eq!(v, b"foobar\0\0\0\0".to_vec()); // Truncate to a smaller length, don't seek, and then write something. // Ensure that the intermediate zeroes are all filled in (we have `seek`ed // past the end of the file). check!(file.set_len(2)); assert_eq!(check!(file.metadata()).len(), 2); check!(file.write(b"wut")); check!(file.sync_all()); assert_eq!(check!(file.metadata()).len(), 9); let mut v = Vec::new(); check!(check!(File::open(&path)).read_to_end(&mut v)); assert_eq!(v, b"fo\0\0\0\0wut".to_vec()); } #[test] fn open_flavors() { use crate::fs::OpenOptions as OO; fn c(t: &T) -> T { t.clone() } let tmpdir = tmpdir(); let mut r = OO::new(); r.read(true); let mut w = OO::new(); w.write(true); let mut rw = OO::new(); rw.read(true).write(true); let mut a = OO::new(); a.append(true); let mut ra = OO::new(); ra.read(true).append(true); #[cfg(windows)] let invalid_options = 87; // ERROR_INVALID_PARAMETER #[cfg(all(unix, not(target_os = "vxworks")))] let invalid_options = "Invalid argument"; #[cfg(target_os = "vxworks")] let invalid_options = "invalid argument"; // Test various combinations of creation modes and access modes. // // Allowed: // creation mode | read | write | read-write | append | read-append | // :-----------------------|:-----:|:-----:|:----------:|:------:|:-----------:| // not set (open existing) | X | X | X | X | X | // create | | X | X | X | X | // truncate | | X | X | | | // create and truncate | | X | X | | | // create_new | | X | X | X | X | // // tested in reverse order, so 'create_new' creates the file, and 'open existing' opens it. // write-only check!(c(&w).create_new(true).open(&tmpdir.join("a"))); check!(c(&w).create(true).truncate(true).open(&tmpdir.join("a"))); check!(c(&w).truncate(true).open(&tmpdir.join("a"))); check!(c(&w).create(true).open(&tmpdir.join("a"))); check!(c(&w).open(&tmpdir.join("a"))); // read-only error!(c(&r).create_new(true).open(&tmpdir.join("b")), invalid_options); error!(c(&r).create(true).truncate(true).open(&tmpdir.join("b")), invalid_options); error!(c(&r).truncate(true).open(&tmpdir.join("b")), invalid_options); error!(c(&r).create(true).open(&tmpdir.join("b")), invalid_options); check!(c(&r).open(&tmpdir.join("a"))); // try opening the file created with write_only // read-write check!(c(&rw).create_new(true).open(&tmpdir.join("c"))); check!(c(&rw).create(true).truncate(true).open(&tmpdir.join("c"))); check!(c(&rw).truncate(true).open(&tmpdir.join("c"))); check!(c(&rw).create(true).open(&tmpdir.join("c"))); check!(c(&rw).open(&tmpdir.join("c"))); // append check!(c(&a).create_new(true).open(&tmpdir.join("d"))); error!(c(&a).create(true).truncate(true).open(&tmpdir.join("d")), invalid_options); error!(c(&a).truncate(true).open(&tmpdir.join("d")), invalid_options); check!(c(&a).create(true).open(&tmpdir.join("d"))); check!(c(&a).open(&tmpdir.join("d"))); // read-append check!(c(&ra).create_new(true).open(&tmpdir.join("e"))); error!(c(&ra).create(true).truncate(true).open(&tmpdir.join("e")), invalid_options); error!(c(&ra).truncate(true).open(&tmpdir.join("e")), invalid_options); check!(c(&ra).create(true).open(&tmpdir.join("e"))); check!(c(&ra).open(&tmpdir.join("e"))); // Test opening a file without setting an access mode let mut blank = OO::new(); error!(blank.create(true).open(&tmpdir.join("f")), invalid_options); // Test write works check!(check!(File::create(&tmpdir.join("h"))).write("foobar".as_bytes())); // Test write fails for read-only check!(r.open(&tmpdir.join("h"))); { let mut f = check!(r.open(&tmpdir.join("h"))); assert!(f.write("wut".as_bytes()).is_err()); } // Test write overwrites { let mut f = check!(c(&w).open(&tmpdir.join("h"))); check!(f.write("baz".as_bytes())); } { let mut f = check!(c(&r).open(&tmpdir.join("h"))); let mut b = vec![0; 6]; check!(f.read(&mut b)); assert_eq!(b, "bazbar".as_bytes()); } // Test truncate works { let mut f = check!(c(&w).truncate(true).open(&tmpdir.join("h"))); check!(f.write("foo".as_bytes())); } assert_eq!(check!(fs::metadata(&tmpdir.join("h"))).len(), 3); // Test append works assert_eq!(check!(fs::metadata(&tmpdir.join("h"))).len(), 3); { let mut f = check!(c(&a).open(&tmpdir.join("h"))); check!(f.write("bar".as_bytes())); } assert_eq!(check!(fs::metadata(&tmpdir.join("h"))).len(), 6); // Test .append(true) equals .write(true).append(true) { let mut f = check!(c(&w).append(true).open(&tmpdir.join("h"))); check!(f.write("baz".as_bytes())); } assert_eq!(check!(fs::metadata(&tmpdir.join("h"))).len(), 9); } #[test] fn _assert_send_sync() { fn _assert_send_sync() {} _assert_send_sync::(); } #[test] fn binary_file() { let mut bytes = [0; 1024]; crate::test_helpers::test_rng().fill_bytes(&mut bytes); let tmpdir = tmpdir(); check!(check!(File::create(&tmpdir.join("test"))).write(&bytes)); let mut v = Vec::new(); check!(check!(File::open(&tmpdir.join("test"))).read_to_end(&mut v)); assert!(v == &bytes[..]); } #[test] fn write_then_read() { let mut bytes = [0; 1024]; crate::test_helpers::test_rng().fill_bytes(&mut bytes); let tmpdir = tmpdir(); check!(fs::write(&tmpdir.join("test"), &bytes[..])); let v = check!(fs::read(&tmpdir.join("test"))); assert!(v == &bytes[..]); check!(fs::write(&tmpdir.join("not-utf8"), &[0xFF])); error_contains!( fs::read_to_string(&tmpdir.join("not-utf8")), "stream did not contain valid UTF-8" ); let s = "𐁁𐀓𐀠𐀴𐀍"; check!(fs::write(&tmpdir.join("utf8"), s.as_bytes())); let string = check!(fs::read_to_string(&tmpdir.join("utf8"))); assert_eq!(string, s); } #[test] fn file_try_clone() { let tmpdir = tmpdir(); let mut f1 = check!(OpenOptions::new().read(true).write(true).create(true).open(&tmpdir.join("test"))); let mut f2 = check!(f1.try_clone()); check!(f1.write_all(b"hello world")); check!(f1.seek(SeekFrom::Start(2))); let mut buf = vec![]; check!(f2.read_to_end(&mut buf)); assert_eq!(buf, b"llo world"); drop(f2); check!(f1.write_all(b"!")); } #[test] #[cfg(not(windows))] fn unlink_readonly() { let tmpdir = tmpdir(); let path = tmpdir.join("file"); check!(File::create(&path)); let mut perm = check!(fs::metadata(&path)).permissions(); perm.set_readonly(true); check!(fs::set_permissions(&path, perm)); check!(fs::remove_file(&path)); } #[test] fn mkdir_trailing_slash() { let tmpdir = tmpdir(); let path = tmpdir.join("file"); check!(fs::create_dir_all(&path.join("a/"))); } #[test] fn canonicalize_works_simple() { let tmpdir = tmpdir(); let tmpdir = fs::canonicalize(tmpdir.path()).unwrap(); let file = tmpdir.join("test"); File::create(&file).unwrap(); assert_eq!(fs::canonicalize(&file).unwrap(), file); } #[test] fn realpath_works() { let tmpdir = tmpdir(); if !got_symlink_permission(&tmpdir) { return; }; let tmpdir = fs::canonicalize(tmpdir.path()).unwrap(); let file = tmpdir.join("test"); let dir = tmpdir.join("test2"); let link = dir.join("link"); let linkdir = tmpdir.join("test3"); File::create(&file).unwrap(); fs::create_dir(&dir).unwrap(); symlink_file(&file, &link).unwrap(); symlink_dir(&dir, &linkdir).unwrap(); assert!(link.symlink_metadata().unwrap().file_type().is_symlink()); assert_eq!(fs::canonicalize(&tmpdir).unwrap(), tmpdir); assert_eq!(fs::canonicalize(&file).unwrap(), file); assert_eq!(fs::canonicalize(&link).unwrap(), file); assert_eq!(fs::canonicalize(&linkdir).unwrap(), dir); assert_eq!(fs::canonicalize(&linkdir.join("link")).unwrap(), file); } #[test] fn realpath_works_tricky() { let tmpdir = tmpdir(); if !got_symlink_permission(&tmpdir) { return; }; let tmpdir = fs::canonicalize(tmpdir.path()).unwrap(); let a = tmpdir.join("a"); let b = a.join("b"); let c = b.join("c"); let d = a.join("d"); let e = d.join("e"); let f = a.join("f"); fs::create_dir_all(&b).unwrap(); fs::create_dir_all(&d).unwrap(); File::create(&f).unwrap(); if cfg!(not(windows)) { symlink_file("../d/e", &c).unwrap(); symlink_file("../f", &e).unwrap(); } if cfg!(windows) { symlink_file(r"..\d\e", &c).unwrap(); symlink_file(r"..\f", &e).unwrap(); } assert_eq!(fs::canonicalize(&c).unwrap(), f); assert_eq!(fs::canonicalize(&e).unwrap(), f); } #[test] fn dir_entry_methods() { let tmpdir = tmpdir(); fs::create_dir_all(&tmpdir.join("a")).unwrap(); File::create(&tmpdir.join("b")).unwrap(); for file in tmpdir.path().read_dir().unwrap().map(|f| f.unwrap()) { let fname = file.file_name(); match fname.to_str() { Some("a") => { assert!(file.file_type().unwrap().is_dir()); assert!(file.metadata().unwrap().is_dir()); } Some("b") => { assert!(file.file_type().unwrap().is_file()); assert!(file.metadata().unwrap().is_file()); } f => panic!("unknown file name: {f:?}"), } } } #[test] fn dir_entry_debug() { let tmpdir = tmpdir(); File::create(&tmpdir.join("b")).unwrap(); let mut read_dir = tmpdir.path().read_dir().unwrap(); let dir_entry = read_dir.next().unwrap().unwrap(); let actual = format!("{dir_entry:?}"); let expected = format!("DirEntry({:?})", dir_entry.0.path()); assert_eq!(actual, expected); } #[test] fn read_dir_not_found() { let res = fs::read_dir("/path/that/does/not/exist"); assert_eq!(res.err().unwrap().kind(), ErrorKind::NotFound); } #[test] fn file_open_not_found() { let res = File::open("/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()); } #[test] fn metadata_access_times() { let tmpdir = tmpdir(); let b = tmpdir.join("b"); File::create(&b).unwrap(); let a = check!(fs::metadata(&tmpdir.path())); let b = check!(fs::metadata(&b)); assert_eq!(check!(a.accessed()), check!(a.accessed())); assert_eq!(check!(a.modified()), check!(a.modified())); assert_eq!(check!(b.accessed()), check!(b.modified())); if cfg!(target_os = "macos") || cfg!(target_os = "windows") { check!(a.created()); check!(b.created()); } if cfg!(target_os = "linux") { // Not always available match (a.created(), b.created()) { (Ok(t1), Ok(t2)) => assert!(t1 <= t2), (Err(e1), Err(e2)) if e1.kind() == ErrorKind::Uncategorized && e2.kind() == ErrorKind::Uncategorized || e1.kind() == ErrorKind::Unsupported && e2.kind() == ErrorKind::Unsupported => {} (a, b) => { panic!("creation time must be always supported or not supported: {a:?} {b:?}") } } } } /// Test creating hard links to symlinks. #[test] fn symlink_hard_link() { let tmpdir = tmpdir(); if !got_symlink_permission(&tmpdir) { return; }; if !able_to_not_follow_symlinks_while_hard_linking() { return; } // Create "file", a file. check!(fs::File::create(tmpdir.join("file"))); // Create "symlink", a symlink to "file". check!(symlink_file("file", tmpdir.join("symlink"))); // Create "hard_link", a hard link to "symlink". check!(fs::hard_link(tmpdir.join("symlink"), tmpdir.join("hard_link"))); // "hard_link" should appear as a symlink. assert!(check!(fs::symlink_metadata(tmpdir.join("hard_link"))).file_type().is_symlink()); // We should be able to open "file" via any of the above names. let _ = check!(fs::File::open(tmpdir.join("file"))); assert!(fs::File::open(tmpdir.join("file.renamed")).is_err()); let _ = check!(fs::File::open(tmpdir.join("symlink"))); let _ = check!(fs::File::open(tmpdir.join("hard_link"))); // Rename "file" to "file.renamed". check!(fs::rename(tmpdir.join("file"), tmpdir.join("file.renamed"))); // Now, the symlink and the hard link should be dangling. assert!(fs::File::open(tmpdir.join("file")).is_err()); let _ = check!(fs::File::open(tmpdir.join("file.renamed"))); assert!(fs::File::open(tmpdir.join("symlink")).is_err()); assert!(fs::File::open(tmpdir.join("hard_link")).is_err()); // The symlink and the hard link should both still point to "file". assert!(fs::read_link(tmpdir.join("file")).is_err()); assert!(fs::read_link(tmpdir.join("file.renamed")).is_err()); assert_eq!(check!(fs::read_link(tmpdir.join("symlink"))), Path::new("file")); assert_eq!(check!(fs::read_link(tmpdir.join("hard_link"))), Path::new("file")); // Remove "file.renamed". check!(fs::remove_file(tmpdir.join("file.renamed"))); // Now, we can't open the file by any name. assert!(fs::File::open(tmpdir.join("file")).is_err()); assert!(fs::File::open(tmpdir.join("file.renamed")).is_err()); assert!(fs::File::open(tmpdir.join("symlink")).is_err()); assert!(fs::File::open(tmpdir.join("hard_link")).is_err()); // "hard_link" should still appear as a symlink. assert!(check!(fs::symlink_metadata(tmpdir.join("hard_link"))).file_type().is_symlink()); } /// Ensure `fs::create_dir` works on Windows with longer paths. #[test] #[cfg(windows)] fn create_dir_long_paths() { use crate::{ffi::OsStr, iter, os::windows::ffi::OsStrExt}; const PATH_LEN: usize = 247; let tmpdir = tmpdir(); let mut path = tmpdir.path().to_path_buf(); path.push("a"); let mut path = path.into_os_string(); let utf16_len = path.encode_wide().count(); if utf16_len >= PATH_LEN { // Skip the test in the unlikely event the local user has a long temp directory path. // This should not affect CI. return; } // Increase the length of the path. path.extend(iter::repeat(OsStr::new("a")).take(PATH_LEN - utf16_len)); // This should succeed. fs::create_dir(&path).unwrap(); // This will fail if the path isn't converted to verbatim. path.push("a"); fs::create_dir(&path).unwrap(); // #90940: Ensure an empty path returns the "Not Found" error. let path = Path::new(""); assert_eq!(path.canonicalize().unwrap_err().kind(), crate::io::ErrorKind::NotFound); } /// Ensure ReadDir works on large directories. /// Regression test for https://github.com/rust-lang/rust/issues/93384. #[test] fn read_large_dir() { let tmpdir = tmpdir(); let count = 32 * 1024; for i in 0..count { check!(fs::File::create(tmpdir.join(&i.to_string()))); } for entry in fs::read_dir(tmpdir.path()).unwrap() { entry.unwrap(); } } /// Test the fallback for getting the metadata of files like hiberfil.sys that /// Windows holds a special lock on, preventing normal means of querying /// metadata. See #96980. /// /// Note this fails in CI because `hiberfil.sys` does not actually exist there. /// Therefore it's marked as ignored. #[test] #[ignore] #[cfg(windows)] fn hiberfil_sys() { let hiberfil = Path::new(r"C:\hiberfil.sys"); assert_eq!(true, hiberfil.try_exists().unwrap()); fs::symlink_metadata(hiberfil).unwrap(); fs::metadata(hiberfil).unwrap(); assert_eq!(true, hiberfil.exists()); } /// Test that two different ways of obtaining the FileType give the same result. /// Cf. https://github.com/rust-lang/rust/issues/104900 #[test] fn test_eq_direntry_metadata() { let tmpdir = tmpdir(); let file_path = tmpdir.join("file"); File::create(file_path).unwrap(); for e in fs::read_dir(tmpdir.path()).unwrap() { let e = e.unwrap(); let p = e.path(); let ft1 = e.file_type().unwrap(); let ft2 = p.metadata().unwrap().file_type(); assert_eq!(ft1, ft2); } } /// Regression test for https://github.com/rust-lang/rust/issues/50619. #[test] #[cfg(target_os = "linux")] fn test_read_dir_infinite_loop() { use crate::io::ErrorKind; use crate::process::Command; // Create a zombie child process let Ok(mut child) = Command::new("echo").spawn() else { return }; // Make sure the process is (un)dead match child.kill() { // InvalidInput means the child already exited Err(e) if e.kind() != ErrorKind::InvalidInput => return, _ => {} } // open() on this path will succeed, but readdir() will fail let id = child.id(); let path = format!("/proc/{id}/net"); // Skip the test if we can't open the directory in the first place let Ok(dir) = fs::read_dir(path) else { return }; // Check for duplicate errors assert!(dir.filter(|e| e.is_err()).take(2).count() < 2); } #[test] fn rename_directory() { let tmpdir = tmpdir(); let old_path = tmpdir.join("foo/bar/baz"); fs::create_dir_all(&old_path).unwrap(); let test_file = &old_path.join("temp.txt"); File::create(test_file).unwrap(); let new_path = tmpdir.join("quux/blat"); fs::create_dir_all(&new_path).unwrap(); fs::rename(&old_path, &new_path.join("newdir")).unwrap(); assert!(new_path.join("newdir").is_dir()); assert!(new_path.join("newdir/temp.txt").exists()); }