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