1 use std::fs::File;
2 use std::os::unix::fs::symlink;
3 use std::os::unix::prelude::AsRawFd;
4 
5 use libc::{S_IFMT, S_IFLNK};
6 
7 use nix::fcntl;
8 use nix::sys::stat::{self, fchmod, fchmodat, fstat, lstat, stat};
9 use nix::sys::stat::{FileStat, Mode, FchmodatFlags};
10 use nix::unistd::chdir;
11 use nix::Result;
12 use tempdir::TempDir;
13 
14 #[allow(unused_comparisons)]
15 // uid and gid are signed on Windows, but not on other platforms. This function
16 // allows warning free compiles on all platforms, and can be removed when
17 // expression-level #[allow] is available.
valid_uid_gid(stat: FileStat) -> bool18 fn valid_uid_gid(stat: FileStat) -> bool {
19     // uid could be 0 for the `root` user. This quite possible when
20     // the tests are being run on a rooted Android device.
21     stat.st_uid >= 0 && stat.st_gid >= 0
22 }
23 
assert_stat_results(stat_result: Result<FileStat>)24 fn assert_stat_results(stat_result: Result<FileStat>) {
25     let stats = stat_result.expect("stat call failed");
26     assert!(stats.st_dev > 0);      // must be positive integer, exact number machine dependent
27     assert!(stats.st_ino > 0);      // inode is positive integer, exact number machine dependent
28     assert!(stats.st_mode > 0);     // must be positive integer
29     assert!(stats.st_nlink == 1);   // there links created, must be 1
30     assert!(valid_uid_gid(stats));  // must be positive integers
31     assert!(stats.st_size == 0);    // size is 0 because we did not write anything to the file
32     assert!(stats.st_blksize > 0);  // must be positive integer, exact number machine dependent
33     assert!(stats.st_blocks <= 16);  // Up to 16 blocks can be allocated for a blank file
34 }
35 
assert_lstat_results(stat_result: Result<FileStat>)36 fn assert_lstat_results(stat_result: Result<FileStat>) {
37     let stats = stat_result.expect("stat call failed");
38     assert!(stats.st_dev > 0);      // must be positive integer, exact number machine dependent
39     assert!(stats.st_ino > 0);      // inode is positive integer, exact number machine dependent
40     assert!(stats.st_mode > 0);     // must be positive integer
41 
42     // st_mode is c_uint (u32 on Android) while S_IFMT is mode_t
43     // (u16 on Android), and that will be a compile error.
44     // On other platforms they are the same (either both are u16 or u32).
45     assert!((stats.st_mode as usize) & (S_IFMT as usize) == S_IFLNK as usize); // should be a link
46     assert!(stats.st_nlink == 1);   // there links created, must be 1
47     assert!(valid_uid_gid(stats));  // must be positive integers
48     assert!(stats.st_size > 0);    // size is > 0 because it points to another file
49     assert!(stats.st_blksize > 0);  // must be positive integer, exact number machine dependent
50 
51     // st_blocks depends on whether the machine's file system uses fast
52     // or slow symlinks, so just make sure it's not negative
53     // (Android's st_blocks is ulonglong which is always non-negative.)
54     assert!(stats.st_blocks >= 0);
55 }
56 
57 #[test]
test_stat_and_fstat()58 fn test_stat_and_fstat() {
59     let tempdir = TempDir::new("nix-test_stat_and_fstat").unwrap();
60     let filename = tempdir.path().join("foo.txt");
61     let file = File::create(&filename).unwrap();
62 
63     let stat_result = stat(&filename);
64     assert_stat_results(stat_result);
65 
66     let fstat_result = fstat(file.as_raw_fd());
67     assert_stat_results(fstat_result);
68 }
69 
70 #[test]
test_fstatat()71 fn test_fstatat() {
72     let tempdir = TempDir::new("nix-test_fstatat").unwrap();
73     let filename = tempdir.path().join("foo.txt");
74     File::create(&filename).unwrap();
75     let dirfd = fcntl::open(tempdir.path(),
76                             fcntl::OFlag::empty(),
77                             stat::Mode::empty());
78 
79     let result = stat::fstatat(dirfd.unwrap(),
80                                &filename,
81                                fcntl::AtFlags::empty());
82     assert_stat_results(result);
83 }
84 
85 #[test]
test_stat_fstat_lstat()86 fn test_stat_fstat_lstat() {
87     let tempdir = TempDir::new("nix-test_stat_fstat_lstat").unwrap();
88     let filename = tempdir.path().join("bar.txt");
89     let linkname = tempdir.path().join("barlink");
90 
91     File::create(&filename).unwrap();
92     symlink("bar.txt", &linkname).unwrap();
93     let link = File::open(&linkname).unwrap();
94 
95     // should be the same result as calling stat,
96     // since it's a regular file
97     let stat_result = stat(&filename);
98     assert_stat_results(stat_result);
99 
100     let lstat_result = lstat(&linkname);
101     assert_lstat_results(lstat_result);
102 
103     let fstat_result = fstat(link.as_raw_fd());
104     assert_stat_results(fstat_result);
105 }
106 
107 #[test]
test_fchmod()108 fn test_fchmod() {
109     let tempdir = TempDir::new("nix-test_fchmod").unwrap();
110     let filename = tempdir.path().join("foo.txt");
111     let file = File::create(&filename).unwrap();
112 
113     let mut mode1 = Mode::empty();
114     mode1.insert(Mode::S_IRUSR);
115     mode1.insert(Mode::S_IWUSR);
116     fchmod(file.as_raw_fd(), mode1).unwrap();
117 
118     let file_stat1 = stat(&filename).unwrap();
119     assert_eq!(file_stat1.st_mode & 0o7777, mode1.bits());
120 
121     let mut mode2 = Mode::empty();
122     mode2.insert(Mode::S_IROTH);
123     fchmod(file.as_raw_fd(), mode2).unwrap();
124 
125     let file_stat2 = stat(&filename).unwrap();
126     assert_eq!(file_stat2.st_mode & 0o7777, mode2.bits());
127 }
128 
129 #[test]
test_fchmodat()130 fn test_fchmodat() {
131     let tempdir = TempDir::new("nix-test_fchmodat").unwrap();
132     let filename = "foo.txt";
133     let fullpath = tempdir.path().join(filename);
134     File::create(&fullpath).unwrap();
135 
136     let dirfd = fcntl::open(tempdir.path(), fcntl::OFlag::empty(), stat::Mode::empty()).unwrap();
137 
138     let mut mode1 = Mode::empty();
139     mode1.insert(Mode::S_IRUSR);
140     mode1.insert(Mode::S_IWUSR);
141     fchmodat(Some(dirfd), filename, mode1, FchmodatFlags::FollowSymlink).unwrap();
142 
143     let file_stat1 = stat(&fullpath).unwrap();
144     assert_eq!(file_stat1.st_mode & 0o7777, mode1.bits());
145 
146     chdir(tempdir.path()).unwrap();
147 
148     let mut mode2 = Mode::empty();
149     mode2.insert(Mode::S_IROTH);
150     fchmodat(None, filename, mode2, FchmodatFlags::FollowSymlink).unwrap();
151 
152     let file_stat2 = stat(&fullpath).unwrap();
153     assert_eq!(file_stat2.st_mode & 0o7777, mode2.bits());
154 }
155