| Andrew Walbran | 12f6140 | 2020-10-14 11:10:53 +0100 | [diff] [blame] | 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")))] |
| Joel Galenson | e7950d9 | 2021-06-21 14:41:02 -0700 | [diff] [blame] | 13 | use libc::{S_IFMT, S_IFLNK}; |
| 14 | #[cfg(not(target_os = "redox"))] |
| 15 | use libc::mode_t; |
| Andrew Walbran | 12f6140 | 2020-10-14 11:10:53 +0100 | [diff] [blame] | 16 | |
| 17 | #[cfg(not(target_os = "redox"))] |
| Joel Galenson | 981b40a | 2021-08-09 10:34:35 -0700 | [diff] [blame] | 18 | use nix::fcntl; |
| Andrew Walbran | 12f6140 | 2020-10-14 11:10:53 +0100 | [diff] [blame] | 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; |
| Andrew Walbran | 12f6140 | 2020-10-14 11:10:53 +0100 | [diff] [blame] | 47 | |
| 48 | #[cfg(not(any(target_os = "netbsd", target_os = "redox")))] |
| 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 |
| Andrew Walbran | 12f6140 | 2020-10-14 11:10:53 +0100 | [diff] [blame] | 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")))] |
| Joel Galenson | e7950d9 | 2021-06-21 14:41:02 -0700 | [diff] [blame] | 61 | // (Android's st_blocks is ulonglong which is always non-negative.) |
| 62 | #[cfg_attr(target_os = "android", allow(unused_comparisons))] |
| Andrew Walbran | 12f6140 | 2020-10-14 11:10:53 +0100 | [diff] [blame] | 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 |
| Andrew Walbran | 12f6140 | 2020-10-14 11:10:53 +0100 | [diff] [blame] | 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 |
| Andrew Walbran | 12f6140 | 2020-10-14 11:10:53 +0100 | [diff] [blame] | 79 | assert!(stats.st_blocks >= 0); |
| 80 | } |
| 81 | |
| 82 | #[test] |
| 83 | #[cfg(not(any(target_os = "netbsd", target_os = "redox")))] |
| 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")))] |
| 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")))] |
| 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] |
| 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(); |
| Joel Galenson | e7950d9 | 2021-06-21 14:41:02 -0700 | [diff] [blame] | 151 | assert_eq!(file_stat1.st_mode as mode_t & 0o7777, mode1.bits()); |
| Andrew Walbran | 12f6140 | 2020-10-14 11:10:53 +0100 | [diff] [blame] | 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(); |
| Joel Galenson | e7950d9 | 2021-06-21 14:41:02 -0700 | [diff] [blame] | 158 | assert_eq!(file_stat2.st_mode as mode_t & 0o7777, mode2.bits()); |
| Andrew Walbran | 12f6140 | 2020-10-14 11:10:53 +0100 | [diff] [blame] | 159 | } |
| 160 | |
| 161 | #[test] |
| 162 | #[cfg(not(target_os = "redox"))] |
| 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(); |
| Joel Galenson | e7950d9 | 2021-06-21 14:41:02 -0700 | [diff] [blame] | 178 | assert_eq!(file_stat1.st_mode as mode_t & 0o7777, mode1.bits()); |
| Andrew Walbran | 12f6140 | 2020-10-14 11:10:53 +0100 | [diff] [blame] | 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(); |
| Joel Galenson | e7950d9 | 2021-06-21 14:41:02 -0700 | [diff] [blame] | 187 | assert_eq!(file_stat2.st_mode as mode_t & 0o7777, mode2.bits()); |
| Andrew Walbran | 12f6140 | 2020-10-14 11:10:53 +0100 | [diff] [blame] | 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"))] |
| 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"))] |
| 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"))] |
| 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"))] |
| 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"))] |
| 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"))] |
| 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"))] |
| 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"))] |
| 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(); |
| Joel Galenson | 981b40a | 2021-08-09 10:34:35 -0700 | [diff] [blame] | 307 | assert_eq!(result, Errno::ENOTDIR); |
| Andrew Walbran | 12f6140 | 2020-10-14 11:10:53 +0100 | [diff] [blame] | 308 | } |