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};
14 #[cfg(not(target_os = "redox"))]
15 use libc::mode_t;
16 
17 #[cfg(not(target_os = "redox"))]
18 use nix::{fcntl, Error};
19 #[cfg(not(target_os = "redox"))]
20 use nix::errno::Errno;
21 #[cfg(not(target_os = "redox"))]
22 use nix::sys::stat::{self, futimens, utimes};
23 use nix::sys::stat::{fchmod, stat};
24 #[cfg(not(target_os = "redox"))]
25 use 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"))]
32 use nix::sys::stat::lutimes;
33 #[cfg(not(target_os = "redox"))]
34 use nix::sys::stat::{FchmodatFlags, UtimensatFlags};
35 use nix::sys::stat::Mode;
36 
37 #[cfg(not(any(target_os = "netbsd", target_os = "redox")))]
38 use nix::sys::stat::FileStat;
39 
40 #[cfg(not(target_os = "redox"))]
41 use nix::sys::time::{TimeSpec, TimeVal, TimeValLike};
42 #[cfg(not(target_os = "redox"))]
43 use nix::unistd::chdir;
44 
45 #[cfg(not(any(target_os = "netbsd", target_os = "redox")))]
46 use nix::Result;
47 
48 #[cfg(not(any(target_os = "netbsd", target_os = "redox")))]
assert_stat_results(stat_result: Result<FileStat>)49 fn 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
55     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")))]
61 // (Android's st_blocks is ulonglong which is always non-negative.)
62 #[cfg_attr(target_os = "android", allow(unused_comparisons))]
assert_lstat_results(stat_result: Result<FileStat>)63 fn 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
74     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
79     assert!(stats.st_blocks >= 0);
80 }
81 
82 #[test]
83 #[cfg(not(any(target_os = "netbsd", target_os = "redox")))]
test_stat_and_fstat()84 fn 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")))]
test_fstatat()100 fn 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")))]
test_stat_fstat_lstat()116 fn 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]
test_fchmod()140 fn 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();
151     assert_eq!(file_stat1.st_mode as mode_t & 0o7777, mode1.bits());
152 
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();
158     assert_eq!(file_stat2.st_mode as mode_t & 0o7777, mode2.bits());
159 }
160 
161 #[test]
162 #[cfg(not(target_os = "redox"))]
test_fchmodat()163 fn 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();
178     assert_eq!(file_stat1.st_mode as mode_t & 0o7777, mode1.bits());
179 
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();
187     assert_eq!(file_stat2.st_mode as mode_t & 0o7777, mode2.bits());
188 }
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"))]
assert_times_eq(exp_atime_sec: u64, exp_mtime_sec: u64, attr: &fs::Metadata)195 fn 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"))]
test_utimes()206 fn 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"))]
test_lutimes()222 fn 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"))]
test_futimens()242 fn 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"))]
test_utimensat()255 fn 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"))]
test_mkdirat_success_path()277 fn 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"))]
test_mkdirat_success_mode()287 fn 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"))]
test_mkdirat_fail()300 fn 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();
307     assert_eq!(result, Error::Sys(Errno::ENOTDIR));
308 }
309