xref: /third_party/rust/crates/nix/test/test_stat.rs (revision 3da5c369)
1#[cfg(not(any(target_os = "redox", target_os = "haiku")))]
2use std::fs;
3use std::fs::File;
4#[cfg(not(target_os = "redox"))]
5use std::os::unix::fs::symlink;
6#[cfg(not(any(target_os = "redox", target_os = "haiku")))]
7use std::os::unix::fs::PermissionsExt;
8use std::os::unix::prelude::AsRawFd;
9#[cfg(not(target_os = "redox"))]
10use std::path::Path;
11#[cfg(not(any(target_os = "redox", target_os = "haiku")))]
12use std::time::{Duration, UNIX_EPOCH};
13
14use libc::mode_t;
15#[cfg(not(any(target_os = "netbsd", target_os = "redox")))]
16use libc::{S_IFLNK, S_IFMT};
17
18#[cfg(not(target_os = "redox"))]
19use nix::errno::Errno;
20#[cfg(not(target_os = "redox"))]
21use 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))]
29use nix::sys::stat::lutimes;
30#[cfg(not(any(target_os = "redox", target_os = "haiku")))]
31use nix::sys::stat::utimensat;
32#[cfg(not(target_os = "redox"))]
33use nix::sys::stat::FchmodatFlags;
34use nix::sys::stat::Mode;
35#[cfg(not(any(target_os = "redox", target_os = "haiku")))]
36use nix::sys::stat::UtimensatFlags;
37#[cfg(not(target_os = "redox"))]
38use nix::sys::stat::{self};
39use nix::sys::stat::{fchmod, stat};
40#[cfg(not(target_os = "redox"))]
41use nix::sys::stat::{fchmodat, mkdirat};
42#[cfg(not(any(target_os = "redox", target_os = "haiku")))]
43use nix::sys::stat::{futimens, utimes};
44
45#[cfg(not(any(target_os = "netbsd", target_os = "redox")))]
46use nix::sys::stat::FileStat;
47
48#[cfg(not(any(target_os = "redox", target_os = "haiku")))]
49use nix::sys::time::{TimeSpec, TimeVal, TimeValLike};
50#[cfg(not(target_os = "redox"))]
51use nix::unistd::chdir;
52
53#[cfg(not(any(target_os = "netbsd", target_os = "redox")))]
54use nix::Result;
55
56#[cfg(not(any(target_os = "netbsd", target_os = "redox")))]
57fn 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
72fn 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")))]
96fn 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")))]
112fn 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")))]
126fn 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]
150fn 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"))]
173fn 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")))]
208fn 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")))]
225fn 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))]
243fn 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")))]
270fn 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")))]
284fn 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"))]
320fn 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")))]
332fn 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"))]
350fn 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)))]
373fn 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)))]
395fn 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