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