blob: 424371fa82146edd55300f6403bde0ff7e704e46 [file] [log] [blame]
Andrew Walbran12f61402020-10-14 11:10:53 +01001#[cfg(not(target_os = "redox"))]
2use std::fs;
3use std::fs::File;
4#[cfg(not(target_os = "redox"))]
5use std::os::unix::fs::{symlink, PermissionsExt};
6use std::os::unix::prelude::AsRawFd;
7#[cfg(not(target_os = "redox"))]
8use std::time::{Duration, UNIX_EPOCH};
9#[cfg(not(target_os = "redox"))]
10use std::path::Path;
11
12#[cfg(not(any(target_os = "netbsd", target_os = "redox")))]
Joel Galensone7950d92021-06-21 14:41:02 -070013use libc::{S_IFMT, S_IFLNK};
14#[cfg(not(target_os = "redox"))]
15use libc::mode_t;
Andrew Walbran12f61402020-10-14 11:10:53 +010016
17#[cfg(not(target_os = "redox"))]
Joel Galenson981b40a2021-08-09 10:34:35 -070018use nix::fcntl;
Andrew Walbran12f61402020-10-14 11:10:53 +010019#[cfg(not(target_os = "redox"))]
20use nix::errno::Errno;
21#[cfg(not(target_os = "redox"))]
22use nix::sys::stat::{self, futimens, utimes};
23use nix::sys::stat::{fchmod, stat};
24#[cfg(not(target_os = "redox"))]
25use nix::sys::stat::{fchmodat, utimensat, mkdirat};
26#[cfg(any(target_os = "linux",
27 target_os = "haiku",
28 target_os = "ios",
29 target_os = "macos",
30 target_os = "freebsd",
31 target_os = "netbsd"))]
32use nix::sys::stat::lutimes;
33#[cfg(not(target_os = "redox"))]
34use nix::sys::stat::{FchmodatFlags, UtimensatFlags};
35use nix::sys::stat::Mode;
36
37#[cfg(not(any(target_os = "netbsd", target_os = "redox")))]
38use nix::sys::stat::FileStat;
39
40#[cfg(not(target_os = "redox"))]
41use nix::sys::time::{TimeSpec, TimeVal, TimeValLike};
42#[cfg(not(target_os = "redox"))]
43use nix::unistd::chdir;
44
45#[cfg(not(any(target_os = "netbsd", target_os = "redox")))]
46use nix::Result;
Andrew Walbran12f61402020-10-14 11:10:53 +010047
48#[cfg(not(any(target_os = "netbsd", target_os = "redox")))]
49fn assert_stat_results(stat_result: Result<FileStat>) {
50 let stats = stat_result.expect("stat call failed");
51 assert!(stats.st_dev > 0); // must be positive integer, exact number machine dependent
52 assert!(stats.st_ino > 0); // inode is positive integer, exact number machine dependent
53 assert!(stats.st_mode > 0); // must be positive integer
54 assert_eq!(stats.st_nlink, 1); // there links created, must be 1
Andrew Walbran12f61402020-10-14 11:10:53 +010055 assert_eq!(stats.st_size, 0); // size is 0 because we did not write anything to the file
56 assert!(stats.st_blksize > 0); // must be positive integer, exact number machine dependent
57 assert!(stats.st_blocks <= 16); // Up to 16 blocks can be allocated for a blank file
58}
59
60#[cfg(not(any(target_os = "netbsd", target_os = "redox")))]
Joel Galensone7950d92021-06-21 14:41:02 -070061// (Android's st_blocks is ulonglong which is always non-negative.)
62#[cfg_attr(target_os = "android", allow(unused_comparisons))]
Andrew Walbran12f61402020-10-14 11:10:53 +010063fn assert_lstat_results(stat_result: Result<FileStat>) {
64 let stats = stat_result.expect("stat call failed");
65 assert!(stats.st_dev > 0); // must be positive integer, exact number machine dependent
66 assert!(stats.st_ino > 0); // inode is positive integer, exact number machine dependent
67 assert!(stats.st_mode > 0); // must be positive integer
68
69 // st_mode is c_uint (u32 on Android) while S_IFMT is mode_t
70 // (u16 on Android), and that will be a compile error.
71 // On other platforms they are the same (either both are u16 or u32).
72 assert_eq!((stats.st_mode as usize) & (S_IFMT as usize), S_IFLNK as usize); // should be a link
73 assert_eq!(stats.st_nlink, 1); // there links created, must be 1
Andrew Walbran12f61402020-10-14 11:10:53 +010074 assert!(stats.st_size > 0); // size is > 0 because it points to another file
75 assert!(stats.st_blksize > 0); // must be positive integer, exact number machine dependent
76
77 // st_blocks depends on whether the machine's file system uses fast
78 // or slow symlinks, so just make sure it's not negative
Andrew Walbran12f61402020-10-14 11:10:53 +010079 assert!(stats.st_blocks >= 0);
80}
81
82#[test]
83#[cfg(not(any(target_os = "netbsd", target_os = "redox")))]
84fn test_stat_and_fstat() {
85 use nix::sys::stat::fstat;
86
87 let tempdir = tempfile::tempdir().unwrap();
88 let filename = tempdir.path().join("foo.txt");
89 let file = File::create(&filename).unwrap();
90
91 let stat_result = stat(&filename);
92 assert_stat_results(stat_result);
93
94 let fstat_result = fstat(file.as_raw_fd());
95 assert_stat_results(fstat_result);
96}
97
98#[test]
99#[cfg(not(any(target_os = "netbsd", target_os = "redox")))]
100fn test_fstatat() {
101 let tempdir = tempfile::tempdir().unwrap();
102 let filename = tempdir.path().join("foo.txt");
103 File::create(&filename).unwrap();
104 let dirfd = fcntl::open(tempdir.path(),
105 fcntl::OFlag::empty(),
106 stat::Mode::empty());
107
108 let result = stat::fstatat(dirfd.unwrap(),
109 &filename,
110 fcntl::AtFlags::empty());
111 assert_stat_results(result);
112}
113
114#[test]
115#[cfg(not(any(target_os = "netbsd", target_os = "redox")))]
116fn test_stat_fstat_lstat() {
117 use nix::sys::stat::{fstat, lstat};
118
119 let tempdir = tempfile::tempdir().unwrap();
120 let filename = tempdir.path().join("bar.txt");
121 let linkname = tempdir.path().join("barlink");
122
123 File::create(&filename).unwrap();
124 symlink("bar.txt", &linkname).unwrap();
125 let link = File::open(&linkname).unwrap();
126
127 // should be the same result as calling stat,
128 // since it's a regular file
129 let stat_result = stat(&filename);
130 assert_stat_results(stat_result);
131
132 let lstat_result = lstat(&linkname);
133 assert_lstat_results(lstat_result);
134
135 let fstat_result = fstat(link.as_raw_fd());
136 assert_stat_results(fstat_result);
137}
138
139#[test]
140fn test_fchmod() {
141 let tempdir = tempfile::tempdir().unwrap();
142 let filename = tempdir.path().join("foo.txt");
143 let file = File::create(&filename).unwrap();
144
145 let mut mode1 = Mode::empty();
146 mode1.insert(Mode::S_IRUSR);
147 mode1.insert(Mode::S_IWUSR);
148 fchmod(file.as_raw_fd(), mode1).unwrap();
149
150 let file_stat1 = stat(&filename).unwrap();
Joel Galensone7950d92021-06-21 14:41:02 -0700151 assert_eq!(file_stat1.st_mode as mode_t & 0o7777, mode1.bits());
Andrew Walbran12f61402020-10-14 11:10:53 +0100152
153 let mut mode2 = Mode::empty();
154 mode2.insert(Mode::S_IROTH);
155 fchmod(file.as_raw_fd(), mode2).unwrap();
156
157 let file_stat2 = stat(&filename).unwrap();
Joel Galensone7950d92021-06-21 14:41:02 -0700158 assert_eq!(file_stat2.st_mode as mode_t & 0o7777, mode2.bits());
Andrew Walbran12f61402020-10-14 11:10:53 +0100159}
160
161#[test]
162#[cfg(not(target_os = "redox"))]
163fn test_fchmodat() {
164 let _dr = crate::DirRestore::new();
165 let tempdir = tempfile::tempdir().unwrap();
166 let filename = "foo.txt";
167 let fullpath = tempdir.path().join(filename);
168 File::create(&fullpath).unwrap();
169
170 let dirfd = fcntl::open(tempdir.path(), fcntl::OFlag::empty(), stat::Mode::empty()).unwrap();
171
172 let mut mode1 = Mode::empty();
173 mode1.insert(Mode::S_IRUSR);
174 mode1.insert(Mode::S_IWUSR);
175 fchmodat(Some(dirfd), filename, mode1, FchmodatFlags::FollowSymlink).unwrap();
176
177 let file_stat1 = stat(&fullpath).unwrap();
Joel Galensone7950d92021-06-21 14:41:02 -0700178 assert_eq!(file_stat1.st_mode as mode_t & 0o7777, mode1.bits());
Andrew Walbran12f61402020-10-14 11:10:53 +0100179
180 chdir(tempdir.path()).unwrap();
181
182 let mut mode2 = Mode::empty();
183 mode2.insert(Mode::S_IROTH);
184 fchmodat(None, filename, mode2, FchmodatFlags::FollowSymlink).unwrap();
185
186 let file_stat2 = stat(&fullpath).unwrap();
Joel Galensone7950d92021-06-21 14:41:02 -0700187 assert_eq!(file_stat2.st_mode as mode_t & 0o7777, mode2.bits());
Andrew Walbran12f61402020-10-14 11:10:53 +0100188}
189
190/// Asserts that the atime and mtime in a file's metadata match expected values.
191///
192/// The atime and mtime are expressed with a resolution of seconds because some file systems
193/// (like macOS's HFS+) do not have higher granularity.
194#[cfg(not(target_os = "redox"))]
195fn assert_times_eq(exp_atime_sec: u64, exp_mtime_sec: u64, attr: &fs::Metadata) {
196 assert_eq!(
197 Duration::new(exp_atime_sec, 0),
198 attr.accessed().unwrap().duration_since(UNIX_EPOCH).unwrap());
199 assert_eq!(
200 Duration::new(exp_mtime_sec, 0),
201 attr.modified().unwrap().duration_since(UNIX_EPOCH).unwrap());
202}
203
204#[test]
205#[cfg(not(target_os = "redox"))]
206fn test_utimes() {
207 let tempdir = tempfile::tempdir().unwrap();
208 let fullpath = tempdir.path().join("file");
209 drop(File::create(&fullpath).unwrap());
210
211 utimes(&fullpath, &TimeVal::seconds(9990), &TimeVal::seconds(5550)).unwrap();
212 assert_times_eq(9990, 5550, &fs::metadata(&fullpath).unwrap());
213}
214
215#[test]
216#[cfg(any(target_os = "linux",
217 target_os = "haiku",
218 target_os = "ios",
219 target_os = "macos",
220 target_os = "freebsd",
221 target_os = "netbsd"))]
222fn test_lutimes() {
223 let tempdir = tempfile::tempdir().unwrap();
224 let target = tempdir.path().join("target");
225 let fullpath = tempdir.path().join("symlink");
226 drop(File::create(&target).unwrap());
227 symlink(&target, &fullpath).unwrap();
228
229 let exp_target_metadata = fs::symlink_metadata(&target).unwrap();
230 lutimes(&fullpath, &TimeVal::seconds(4560), &TimeVal::seconds(1230)).unwrap();
231 assert_times_eq(4560, 1230, &fs::symlink_metadata(&fullpath).unwrap());
232
233 let target_metadata = fs::symlink_metadata(&target).unwrap();
234 assert_eq!(exp_target_metadata.accessed().unwrap(), target_metadata.accessed().unwrap(),
235 "atime of symlink target was unexpectedly modified");
236 assert_eq!(exp_target_metadata.modified().unwrap(), target_metadata.modified().unwrap(),
237 "mtime of symlink target was unexpectedly modified");
238}
239
240#[test]
241#[cfg(not(target_os = "redox"))]
242fn test_futimens() {
243 let tempdir = tempfile::tempdir().unwrap();
244 let fullpath = tempdir.path().join("file");
245 drop(File::create(&fullpath).unwrap());
246
247 let fd = fcntl::open(&fullpath, fcntl::OFlag::empty(), stat::Mode::empty()).unwrap();
248
249 futimens(fd, &TimeSpec::seconds(10), &TimeSpec::seconds(20)).unwrap();
250 assert_times_eq(10, 20, &fs::metadata(&fullpath).unwrap());
251}
252
253#[test]
254#[cfg(not(target_os = "redox"))]
255fn test_utimensat() {
256 let _dr = crate::DirRestore::new();
257 let tempdir = tempfile::tempdir().unwrap();
258 let filename = "foo.txt";
259 let fullpath = tempdir.path().join(filename);
260 drop(File::create(&fullpath).unwrap());
261
262 let dirfd = fcntl::open(tempdir.path(), fcntl::OFlag::empty(), stat::Mode::empty()).unwrap();
263
264 utimensat(Some(dirfd), filename, &TimeSpec::seconds(12345), &TimeSpec::seconds(678),
265 UtimensatFlags::FollowSymlink).unwrap();
266 assert_times_eq(12345, 678, &fs::metadata(&fullpath).unwrap());
267
268 chdir(tempdir.path()).unwrap();
269
270 utimensat(None, filename, &TimeSpec::seconds(500), &TimeSpec::seconds(800),
271 UtimensatFlags::FollowSymlink).unwrap();
272 assert_times_eq(500, 800, &fs::metadata(&fullpath).unwrap());
273}
274
275#[test]
276#[cfg(not(target_os = "redox"))]
277fn test_mkdirat_success_path() {
278 let tempdir = tempfile::tempdir().unwrap();
279 let filename = "example_subdir";
280 let dirfd = fcntl::open(tempdir.path(), fcntl::OFlag::empty(), stat::Mode::empty()).unwrap();
281 assert!((mkdirat(dirfd, filename, Mode::S_IRWXU)).is_ok());
282 assert!(Path::exists(&tempdir.path().join(filename)));
283}
284
285#[test]
286#[cfg(not(target_os = "redox"))]
287fn test_mkdirat_success_mode() {
288 let expected_bits = stat::SFlag::S_IFDIR.bits() | stat::Mode::S_IRWXU.bits();
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 let permissions = fs::metadata(tempdir.path().join(filename)).unwrap().permissions();
294 let mode = permissions.mode();
295 assert_eq!(mode as mode_t, expected_bits)
296}
297
298#[test]
299#[cfg(not(target_os = "redox"))]
300fn test_mkdirat_fail() {
301 let tempdir = tempfile::tempdir().unwrap();
302 let not_dir_filename= "example_not_dir";
303 let filename = "example_subdir_dir";
304 let dirfd = fcntl::open(&tempdir.path().join(not_dir_filename), fcntl::OFlag::O_CREAT,
305 stat::Mode::empty()).unwrap();
306 let result = mkdirat(dirfd, filename, Mode::S_IRWXU).unwrap_err();
Joel Galenson981b40a2021-08-09 10:34:35 -0700307 assert_eq!(result, Errno::ENOTDIR);
Andrew Walbran12f61402020-10-14 11:10:53 +0100308}