1 #[cfg(not(target_os = "redox"))]
2 use std::fs;
3 use std::fs::File;
4 #[cfg(not(target_os = "redox"))]
5 use std::os::unix::fs::{symlink, PermissionsExt};
6 use std::os::unix::prelude::AsRawFd;
7 #[cfg(not(target_os = "redox"))]
8 use std::time::{Duration, UNIX_EPOCH};
9 #[cfg(not(target_os = "redox"))]
10 use std::path::Path;
11 
12 #[cfg(not(any(target_os = "netbsd", target_os = "redox")))]
13 use libc::{S_IFMT, S_IFLNK, mode_t};
14 
15 #[cfg(not(target_os = "redox"))]
16 use nix::{fcntl, Error};
17 #[cfg(not(target_os = "redox"))]
18 use nix::errno::Errno;
19 #[cfg(not(target_os = "redox"))]
20 use nix::sys::stat::{self, futimens, utimes};
21 use nix::sys::stat::{fchmod, stat};
22 #[cfg(not(target_os = "redox"))]
23 use nix::sys::stat::{fchmodat, utimensat, mkdirat};
24 #[cfg(any(target_os = "linux",
25           target_os = "haiku",
26           target_os = "ios",
27           target_os = "macos",
28           target_os = "freebsd",
29           target_os = "netbsd"))]
30 use nix::sys::stat::lutimes;
31 #[cfg(not(target_os = "redox"))]
32 use nix::sys::stat::{FchmodatFlags, UtimensatFlags};
33 use nix::sys::stat::Mode;
34 
35 #[cfg(not(any(target_os = "netbsd", target_os = "redox")))]
36 use nix::sys::stat::FileStat;
37 
38 #[cfg(not(target_os = "redox"))]
39 use nix::sys::time::{TimeSpec, TimeVal, TimeValLike};
40 #[cfg(not(target_os = "redox"))]
41 use nix::unistd::chdir;
42 
43 #[cfg(not(any(target_os = "netbsd", target_os = "redox")))]
44 use nix::Result;
45 use tempfile;
46 
47 #[allow(unused_comparisons)]
48 // uid and gid are signed on Windows, but not on other platforms. This function
49 // allows warning free compiles on all platforms, and can be removed when
50 // expression-level #[allow] is available.
51 #[cfg(not(any(target_os = "netbsd", target_os = "redox")))]
valid_uid_gid(stat: FileStat) -> bool52 fn valid_uid_gid(stat: FileStat) -> bool {
53     // uid could be 0 for the `root` user. This quite possible when
54     // the tests are being run on a rooted Android device.
55     stat.st_uid >= 0 && stat.st_gid >= 0
56 }
57 
58 #[cfg(not(any(target_os = "netbsd", target_os = "redox")))]
assert_stat_results(stat_result: Result<FileStat>)59 fn assert_stat_results(stat_result: Result<FileStat>) {
60     let stats = stat_result.expect("stat call failed");
61     assert!(stats.st_dev > 0);      // must be positive integer, exact number machine dependent
62     assert!(stats.st_ino > 0);      // inode is positive integer, exact number machine dependent
63     assert!(stats.st_mode > 0);     // must be positive integer
64     assert_eq!(stats.st_nlink, 1);   // there links created, must be 1
65     assert!(valid_uid_gid(stats));  // must be positive integers
66     assert_eq!(stats.st_size, 0);    // size is 0 because we did not write anything to the file
67     assert!(stats.st_blksize > 0);  // must be positive integer, exact number machine dependent
68     assert!(stats.st_blocks <= 16);  // Up to 16 blocks can be allocated for a blank file
69 }
70 
71 #[cfg(not(any(target_os = "netbsd", target_os = "redox")))]
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!((stats.st_mode as usize) & (S_IFMT as usize), S_IFLNK as usize); // should be a link
82     assert_eq!(stats.st_nlink, 1);   // there links created, must be 1
83     assert!(valid_uid_gid(stats));  // must be positive integers
84     assert!(stats.st_size > 0);    // size is > 0 because it points to another file
85     assert!(stats.st_blksize > 0);  // must be positive integer, exact number machine dependent
86 
87     // st_blocks depends on whether the machine's file system uses fast
88     // or slow symlinks, so just make sure it's not negative
89     // (Android's st_blocks is ulonglong which is always non-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 = fcntl::open(tempdir.path(),
116                             fcntl::OFlag::empty(),
117                             stat::Mode::empty());
118 
119     let result = stat::fstatat(dirfd.unwrap(),
120                                &filename,
121                                fcntl::AtFlags::empty());
122     assert_stat_results(result);
123 }
124 
125 #[test]
126 #[cfg(not(any(target_os = "netbsd", target_os = "redox")))]
test_stat_fstat_lstat()127 fn test_stat_fstat_lstat() {
128     use nix::sys::stat::{fstat, lstat};
129 
130     let tempdir = tempfile::tempdir().unwrap();
131     let filename = tempdir.path().join("bar.txt");
132     let linkname = tempdir.path().join("barlink");
133 
134     File::create(&filename).unwrap();
135     symlink("bar.txt", &linkname).unwrap();
136     let link = File::open(&linkname).unwrap();
137 
138     // should be the same result as calling stat,
139     // since it's a regular file
140     let stat_result = stat(&filename);
141     assert_stat_results(stat_result);
142 
143     let lstat_result = lstat(&linkname);
144     assert_lstat_results(lstat_result);
145 
146     let fstat_result = fstat(link.as_raw_fd());
147     assert_stat_results(fstat_result);
148 }
149 
150 #[test]
test_fchmod()151 fn test_fchmod() {
152     let tempdir = tempfile::tempdir().unwrap();
153     let filename = tempdir.path().join("foo.txt");
154     let file = File::create(&filename).unwrap();
155 
156     let mut mode1 = Mode::empty();
157     mode1.insert(Mode::S_IRUSR);
158     mode1.insert(Mode::S_IWUSR);
159     fchmod(file.as_raw_fd(), mode1).unwrap();
160 
161     let file_stat1 = stat(&filename).unwrap();
162     assert_eq!(file_stat1.st_mode & 0o7777, mode1.bits());
163 
164     let mut mode2 = Mode::empty();
165     mode2.insert(Mode::S_IROTH);
166     fchmod(file.as_raw_fd(), mode2).unwrap();
167 
168     let file_stat2 = stat(&filename).unwrap();
169     assert_eq!(file_stat2.st_mode & 0o7777, mode2.bits());
170 }
171 
172 #[test]
173 #[cfg(not(target_os = "redox"))]
test_fchmodat()174 fn test_fchmodat() {
175     let _dr = crate::DirRestore::new();
176     let tempdir = tempfile::tempdir().unwrap();
177     let filename = "foo.txt";
178     let fullpath = tempdir.path().join(filename);
179     File::create(&fullpath).unwrap();
180 
181     let dirfd = fcntl::open(tempdir.path(), fcntl::OFlag::empty(), stat::Mode::empty()).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).unwrap();
187 
188     let file_stat1 = stat(&fullpath).unwrap();
189     assert_eq!(file_stat1.st_mode & 0o7777, mode1.bits());
190 
191     chdir(tempdir.path()).unwrap();
192 
193     let mut mode2 = Mode::empty();
194     mode2.insert(Mode::S_IROTH);
195     fchmodat(None, filename, mode2, FchmodatFlags::FollowSymlink).unwrap();
196 
197     let file_stat2 = stat(&fullpath).unwrap();
198     assert_eq!(file_stat2.st_mode & 0o7777, mode2.bits());
199 }
200 
201 /// Asserts that the atime and mtime in a file's metadata match expected values.
202 ///
203 /// The atime and mtime are expressed with a resolution of seconds because some file systems
204 /// (like macOS's HFS+) do not have higher granularity.
205 #[cfg(not(target_os = "redox"))]
assert_times_eq(exp_atime_sec: u64, exp_mtime_sec: u64, attr: &fs::Metadata)206 fn assert_times_eq(exp_atime_sec: u64, exp_mtime_sec: u64, attr: &fs::Metadata) {
207     assert_eq!(
208         Duration::new(exp_atime_sec, 0),
209         attr.accessed().unwrap().duration_since(UNIX_EPOCH).unwrap());
210     assert_eq!(
211         Duration::new(exp_mtime_sec, 0),
212         attr.modified().unwrap().duration_since(UNIX_EPOCH).unwrap());
213 }
214 
215 #[test]
216 #[cfg(not(target_os = "redox"))]
test_utimes()217 fn test_utimes() {
218     let tempdir = tempfile::tempdir().unwrap();
219     let fullpath = tempdir.path().join("file");
220     drop(File::create(&fullpath).unwrap());
221 
222     utimes(&fullpath, &TimeVal::seconds(9990), &TimeVal::seconds(5550)).unwrap();
223     assert_times_eq(9990, 5550, &fs::metadata(&fullpath).unwrap());
224 }
225 
226 #[test]
227 #[cfg(any(target_os = "linux",
228           target_os = "haiku",
229           target_os = "ios",
230           target_os = "macos",
231           target_os = "freebsd",
232           target_os = "netbsd"))]
test_lutimes()233 fn test_lutimes() {
234     let tempdir = tempfile::tempdir().unwrap();
235     let target = tempdir.path().join("target");
236     let fullpath = tempdir.path().join("symlink");
237     drop(File::create(&target).unwrap());
238     symlink(&target, &fullpath).unwrap();
239 
240     let exp_target_metadata = fs::symlink_metadata(&target).unwrap();
241     lutimes(&fullpath, &TimeVal::seconds(4560), &TimeVal::seconds(1230)).unwrap();
242     assert_times_eq(4560, 1230, &fs::symlink_metadata(&fullpath).unwrap());
243 
244     let target_metadata = fs::symlink_metadata(&target).unwrap();
245     assert_eq!(exp_target_metadata.accessed().unwrap(), target_metadata.accessed().unwrap(),
246                "atime of symlink target was unexpectedly modified");
247     assert_eq!(exp_target_metadata.modified().unwrap(), target_metadata.modified().unwrap(),
248                "mtime of symlink target was unexpectedly modified");
249 }
250 
251 #[test]
252 #[cfg(not(target_os = "redox"))]
test_futimens()253 fn test_futimens() {
254     let tempdir = tempfile::tempdir().unwrap();
255     let fullpath = tempdir.path().join("file");
256     drop(File::create(&fullpath).unwrap());
257 
258     let fd = fcntl::open(&fullpath, fcntl::OFlag::empty(), stat::Mode::empty()).unwrap();
259 
260     futimens(fd, &TimeSpec::seconds(10), &TimeSpec::seconds(20)).unwrap();
261     assert_times_eq(10, 20, &fs::metadata(&fullpath).unwrap());
262 }
263 
264 #[test]
265 #[cfg(not(target_os = "redox"))]
test_utimensat()266 fn test_utimensat() {
267     let _dr = crate::DirRestore::new();
268     let tempdir = tempfile::tempdir().unwrap();
269     let filename = "foo.txt";
270     let fullpath = tempdir.path().join(filename);
271     drop(File::create(&fullpath).unwrap());
272 
273     let dirfd = fcntl::open(tempdir.path(), fcntl::OFlag::empty(), stat::Mode::empty()).unwrap();
274 
275     utimensat(Some(dirfd), filename, &TimeSpec::seconds(12345), &TimeSpec::seconds(678),
276               UtimensatFlags::FollowSymlink).unwrap();
277     assert_times_eq(12345, 678, &fs::metadata(&fullpath).unwrap());
278 
279     chdir(tempdir.path()).unwrap();
280 
281     utimensat(None, filename, &TimeSpec::seconds(500), &TimeSpec::seconds(800),
282               UtimensatFlags::FollowSymlink).unwrap();
283     assert_times_eq(500, 800, &fs::metadata(&fullpath).unwrap());
284 }
285 
286 #[test]
287 #[cfg(not(target_os = "redox"))]
test_mkdirat_success_path()288 fn test_mkdirat_success_path() {
289     let tempdir = tempfile::tempdir().unwrap();
290     let filename = "example_subdir";
291     let dirfd = fcntl::open(tempdir.path(), fcntl::OFlag::empty(), stat::Mode::empty()).unwrap();
292     assert!((mkdirat(dirfd, filename, Mode::S_IRWXU)).is_ok());
293     assert!(Path::exists(&tempdir.path().join(filename)));
294 }
295 
296 #[test]
297 #[cfg(not(target_os = "redox"))]
test_mkdirat_success_mode()298 fn test_mkdirat_success_mode() {
299     let expected_bits = stat::SFlag::S_IFDIR.bits() | stat::Mode::S_IRWXU.bits();
300     let tempdir = tempfile::tempdir().unwrap();
301     let filename = "example_subdir";
302     let dirfd = fcntl::open(tempdir.path(), fcntl::OFlag::empty(), stat::Mode::empty()).unwrap();
303     assert!((mkdirat(dirfd, filename, Mode::S_IRWXU)).is_ok());
304     let permissions = fs::metadata(tempdir.path().join(filename)).unwrap().permissions();
305     let mode = permissions.mode();
306     assert_eq!(mode as mode_t, expected_bits)
307 }
308 
309 #[test]
310 #[cfg(not(target_os = "redox"))]
test_mkdirat_fail()311 fn test_mkdirat_fail() {
312     let tempdir = tempfile::tempdir().unwrap();
313     let not_dir_filename= "example_not_dir";
314     let filename = "example_subdir_dir";
315     let dirfd = fcntl::open(&tempdir.path().join(not_dir_filename), fcntl::OFlag::O_CREAT,
316                             stat::Mode::empty()).unwrap();
317     let result = mkdirat(dirfd, filename, Mode::S_IRWXU).unwrap_err();
318     assert_eq!(result, Error::Sys(Errno::ENOTDIR));
319 }
320