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