1 use crate::io::prelude::*;
2
3 use crate::env;
4 use crate::fs::{self, File, FileTimes, OpenOptions};
5 use crate::io::{BorrowedBuf, ErrorKind, SeekFrom};
6 use crate::mem::MaybeUninit;
7 use crate::path::Path;
8 use crate::str;
9 use crate::sync::Arc;
10 use crate::sys_common::io::test::{tmpdir, TempDir};
11 use crate::thread;
12 use crate::time::{Duration, Instant, SystemTime};
13
14 use rand::RngCore;
15
16 #[cfg(target_os = "macos")]
17 use crate::ffi::{c_char, c_int};
18 #[cfg(unix)]
19 use crate::os::unix::fs::symlink as symlink_dir;
20 #[cfg(unix)]
21 use crate::os::unix::fs::symlink as symlink_file;
22 #[cfg(unix)]
23 use crate::os::unix::fs::symlink as symlink_junction;
24 #[cfg(windows)]
25 use crate::os::windows::fs::{symlink_dir, symlink_file};
26 #[cfg(windows)]
27 use crate::sys::fs::symlink_junction;
28 #[cfg(target_os = "macos")]
29 use crate::sys::weak::weak;
30
31 macro_rules! check {
32 ($e:expr) => {
33 match $e {
34 Ok(t) => t,
35 Err(e) => panic!("{} failed with: {e}", stringify!($e)),
36 }
37 };
38 }
39
40 #[cfg(windows)]
41 macro_rules! error {
42 ($e:expr, $s:expr) => {
43 match $e {
44 Ok(_) => panic!("Unexpected success. Should've been: {:?}", $s),
45 Err(ref err) => {
46 assert!(err.raw_os_error() == Some($s), "`{}` did not have a code of `{}`", err, $s)
47 }
48 }
49 };
50 }
51
52 #[cfg(unix)]
53 macro_rules! error {
54 ($e:expr, $s:expr) => {
55 error_contains!($e, $s)
56 };
57 }
58
59 macro_rules! error_contains {
60 ($e:expr, $s:expr) => {
61 match $e {
62 Ok(_) => panic!("Unexpected success. Should've been: {:?}", $s),
63 Err(ref err) => {
64 assert!(err.to_string().contains($s), "`{}` did not contain `{}`", err, $s)
65 }
66 }
67 };
68 }
69
70 // Several test fail on windows if the user does not have permission to
71 // create symlinks (the `SeCreateSymbolicLinkPrivilege`). Instead of
72 // disabling these test on Windows, use this function to test whether we
73 // have permission, and return otherwise. This way, we still don't run these
74 // tests most of the time, but at least we do if the user has the right
75 // permissions.
got_symlink_permission(tmpdir: &TempDir) -> bool76 pub fn got_symlink_permission(tmpdir: &TempDir) -> bool {
77 if cfg!(unix) {
78 return true;
79 }
80 let link = tmpdir.join("some_hopefully_unique_link_name");
81
82 match symlink_file(r"nonexisting_target", link) {
83 // ERROR_PRIVILEGE_NOT_HELD = 1314
84 Err(ref err) if err.raw_os_error() == Some(1314) => false,
85 Ok(_) | Err(_) => true,
86 }
87 }
88
89 #[cfg(target_os = "macos")]
able_to_not_follow_symlinks_while_hard_linking() -> bool90 fn able_to_not_follow_symlinks_while_hard_linking() -> bool {
91 weak!(fn linkat(c_int, *const c_char, c_int, *const c_char, c_int) -> c_int);
92 linkat.get().is_some()
93 }
94
95 #[cfg(not(target_os = "macos"))]
able_to_not_follow_symlinks_while_hard_linking() -> bool96 fn able_to_not_follow_symlinks_while_hard_linking() -> bool {
97 return true;
98 }
99
100 #[test]
file_test_io_smoke_test()101 fn file_test_io_smoke_test() {
102 let message = "it's alright. have a good time";
103 let tmpdir = tmpdir();
104 let filename = &tmpdir.join("file_rt_io_file_test.txt");
105 {
106 let mut write_stream = check!(File::create(filename));
107 check!(write_stream.write(message.as_bytes()));
108 }
109 {
110 let mut read_stream = check!(File::open(filename));
111 let mut read_buf = [0; 1028];
112 let read_str = match check!(read_stream.read(&mut read_buf)) {
113 0 => panic!("shouldn't happen"),
114 n => str::from_utf8(&read_buf[..n]).unwrap().to_string(),
115 };
116 assert_eq!(read_str, message);
117 }
118 check!(fs::remove_file(filename));
119 }
120
121 #[test]
invalid_path_raises()122 fn invalid_path_raises() {
123 let tmpdir = tmpdir();
124 let filename = &tmpdir.join("file_that_does_not_exist.txt");
125 let result = File::open(filename);
126
127 #[cfg(all(unix, not(target_os = "vxworks")))]
128 error!(result, "No such file or directory");
129 #[cfg(target_os = "vxworks")]
130 error!(result, "no such file or directory");
131 #[cfg(windows)]
132 error!(result, 2); // ERROR_FILE_NOT_FOUND
133 }
134
135 #[test]
file_test_iounlinking_invalid_path_should_raise_condition()136 fn file_test_iounlinking_invalid_path_should_raise_condition() {
137 let tmpdir = tmpdir();
138 let filename = &tmpdir.join("file_another_file_that_does_not_exist.txt");
139
140 let result = fs::remove_file(filename);
141
142 #[cfg(all(unix, not(target_os = "vxworks")))]
143 error!(result, "No such file or directory");
144 #[cfg(target_os = "vxworks")]
145 error!(result, "no such file or directory");
146 #[cfg(windows)]
147 error!(result, 2); // ERROR_FILE_NOT_FOUND
148 }
149
150 #[test]
file_test_io_non_positional_read()151 fn file_test_io_non_positional_read() {
152 let message: &str = "ten-four";
153 let mut read_mem = [0; 8];
154 let tmpdir = tmpdir();
155 let filename = &tmpdir.join("file_rt_io_file_test_positional.txt");
156 {
157 let mut rw_stream = check!(File::create(filename));
158 check!(rw_stream.write(message.as_bytes()));
159 }
160 {
161 let mut read_stream = check!(File::open(filename));
162 {
163 let read_buf = &mut read_mem[0..4];
164 check!(read_stream.read(read_buf));
165 }
166 {
167 let read_buf = &mut read_mem[4..8];
168 check!(read_stream.read(read_buf));
169 }
170 }
171 check!(fs::remove_file(filename));
172 let read_str = str::from_utf8(&read_mem).unwrap();
173 assert_eq!(read_str, message);
174 }
175
176 #[test]
file_test_io_seek_and_tell_smoke_test()177 fn file_test_io_seek_and_tell_smoke_test() {
178 let message = "ten-four";
179 let mut read_mem = [0; 4];
180 let set_cursor = 4 as u64;
181 let tell_pos_pre_read;
182 let tell_pos_post_read;
183 let tmpdir = tmpdir();
184 let filename = &tmpdir.join("file_rt_io_file_test_seeking.txt");
185 {
186 let mut rw_stream = check!(File::create(filename));
187 check!(rw_stream.write(message.as_bytes()));
188 }
189 {
190 let mut read_stream = check!(File::open(filename));
191 check!(read_stream.seek(SeekFrom::Start(set_cursor)));
192 tell_pos_pre_read = check!(read_stream.seek(SeekFrom::Current(0)));
193 check!(read_stream.read(&mut read_mem));
194 tell_pos_post_read = check!(read_stream.seek(SeekFrom::Current(0)));
195 }
196 check!(fs::remove_file(filename));
197 let read_str = str::from_utf8(&read_mem).unwrap();
198 assert_eq!(read_str, &message[4..8]);
199 assert_eq!(tell_pos_pre_read, set_cursor);
200 assert_eq!(tell_pos_post_read, message.len() as u64);
201 }
202
203 #[test]
file_test_io_seek_and_write()204 fn file_test_io_seek_and_write() {
205 let initial_msg = "food-is-yummy";
206 let overwrite_msg = "-the-bar!!";
207 let final_msg = "foo-the-bar!!";
208 let seek_idx = 3;
209 let mut read_mem = [0; 13];
210 let tmpdir = tmpdir();
211 let filename = &tmpdir.join("file_rt_io_file_test_seek_and_write.txt");
212 {
213 let mut rw_stream = check!(File::create(filename));
214 check!(rw_stream.write(initial_msg.as_bytes()));
215 check!(rw_stream.seek(SeekFrom::Start(seek_idx)));
216 check!(rw_stream.write(overwrite_msg.as_bytes()));
217 }
218 {
219 let mut read_stream = check!(File::open(filename));
220 check!(read_stream.read(&mut read_mem));
221 }
222 check!(fs::remove_file(filename));
223 let read_str = str::from_utf8(&read_mem).unwrap();
224 assert!(read_str == final_msg);
225 }
226
227 #[test]
file_test_io_seek_shakedown()228 fn file_test_io_seek_shakedown() {
229 // 01234567890123
230 let initial_msg = "qwer-asdf-zxcv";
231 let chunk_one: &str = "qwer";
232 let chunk_two: &str = "asdf";
233 let chunk_three: &str = "zxcv";
234 let mut read_mem = [0; 4];
235 let tmpdir = tmpdir();
236 let filename = &tmpdir.join("file_rt_io_file_test_seek_shakedown.txt");
237 {
238 let mut rw_stream = check!(File::create(filename));
239 check!(rw_stream.write(initial_msg.as_bytes()));
240 }
241 {
242 let mut read_stream = check!(File::open(filename));
243
244 check!(read_stream.seek(SeekFrom::End(-4)));
245 check!(read_stream.read(&mut read_mem));
246 assert_eq!(str::from_utf8(&read_mem).unwrap(), chunk_three);
247
248 check!(read_stream.seek(SeekFrom::Current(-9)));
249 check!(read_stream.read(&mut read_mem));
250 assert_eq!(str::from_utf8(&read_mem).unwrap(), chunk_two);
251
252 check!(read_stream.seek(SeekFrom::Start(0)));
253 check!(read_stream.read(&mut read_mem));
254 assert_eq!(str::from_utf8(&read_mem).unwrap(), chunk_one);
255 }
256 check!(fs::remove_file(filename));
257 }
258
259 #[test]
file_test_io_eof()260 fn file_test_io_eof() {
261 let tmpdir = tmpdir();
262 let filename = tmpdir.join("file_rt_io_file_test_eof.txt");
263 let mut buf = [0; 256];
264 {
265 let oo = OpenOptions::new().create_new(true).write(true).read(true).clone();
266 let mut rw = check!(oo.open(&filename));
267 assert_eq!(check!(rw.read(&mut buf)), 0);
268 assert_eq!(check!(rw.read(&mut buf)), 0);
269 }
270 check!(fs::remove_file(&filename));
271 }
272
273 #[test]
274 #[cfg(unix)]
file_test_io_read_write_at()275 fn file_test_io_read_write_at() {
276 use crate::os::unix::fs::FileExt;
277
278 let tmpdir = tmpdir();
279 let filename = tmpdir.join("file_rt_io_file_test_read_write_at.txt");
280 let mut buf = [0; 256];
281 let write1 = "asdf";
282 let write2 = "qwer-";
283 let write3 = "-zxcv";
284 let content = "qwer-asdf-zxcv";
285 {
286 let oo = OpenOptions::new().create_new(true).write(true).read(true).clone();
287 let mut rw = check!(oo.open(&filename));
288 assert_eq!(check!(rw.write_at(write1.as_bytes(), 5)), write1.len());
289 assert_eq!(check!(rw.seek(SeekFrom::Current(0))), 0);
290 assert_eq!(check!(rw.read_at(&mut buf, 5)), write1.len());
291 assert_eq!(str::from_utf8(&buf[..write1.len()]), Ok(write1));
292 assert_eq!(check!(rw.seek(SeekFrom::Current(0))), 0);
293 assert_eq!(check!(rw.read_at(&mut buf[..write2.len()], 0)), write2.len());
294 assert_eq!(str::from_utf8(&buf[..write2.len()]), Ok("\0\0\0\0\0"));
295 assert_eq!(check!(rw.seek(SeekFrom::Current(0))), 0);
296 assert_eq!(check!(rw.write(write2.as_bytes())), write2.len());
297 assert_eq!(check!(rw.seek(SeekFrom::Current(0))), 5);
298 assert_eq!(check!(rw.read(&mut buf)), write1.len());
299 assert_eq!(str::from_utf8(&buf[..write1.len()]), Ok(write1));
300 assert_eq!(check!(rw.seek(SeekFrom::Current(0))), 9);
301 assert_eq!(check!(rw.read_at(&mut buf[..write2.len()], 0)), write2.len());
302 assert_eq!(str::from_utf8(&buf[..write2.len()]), Ok(write2));
303 assert_eq!(check!(rw.seek(SeekFrom::Current(0))), 9);
304 assert_eq!(check!(rw.write_at(write3.as_bytes(), 9)), write3.len());
305 assert_eq!(check!(rw.seek(SeekFrom::Current(0))), 9);
306 }
307 {
308 let mut read = check!(File::open(&filename));
309 assert_eq!(check!(read.read_at(&mut buf, 0)), content.len());
310 assert_eq!(str::from_utf8(&buf[..content.len()]), Ok(content));
311 assert_eq!(check!(read.seek(SeekFrom::Current(0))), 0);
312 assert_eq!(check!(read.seek(SeekFrom::End(-5))), 9);
313 assert_eq!(check!(read.read_at(&mut buf, 0)), content.len());
314 assert_eq!(str::from_utf8(&buf[..content.len()]), Ok(content));
315 assert_eq!(check!(read.seek(SeekFrom::Current(0))), 9);
316 assert_eq!(check!(read.read(&mut buf)), write3.len());
317 assert_eq!(str::from_utf8(&buf[..write3.len()]), Ok(write3));
318 assert_eq!(check!(read.seek(SeekFrom::Current(0))), 14);
319 assert_eq!(check!(read.read_at(&mut buf, 0)), content.len());
320 assert_eq!(str::from_utf8(&buf[..content.len()]), Ok(content));
321 assert_eq!(check!(read.seek(SeekFrom::Current(0))), 14);
322 assert_eq!(check!(read.read_at(&mut buf, 14)), 0);
323 assert_eq!(check!(read.read_at(&mut buf, 15)), 0);
324 assert_eq!(check!(read.seek(SeekFrom::Current(0))), 14);
325 }
326 check!(fs::remove_file(&filename));
327 }
328
329 #[test]
330 #[cfg(unix)]
set_get_unix_permissions()331 fn set_get_unix_permissions() {
332 use crate::os::unix::fs::PermissionsExt;
333
334 let tmpdir = tmpdir();
335 let filename = &tmpdir.join("set_get_unix_permissions");
336 check!(fs::create_dir(filename));
337 let mask = 0o7777;
338
339 check!(fs::set_permissions(filename, fs::Permissions::from_mode(0)));
340 let metadata0 = check!(fs::metadata(filename));
341 assert_eq!(mask & metadata0.permissions().mode(), 0);
342
343 check!(fs::set_permissions(filename, fs::Permissions::from_mode(0o1777)));
344 let metadata1 = check!(fs::metadata(filename));
345 #[cfg(all(unix, not(target_os = "vxworks")))]
346 assert_eq!(mask & metadata1.permissions().mode(), 0o1777);
347 #[cfg(target_os = "vxworks")]
348 assert_eq!(mask & metadata1.permissions().mode(), 0o0777);
349 }
350
351 #[test]
352 #[cfg(windows)]
file_test_io_seek_read_write()353 fn file_test_io_seek_read_write() {
354 use crate::os::windows::fs::FileExt;
355
356 let tmpdir = tmpdir();
357 let filename = tmpdir.join("file_rt_io_file_test_seek_read_write.txt");
358 let mut buf = [0; 256];
359 let write1 = "asdf";
360 let write2 = "qwer-";
361 let write3 = "-zxcv";
362 let content = "qwer-asdf-zxcv";
363 {
364 let oo = OpenOptions::new().create_new(true).write(true).read(true).clone();
365 let mut rw = check!(oo.open(&filename));
366 assert_eq!(check!(rw.seek_write(write1.as_bytes(), 5)), write1.len());
367 assert_eq!(check!(rw.seek(SeekFrom::Current(0))), 9);
368 assert_eq!(check!(rw.seek_read(&mut buf, 5)), write1.len());
369 assert_eq!(str::from_utf8(&buf[..write1.len()]), Ok(write1));
370 assert_eq!(check!(rw.seek(SeekFrom::Current(0))), 9);
371 assert_eq!(check!(rw.seek(SeekFrom::Start(0))), 0);
372 assert_eq!(check!(rw.write(write2.as_bytes())), write2.len());
373 assert_eq!(check!(rw.seek(SeekFrom::Current(0))), 5);
374 assert_eq!(check!(rw.read(&mut buf)), write1.len());
375 assert_eq!(str::from_utf8(&buf[..write1.len()]), Ok(write1));
376 assert_eq!(check!(rw.seek(SeekFrom::Current(0))), 9);
377 assert_eq!(check!(rw.seek_read(&mut buf[..write2.len()], 0)), write2.len());
378 assert_eq!(str::from_utf8(&buf[..write2.len()]), Ok(write2));
379 assert_eq!(check!(rw.seek(SeekFrom::Current(0))), 5);
380 assert_eq!(check!(rw.seek_write(write3.as_bytes(), 9)), write3.len());
381 assert_eq!(check!(rw.seek(SeekFrom::Current(0))), 14);
382 }
383 {
384 let mut read = check!(File::open(&filename));
385 assert_eq!(check!(read.seek_read(&mut buf, 0)), content.len());
386 assert_eq!(str::from_utf8(&buf[..content.len()]), Ok(content));
387 assert_eq!(check!(read.seek(SeekFrom::Current(0))), 14);
388 assert_eq!(check!(read.seek(SeekFrom::End(-5))), 9);
389 assert_eq!(check!(read.seek_read(&mut buf, 0)), content.len());
390 assert_eq!(str::from_utf8(&buf[..content.len()]), Ok(content));
391 assert_eq!(check!(read.seek(SeekFrom::Current(0))), 14);
392 assert_eq!(check!(read.seek(SeekFrom::End(-5))), 9);
393 assert_eq!(check!(read.read(&mut buf)), write3.len());
394 assert_eq!(str::from_utf8(&buf[..write3.len()]), Ok(write3));
395 assert_eq!(check!(read.seek(SeekFrom::Current(0))), 14);
396 assert_eq!(check!(read.seek_read(&mut buf, 0)), content.len());
397 assert_eq!(str::from_utf8(&buf[..content.len()]), Ok(content));
398 assert_eq!(check!(read.seek(SeekFrom::Current(0))), 14);
399 assert_eq!(check!(read.seek_read(&mut buf, 14)), 0);
400 assert_eq!(check!(read.seek_read(&mut buf, 15)), 0);
401 }
402 check!(fs::remove_file(&filename));
403 }
404
405 #[test]
file_test_read_buf()406 fn file_test_read_buf() {
407 let tmpdir = tmpdir();
408 let filename = &tmpdir.join("test");
409 check!(fs::write(filename, &[1, 2, 3, 4]));
410
411 let mut buf: [MaybeUninit<u8>; 128] = MaybeUninit::uninit_array();
412 let mut buf = BorrowedBuf::from(buf.as_mut_slice());
413 let mut file = check!(File::open(filename));
414 check!(file.read_buf(buf.unfilled()));
415 assert_eq!(buf.filled(), &[1, 2, 3, 4]);
416 // File::read_buf should omit buffer initialization.
417 assert_eq!(buf.init_len(), 4);
418
419 check!(fs::remove_file(filename));
420 }
421
422 #[test]
file_test_stat_is_correct_on_is_file()423 fn file_test_stat_is_correct_on_is_file() {
424 let tmpdir = tmpdir();
425 let filename = &tmpdir.join("file_stat_correct_on_is_file.txt");
426 {
427 let mut opts = OpenOptions::new();
428 let mut fs = check!(opts.read(true).write(true).create(true).open(filename));
429 let msg = "hw";
430 fs.write(msg.as_bytes()).unwrap();
431
432 let fstat_res = check!(fs.metadata());
433 assert!(fstat_res.is_file());
434 }
435 let stat_res_fn = check!(fs::metadata(filename));
436 assert!(stat_res_fn.is_file());
437 let stat_res_meth = check!(filename.metadata());
438 assert!(stat_res_meth.is_file());
439 check!(fs::remove_file(filename));
440 }
441
442 #[test]
file_test_stat_is_correct_on_is_dir()443 fn file_test_stat_is_correct_on_is_dir() {
444 let tmpdir = tmpdir();
445 let filename = &tmpdir.join("file_stat_correct_on_is_dir");
446 check!(fs::create_dir(filename));
447 let stat_res_fn = check!(fs::metadata(filename));
448 assert!(stat_res_fn.is_dir());
449 let stat_res_meth = check!(filename.metadata());
450 assert!(stat_res_meth.is_dir());
451 check!(fs::remove_dir(filename));
452 }
453
454 #[test]
file_test_fileinfo_false_when_checking_is_file_on_a_directory()455 fn file_test_fileinfo_false_when_checking_is_file_on_a_directory() {
456 let tmpdir = tmpdir();
457 let dir = &tmpdir.join("fileinfo_false_on_dir");
458 check!(fs::create_dir(dir));
459 assert!(!dir.is_file());
460 check!(fs::remove_dir(dir));
461 }
462
463 #[test]
file_test_fileinfo_check_exists_before_and_after_file_creation()464 fn file_test_fileinfo_check_exists_before_and_after_file_creation() {
465 let tmpdir = tmpdir();
466 let file = &tmpdir.join("fileinfo_check_exists_b_and_a.txt");
467 check!(check!(File::create(file)).write(b"foo"));
468 assert!(file.exists());
469 check!(fs::remove_file(file));
470 assert!(!file.exists());
471 }
472
473 #[test]
file_test_directoryinfo_check_exists_before_and_after_mkdir()474 fn file_test_directoryinfo_check_exists_before_and_after_mkdir() {
475 let tmpdir = tmpdir();
476 let dir = &tmpdir.join("before_and_after_dir");
477 assert!(!dir.exists());
478 check!(fs::create_dir(dir));
479 assert!(dir.exists());
480 assert!(dir.is_dir());
481 check!(fs::remove_dir(dir));
482 assert!(!dir.exists());
483 }
484
485 #[test]
file_test_directoryinfo_readdir()486 fn file_test_directoryinfo_readdir() {
487 let tmpdir = tmpdir();
488 let dir = &tmpdir.join("di_readdir");
489 check!(fs::create_dir(dir));
490 let prefix = "foo";
491 for n in 0..3 {
492 let f = dir.join(&format!("{n}.txt"));
493 let mut w = check!(File::create(&f));
494 let msg_str = format!("{}{}", prefix, n.to_string());
495 let msg = msg_str.as_bytes();
496 check!(w.write(msg));
497 }
498 let files = check!(fs::read_dir(dir));
499 let mut mem = [0; 4];
500 for f in files {
501 let f = f.unwrap().path();
502 {
503 let n = f.file_stem().unwrap();
504 check!(check!(File::open(&f)).read(&mut mem));
505 let read_str = str::from_utf8(&mem).unwrap();
506 let expected = format!("{}{}", prefix, n.to_str().unwrap());
507 assert_eq!(expected, read_str);
508 }
509 check!(fs::remove_file(&f));
510 }
511 check!(fs::remove_dir(dir));
512 }
513
514 #[test]
file_create_new_already_exists_error()515 fn file_create_new_already_exists_error() {
516 let tmpdir = tmpdir();
517 let file = &tmpdir.join("file_create_new_error_exists");
518 check!(fs::File::create(file));
519 let e = fs::OpenOptions::new().write(true).create_new(true).open(file).unwrap_err();
520 assert_eq!(e.kind(), ErrorKind::AlreadyExists);
521 }
522
523 #[test]
mkdir_path_already_exists_error()524 fn mkdir_path_already_exists_error() {
525 let tmpdir = tmpdir();
526 let dir = &tmpdir.join("mkdir_error_twice");
527 check!(fs::create_dir(dir));
528 let e = fs::create_dir(dir).unwrap_err();
529 assert_eq!(e.kind(), ErrorKind::AlreadyExists);
530 }
531
532 #[test]
recursive_mkdir()533 fn recursive_mkdir() {
534 let tmpdir = tmpdir();
535 let dir = tmpdir.join("d1/d2");
536 check!(fs::create_dir_all(&dir));
537 assert!(dir.is_dir())
538 }
539
540 #[test]
recursive_mkdir_failure()541 fn recursive_mkdir_failure() {
542 let tmpdir = tmpdir();
543 let dir = tmpdir.join("d1");
544 let file = dir.join("f1");
545
546 check!(fs::create_dir_all(&dir));
547 check!(File::create(&file));
548
549 let result = fs::create_dir_all(&file);
550
551 assert!(result.is_err());
552 }
553
554 #[test]
concurrent_recursive_mkdir()555 fn concurrent_recursive_mkdir() {
556 for _ in 0..100 {
557 let dir = tmpdir();
558 let mut dir = dir.join("a");
559 for _ in 0..40 {
560 dir = dir.join("a");
561 }
562 let mut join = vec![];
563 for _ in 0..8 {
564 let dir = dir.clone();
565 join.push(thread::spawn(move || {
566 check!(fs::create_dir_all(&dir));
567 }))
568 }
569
570 // No `Display` on result of `join()`
571 join.drain(..).map(|join| join.join().unwrap()).count();
572 }
573 }
574
575 #[test]
recursive_mkdir_slash()576 fn recursive_mkdir_slash() {
577 check!(fs::create_dir_all(Path::new("/")));
578 }
579
580 #[test]
recursive_mkdir_dot()581 fn recursive_mkdir_dot() {
582 check!(fs::create_dir_all(Path::new(".")));
583 }
584
585 #[test]
recursive_mkdir_empty()586 fn recursive_mkdir_empty() {
587 check!(fs::create_dir_all(Path::new("")));
588 }
589
590 #[test]
recursive_rmdir()591 fn recursive_rmdir() {
592 let tmpdir = tmpdir();
593 let d1 = tmpdir.join("d1");
594 let dt = d1.join("t");
595 let dtt = dt.join("t");
596 let d2 = tmpdir.join("d2");
597 let canary = d2.join("do_not_delete");
598 check!(fs::create_dir_all(&dtt));
599 check!(fs::create_dir_all(&d2));
600 check!(check!(File::create(&canary)).write(b"foo"));
601 check!(symlink_junction(&d2, &dt.join("d2")));
602 let _ = symlink_file(&canary, &d1.join("canary"));
603 check!(fs::remove_dir_all(&d1));
604
605 assert!(!d1.is_dir());
606 assert!(canary.exists());
607 }
608
609 #[test]
recursive_rmdir_of_symlink()610 fn recursive_rmdir_of_symlink() {
611 // test we do not recursively delete a symlink but only dirs.
612 let tmpdir = tmpdir();
613 let link = tmpdir.join("d1");
614 let dir = tmpdir.join("d2");
615 let canary = dir.join("do_not_delete");
616 check!(fs::create_dir_all(&dir));
617 check!(check!(File::create(&canary)).write(b"foo"));
618 check!(symlink_junction(&dir, &link));
619 check!(fs::remove_dir_all(&link));
620
621 assert!(!link.is_dir());
622 assert!(canary.exists());
623 }
624
625 #[test]
recursive_rmdir_of_file_fails()626 fn recursive_rmdir_of_file_fails() {
627 // test we do not delete a directly specified file.
628 let tmpdir = tmpdir();
629 let canary = tmpdir.join("do_not_delete");
630 check!(check!(File::create(&canary)).write(b"foo"));
631 let result = fs::remove_dir_all(&canary);
632 #[cfg(unix)]
633 error!(result, "Not a directory");
634 #[cfg(windows)]
635 error!(result, 267); // ERROR_DIRECTORY - The directory name is invalid.
636 assert!(result.is_err());
637 assert!(canary.exists());
638 }
639
640 #[test]
641 // only Windows makes a distinction between file and directory symlinks.
642 #[cfg(windows)]
recursive_rmdir_of_file_symlink()643 fn recursive_rmdir_of_file_symlink() {
644 let tmpdir = tmpdir();
645 if !got_symlink_permission(&tmpdir) {
646 return;
647 };
648
649 let f1 = tmpdir.join("f1");
650 let f2 = tmpdir.join("f2");
651 check!(check!(File::create(&f1)).write(b"foo"));
652 check!(symlink_file(&f1, &f2));
653 match fs::remove_dir_all(&f2) {
654 Ok(..) => panic!("wanted a failure"),
655 Err(..) => {}
656 }
657 }
658
659 #[test]
660 #[ignore] // takes too much time
recursive_rmdir_toctou()661 fn recursive_rmdir_toctou() {
662 // Test for time-of-check to time-of-use issues.
663 //
664 // Scenario:
665 // The attacker wants to get directory contents deleted, to which they do not have access.
666 // They have a way to get a privileged Rust binary call `std::fs::remove_dir_all()` on a
667 // directory they control, e.g. in their home directory.
668 //
669 // The POC sets up the `attack_dest/attack_file` which the attacker wants to have deleted.
670 // The attacker repeatedly creates a directory and replaces it with a symlink from
671 // `victim_del` to `attack_dest` while the victim code calls `std::fs::remove_dir_all()`
672 // on `victim_del`. After a few seconds the attack has succeeded and
673 // `attack_dest/attack_file` is deleted.
674 let tmpdir = tmpdir();
675 let victim_del_path = tmpdir.join("victim_del");
676 let victim_del_path_clone = victim_del_path.clone();
677
678 // setup dest
679 let attack_dest_dir = tmpdir.join("attack_dest");
680 let attack_dest_dir = attack_dest_dir.as_path();
681 fs::create_dir(attack_dest_dir).unwrap();
682 let attack_dest_file = tmpdir.join("attack_dest/attack_file");
683 File::create(&attack_dest_file).unwrap();
684
685 let drop_canary_arc = Arc::new(());
686 let drop_canary_weak = Arc::downgrade(&drop_canary_arc);
687
688 eprintln!("x: {:?}", &victim_del_path);
689
690 // victim just continuously removes `victim_del`
691 thread::spawn(move || {
692 while drop_canary_weak.upgrade().is_some() {
693 let _ = fs::remove_dir_all(&victim_del_path_clone);
694 }
695 });
696
697 // attacker (could of course be in a separate process)
698 let start_time = Instant::now();
699 while Instant::now().duration_since(start_time) < Duration::from_secs(1000) {
700 if !attack_dest_file.exists() {
701 panic!(
702 "Victim deleted symlinked file outside of victim_del. Attack succeeded in {:?}.",
703 Instant::now().duration_since(start_time)
704 );
705 }
706 let _ = fs::create_dir(&victim_del_path);
707 let _ = fs::remove_dir(&victim_del_path);
708 let _ = symlink_dir(attack_dest_dir, &victim_del_path);
709 }
710 }
711
712 #[test]
unicode_path_is_dir()713 fn unicode_path_is_dir() {
714 assert!(Path::new(".").is_dir());
715 assert!(!Path::new("test/stdtest/fs.rs").is_dir());
716
717 let tmpdir = tmpdir();
718
719 let mut dirpath = tmpdir.path().to_path_buf();
720 dirpath.push("test-가一ー你好");
721 check!(fs::create_dir(&dirpath));
722 assert!(dirpath.is_dir());
723
724 let mut filepath = dirpath;
725 filepath.push("unicode-file-\u{ac00}\u{4e00}\u{30fc}\u{4f60}\u{597d}.rs");
726 check!(File::create(&filepath)); // ignore return; touch only
727 assert!(!filepath.is_dir());
728 assert!(filepath.exists());
729 }
730
731 #[test]
unicode_path_exists()732 fn unicode_path_exists() {
733 assert!(Path::new(".").exists());
734 assert!(!Path::new("test/nonexistent-bogus-path").exists());
735
736 let tmpdir = tmpdir();
737 let unicode = tmpdir.path();
738 let unicode = unicode.join("test-각丁ー再见");
739 check!(fs::create_dir(&unicode));
740 assert!(unicode.exists());
741 assert!(!Path::new("test/unicode-bogus-path-각丁ー再见").exists());
742 }
743
744 #[test]
copy_file_does_not_exist()745 fn copy_file_does_not_exist() {
746 let from = Path::new("test/nonexistent-bogus-path");
747 let to = Path::new("test/other-bogus-path");
748
749 match fs::copy(&from, &to) {
750 Ok(..) => panic!(),
751 Err(..) => {
752 assert!(!from.exists());
753 assert!(!to.exists());
754 }
755 }
756 }
757
758 #[test]
copy_src_does_not_exist()759 fn copy_src_does_not_exist() {
760 let tmpdir = tmpdir();
761 let from = Path::new("test/nonexistent-bogus-path");
762 let to = tmpdir.join("out.txt");
763 check!(check!(File::create(&to)).write(b"hello"));
764 assert!(fs::copy(&from, &to).is_err());
765 assert!(!from.exists());
766 let mut v = Vec::new();
767 check!(check!(File::open(&to)).read_to_end(&mut v));
768 assert_eq!(v, b"hello");
769 }
770
771 #[test]
copy_file_ok()772 fn copy_file_ok() {
773 let tmpdir = tmpdir();
774 let input = tmpdir.join("in.txt");
775 let out = tmpdir.join("out.txt");
776
777 check!(check!(File::create(&input)).write(b"hello"));
778 check!(fs::copy(&input, &out));
779 let mut v = Vec::new();
780 check!(check!(File::open(&out)).read_to_end(&mut v));
781 assert_eq!(v, b"hello");
782
783 assert_eq!(check!(input.metadata()).permissions(), check!(out.metadata()).permissions());
784 }
785
786 #[test]
copy_file_dst_dir()787 fn copy_file_dst_dir() {
788 let tmpdir = tmpdir();
789 let out = tmpdir.join("out");
790
791 check!(File::create(&out));
792 match fs::copy(&*out, tmpdir.path()) {
793 Ok(..) => panic!(),
794 Err(..) => {}
795 }
796 }
797
798 #[test]
copy_file_dst_exists()799 fn copy_file_dst_exists() {
800 let tmpdir = tmpdir();
801 let input = tmpdir.join("in");
802 let output = tmpdir.join("out");
803
804 check!(check!(File::create(&input)).write("foo".as_bytes()));
805 check!(check!(File::create(&output)).write("bar".as_bytes()));
806 check!(fs::copy(&input, &output));
807
808 let mut v = Vec::new();
809 check!(check!(File::open(&output)).read_to_end(&mut v));
810 assert_eq!(v, b"foo".to_vec());
811 }
812
813 #[test]
copy_file_src_dir()814 fn copy_file_src_dir() {
815 let tmpdir = tmpdir();
816 let out = tmpdir.join("out");
817
818 match fs::copy(tmpdir.path(), &out) {
819 Ok(..) => panic!(),
820 Err(..) => {}
821 }
822 assert!(!out.exists());
823 }
824
825 #[test]
copy_file_preserves_perm_bits()826 fn copy_file_preserves_perm_bits() {
827 let tmpdir = tmpdir();
828 let input = tmpdir.join("in.txt");
829 let out = tmpdir.join("out.txt");
830
831 let attr = check!(check!(File::create(&input)).metadata());
832 let mut p = attr.permissions();
833 p.set_readonly(true);
834 check!(fs::set_permissions(&input, p));
835 check!(fs::copy(&input, &out));
836 assert!(check!(out.metadata()).permissions().readonly());
837 check!(fs::set_permissions(&input, attr.permissions()));
838 check!(fs::set_permissions(&out, attr.permissions()));
839 }
840
841 #[test]
842 #[cfg(windows)]
copy_file_preserves_streams()843 fn copy_file_preserves_streams() {
844 let tmp = tmpdir();
845 check!(check!(File::create(tmp.join("in.txt:bunny"))).write("carrot".as_bytes()));
846 assert_eq!(check!(fs::copy(tmp.join("in.txt"), tmp.join("out.txt"))), 0);
847 assert_eq!(check!(tmp.join("out.txt").metadata()).len(), 0);
848 let mut v = Vec::new();
849 check!(check!(File::open(tmp.join("out.txt:bunny"))).read_to_end(&mut v));
850 assert_eq!(v, b"carrot".to_vec());
851 }
852
853 #[test]
copy_file_returns_metadata_len()854 fn copy_file_returns_metadata_len() {
855 let tmp = tmpdir();
856 let in_path = tmp.join("in.txt");
857 let out_path = tmp.join("out.txt");
858 check!(check!(File::create(&in_path)).write(b"lettuce"));
859 #[cfg(windows)]
860 check!(check!(File::create(tmp.join("in.txt:bunny"))).write(b"carrot"));
861 let copied_len = check!(fs::copy(&in_path, &out_path));
862 assert_eq!(check!(out_path.metadata()).len(), copied_len);
863 }
864
865 #[test]
copy_file_follows_dst_symlink()866 fn copy_file_follows_dst_symlink() {
867 let tmp = tmpdir();
868 if !got_symlink_permission(&tmp) {
869 return;
870 };
871
872 let in_path = tmp.join("in.txt");
873 let out_path = tmp.join("out.txt");
874 let out_path_symlink = tmp.join("out_symlink.txt");
875
876 check!(fs::write(&in_path, "foo"));
877 check!(fs::write(&out_path, "bar"));
878 check!(symlink_file(&out_path, &out_path_symlink));
879
880 check!(fs::copy(&in_path, &out_path_symlink));
881
882 assert!(check!(out_path_symlink.symlink_metadata()).file_type().is_symlink());
883 assert_eq!(check!(fs::read(&out_path_symlink)), b"foo".to_vec());
884 assert_eq!(check!(fs::read(&out_path)), b"foo".to_vec());
885 }
886
887 #[test]
symlinks_work()888 fn symlinks_work() {
889 let tmpdir = tmpdir();
890 if !got_symlink_permission(&tmpdir) {
891 return;
892 };
893
894 let input = tmpdir.join("in.txt");
895 let out = tmpdir.join("out.txt");
896
897 check!(check!(File::create(&input)).write("foobar".as_bytes()));
898 check!(symlink_file(&input, &out));
899 assert!(check!(out.symlink_metadata()).file_type().is_symlink());
900 assert_eq!(check!(fs::metadata(&out)).len(), check!(fs::metadata(&input)).len());
901 let mut v = Vec::new();
902 check!(check!(File::open(&out)).read_to_end(&mut v));
903 assert_eq!(v, b"foobar".to_vec());
904 }
905
906 #[test]
symlink_noexist()907 fn symlink_noexist() {
908 // Symlinks can point to things that don't exist
909 let tmpdir = tmpdir();
910 if !got_symlink_permission(&tmpdir) {
911 return;
912 };
913
914 // Use a relative path for testing. Symlinks get normalized by Windows,
915 // so we might not get the same path back for absolute paths
916 check!(symlink_file(&"foo", &tmpdir.join("bar")));
917 assert_eq!(check!(fs::read_link(&tmpdir.join("bar"))).to_str().unwrap(), "foo");
918 }
919
920 #[test]
read_link()921 fn read_link() {
922 let tmpdir = tmpdir();
923 if cfg!(windows) {
924 // directory symlink
925 assert_eq!(check!(fs::read_link(r"C:\Users\All Users")), Path::new(r"C:\ProgramData"));
926 // junction
927 assert_eq!(check!(fs::read_link(r"C:\Users\Default User")), Path::new(r"C:\Users\Default"));
928 // junction with special permissions
929 // Since not all localized windows versions contain the folder "Documents and Settings" in english,
930 // we will briefly check, if it exists and otherwise skip the test. Except during CI we will always execute the test.
931 if Path::new(r"C:\Documents and Settings\").exists() || env::var_os("CI").is_some() {
932 assert_eq!(
933 check!(fs::read_link(r"C:\Documents and Settings\")),
934 Path::new(r"C:\Users")
935 );
936 }
937 // Check that readlink works with non-drive paths on Windows.
938 let link = tmpdir.join("link_unc");
939 check!(symlink_dir(r"\\localhost\c$\", &link));
940 assert_eq!(check!(fs::read_link(&link)), Path::new(r"\\localhost\c$\"));
941 }
942 let link = tmpdir.join("link");
943 if !got_symlink_permission(&tmpdir) {
944 return;
945 };
946 check!(symlink_file(&"foo", &link));
947 assert_eq!(check!(fs::read_link(&link)).to_str().unwrap(), "foo");
948 }
949
950 #[test]
readlink_not_symlink()951 fn readlink_not_symlink() {
952 let tmpdir = tmpdir();
953 match fs::read_link(tmpdir.path()) {
954 Ok(..) => panic!("wanted a failure"),
955 Err(..) => {}
956 }
957 }
958
959 #[test]
links_work()960 fn links_work() {
961 let tmpdir = tmpdir();
962 let input = tmpdir.join("in.txt");
963 let out = tmpdir.join("out.txt");
964
965 check!(check!(File::create(&input)).write("foobar".as_bytes()));
966 check!(fs::hard_link(&input, &out));
967 assert_eq!(check!(fs::metadata(&out)).len(), check!(fs::metadata(&input)).len());
968 assert_eq!(check!(fs::metadata(&out)).len(), check!(input.metadata()).len());
969 let mut v = Vec::new();
970 check!(check!(File::open(&out)).read_to_end(&mut v));
971 assert_eq!(v, b"foobar".to_vec());
972
973 // can't link to yourself
974 match fs::hard_link(&input, &input) {
975 Ok(..) => panic!("wanted a failure"),
976 Err(..) => {}
977 }
978 // can't link to something that doesn't exist
979 match fs::hard_link(&tmpdir.join("foo"), &tmpdir.join("bar")) {
980 Ok(..) => panic!("wanted a failure"),
981 Err(..) => {}
982 }
983 }
984
985 #[test]
chmod_works()986 fn chmod_works() {
987 let tmpdir = tmpdir();
988 let file = tmpdir.join("in.txt");
989
990 check!(File::create(&file));
991 let attr = check!(fs::metadata(&file));
992 assert!(!attr.permissions().readonly());
993 let mut p = attr.permissions();
994 p.set_readonly(true);
995 check!(fs::set_permissions(&file, p.clone()));
996 let attr = check!(fs::metadata(&file));
997 assert!(attr.permissions().readonly());
998
999 match fs::set_permissions(&tmpdir.join("foo"), p.clone()) {
1000 Ok(..) => panic!("wanted an error"),
1001 Err(..) => {}
1002 }
1003
1004 p.set_readonly(false);
1005 check!(fs::set_permissions(&file, p));
1006 }
1007
1008 #[test]
fchmod_works()1009 fn fchmod_works() {
1010 let tmpdir = tmpdir();
1011 let path = tmpdir.join("in.txt");
1012
1013 let file = check!(File::create(&path));
1014 let attr = check!(fs::metadata(&path));
1015 assert!(!attr.permissions().readonly());
1016 let mut p = attr.permissions();
1017 p.set_readonly(true);
1018 check!(file.set_permissions(p.clone()));
1019 let attr = check!(fs::metadata(&path));
1020 assert!(attr.permissions().readonly());
1021
1022 p.set_readonly(false);
1023 check!(file.set_permissions(p));
1024 }
1025
1026 #[test]
sync_doesnt_kill_anything()1027 fn sync_doesnt_kill_anything() {
1028 let tmpdir = tmpdir();
1029 let path = tmpdir.join("in.txt");
1030
1031 let mut file = check!(File::create(&path));
1032 check!(file.sync_all());
1033 check!(file.sync_data());
1034 check!(file.write(b"foo"));
1035 check!(file.sync_all());
1036 check!(file.sync_data());
1037 }
1038
1039 #[test]
truncate_works()1040 fn truncate_works() {
1041 let tmpdir = tmpdir();
1042 let path = tmpdir.join("in.txt");
1043
1044 let mut file = check!(File::create(&path));
1045 check!(file.write(b"foo"));
1046 check!(file.sync_all());
1047
1048 // Do some simple things with truncation
1049 assert_eq!(check!(file.metadata()).len(), 3);
1050 check!(file.set_len(10));
1051 assert_eq!(check!(file.metadata()).len(), 10);
1052 check!(file.write(b"bar"));
1053 check!(file.sync_all());
1054 assert_eq!(check!(file.metadata()).len(), 10);
1055
1056 let mut v = Vec::new();
1057 check!(check!(File::open(&path)).read_to_end(&mut v));
1058 assert_eq!(v, b"foobar\0\0\0\0".to_vec());
1059
1060 // Truncate to a smaller length, don't seek, and then write something.
1061 // Ensure that the intermediate zeroes are all filled in (we have `seek`ed
1062 // past the end of the file).
1063 check!(file.set_len(2));
1064 assert_eq!(check!(file.metadata()).len(), 2);
1065 check!(file.write(b"wut"));
1066 check!(file.sync_all());
1067 assert_eq!(check!(file.metadata()).len(), 9);
1068 let mut v = Vec::new();
1069 check!(check!(File::open(&path)).read_to_end(&mut v));
1070 assert_eq!(v, b"fo\0\0\0\0wut".to_vec());
1071 }
1072
1073 #[test]
open_flavors()1074 fn open_flavors() {
1075 use crate::fs::OpenOptions as OO;
1076 fn c<T: Clone>(t: &T) -> T {
1077 t.clone()
1078 }
1079
1080 let tmpdir = tmpdir();
1081
1082 let mut r = OO::new();
1083 r.read(true);
1084 let mut w = OO::new();
1085 w.write(true);
1086 let mut rw = OO::new();
1087 rw.read(true).write(true);
1088 let mut a = OO::new();
1089 a.append(true);
1090 let mut ra = OO::new();
1091 ra.read(true).append(true);
1092
1093 #[cfg(windows)]
1094 let invalid_options = 87; // ERROR_INVALID_PARAMETER
1095 #[cfg(all(unix, not(target_os = "vxworks")))]
1096 let invalid_options = "Invalid argument";
1097 #[cfg(target_os = "vxworks")]
1098 let invalid_options = "invalid argument";
1099
1100 // Test various combinations of creation modes and access modes.
1101 //
1102 // Allowed:
1103 // creation mode | read | write | read-write | append | read-append |
1104 // :-----------------------|:-----:|:-----:|:----------:|:------:|:-----------:|
1105 // not set (open existing) | X | X | X | X | X |
1106 // create | | X | X | X | X |
1107 // truncate | | X | X | | |
1108 // create and truncate | | X | X | | |
1109 // create_new | | X | X | X | X |
1110 //
1111 // tested in reverse order, so 'create_new' creates the file, and 'open existing' opens it.
1112
1113 // write-only
1114 check!(c(&w).create_new(true).open(&tmpdir.join("a")));
1115 check!(c(&w).create(true).truncate(true).open(&tmpdir.join("a")));
1116 check!(c(&w).truncate(true).open(&tmpdir.join("a")));
1117 check!(c(&w).create(true).open(&tmpdir.join("a")));
1118 check!(c(&w).open(&tmpdir.join("a")));
1119
1120 // read-only
1121 error!(c(&r).create_new(true).open(&tmpdir.join("b")), invalid_options);
1122 error!(c(&r).create(true).truncate(true).open(&tmpdir.join("b")), invalid_options);
1123 error!(c(&r).truncate(true).open(&tmpdir.join("b")), invalid_options);
1124 error!(c(&r).create(true).open(&tmpdir.join("b")), invalid_options);
1125 check!(c(&r).open(&tmpdir.join("a"))); // try opening the file created with write_only
1126
1127 // read-write
1128 check!(c(&rw).create_new(true).open(&tmpdir.join("c")));
1129 check!(c(&rw).create(true).truncate(true).open(&tmpdir.join("c")));
1130 check!(c(&rw).truncate(true).open(&tmpdir.join("c")));
1131 check!(c(&rw).create(true).open(&tmpdir.join("c")));
1132 check!(c(&rw).open(&tmpdir.join("c")));
1133
1134 // append
1135 check!(c(&a).create_new(true).open(&tmpdir.join("d")));
1136 error!(c(&a).create(true).truncate(true).open(&tmpdir.join("d")), invalid_options);
1137 error!(c(&a).truncate(true).open(&tmpdir.join("d")), invalid_options);
1138 check!(c(&a).create(true).open(&tmpdir.join("d")));
1139 check!(c(&a).open(&tmpdir.join("d")));
1140
1141 // read-append
1142 check!(c(&ra).create_new(true).open(&tmpdir.join("e")));
1143 error!(c(&ra).create(true).truncate(true).open(&tmpdir.join("e")), invalid_options);
1144 error!(c(&ra).truncate(true).open(&tmpdir.join("e")), invalid_options);
1145 check!(c(&ra).create(true).open(&tmpdir.join("e")));
1146 check!(c(&ra).open(&tmpdir.join("e")));
1147
1148 // Test opening a file without setting an access mode
1149 let mut blank = OO::new();
1150 error!(blank.create(true).open(&tmpdir.join("f")), invalid_options);
1151
1152 // Test write works
1153 check!(check!(File::create(&tmpdir.join("h"))).write("foobar".as_bytes()));
1154
1155 // Test write fails for read-only
1156 check!(r.open(&tmpdir.join("h")));
1157 {
1158 let mut f = check!(r.open(&tmpdir.join("h")));
1159 assert!(f.write("wut".as_bytes()).is_err());
1160 }
1161
1162 // Test write overwrites
1163 {
1164 let mut f = check!(c(&w).open(&tmpdir.join("h")));
1165 check!(f.write("baz".as_bytes()));
1166 }
1167 {
1168 let mut f = check!(c(&r).open(&tmpdir.join("h")));
1169 let mut b = vec![0; 6];
1170 check!(f.read(&mut b));
1171 assert_eq!(b, "bazbar".as_bytes());
1172 }
1173
1174 // Test truncate works
1175 {
1176 let mut f = check!(c(&w).truncate(true).open(&tmpdir.join("h")));
1177 check!(f.write("foo".as_bytes()));
1178 }
1179 assert_eq!(check!(fs::metadata(&tmpdir.join("h"))).len(), 3);
1180
1181 // Test append works
1182 assert_eq!(check!(fs::metadata(&tmpdir.join("h"))).len(), 3);
1183 {
1184 let mut f = check!(c(&a).open(&tmpdir.join("h")));
1185 check!(f.write("bar".as_bytes()));
1186 }
1187 assert_eq!(check!(fs::metadata(&tmpdir.join("h"))).len(), 6);
1188
1189 // Test .append(true) equals .write(true).append(true)
1190 {
1191 let mut f = check!(c(&w).append(true).open(&tmpdir.join("h")));
1192 check!(f.write("baz".as_bytes()));
1193 }
1194 assert_eq!(check!(fs::metadata(&tmpdir.join("h"))).len(), 9);
1195 }
1196
1197 #[test]
_assert_send_sync()1198 fn _assert_send_sync() {
1199 fn _assert_send_sync<T: Send + Sync>() {}
1200 _assert_send_sync::<OpenOptions>();
1201 }
1202
1203 #[test]
binary_file()1204 fn binary_file() {
1205 let mut bytes = [0; 1024];
1206 crate::test_helpers::test_rng().fill_bytes(&mut bytes);
1207
1208 let tmpdir = tmpdir();
1209
1210 check!(check!(File::create(&tmpdir.join("test"))).write(&bytes));
1211 let mut v = Vec::new();
1212 check!(check!(File::open(&tmpdir.join("test"))).read_to_end(&mut v));
1213 assert!(v == &bytes[..]);
1214 }
1215
1216 #[test]
write_then_read()1217 fn write_then_read() {
1218 let mut bytes = [0; 1024];
1219 crate::test_helpers::test_rng().fill_bytes(&mut bytes);
1220
1221 let tmpdir = tmpdir();
1222
1223 check!(fs::write(&tmpdir.join("test"), &bytes[..]));
1224 let v = check!(fs::read(&tmpdir.join("test")));
1225 assert!(v == &bytes[..]);
1226
1227 check!(fs::write(&tmpdir.join("not-utf8"), &[0xFF]));
1228 error_contains!(
1229 fs::read_to_string(&tmpdir.join("not-utf8")),
1230 "stream did not contain valid UTF-8"
1231 );
1232
1233 let s = "";
1234 check!(fs::write(&tmpdir.join("utf8"), s.as_bytes()));
1235 let string = check!(fs::read_to_string(&tmpdir.join("utf8")));
1236 assert_eq!(string, s);
1237 }
1238
1239 #[test]
file_try_clone()1240 fn file_try_clone() {
1241 let tmpdir = tmpdir();
1242
1243 let mut f1 =
1244 check!(OpenOptions::new().read(true).write(true).create(true).open(&tmpdir.join("test")));
1245 let mut f2 = check!(f1.try_clone());
1246
1247 check!(f1.write_all(b"hello world"));
1248 check!(f1.seek(SeekFrom::Start(2)));
1249
1250 let mut buf = vec![];
1251 check!(f2.read_to_end(&mut buf));
1252 assert_eq!(buf, b"llo world");
1253 drop(f2);
1254
1255 check!(f1.write_all(b"!"));
1256 }
1257
1258 #[test]
1259 #[cfg(not(windows))]
unlink_readonly()1260 fn unlink_readonly() {
1261 let tmpdir = tmpdir();
1262 let path = tmpdir.join("file");
1263 check!(File::create(&path));
1264 let mut perm = check!(fs::metadata(&path)).permissions();
1265 perm.set_readonly(true);
1266 check!(fs::set_permissions(&path, perm));
1267 check!(fs::remove_file(&path));
1268 }
1269
1270 #[test]
mkdir_trailing_slash()1271 fn mkdir_trailing_slash() {
1272 let tmpdir = tmpdir();
1273 let path = tmpdir.join("file");
1274 check!(fs::create_dir_all(&path.join("a/")));
1275 }
1276
1277 #[test]
canonicalize_works_simple()1278 fn canonicalize_works_simple() {
1279 let tmpdir = tmpdir();
1280 let tmpdir = fs::canonicalize(tmpdir.path()).unwrap();
1281 let file = tmpdir.join("test");
1282 File::create(&file).unwrap();
1283 assert_eq!(fs::canonicalize(&file).unwrap(), file);
1284 }
1285
1286 #[test]
realpath_works()1287 fn realpath_works() {
1288 let tmpdir = tmpdir();
1289 if !got_symlink_permission(&tmpdir) {
1290 return;
1291 };
1292
1293 let tmpdir = fs::canonicalize(tmpdir.path()).unwrap();
1294 let file = tmpdir.join("test");
1295 let dir = tmpdir.join("test2");
1296 let link = dir.join("link");
1297 let linkdir = tmpdir.join("test3");
1298
1299 File::create(&file).unwrap();
1300 fs::create_dir(&dir).unwrap();
1301 symlink_file(&file, &link).unwrap();
1302 symlink_dir(&dir, &linkdir).unwrap();
1303
1304 assert!(link.symlink_metadata().unwrap().file_type().is_symlink());
1305
1306 assert_eq!(fs::canonicalize(&tmpdir).unwrap(), tmpdir);
1307 assert_eq!(fs::canonicalize(&file).unwrap(), file);
1308 assert_eq!(fs::canonicalize(&link).unwrap(), file);
1309 assert_eq!(fs::canonicalize(&linkdir).unwrap(), dir);
1310 assert_eq!(fs::canonicalize(&linkdir.join("link")).unwrap(), file);
1311 }
1312
1313 #[test]
realpath_works_tricky()1314 fn realpath_works_tricky() {
1315 let tmpdir = tmpdir();
1316 if !got_symlink_permission(&tmpdir) {
1317 return;
1318 };
1319
1320 let tmpdir = fs::canonicalize(tmpdir.path()).unwrap();
1321 let a = tmpdir.join("a");
1322 let b = a.join("b");
1323 let c = b.join("c");
1324 let d = a.join("d");
1325 let e = d.join("e");
1326 let f = a.join("f");
1327
1328 fs::create_dir_all(&b).unwrap();
1329 fs::create_dir_all(&d).unwrap();
1330 File::create(&f).unwrap();
1331 if cfg!(not(windows)) {
1332 symlink_file("../d/e", &c).unwrap();
1333 symlink_file("../f", &e).unwrap();
1334 }
1335 if cfg!(windows) {
1336 symlink_file(r"..\d\e", &c).unwrap();
1337 symlink_file(r"..\f", &e).unwrap();
1338 }
1339
1340 assert_eq!(fs::canonicalize(&c).unwrap(), f);
1341 assert_eq!(fs::canonicalize(&e).unwrap(), f);
1342 }
1343
1344 #[test]
dir_entry_methods()1345 fn dir_entry_methods() {
1346 let tmpdir = tmpdir();
1347
1348 fs::create_dir_all(&tmpdir.join("a")).unwrap();
1349 File::create(&tmpdir.join("b")).unwrap();
1350
1351 for file in tmpdir.path().read_dir().unwrap().map(|f| f.unwrap()) {
1352 let fname = file.file_name();
1353 match fname.to_str() {
1354 Some("a") => {
1355 assert!(file.file_type().unwrap().is_dir());
1356 assert!(file.metadata().unwrap().is_dir());
1357 }
1358 Some("b") => {
1359 assert!(file.file_type().unwrap().is_file());
1360 assert!(file.metadata().unwrap().is_file());
1361 }
1362 f => panic!("unknown file name: {f:?}"),
1363 }
1364 }
1365 }
1366
1367 #[test]
dir_entry_debug()1368 fn dir_entry_debug() {
1369 let tmpdir = tmpdir();
1370 File::create(&tmpdir.join("b")).unwrap();
1371 let mut read_dir = tmpdir.path().read_dir().unwrap();
1372 let dir_entry = read_dir.next().unwrap().unwrap();
1373 let actual = format!("{dir_entry:?}");
1374 let expected = format!("DirEntry({:?})", dir_entry.0.path());
1375 assert_eq!(actual, expected);
1376 }
1377
1378 #[test]
read_dir_not_found()1379 fn read_dir_not_found() {
1380 let res = fs::read_dir("/path/that/does/not/exist");
1381 assert_eq!(res.err().unwrap().kind(), ErrorKind::NotFound);
1382 }
1383
1384 #[test]
file_open_not_found()1385 fn file_open_not_found() {
1386 let res = File::open("/path/that/does/not/exist");
1387 assert_eq!(res.err().unwrap().kind(), ErrorKind::NotFound);
1388 }
1389
1390 #[test]
create_dir_all_with_junctions()1391 fn create_dir_all_with_junctions() {
1392 let tmpdir = tmpdir();
1393 let target = tmpdir.join("target");
1394
1395 let junction = tmpdir.join("junction");
1396 let b = junction.join("a/b");
1397
1398 let link = tmpdir.join("link");
1399 let d = link.join("c/d");
1400
1401 fs::create_dir(&target).unwrap();
1402
1403 check!(symlink_junction(&target, &junction));
1404 check!(fs::create_dir_all(&b));
1405 // the junction itself is not a directory, but `is_dir()` on a Path
1406 // follows links
1407 assert!(junction.is_dir());
1408 assert!(b.exists());
1409
1410 if !got_symlink_permission(&tmpdir) {
1411 return;
1412 };
1413 check!(symlink_dir(&target, &link));
1414 check!(fs::create_dir_all(&d));
1415 assert!(link.is_dir());
1416 assert!(d.exists());
1417 }
1418
1419 #[test]
metadata_access_times()1420 fn metadata_access_times() {
1421 let tmpdir = tmpdir();
1422
1423 let b = tmpdir.join("b");
1424 File::create(&b).unwrap();
1425
1426 let a = check!(fs::metadata(&tmpdir.path()));
1427 let b = check!(fs::metadata(&b));
1428
1429 assert_eq!(check!(a.accessed()), check!(a.accessed()));
1430 assert_eq!(check!(a.modified()), check!(a.modified()));
1431 assert_eq!(check!(b.accessed()), check!(b.modified()));
1432
1433 if cfg!(target_os = "macos") || cfg!(target_os = "windows") {
1434 check!(a.created());
1435 check!(b.created());
1436 }
1437
1438 if cfg!(target_os = "linux") {
1439 // Not always available
1440 match (a.created(), b.created()) {
1441 (Ok(t1), Ok(t2)) => assert!(t1 <= t2),
1442 (Err(e1), Err(e2))
1443 if e1.kind() == ErrorKind::Uncategorized
1444 && e2.kind() == ErrorKind::Uncategorized
1445 || e1.kind() == ErrorKind::Unsupported
1446 && e2.kind() == ErrorKind::Unsupported => {}
1447 (a, b) => {
1448 panic!("creation time must be always supported or not supported: {a:?} {b:?}")
1449 }
1450 }
1451 }
1452 }
1453
1454 /// Test creating hard links to symlinks.
1455 #[test]
symlink_hard_link()1456 fn symlink_hard_link() {
1457 let tmpdir = tmpdir();
1458 if !got_symlink_permission(&tmpdir) {
1459 return;
1460 };
1461 if !able_to_not_follow_symlinks_while_hard_linking() {
1462 return;
1463 }
1464
1465 // Create "file", a file.
1466 check!(fs::File::create(tmpdir.join("file")));
1467
1468 // Create "symlink", a symlink to "file".
1469 check!(symlink_file("file", tmpdir.join("symlink")));
1470
1471 // Create "hard_link", a hard link to "symlink".
1472 check!(fs::hard_link(tmpdir.join("symlink"), tmpdir.join("hard_link")));
1473
1474 // "hard_link" should appear as a symlink.
1475 assert!(check!(fs::symlink_metadata(tmpdir.join("hard_link"))).file_type().is_symlink());
1476
1477 // We should be able to open "file" via any of the above names.
1478 let _ = check!(fs::File::open(tmpdir.join("file")));
1479 assert!(fs::File::open(tmpdir.join("file.renamed")).is_err());
1480 let _ = check!(fs::File::open(tmpdir.join("symlink")));
1481 let _ = check!(fs::File::open(tmpdir.join("hard_link")));
1482
1483 // Rename "file" to "file.renamed".
1484 check!(fs::rename(tmpdir.join("file"), tmpdir.join("file.renamed")));
1485
1486 // Now, the symlink and the hard link should be dangling.
1487 assert!(fs::File::open(tmpdir.join("file")).is_err());
1488 let _ = check!(fs::File::open(tmpdir.join("file.renamed")));
1489 assert!(fs::File::open(tmpdir.join("symlink")).is_err());
1490 assert!(fs::File::open(tmpdir.join("hard_link")).is_err());
1491
1492 // The symlink and the hard link should both still point to "file".
1493 assert!(fs::read_link(tmpdir.join("file")).is_err());
1494 assert!(fs::read_link(tmpdir.join("file.renamed")).is_err());
1495 assert_eq!(check!(fs::read_link(tmpdir.join("symlink"))), Path::new("file"));
1496 assert_eq!(check!(fs::read_link(tmpdir.join("hard_link"))), Path::new("file"));
1497
1498 // Remove "file.renamed".
1499 check!(fs::remove_file(tmpdir.join("file.renamed")));
1500
1501 // Now, we can't open the file by any name.
1502 assert!(fs::File::open(tmpdir.join("file")).is_err());
1503 assert!(fs::File::open(tmpdir.join("file.renamed")).is_err());
1504 assert!(fs::File::open(tmpdir.join("symlink")).is_err());
1505 assert!(fs::File::open(tmpdir.join("hard_link")).is_err());
1506
1507 // "hard_link" should still appear as a symlink.
1508 assert!(check!(fs::symlink_metadata(tmpdir.join("hard_link"))).file_type().is_symlink());
1509 }
1510
1511 /// Ensure `fs::create_dir` works on Windows with longer paths.
1512 #[test]
1513 #[cfg(windows)]
create_dir_long_paths()1514 fn create_dir_long_paths() {
1515 use crate::{ffi::OsStr, iter, os::windows::ffi::OsStrExt};
1516 const PATH_LEN: usize = 247;
1517
1518 let tmpdir = tmpdir();
1519 let mut path = tmpdir.path().to_path_buf();
1520 path.push("a");
1521 let mut path = path.into_os_string();
1522
1523 let utf16_len = path.encode_wide().count();
1524 if utf16_len >= PATH_LEN {
1525 // Skip the test in the unlikely event the local user has a long temp directory path.
1526 // This should not affect CI.
1527 return;
1528 }
1529 // Increase the length of the path.
1530 path.extend(iter::repeat(OsStr::new("a")).take(PATH_LEN - utf16_len));
1531
1532 // This should succeed.
1533 fs::create_dir(&path).unwrap();
1534
1535 // This will fail if the path isn't converted to verbatim.
1536 path.push("a");
1537 fs::create_dir(&path).unwrap();
1538
1539 // #90940: Ensure an empty path returns the "Not Found" error.
1540 let path = Path::new("");
1541 assert_eq!(path.canonicalize().unwrap_err().kind(), crate::io::ErrorKind::NotFound);
1542 }
1543
1544 /// Ensure ReadDir works on large directories.
1545 /// Regression test for https://github.com/rust-lang/rust/issues/93384.
1546 #[test]
read_large_dir()1547 fn read_large_dir() {
1548 let tmpdir = tmpdir();
1549
1550 let count = 32 * 1024;
1551 for i in 0..count {
1552 check!(fs::File::create(tmpdir.join(&i.to_string())));
1553 }
1554
1555 for entry in fs::read_dir(tmpdir.path()).unwrap() {
1556 entry.unwrap();
1557 }
1558 }
1559
1560 /// Test the fallback for getting the metadata of files like hiberfil.sys that
1561 /// Windows holds a special lock on, preventing normal means of querying
1562 /// metadata. See #96980.
1563 ///
1564 /// Note this fails in CI because `hiberfil.sys` does not actually exist there.
1565 /// Therefore it's marked as ignored.
1566 #[test]
1567 #[ignore]
1568 #[cfg(windows)]
hiberfil_sys()1569 fn hiberfil_sys() {
1570 let hiberfil = Path::new(r"C:\hiberfil.sys");
1571 assert_eq!(true, hiberfil.try_exists().unwrap());
1572 fs::symlink_metadata(hiberfil).unwrap();
1573 fs::metadata(hiberfil).unwrap();
1574 assert_eq!(true, hiberfil.exists());
1575 }
1576
1577 /// Test that two different ways of obtaining the FileType give the same result.
1578 /// Cf. https://github.com/rust-lang/rust/issues/104900
1579 #[test]
test_eq_direntry_metadata()1580 fn test_eq_direntry_metadata() {
1581 let tmpdir = tmpdir();
1582 let file_path = tmpdir.join("file");
1583 File::create(file_path).unwrap();
1584 for e in fs::read_dir(tmpdir.path()).unwrap() {
1585 let e = e.unwrap();
1586 let p = e.path();
1587 let ft1 = e.file_type().unwrap();
1588 let ft2 = p.metadata().unwrap().file_type();
1589 assert_eq!(ft1, ft2);
1590 }
1591 }
1592
1593 /// Regression test for https://github.com/rust-lang/rust/issues/50619.
1594 #[test]
1595 #[cfg(target_os = "linux")]
test_read_dir_infinite_loop()1596 fn test_read_dir_infinite_loop() {
1597 use crate::io::ErrorKind;
1598 use crate::process::Command;
1599
1600 // Create a zombie child process
1601 let Ok(mut child) = Command::new("echo").spawn() else { return };
1602
1603 // Make sure the process is (un)dead
1604 match child.kill() {
1605 // InvalidInput means the child already exited
1606 Err(e) if e.kind() != ErrorKind::InvalidInput => return,
1607 _ => {}
1608 }
1609
1610 // open() on this path will succeed, but readdir() will fail
1611 let id = child.id();
1612 let path = format!("/proc/{id}/net");
1613
1614 // Skip the test if we can't open the directory in the first place
1615 let Ok(dir) = fs::read_dir(path) else { return };
1616
1617 // Check for duplicate errors
1618 assert!(dir.filter(|e| e.is_err()).take(2).count() < 2);
1619 }
1620
1621 #[test]
rename_directory()1622 fn rename_directory() {
1623 let tmpdir = tmpdir();
1624 let old_path = tmpdir.join("foo/bar/baz");
1625 fs::create_dir_all(&old_path).unwrap();
1626 let test_file = &old_path.join("temp.txt");
1627
1628 File::create(test_file).unwrap();
1629
1630 let new_path = tmpdir.join("quux/blat");
1631 fs::create_dir_all(&new_path).unwrap();
1632 fs::rename(&old_path, &new_path.join("newdir")).unwrap();
1633 assert!(new_path.join("newdir").is_dir());
1634 assert!(new_path.join("newdir/temp.txt").exists());
1635 }
1636
1637 #[test]
test_file_times()1638 fn test_file_times() {
1639 #[cfg(target_os = "ios")]
1640 use crate::os::ios::fs::FileTimesExt;
1641 #[cfg(target_os = "macos")]
1642 use crate::os::macos::fs::FileTimesExt;
1643 #[cfg(target_os = "tvos")]
1644 use crate::os::tvos::fs::FileTimesExt;
1645 #[cfg(target_os = "tvos")]
1646 use crate::os::tvos::fs::FileTimesExt;
1647 #[cfg(target_os = "watchos")]
1648 use crate::os::watchos::fs::FileTimesExt;
1649 #[cfg(windows)]
1650 use crate::os::windows::fs::FileTimesExt;
1651
1652 let tmp = tmpdir();
1653 let file = File::create(tmp.join("foo")).unwrap();
1654 let mut times = FileTimes::new();
1655 let accessed = SystemTime::UNIX_EPOCH + Duration::from_secs(12345);
1656 let modified = SystemTime::UNIX_EPOCH + Duration::from_secs(54321);
1657 times = times.set_accessed(accessed).set_modified(modified);
1658 #[cfg(any(
1659 windows,
1660 target_os = "macos",
1661 target_os = "ios",
1662 target_os = "watchos",
1663 target_os = "tvos",
1664 ))]
1665 let created = SystemTime::UNIX_EPOCH + Duration::from_secs(32123);
1666 #[cfg(any(
1667 windows,
1668 target_os = "macos",
1669 target_os = "ios",
1670 target_os = "watchos",
1671 target_os = "tvos",
1672 ))]
1673 {
1674 times = times.set_created(created);
1675 }
1676 match file.set_times(times) {
1677 // Allow unsupported errors on platforms which don't support setting times.
1678 #[cfg(not(any(
1679 windows,
1680 all(
1681 unix,
1682 not(any(
1683 target_os = "android",
1684 target_os = "redox",
1685 target_os = "espidf",
1686 target_os = "horizon"
1687 ))
1688 )
1689 )))]
1690 Err(e) if e.kind() == ErrorKind::Unsupported => return,
1691 Err(e) => panic!("error setting file times: {e:?}"),
1692 Ok(_) => {}
1693 }
1694 let metadata = file.metadata().unwrap();
1695 assert_eq!(metadata.accessed().unwrap(), accessed);
1696 assert_eq!(metadata.modified().unwrap(), modified);
1697 #[cfg(any(
1698 windows,
1699 target_os = "macos",
1700 target_os = "ios",
1701 target_os = "watchos",
1702 target_os = "tvos",
1703 ))]
1704 {
1705 assert_eq!(metadata.created().unwrap(), created);
1706 }
1707 }
1708