1 #[cfg(not(any(target_os = "redox", target_os = "haiku")))]
2 use std::fs;
3 use std::fs::File;
4 #[cfg(not(target_os = "redox"))]
5 use std::os::unix::fs::symlink;
6 #[cfg(not(any(target_os = "redox", target_os = "haiku")))]
7 use std::os::unix::fs::PermissionsExt;
8 use std::os::unix::prelude::AsRawFd;
9 #[cfg(not(target_os = "redox"))]
10 use std::path::Path;
11 #[cfg(not(any(target_os = "redox", target_os = "haiku")))]
12 use std::time::{Duration, UNIX_EPOCH};
13
14 use libc::mode_t;
15 #[cfg(not(any(target_os = "netbsd", target_os = "redox")))]
16 use libc::{S_IFLNK, S_IFMT};
17
18 #[cfg(not(target_os = "redox"))]
19 use nix::errno::Errno;
20 #[cfg(not(target_os = "redox"))]
21 use nix::fcntl;
22 #[cfg(any(
23 target_os = "linux",
24 apple_targets,
25 target_os = "freebsd",
26 target_os = "netbsd"
27 ))]
28 use nix::sys::stat::lutimes;
29 #[cfg(not(any(target_os = "redox", target_os = "haiku")))]
30 use nix::sys::stat::utimensat;
31 #[cfg(not(target_os = "redox"))]
32 use nix::sys::stat::FchmodatFlags;
33 use nix::sys::stat::Mode;
34 #[cfg(not(any(target_os = "redox", target_os = "haiku")))]
35 use nix::sys::stat::UtimensatFlags;
36 #[cfg(not(target_os = "redox"))]
37 use nix::sys::stat::{self};
38 use nix::sys::stat::{fchmod, stat};
39 #[cfg(not(target_os = "redox"))]
40 use nix::sys::stat::{fchmodat, mkdirat};
41 #[cfg(not(any(target_os = "redox", target_os = "haiku")))]
42 use nix::sys::stat::{futimens, utimes};
43
44 #[cfg(not(any(target_os = "netbsd", target_os = "redox")))]
45 use nix::sys::stat::FileStat;
46
47 #[cfg(not(any(target_os = "redox", target_os = "haiku")))]
48 use nix::sys::time::{TimeSpec, TimeVal, TimeValLike};
49 #[cfg(not(target_os = "redox"))]
50 use nix::unistd::chdir;
51
52 #[cfg(not(any(target_os = "netbsd", target_os = "redox")))]
53 use nix::Result;
54
55 #[cfg(not(any(target_os = "netbsd", target_os = "redox")))]
assert_stat_results(stat_result: Result<FileStat>)56 fn assert_stat_results(stat_result: Result<FileStat>) {
57 let stats = stat_result.expect("stat call failed");
58 assert!(stats.st_dev > 0); // must be positive integer, exact number machine dependent
59 assert!(stats.st_ino > 0); // inode is positive integer, exact number machine dependent
60 assert!(stats.st_mode > 0); // must be positive integer
61 assert_eq!(stats.st_nlink, 1); // there links created, must be 1
62 assert_eq!(stats.st_size, 0); // size is 0 because we did not write anything to the file
63 assert!(stats.st_blksize > 0); // must be positive integer, exact number machine dependent
64 assert!(stats.st_blocks <= 16); // Up to 16 blocks can be allocated for a blank file
65 }
66
67 #[cfg(not(any(target_os = "netbsd", target_os = "redox")))]
68 // (Android's st_blocks is ulonglong which is always non-negative.)
69 #[cfg_attr(target_os = "android", allow(unused_comparisons))]
70 #[allow(clippy::absurd_extreme_comparisons)] // Not absurd on all OSes
assert_lstat_results(stat_result: Result<FileStat>)71 fn assert_lstat_results(stat_result: Result<FileStat>) {
72 let stats = stat_result.expect("stat call failed");
73 assert!(stats.st_dev > 0); // must be positive integer, exact number machine dependent
74 assert!(stats.st_ino > 0); // inode is positive integer, exact number machine dependent
75 assert!(stats.st_mode > 0); // must be positive integer
76
77 // st_mode is c_uint (u32 on Android) while S_IFMT is mode_t
78 // (u16 on Android), and that will be a compile error.
79 // On other platforms they are the same (either both are u16 or u32).
80 assert_eq!(
81 (stats.st_mode as usize) & (S_IFMT as usize),
82 S_IFLNK as usize
83 ); // should be a link
84 assert_eq!(stats.st_nlink, 1); // there links created, must be 1
85 assert!(stats.st_size > 0); // size is > 0 because it points to another file
86 assert!(stats.st_blksize > 0); // must be positive integer, exact number machine dependent
87
88 // st_blocks depends on whether the machine's file system uses fast
89 // or slow symlinks, so just make sure it's not negative
90 assert!(stats.st_blocks >= 0);
91 }
92
93 #[test]
94 #[cfg(not(any(target_os = "netbsd", target_os = "redox")))]
test_stat_and_fstat()95 fn test_stat_and_fstat() {
96 use nix::sys::stat::fstat;
97
98 let tempdir = tempfile::tempdir().unwrap();
99 let filename = tempdir.path().join("foo.txt");
100 let file = File::create(&filename).unwrap();
101
102 let stat_result = stat(&filename);
103 assert_stat_results(stat_result);
104
105 let fstat_result = fstat(file.as_raw_fd());
106 assert_stat_results(fstat_result);
107 }
108
109 #[test]
110 #[cfg(not(any(target_os = "netbsd", target_os = "redox")))]
test_fstatat()111 fn test_fstatat() {
112 let tempdir = tempfile::tempdir().unwrap();
113 let filename = tempdir.path().join("foo.txt");
114 File::create(&filename).unwrap();
115 let dirfd =
116 fcntl::open(tempdir.path(), fcntl::OFlag::empty(), stat::Mode::empty());
117
118 let result =
119 stat::fstatat(Some(dirfd.unwrap()), &filename, fcntl::AtFlags::empty());
120 assert_stat_results(result);
121 }
122
123 #[test]
124 #[cfg(not(any(target_os = "netbsd", target_os = "redox")))]
test_stat_fstat_lstat()125 fn test_stat_fstat_lstat() {
126 use nix::sys::stat::{fstat, lstat};
127
128 let tempdir = tempfile::tempdir().unwrap();
129 let filename = tempdir.path().join("bar.txt");
130 let linkname = tempdir.path().join("barlink");
131
132 File::create(&filename).unwrap();
133 symlink("bar.txt", &linkname).unwrap();
134 let link = File::open(&linkname).unwrap();
135
136 // should be the same result as calling stat,
137 // since it's a regular file
138 let stat_result = stat(&filename);
139 assert_stat_results(stat_result);
140
141 let lstat_result = lstat(&linkname);
142 assert_lstat_results(lstat_result);
143
144 let fstat_result = fstat(link.as_raw_fd());
145 assert_stat_results(fstat_result);
146 }
147
148 #[test]
test_fchmod()149 fn test_fchmod() {
150 let tempdir = tempfile::tempdir().unwrap();
151 let filename = tempdir.path().join("foo.txt");
152 let file = File::create(&filename).unwrap();
153
154 let mut mode1 = Mode::empty();
155 mode1.insert(Mode::S_IRUSR);
156 mode1.insert(Mode::S_IWUSR);
157 fchmod(file.as_raw_fd(), mode1).unwrap();
158
159 let file_stat1 = stat(&filename).unwrap();
160 assert_eq!(file_stat1.st_mode as mode_t & 0o7777, mode1.bits());
161
162 let mut mode2 = Mode::empty();
163 mode2.insert(Mode::S_IROTH);
164 fchmod(file.as_raw_fd(), mode2).unwrap();
165
166 let file_stat2 = stat(&filename).unwrap();
167 assert_eq!(file_stat2.st_mode as mode_t & 0o7777, mode2.bits());
168 }
169
170 #[test]
171 #[cfg(not(target_os = "redox"))]
test_fchmodat()172 fn test_fchmodat() {
173 let _dr = crate::DirRestore::new();
174 let tempdir = tempfile::tempdir().unwrap();
175 let filename = "foo.txt";
176 let fullpath = tempdir.path().join(filename);
177 File::create(&fullpath).unwrap();
178
179 let dirfd =
180 fcntl::open(tempdir.path(), fcntl::OFlag::empty(), stat::Mode::empty())
181 .unwrap();
182
183 let mut mode1 = Mode::empty();
184 mode1.insert(Mode::S_IRUSR);
185 mode1.insert(Mode::S_IWUSR);
186 fchmodat(Some(dirfd), filename, mode1, FchmodatFlags::FollowSymlink)
187 .unwrap();
188
189 let file_stat1 = stat(&fullpath).unwrap();
190 assert_eq!(file_stat1.st_mode as mode_t & 0o7777, mode1.bits());
191
192 chdir(tempdir.path()).unwrap();
193
194 let mut mode2 = Mode::empty();
195 mode2.insert(Mode::S_IROTH);
196 fchmodat(None, filename, mode2, FchmodatFlags::FollowSymlink).unwrap();
197
198 let file_stat2 = stat(&fullpath).unwrap();
199 assert_eq!(file_stat2.st_mode as mode_t & 0o7777, mode2.bits());
200 }
201
202 /// Asserts that the atime and mtime in a file's metadata match expected values.
203 ///
204 /// The atime and mtime are expressed with a resolution of seconds because some file systems
205 /// (like macOS's HFS+) do not have higher granularity.
206 #[cfg(not(any(target_os = "redox", target_os = "haiku")))]
assert_times_eq( exp_atime_sec: u64, exp_mtime_sec: u64, attr: &fs::Metadata, )207 fn assert_times_eq(
208 exp_atime_sec: u64,
209 exp_mtime_sec: u64,
210 attr: &fs::Metadata,
211 ) {
212 assert_eq!(
213 Duration::new(exp_atime_sec, 0),
214 attr.accessed().unwrap().duration_since(UNIX_EPOCH).unwrap()
215 );
216 assert_eq!(
217 Duration::new(exp_mtime_sec, 0),
218 attr.modified().unwrap().duration_since(UNIX_EPOCH).unwrap()
219 );
220 }
221
222 #[test]
223 #[cfg(not(any(target_os = "redox", target_os = "haiku")))]
test_utimes()224 fn test_utimes() {
225 let tempdir = tempfile::tempdir().unwrap();
226 let fullpath = tempdir.path().join("file");
227 drop(File::create(&fullpath).unwrap());
228
229 utimes(&fullpath, &TimeVal::seconds(9990), &TimeVal::seconds(5550))
230 .unwrap();
231 assert_times_eq(9990, 5550, &fs::metadata(&fullpath).unwrap());
232 }
233
234 #[test]
235 #[cfg(any(
236 target_os = "linux",
237 apple_targets,
238 target_os = "freebsd",
239 target_os = "netbsd"
240 ))]
test_lutimes()241 fn test_lutimes() {
242 let tempdir = tempfile::tempdir().unwrap();
243 let target = tempdir.path().join("target");
244 let fullpath = tempdir.path().join("symlink");
245 drop(File::create(&target).unwrap());
246 symlink(&target, &fullpath).unwrap();
247
248 let exp_target_metadata = fs::symlink_metadata(&target).unwrap();
249 lutimes(&fullpath, &TimeVal::seconds(4560), &TimeVal::seconds(1230))
250 .unwrap();
251 assert_times_eq(4560, 1230, &fs::symlink_metadata(&fullpath).unwrap());
252
253 let target_metadata = fs::symlink_metadata(&target).unwrap();
254 assert_eq!(
255 exp_target_metadata.accessed().unwrap(),
256 target_metadata.accessed().unwrap(),
257 "atime of symlink target was unexpectedly modified"
258 );
259 assert_eq!(
260 exp_target_metadata.modified().unwrap(),
261 target_metadata.modified().unwrap(),
262 "mtime of symlink target was unexpectedly modified"
263 );
264 }
265
266 #[test]
267 #[cfg(not(any(target_os = "redox", target_os = "haiku")))]
test_futimens()268 fn test_futimens() {
269 let tempdir = tempfile::tempdir().unwrap();
270 let fullpath = tempdir.path().join("file");
271 drop(File::create(&fullpath).unwrap());
272
273 let fd = fcntl::open(&fullpath, fcntl::OFlag::empty(), stat::Mode::empty())
274 .unwrap();
275
276 futimens(fd, &TimeSpec::seconds(10), &TimeSpec::seconds(20)).unwrap();
277 assert_times_eq(10, 20, &fs::metadata(&fullpath).unwrap());
278 }
279
280 #[test]
281 #[cfg(not(any(target_os = "redox", target_os = "haiku")))]
test_utimensat()282 fn test_utimensat() {
283 let _dr = crate::DirRestore::new();
284 let tempdir = tempfile::tempdir().unwrap();
285 let filename = "foo.txt";
286 let fullpath = tempdir.path().join(filename);
287 drop(File::create(&fullpath).unwrap());
288
289 let dirfd =
290 fcntl::open(tempdir.path(), fcntl::OFlag::empty(), stat::Mode::empty())
291 .unwrap();
292
293 utimensat(
294 Some(dirfd),
295 filename,
296 &TimeSpec::seconds(12345),
297 &TimeSpec::seconds(678),
298 UtimensatFlags::FollowSymlink,
299 )
300 .unwrap();
301 assert_times_eq(12345, 678, &fs::metadata(&fullpath).unwrap());
302
303 chdir(tempdir.path()).unwrap();
304
305 utimensat(
306 None,
307 filename,
308 &TimeSpec::seconds(500),
309 &TimeSpec::seconds(800),
310 UtimensatFlags::FollowSymlink,
311 )
312 .unwrap();
313 assert_times_eq(500, 800, &fs::metadata(&fullpath).unwrap());
314 }
315
316 #[test]
317 #[cfg(not(target_os = "redox"))]
test_mkdirat_success_path()318 fn test_mkdirat_success_path() {
319 let tempdir = tempfile::tempdir().unwrap();
320 let filename = "example_subdir";
321 let dirfd =
322 fcntl::open(tempdir.path(), fcntl::OFlag::empty(), stat::Mode::empty())
323 .unwrap();
324 mkdirat(Some(dirfd), filename, Mode::S_IRWXU).expect("mkdirat failed");
325 assert!(Path::exists(&tempdir.path().join(filename)));
326 }
327
328 #[test]
329 #[cfg(not(any(target_os = "redox", target_os = "haiku")))]
test_mkdirat_success_mode()330 fn test_mkdirat_success_mode() {
331 let expected_bits =
332 stat::SFlag::S_IFDIR.bits() | stat::Mode::S_IRWXU.bits();
333 let tempdir = tempfile::tempdir().unwrap();
334 let filename = "example_subdir";
335 let dirfd =
336 fcntl::open(tempdir.path(), fcntl::OFlag::empty(), stat::Mode::empty())
337 .unwrap();
338 mkdirat(Some(dirfd), filename, Mode::S_IRWXU).expect("mkdirat failed");
339 let permissions = fs::metadata(tempdir.path().join(filename))
340 .unwrap()
341 .permissions();
342 let mode = permissions.mode();
343 assert_eq!(mode as mode_t, expected_bits)
344 }
345
346 #[test]
347 #[cfg(not(target_os = "redox"))]
test_mkdirat_fail()348 fn test_mkdirat_fail() {
349 let tempdir = tempfile::tempdir().unwrap();
350 let not_dir_filename = "example_not_dir";
351 let filename = "example_subdir_dir";
352 let dirfd = fcntl::open(
353 &tempdir.path().join(not_dir_filename),
354 fcntl::OFlag::O_CREAT,
355 stat::Mode::empty(),
356 )
357 .unwrap();
358 let result = mkdirat(Some(dirfd), filename, Mode::S_IRWXU).unwrap_err();
359 assert_eq!(result, Errno::ENOTDIR);
360 }
361
362 #[test]
363 #[cfg(not(any(
364 freebsdlike,
365 apple_targets,
366 target_os = "haiku",
367 target_os = "redox"
368 )))]
test_mknod()369 fn test_mknod() {
370 use stat::{lstat, mknod, SFlag};
371
372 let file_name = "test_file";
373 let tempdir = tempfile::tempdir().unwrap();
374 let target = tempdir.path().join(file_name);
375 mknod(&target, SFlag::S_IFREG, Mode::S_IRWXU, 0).unwrap();
376 let mode = lstat(&target).unwrap().st_mode as mode_t;
377 assert_eq!(mode & libc::S_IFREG, libc::S_IFREG);
378 assert_eq!(mode & libc::S_IRWXU, libc::S_IRWXU);
379 }
380
381 #[test]
382 #[cfg(not(any(
383 solarish,
384 freebsdlike,
385 apple_targets,
386 target_os = "haiku",
387 target_os = "redox"
388 )))]
test_mknodat()389 fn test_mknodat() {
390 use fcntl::{AtFlags, OFlag};
391 use nix::dir::Dir;
392 use stat::{fstatat, mknodat, SFlag};
393
394 let file_name = "test_file";
395 let tempdir = tempfile::tempdir().unwrap();
396 let target_dir =
397 Dir::open(tempdir.path(), OFlag::O_DIRECTORY, Mode::S_IRWXU).unwrap();
398 mknodat(
399 Some(target_dir.as_raw_fd()),
400 file_name,
401 SFlag::S_IFREG,
402 Mode::S_IRWXU,
403 0,
404 )
405 .unwrap();
406 let mode = fstatat(
407 Some(target_dir.as_raw_fd()),
408 file_name,
409 AtFlags::AT_SYMLINK_NOFOLLOW,
410 )
411 .unwrap()
412 .st_mode as mode_t;
413 assert_eq!(mode & libc::S_IFREG, libc::S_IFREG);
414 assert_eq!(mode & libc::S_IRWXU, libc::S_IRWXU);
415 }
416
417 #[test]
418 #[cfg(not(any(target_os = "redox", target_os = "haiku")))]
test_futimens_unchanged()419 fn test_futimens_unchanged() {
420 let tempdir = tempfile::tempdir().unwrap();
421 let fullpath = tempdir.path().join("file");
422 drop(File::create(&fullpath).unwrap());
423 let fd = fcntl::open(&fullpath, fcntl::OFlag::empty(), stat::Mode::empty())
424 .unwrap();
425
426 let old_atime = fs::metadata(fullpath.as_path())
427 .unwrap()
428 .accessed()
429 .unwrap();
430 let old_mtime = fs::metadata(fullpath.as_path())
431 .unwrap()
432 .modified()
433 .unwrap();
434
435 futimens(fd, &TimeSpec::UTIME_OMIT, &TimeSpec::UTIME_OMIT).unwrap();
436
437 let new_atime = fs::metadata(fullpath.as_path())
438 .unwrap()
439 .accessed()
440 .unwrap();
441 let new_mtime = fs::metadata(fullpath.as_path())
442 .unwrap()
443 .modified()
444 .unwrap();
445 assert_eq!(old_atime, new_atime);
446 assert_eq!(old_mtime, new_mtime);
447 }
448
449 #[test]
450 #[cfg(not(any(target_os = "redox", target_os = "haiku")))]
test_utimensat_unchanged()451 fn test_utimensat_unchanged() {
452 let _dr = crate::DirRestore::new();
453 let tempdir = tempfile::tempdir().unwrap();
454 let filename = "foo.txt";
455 let fullpath = tempdir.path().join(filename);
456 drop(File::create(&fullpath).unwrap());
457 let dirfd =
458 fcntl::open(tempdir.path(), fcntl::OFlag::empty(), stat::Mode::empty())
459 .unwrap();
460
461 let old_atime = fs::metadata(fullpath.as_path())
462 .unwrap()
463 .accessed()
464 .unwrap();
465 let old_mtime = fs::metadata(fullpath.as_path())
466 .unwrap()
467 .modified()
468 .unwrap();
469 utimensat(
470 Some(dirfd),
471 filename,
472 &TimeSpec::UTIME_OMIT,
473 &TimeSpec::UTIME_OMIT,
474 UtimensatFlags::NoFollowSymlink,
475 )
476 .unwrap();
477 let new_atime = fs::metadata(fullpath.as_path())
478 .unwrap()
479 .accessed()
480 .unwrap();
481 let new_mtime = fs::metadata(fullpath.as_path())
482 .unwrap()
483 .modified()
484 .unwrap();
485 assert_eq!(old_atime, new_atime);
486 assert_eq!(old_mtime, new_mtime);
487 }
488