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