1#[cfg(not(any(target_os = "redox", target_os = "haiku")))] 2use std::fs; 3use std::fs::File; 4#[cfg(not(target_os = "redox"))] 5use std::os::unix::fs::symlink; 6#[cfg(not(any(target_os = "redox", target_os = "haiku")))] 7use std::os::unix::fs::PermissionsExt; 8use std::os::unix::prelude::AsRawFd; 9#[cfg(not(target_os = "redox"))] 10use std::path::Path; 11#[cfg(not(any(target_os = "redox", target_os = "haiku")))] 12use std::time::{Duration, UNIX_EPOCH}; 13 14use libc::mode_t; 15#[cfg(not(any(target_os = "netbsd", target_os = "redox")))] 16use libc::{S_IFLNK, S_IFMT}; 17 18#[cfg(not(target_os = "redox"))] 19use nix::errno::Errno; 20#[cfg(not(target_os = "redox"))] 21use nix::fcntl; 22#[cfg(any( 23 target_os = "linux", 24 target_os = "ios", 25 target_os = "macos", 26 target_os = "freebsd", 27 target_os = "netbsd" 28))] 29use nix::sys::stat::lutimes; 30#[cfg(not(any(target_os = "redox", target_os = "haiku")))] 31use nix::sys::stat::utimensat; 32#[cfg(not(target_os = "redox"))] 33use nix::sys::stat::FchmodatFlags; 34use nix::sys::stat::Mode; 35#[cfg(not(any(target_os = "redox", target_os = "haiku")))] 36use nix::sys::stat::UtimensatFlags; 37#[cfg(not(target_os = "redox"))] 38use nix::sys::stat::{self}; 39use nix::sys::stat::{fchmod, stat}; 40#[cfg(not(target_os = "redox"))] 41use nix::sys::stat::{fchmodat, mkdirat}; 42#[cfg(not(any(target_os = "redox", target_os = "haiku")))] 43use nix::sys::stat::{futimens, utimes}; 44 45#[cfg(not(any(target_os = "netbsd", target_os = "redox")))] 46use nix::sys::stat::FileStat; 47 48#[cfg(not(any(target_os = "redox", target_os = "haiku")))] 49use nix::sys::time::{TimeSpec, TimeVal, TimeValLike}; 50#[cfg(not(target_os = "redox"))] 51use nix::unistd::chdir; 52 53#[cfg(not(any(target_os = "netbsd", target_os = "redox")))] 54use nix::Result; 55 56#[cfg(not(any(target_os = "netbsd", target_os = "redox")))] 57fn assert_stat_results(stat_result: Result<FileStat>) { 58 let stats = stat_result.expect("stat call failed"); 59 assert!(stats.st_dev > 0); // must be positive integer, exact number machine dependent 60 assert!(stats.st_ino > 0); // inode is positive integer, exact number machine dependent 61 assert!(stats.st_mode > 0); // must be positive integer 62 assert_eq!(stats.st_nlink, 1); // there links created, must be 1 63 assert_eq!(stats.st_size, 0); // size is 0 because we did not write anything to the file 64 assert!(stats.st_blksize > 0); // must be positive integer, exact number machine dependent 65 assert!(stats.st_blocks <= 16); // Up to 16 blocks can be allocated for a blank file 66} 67 68#[cfg(not(any(target_os = "netbsd", target_os = "redox")))] 69// (Android's st_blocks is ulonglong which is always non-negative.) 70#[cfg_attr(target_os = "android", allow(unused_comparisons))] 71#[allow(clippy::absurd_extreme_comparisons)] // Not absurd on all OSes 72fn 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!( 82 (stats.st_mode as usize) & (S_IFMT as usize), 83 S_IFLNK as usize 84 ); // should be a link 85 assert_eq!(stats.st_nlink, 1); // there links created, must be 1 86 assert!(stats.st_size > 0); // size is > 0 because it points to another file 87 assert!(stats.st_blksize > 0); // must be positive integer, exact number machine dependent 88 89 // st_blocks depends on whether the machine's file system uses fast 90 // or slow symlinks, so just make sure it's not negative 91 assert!(stats.st_blocks >= 0); 92} 93 94#[test] 95#[cfg(not(any(target_os = "netbsd", target_os = "redox")))] 96fn test_stat_and_fstat() { 97 use nix::sys::stat::fstat; 98 99 let tempdir = tempfile::tempdir().unwrap(); 100 let filename = tempdir.path().join("foo.txt"); 101 let file = File::create(&filename).unwrap(); 102 103 let stat_result = stat(&filename); 104 assert_stat_results(stat_result); 105 106 let fstat_result = fstat(file.as_raw_fd()); 107 assert_stat_results(fstat_result); 108} 109 110#[test] 111#[cfg(not(any(target_os = "netbsd", target_os = "redox")))] 112fn test_fstatat() { 113 let tempdir = tempfile::tempdir().unwrap(); 114 let filename = tempdir.path().join("foo.txt"); 115 File::create(&filename).unwrap(); 116 let dirfd = 117 fcntl::open(tempdir.path(), fcntl::OFlag::empty(), stat::Mode::empty()); 118 119 let result = 120 stat::fstatat(dirfd.unwrap(), &filename, fcntl::AtFlags::empty()); 121 assert_stat_results(result); 122} 123 124#[test] 125#[cfg(not(any(target_os = "netbsd", target_os = "redox")))] 126fn test_stat_fstat_lstat() { 127 use nix::sys::stat::{fstat, lstat}; 128 129 let tempdir = tempfile::tempdir().unwrap(); 130 let filename = tempdir.path().join("bar.txt"); 131 let linkname = tempdir.path().join("barlink"); 132 133 File::create(&filename).unwrap(); 134 symlink("bar.txt", &linkname).unwrap(); 135 let link = File::open(&linkname).unwrap(); 136 137 // should be the same result as calling stat, 138 // since it's a regular file 139 let stat_result = stat(&filename); 140 assert_stat_results(stat_result); 141 142 let lstat_result = lstat(&linkname); 143 assert_lstat_results(lstat_result); 144 145 let fstat_result = fstat(link.as_raw_fd()); 146 assert_stat_results(fstat_result); 147} 148 149#[test] 150fn test_fchmod() { 151 let tempdir = tempfile::tempdir().unwrap(); 152 let filename = tempdir.path().join("foo.txt"); 153 let file = File::create(&filename).unwrap(); 154 155 let mut mode1 = Mode::empty(); 156 mode1.insert(Mode::S_IRUSR); 157 mode1.insert(Mode::S_IWUSR); 158 fchmod(file.as_raw_fd(), mode1).unwrap(); 159 160 let file_stat1 = stat(&filename).unwrap(); 161 assert_eq!(file_stat1.st_mode as mode_t & 0o7777, mode1.bits()); 162 163 let mut mode2 = Mode::empty(); 164 mode2.insert(Mode::S_IROTH); 165 fchmod(file.as_raw_fd(), mode2).unwrap(); 166 167 let file_stat2 = stat(&filename).unwrap(); 168 assert_eq!(file_stat2.st_mode as mode_t & 0o7777, mode2.bits()); 169} 170 171#[test] 172#[cfg(not(target_os = "redox"))] 173fn test_fchmodat() { 174 let _dr = crate::DirRestore::new(); 175 let tempdir = tempfile::tempdir().unwrap(); 176 let filename = "foo.txt"; 177 let fullpath = tempdir.path().join(filename); 178 File::create(&fullpath).unwrap(); 179 180 let dirfd = 181 fcntl::open(tempdir.path(), fcntl::OFlag::empty(), stat::Mode::empty()) 182 .unwrap(); 183 184 let mut mode1 = Mode::empty(); 185 mode1.insert(Mode::S_IRUSR); 186 mode1.insert(Mode::S_IWUSR); 187 fchmodat(Some(dirfd), filename, mode1, FchmodatFlags::FollowSymlink) 188 .unwrap(); 189 190 let file_stat1 = stat(&fullpath).unwrap(); 191 assert_eq!(file_stat1.st_mode as mode_t & 0o7777, mode1.bits()); 192 193 chdir(tempdir.path()).unwrap(); 194 195 let mut mode2 = Mode::empty(); 196 mode2.insert(Mode::S_IROTH); 197 fchmodat(None, filename, mode2, FchmodatFlags::FollowSymlink).unwrap(); 198 199 let file_stat2 = stat(&fullpath).unwrap(); 200 assert_eq!(file_stat2.st_mode as mode_t & 0o7777, mode2.bits()); 201} 202 203/// Asserts that the atime and mtime in a file's metadata match expected values. 204/// 205/// The atime and mtime are expressed with a resolution of seconds because some file systems 206/// (like macOS's HFS+) do not have higher granularity. 207#[cfg(not(any(target_os = "redox", target_os = "haiku")))] 208fn assert_times_eq( 209 exp_atime_sec: u64, 210 exp_mtime_sec: u64, 211 attr: &fs::Metadata, 212) { 213 assert_eq!( 214 Duration::new(exp_atime_sec, 0), 215 attr.accessed().unwrap().duration_since(UNIX_EPOCH).unwrap() 216 ); 217 assert_eq!( 218 Duration::new(exp_mtime_sec, 0), 219 attr.modified().unwrap().duration_since(UNIX_EPOCH).unwrap() 220 ); 221} 222 223#[test] 224#[cfg(not(any(target_os = "redox", target_os = "haiku")))] 225fn test_utimes() { 226 let tempdir = tempfile::tempdir().unwrap(); 227 let fullpath = tempdir.path().join("file"); 228 drop(File::create(&fullpath).unwrap()); 229 230 utimes(&fullpath, &TimeVal::seconds(9990), &TimeVal::seconds(5550)) 231 .unwrap(); 232 assert_times_eq(9990, 5550, &fs::metadata(&fullpath).unwrap()); 233} 234 235#[test] 236#[cfg(any( 237 target_os = "linux", 238 target_os = "ios", 239 target_os = "macos", 240 target_os = "freebsd", 241 target_os = "netbsd" 242))] 243fn test_lutimes() { 244 let tempdir = tempfile::tempdir().unwrap(); 245 let target = tempdir.path().join("target"); 246 let fullpath = tempdir.path().join("symlink"); 247 drop(File::create(&target).unwrap()); 248 symlink(&target, &fullpath).unwrap(); 249 250 let exp_target_metadata = fs::symlink_metadata(&target).unwrap(); 251 lutimes(&fullpath, &TimeVal::seconds(4560), &TimeVal::seconds(1230)) 252 .unwrap(); 253 assert_times_eq(4560, 1230, &fs::symlink_metadata(&fullpath).unwrap()); 254 255 let target_metadata = fs::symlink_metadata(&target).unwrap(); 256 assert_eq!( 257 exp_target_metadata.accessed().unwrap(), 258 target_metadata.accessed().unwrap(), 259 "atime of symlink target was unexpectedly modified" 260 ); 261 assert_eq!( 262 exp_target_metadata.modified().unwrap(), 263 target_metadata.modified().unwrap(), 264 "mtime of symlink target was unexpectedly modified" 265 ); 266} 267 268#[test] 269#[cfg(not(any(target_os = "redox", target_os = "haiku")))] 270fn test_futimens() { 271 let tempdir = tempfile::tempdir().unwrap(); 272 let fullpath = tempdir.path().join("file"); 273 drop(File::create(&fullpath).unwrap()); 274 275 let fd = fcntl::open(&fullpath, fcntl::OFlag::empty(), stat::Mode::empty()) 276 .unwrap(); 277 278 futimens(fd, &TimeSpec::seconds(10), &TimeSpec::seconds(20)).unwrap(); 279 assert_times_eq(10, 20, &fs::metadata(&fullpath).unwrap()); 280} 281 282#[test] 283#[cfg(not(any(target_os = "redox", target_os = "haiku")))] 284fn test_utimensat() { 285 let _dr = crate::DirRestore::new(); 286 let tempdir = tempfile::tempdir().unwrap(); 287 let filename = "foo.txt"; 288 let fullpath = tempdir.path().join(filename); 289 drop(File::create(&fullpath).unwrap()); 290 291 let dirfd = 292 fcntl::open(tempdir.path(), fcntl::OFlag::empty(), stat::Mode::empty()) 293 .unwrap(); 294 295 utimensat( 296 Some(dirfd), 297 filename, 298 &TimeSpec::seconds(12345), 299 &TimeSpec::seconds(678), 300 UtimensatFlags::FollowSymlink, 301 ) 302 .unwrap(); 303 assert_times_eq(12345, 678, &fs::metadata(&fullpath).unwrap()); 304 305 chdir(tempdir.path()).unwrap(); 306 307 utimensat( 308 None, 309 filename, 310 &TimeSpec::seconds(500), 311 &TimeSpec::seconds(800), 312 UtimensatFlags::FollowSymlink, 313 ) 314 .unwrap(); 315 assert_times_eq(500, 800, &fs::metadata(&fullpath).unwrap()); 316} 317 318#[test] 319#[cfg(not(target_os = "redox"))] 320fn test_mkdirat_success_path() { 321 let tempdir = tempfile::tempdir().unwrap(); 322 let filename = "example_subdir"; 323 let dirfd = 324 fcntl::open(tempdir.path(), fcntl::OFlag::empty(), stat::Mode::empty()) 325 .unwrap(); 326 mkdirat(dirfd, filename, Mode::S_IRWXU).expect("mkdirat failed"); 327 assert!(Path::exists(&tempdir.path().join(filename))); 328} 329 330#[test] 331#[cfg(not(any(target_os = "redox", target_os = "haiku")))] 332fn test_mkdirat_success_mode() { 333 let expected_bits = 334 stat::SFlag::S_IFDIR.bits() | stat::Mode::S_IRWXU.bits(); 335 let tempdir = tempfile::tempdir().unwrap(); 336 let filename = "example_subdir"; 337 let dirfd = 338 fcntl::open(tempdir.path(), fcntl::OFlag::empty(), stat::Mode::empty()) 339 .unwrap(); 340 mkdirat(dirfd, filename, Mode::S_IRWXU).expect("mkdirat failed"); 341 let permissions = fs::metadata(tempdir.path().join(filename)) 342 .unwrap() 343 .permissions(); 344 let mode = permissions.mode(); 345 assert_eq!(mode as mode_t, expected_bits) 346} 347 348#[test] 349#[cfg(not(target_os = "redox"))] 350fn test_mkdirat_fail() { 351 let tempdir = tempfile::tempdir().unwrap(); 352 let not_dir_filename = "example_not_dir"; 353 let filename = "example_subdir_dir"; 354 let dirfd = fcntl::open( 355 &tempdir.path().join(not_dir_filename), 356 fcntl::OFlag::O_CREAT, 357 stat::Mode::empty(), 358 ) 359 .unwrap(); 360 let result = mkdirat(dirfd, filename, Mode::S_IRWXU).unwrap_err(); 361 assert_eq!(result, Errno::ENOTDIR); 362} 363 364#[test] 365#[cfg(not(any( 366 target_os = "dragonfly", 367 target_os = "freebsd", 368 target_os = "ios", 369 target_os = "macos", 370 target_os = "haiku", 371 target_os = "redox" 372)))] 373fn test_mknod() { 374 use stat::{lstat, mknod, SFlag}; 375 376 let file_name = "test_file"; 377 let tempdir = tempfile::tempdir().unwrap(); 378 let target = tempdir.path().join(file_name); 379 mknod(&target, SFlag::S_IFREG, Mode::S_IRWXU, 0).unwrap(); 380 let mode = lstat(&target).unwrap().st_mode as mode_t; 381 assert_eq!(mode & libc::S_IFREG, libc::S_IFREG); 382 assert_eq!(mode & libc::S_IRWXU, libc::S_IRWXU); 383} 384 385#[test] 386#[cfg(not(any( 387 target_os = "dragonfly", 388 target_os = "freebsd", 389 target_os = "illumos", 390 target_os = "ios", 391 target_os = "macos", 392 target_os = "haiku", 393 target_os = "redox" 394)))] 395fn test_mknodat() { 396 use fcntl::{AtFlags, OFlag}; 397 use nix::dir::Dir; 398 use stat::{fstatat, mknodat, SFlag}; 399 400 let file_name = "test_file"; 401 let tempdir = tempfile::tempdir().unwrap(); 402 let target_dir = 403 Dir::open(tempdir.path(), OFlag::O_DIRECTORY, Mode::S_IRWXU).unwrap(); 404 mknodat( 405 target_dir.as_raw_fd(), 406 file_name, 407 SFlag::S_IFREG, 408 Mode::S_IRWXU, 409 0, 410 ) 411 .unwrap(); 412 let mode = fstatat( 413 target_dir.as_raw_fd(), 414 file_name, 415 AtFlags::AT_SYMLINK_NOFOLLOW, 416 ) 417 .unwrap() 418 .st_mode as mode_t; 419 assert_eq!(mode & libc::S_IFREG, libc::S_IFREG); 420 assert_eq!(mode & libc::S_IRWXU, libc::S_IRWXU); 421} 422