1//! libc syscalls supporting `rustix::fs`. 2 3use super::super::c; 4use super::super::conv::{ 5 borrowed_fd, c_str, ret, ret_c_int, ret_off_t, ret_owned_fd, ret_ssize_t, 6}; 7#[cfg(any(target_os = "android", target_os = "linux"))] 8use super::super::conv::{syscall_ret, syscall_ret_owned_fd, syscall_ret_ssize_t}; 9#[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))] 10use super::super::offset::libc_fallocate; 11#[cfg(not(any( 12 target_os = "dragonfly", 13 target_os = "haiku", 14 target_os = "illumos", 15 target_os = "ios", 16 target_os = "macos", 17 target_os = "netbsd", 18 target_os = "openbsd", 19 target_os = "redox", 20 target_os = "solaris", 21)))] 22use super::super::offset::libc_posix_fadvise; 23#[cfg(not(any( 24 target_os = "aix", 25 target_os = "android", 26 target_os = "dragonfly", 27 target_os = "fuchsia", 28 target_os = "illumos", 29 target_os = "ios", 30 target_os = "linux", 31 target_os = "macos", 32 target_os = "netbsd", 33 target_os = "openbsd", 34 target_os = "redox", 35 target_os = "solaris", 36)))] 37use super::super::offset::libc_posix_fallocate; 38use super::super::offset::{libc_fstat, libc_fstatat, libc_ftruncate, libc_lseek, libc_off_t}; 39#[cfg(not(any( 40 target_os = "haiku", 41 target_os = "illumos", 42 target_os = "netbsd", 43 target_os = "redox", 44 target_os = "solaris", 45 target_os = "wasi", 46)))] 47use super::super::offset::{libc_fstatfs, libc_statfs}; 48#[cfg(not(any( 49 target_os = "haiku", 50 target_os = "illumos", 51 target_os = "redox", 52 target_os = "solaris", 53 target_os = "wasi", 54)))] 55use super::super::offset::{libc_fstatvfs, libc_statvfs}; 56#[cfg(all( 57 any(target_arch = "arm", target_arch = "mips", target_arch = "x86"), 58 target_env = "gnu", 59))] 60use super::super::time::types::LibcTimespec; 61use crate::fd::{BorrowedFd, OwnedFd}; 62use crate::ffi::CStr; 63#[cfg(any(target_os = "ios", target_os = "macos"))] 64use crate::ffi::CString; 65#[cfg(not(any(target_os = "illumos", target_os = "solaris")))] 66use crate::fs::Access; 67#[cfg(not(any( 68 target_os = "dragonfly", 69 target_os = "haiku", 70 target_os = "illumos", 71 target_os = "ios", 72 target_os = "macos", 73 target_os = "netbsd", 74 target_os = "openbsd", 75 target_os = "redox", 76 target_os = "solaris", 77)))] 78use crate::fs::Advice; 79#[cfg(not(any( 80 target_os = "aix", 81 target_os = "dragonfly", 82 target_os = "illumos", 83 target_os = "netbsd", 84 target_os = "openbsd", 85 target_os = "redox", 86 target_os = "solaris", 87)))] 88use crate::fs::FallocateFlags; 89#[cfg(not(any(target_os = "solaris", target_os = "wasi")))] 90use crate::fs::FlockOperation; 91#[cfg(any(target_os = "android", target_os = "freebsd", target_os = "linux"))] 92use crate::fs::MemfdFlags; 93#[cfg(any( 94 target_os = "android", 95 target_os = "freebsd", 96 target_os = "fuchsia", 97 target_os = "linux", 98))] 99use crate::fs::SealFlags; 100#[cfg(not(any( 101 target_os = "haiku", 102 target_os = "illumos", 103 target_os = "netbsd", 104 target_os = "redox", 105 target_os = "solaris", 106 target_os = "wasi", 107)))] 108use crate::fs::StatFs; 109#[cfg(any(target_os = "android", target_os = "linux"))] 110use crate::fs::{cwd, RenameFlags, ResolveFlags, Statx, StatxFlags}; 111#[cfg(not(any( 112 target_os = "ios", 113 target_os = "macos", 114 target_os = "redox", 115 target_os = "wasi", 116)))] 117use crate::fs::{Dev, FileType}; 118use crate::fs::{Mode, OFlags, Stat, Timestamps}; 119#[cfg(not(any( 120 target_os = "haiku", 121 target_os = "illumos", 122 target_os = "redox", 123 target_os = "solaris", 124 target_os = "wasi", 125)))] 126use crate::fs::{StatVfs, StatVfsMountFlags}; 127use crate::io::{self, SeekFrom}; 128#[cfg(not(target_os = "wasi"))] 129use crate::process::{Gid, Uid}; 130#[cfg(not(all( 131 any(target_arch = "arm", target_arch = "mips", target_arch = "x86"), 132 target_env = "gnu", 133)))] 134use crate::utils::as_ptr; 135use core::convert::TryInto; 136#[cfg(any( 137 target_os = "android", 138 target_os = "ios", 139 target_os = "linux", 140 target_os = "macos", 141))] 142use core::mem::size_of; 143use core::mem::MaybeUninit; 144#[cfg(any(target_os = "android", target_os = "linux"))] 145use core::ptr::null; 146#[cfg(any( 147 target_os = "android", 148 target_os = "ios", 149 target_os = "linux", 150 target_os = "macos", 151))] 152use core::ptr::null_mut; 153#[cfg(any(target_os = "ios", target_os = "macos"))] 154use { 155 super::super::conv::nonnegative_ret, 156 crate::fs::{copyfile_state_t, CloneFlags, CopyfileFlags}, 157}; 158#[cfg(not(target_os = "redox"))] 159use {super::super::offset::libc_openat, crate::fs::AtFlags}; 160 161#[cfg(all( 162 any(target_arch = "arm", target_arch = "mips", target_arch = "x86"), 163 target_env = "gnu", 164))] 165weak!(fn __utimensat64(c::c_int, *const c::c_char, *const LibcTimespec, c::c_int) -> c::c_int); 166#[cfg(all( 167 any(target_arch = "arm", target_arch = "mips", target_arch = "x86"), 168 target_env = "gnu", 169))] 170weak!(fn __futimens64(c::c_int, *const LibcTimespec) -> c::c_int); 171 172/// Use a direct syscall (via libc) for `openat`. 173/// 174/// This is only currently necessary as a workaround for old glibc; see below. 175#[cfg(all(unix, target_env = "gnu"))] 176fn openat_via_syscall( 177 dirfd: BorrowedFd<'_>, 178 path: &CStr, 179 oflags: OFlags, 180 mode: Mode, 181) -> io::Result<OwnedFd> { 182 unsafe { 183 let dirfd = borrowed_fd(dirfd); 184 let path = c_str(path); 185 let oflags = oflags.bits(); 186 let mode = c::c_uint::from(mode.bits()); 187 ret_owned_fd(c::syscall( 188 c::SYS_openat, 189 c::c_long::from(dirfd), 190 path, 191 c::c_long::from(oflags), 192 mode as c::c_long, 193 ) as c::c_int) 194 } 195} 196 197#[cfg(not(target_os = "redox"))] 198pub(crate) fn openat( 199 dirfd: BorrowedFd<'_>, 200 path: &CStr, 201 oflags: OFlags, 202 mode: Mode, 203) -> io::Result<OwnedFd> { 204 // Work around <https://sourceware.org/bugzilla/show_bug.cgi?id=17523>. 205 // Basically old glibc versions don't handle O_TMPFILE correctly. 206 #[cfg(all(unix, target_env = "gnu"))] 207 if oflags.contains(OFlags::TMPFILE) && crate::backend::if_glibc_is_less_than_2_25() { 208 return openat_via_syscall(dirfd, path, oflags, mode); 209 } 210 unsafe { 211 // Pass `mode` as a `c_uint` even if `mode_t` is narrower, since 212 // `libc_openat` is declared as a variadic function and narrower 213 // arguments are promoted. 214 ret_owned_fd(libc_openat( 215 borrowed_fd(dirfd), 216 c_str(path), 217 oflags.bits(), 218 c::c_uint::from(mode.bits()), 219 )) 220 } 221} 222 223#[cfg(not(any( 224 target_os = "haiku", 225 target_os = "illumos", 226 target_os = "netbsd", 227 target_os = "redox", 228 target_os = "solaris", 229 target_os = "wasi", 230)))] 231#[inline] 232pub(crate) fn statfs(filename: &CStr) -> io::Result<StatFs> { 233 unsafe { 234 let mut result = MaybeUninit::<StatFs>::uninit(); 235 ret(libc_statfs(c_str(filename), result.as_mut_ptr()))?; 236 Ok(result.assume_init()) 237 } 238} 239 240#[cfg(not(any( 241 target_os = "haiku", 242 target_os = "illumos", 243 target_os = "redox", 244 target_os = "solaris", 245 target_os = "wasi", 246)))] 247#[inline] 248pub(crate) fn statvfs(filename: &CStr) -> io::Result<StatVfs> { 249 unsafe { 250 let mut result = MaybeUninit::<libc_statvfs>::uninit(); 251 ret(libc_statvfs(c_str(filename), result.as_mut_ptr()))?; 252 Ok(libc_statvfs_to_statvfs(result.assume_init())) 253 } 254} 255 256#[cfg(not(target_os = "redox"))] 257#[inline] 258pub(crate) fn readlinkat(dirfd: BorrowedFd<'_>, path: &CStr, buf: &mut [u8]) -> io::Result<usize> { 259 unsafe { 260 ret_ssize_t(c::readlinkat( 261 borrowed_fd(dirfd), 262 c_str(path), 263 buf.as_mut_ptr().cast::<c::c_char>(), 264 buf.len(), 265 )) 266 .map(|nread| nread as usize) 267 } 268} 269 270#[cfg(not(target_os = "redox"))] 271pub(crate) fn mkdirat(dirfd: BorrowedFd<'_>, path: &CStr, mode: Mode) -> io::Result<()> { 272 unsafe { 273 ret(c::mkdirat( 274 borrowed_fd(dirfd), 275 c_str(path), 276 mode.bits() as c::mode_t, 277 )) 278 } 279} 280 281#[cfg(any(target_os = "android", target_os = "linux"))] 282pub(crate) fn getdents_uninit( 283 fd: BorrowedFd<'_>, 284 buf: &mut [MaybeUninit<u8>], 285) -> io::Result<usize> { 286 unsafe { 287 syscall_ret_ssize_t(c::syscall( 288 c::SYS_getdents64, 289 fd, 290 buf.as_mut_ptr().cast::<c::c_char>(), 291 buf.len(), 292 )) 293 } 294 .map(|nread| nread as usize) 295} 296 297#[cfg(not(target_os = "redox"))] 298pub(crate) fn linkat( 299 old_dirfd: BorrowedFd<'_>, 300 old_path: &CStr, 301 new_dirfd: BorrowedFd<'_>, 302 new_path: &CStr, 303 flags: AtFlags, 304) -> io::Result<()> { 305 unsafe { 306 ret(c::linkat( 307 borrowed_fd(old_dirfd), 308 c_str(old_path), 309 borrowed_fd(new_dirfd), 310 c_str(new_path), 311 flags.bits(), 312 )) 313 } 314} 315 316#[cfg(not(target_os = "redox"))] 317pub(crate) fn unlinkat(dirfd: BorrowedFd<'_>, path: &CStr, flags: AtFlags) -> io::Result<()> { 318 unsafe { ret(c::unlinkat(borrowed_fd(dirfd), c_str(path), flags.bits())) } 319} 320 321#[cfg(not(target_os = "redox"))] 322pub(crate) fn renameat( 323 old_dirfd: BorrowedFd<'_>, 324 old_path: &CStr, 325 new_dirfd: BorrowedFd<'_>, 326 new_path: &CStr, 327) -> io::Result<()> { 328 unsafe { 329 ret(c::renameat( 330 borrowed_fd(old_dirfd), 331 c_str(old_path), 332 borrowed_fd(new_dirfd), 333 c_str(new_path), 334 )) 335 } 336} 337 338#[cfg(all(target_os = "linux", target_env = "gnu"))] 339pub(crate) fn renameat2( 340 old_dirfd: BorrowedFd<'_>, 341 old_path: &CStr, 342 new_dirfd: BorrowedFd<'_>, 343 new_path: &CStr, 344 flags: RenameFlags, 345) -> io::Result<()> { 346 // `getrandom` wasn't supported in glibc until 2.28. 347 weak_or_syscall! { 348 fn renameat2( 349 olddirfd: c::c_int, 350 oldpath: *const c::c_char, 351 newdirfd: c::c_int, 352 newpath: *const c::c_char, 353 flags: c::c_uint 354 ) via SYS_renameat2 -> c::c_int 355 } 356 357 unsafe { 358 ret(renameat2( 359 borrowed_fd(old_dirfd), 360 c_str(old_path), 361 borrowed_fd(new_dirfd), 362 c_str(new_path), 363 flags.bits(), 364 )) 365 } 366} 367 368/// At present, `libc` only has `renameat2` defined for glibc. On other 369/// ABIs, `RenameFlags` has no flags defined, and we use plain `renameat`. 370#[cfg(any( 371 target_os = "android", 372 all(target_os = "linux", not(target_env = "gnu")), 373))] 374#[inline] 375pub(crate) fn renameat2( 376 old_dirfd: BorrowedFd<'_>, 377 old_path: &CStr, 378 new_dirfd: BorrowedFd<'_>, 379 new_path: &CStr, 380 flags: RenameFlags, 381) -> io::Result<()> { 382 assert!(flags.is_empty()); 383 renameat(old_dirfd, old_path, new_dirfd, new_path) 384} 385 386#[cfg(not(target_os = "redox"))] 387pub(crate) fn symlinkat( 388 old_path: &CStr, 389 new_dirfd: BorrowedFd<'_>, 390 new_path: &CStr, 391) -> io::Result<()> { 392 unsafe { 393 ret(c::symlinkat( 394 c_str(old_path), 395 borrowed_fd(new_dirfd), 396 c_str(new_path), 397 )) 398 } 399} 400 401#[cfg(not(target_os = "redox"))] 402pub(crate) fn statat(dirfd: BorrowedFd<'_>, path: &CStr, flags: AtFlags) -> io::Result<Stat> { 403 // 32-bit and mips64 Linux: `struct stat64` is not y2038 compatible; use 404 // `statx`. 405 #[cfg(all( 406 any(target_os = "android", target_os = "linux"), 407 any(target_pointer_width = "32", target_arch = "mips64"), 408 ))] 409 { 410 match statx(dirfd, path, flags, StatxFlags::BASIC_STATS) { 411 Ok(x) => statx_to_stat(x), 412 Err(io::Errno::NOSYS) => statat_old(dirfd, path, flags), 413 Err(err) => Err(err), 414 } 415 } 416 417 // Main version: libc is y2038 safe. Or, the platform is not y2038 safe and 418 // there's nothing practical we can do. 419 #[cfg(not(all( 420 any(target_os = "android", target_os = "linux"), 421 any(target_pointer_width = "32", target_arch = "mips64"), 422 )))] 423 unsafe { 424 let mut stat = MaybeUninit::<Stat>::uninit(); 425 ret(libc_fstatat( 426 borrowed_fd(dirfd), 427 c_str(path), 428 stat.as_mut_ptr(), 429 flags.bits(), 430 ))?; 431 Ok(stat.assume_init()) 432 } 433} 434 435#[cfg(all( 436 any(target_os = "android", target_os = "linux"), 437 any(target_pointer_width = "32", target_arch = "mips64"), 438))] 439fn statat_old(dirfd: BorrowedFd<'_>, path: &CStr, flags: AtFlags) -> io::Result<Stat> { 440 unsafe { 441 let mut result = MaybeUninit::<c::stat64>::uninit(); 442 ret(libc_fstatat( 443 borrowed_fd(dirfd), 444 c_str(path), 445 result.as_mut_ptr(), 446 flags.bits(), 447 ))?; 448 stat64_to_stat(result.assume_init()) 449 } 450} 451 452#[cfg(not(any( 453 target_os = "emscripten", 454 target_os = "illumos", 455 target_os = "redox", 456 target_os = "solaris", 457)))] 458pub(crate) fn accessat( 459 dirfd: BorrowedFd<'_>, 460 path: &CStr, 461 access: Access, 462 flags: AtFlags, 463) -> io::Result<()> { 464 unsafe { 465 ret(c::faccessat( 466 borrowed_fd(dirfd), 467 c_str(path), 468 access.bits(), 469 flags.bits(), 470 )) 471 } 472} 473 474#[cfg(target_os = "emscripten")] 475pub(crate) fn accessat( 476 _dirfd: BorrowedFd<'_>, 477 _path: &CStr, 478 _access: Access, 479 _flags: AtFlags, 480) -> io::Result<()> { 481 Ok(()) 482} 483 484#[cfg(not(target_os = "redox"))] 485pub(crate) fn utimensat( 486 dirfd: BorrowedFd<'_>, 487 path: &CStr, 488 times: &Timestamps, 489 flags: AtFlags, 490) -> io::Result<()> { 491 // 32-bit gnu version: libc has `utimensat` but it is not y2038 safe by 492 // default. 493 #[cfg(all( 494 any(target_arch = "arm", target_arch = "mips", target_arch = "x86"), 495 target_env = "gnu", 496 ))] 497 unsafe { 498 if let Some(libc_utimensat) = __utimensat64.get() { 499 let libc_times: [LibcTimespec; 2] = [ 500 times.last_access.clone().into(), 501 times.last_modification.clone().into(), 502 ]; 503 504 ret(libc_utimensat( 505 borrowed_fd(dirfd), 506 c_str(path), 507 libc_times.as_ptr(), 508 flags.bits(), 509 )) 510 } else { 511 utimensat_old(dirfd, path, times, flags) 512 } 513 } 514 515 // Main version: libc is y2038 safe and has `utimensat`. Or, the platform 516 // is not y2038 safe and there's nothing practical we can do. 517 #[cfg(not(any( 518 target_os = "ios", 519 target_os = "macos", 520 all( 521 any(target_arch = "arm", target_arch = "mips", target_arch = "x86"), 522 target_env = "gnu", 523 ) 524 )))] 525 unsafe { 526 // Assert that `Timestamps` has the expected layout. 527 let _ = core::mem::transmute::<Timestamps, [c::timespec; 2]>(times.clone()); 528 529 ret(c::utimensat( 530 borrowed_fd(dirfd), 531 c_str(path), 532 as_ptr(times).cast(), 533 flags.bits(), 534 )) 535 } 536 537 // `utimensat` was introduced in macOS 10.13. 538 #[cfg(any(target_os = "ios", target_os = "macos"))] 539 unsafe { 540 // ABI details 541 weak! { 542 fn utimensat( 543 c::c_int, 544 *const c::c_char, 545 *const c::timespec, 546 c::c_int 547 ) -> c::c_int 548 } 549 extern "C" { 550 fn setattrlist( 551 path: *const c::c_char, 552 attr_list: *const Attrlist, 553 attr_buf: *const c::c_void, 554 attr_buf_size: c::size_t, 555 options: c::c_ulong, 556 ) -> c::c_int; 557 } 558 const FSOPT_NOFOLLOW: c::c_ulong = 0x0000_0001; 559 560 // If we have `utimensat`, use it. 561 if let Some(have_utimensat) = utimensat.get() { 562 // Assert that `Timestamps` has the expected layout. 563 let _ = core::mem::transmute::<Timestamps, [c::timespec; 2]>(times.clone()); 564 565 return ret(have_utimensat( 566 borrowed_fd(dirfd), 567 c_str(path), 568 as_ptr(times).cast(), 569 flags.bits(), 570 )); 571 } 572 573 // `setattrlistat` was introduced in 10.13 along with `utimensat`, so if 574 // we don't have `utimensat`, we don't have `setattrlistat` either. 575 // Emulate it using `fork`, and `fchdir` and [`setattrlist`]. 576 // 577 // [`setattrlist`]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/setattrlist.2.html 578 match c::fork() { 579 -1 => Err(io::Errno::IO), 580 0 => { 581 if c::fchdir(borrowed_fd(dirfd)) != 0 { 582 let code = match libc_errno::errno().0 { 583 c::EACCES => 2, 584 c::ENOTDIR => 3, 585 _ => 1, 586 }; 587 c::_exit(code); 588 } 589 590 let mut flags_arg = 0; 591 if flags.contains(AtFlags::SYMLINK_NOFOLLOW) { 592 flags_arg |= FSOPT_NOFOLLOW; 593 } 594 595 let (attrbuf_size, times, attrs) = times_to_attrlist(times); 596 597 if setattrlist( 598 c_str(path), 599 &attrs, 600 as_ptr(×).cast(), 601 attrbuf_size, 602 flags_arg, 603 ) != 0 604 { 605 // Translate expected errno codes into ad-hoc integer 606 // values suitable for exit statuses. 607 let code = match libc_errno::errno().0 { 608 c::EACCES => 2, 609 c::ENOTDIR => 3, 610 c::EPERM => 4, 611 c::EROFS => 5, 612 c::ELOOP => 6, 613 c::ENOENT => 7, 614 c::ENAMETOOLONG => 8, 615 c::EINVAL => 9, 616 c::ESRCH => 10, 617 c::ENOTSUP => 11, 618 _ => 1, 619 }; 620 c::_exit(code); 621 } 622 623 c::_exit(0); 624 } 625 child_pid => { 626 let mut wstatus = 0; 627 let _ = ret_c_int(c::waitpid(child_pid, &mut wstatus, 0))?; 628 if c::WIFEXITED(wstatus) { 629 // Translate our ad-hoc exit statuses back to errno codes. 630 match c::WEXITSTATUS(wstatus) { 631 0 => Ok(()), 632 2 => Err(io::Errno::ACCESS), 633 3 => Err(io::Errno::NOTDIR), 634 4 => Err(io::Errno::PERM), 635 5 => Err(io::Errno::ROFS), 636 6 => Err(io::Errno::LOOP), 637 7 => Err(io::Errno::NOENT), 638 8 => Err(io::Errno::NAMETOOLONG), 639 9 => Err(io::Errno::INVAL), 640 10 => Err(io::Errno::SRCH), 641 11 => Err(io::Errno::NOTSUP), 642 _ => Err(io::Errno::IO), 643 } 644 } else { 645 Err(io::Errno::IO) 646 } 647 } 648 } 649 } 650} 651 652#[cfg(all( 653 any(target_arch = "arm", target_arch = "mips", target_arch = "x86"), 654 target_env = "gnu", 655))] 656unsafe fn utimensat_old( 657 dirfd: BorrowedFd<'_>, 658 path: &CStr, 659 times: &Timestamps, 660 flags: AtFlags, 661) -> io::Result<()> { 662 let old_times = [ 663 c::timespec { 664 tv_sec: times 665 .last_access 666 .tv_sec 667 .try_into() 668 .map_err(|_| io::Errno::OVERFLOW)?, 669 tv_nsec: times.last_access.tv_nsec, 670 }, 671 c::timespec { 672 tv_sec: times 673 .last_modification 674 .tv_sec 675 .try_into() 676 .map_err(|_| io::Errno::OVERFLOW)?, 677 tv_nsec: times.last_modification.tv_nsec, 678 }, 679 ]; 680 ret(c::utimensat( 681 borrowed_fd(dirfd), 682 c_str(path), 683 old_times.as_ptr(), 684 flags.bits(), 685 )) 686} 687 688#[cfg(not(any( 689 target_os = "android", 690 target_os = "linux", 691 target_os = "redox", 692 target_os = "wasi", 693)))] 694pub(crate) fn chmodat(dirfd: BorrowedFd<'_>, path: &CStr, mode: Mode) -> io::Result<()> { 695 unsafe { ret(c::fchmodat(borrowed_fd(dirfd), c_str(path), mode.bits(), 0)) } 696} 697 698#[cfg(any(target_os = "android", target_os = "linux"))] 699pub(crate) fn chmodat(dirfd: BorrowedFd<'_>, path: &CStr, mode: Mode) -> io::Result<()> { 700 // Linux's `fchmodat` does not have a flags argument. 701 unsafe { 702 // Pass `mode` as a `c_uint` even if `mode_t` is narrower, since 703 // `libc_openat` is declared as a variadic function and narrower 704 // arguments are promoted. 705 syscall_ret(c::syscall( 706 c::SYS_fchmodat, 707 borrowed_fd(dirfd), 708 c_str(path), 709 c::c_uint::from(mode.bits()), 710 )) 711 } 712} 713 714#[cfg(any(target_os = "ios", target_os = "macos"))] 715pub(crate) fn fclonefileat( 716 srcfd: BorrowedFd<'_>, 717 dst_dirfd: BorrowedFd<'_>, 718 dst: &CStr, 719 flags: CloneFlags, 720) -> io::Result<()> { 721 syscall! { 722 fn fclonefileat( 723 srcfd: BorrowedFd<'_>, 724 dst_dirfd: BorrowedFd<'_>, 725 dst: *const c::c_char, 726 flags: c::c_int 727 ) via SYS_fclonefileat -> c::c_int 728 } 729 730 unsafe { ret(fclonefileat(srcfd, dst_dirfd, c_str(dst), flags.bits())) } 731} 732 733#[cfg(not(any(target_os = "redox", target_os = "wasi")))] 734pub(crate) fn chownat( 735 dirfd: BorrowedFd<'_>, 736 path: &CStr, 737 owner: Option<Uid>, 738 group: Option<Gid>, 739 flags: AtFlags, 740) -> io::Result<()> { 741 unsafe { 742 let (ow, gr) = crate::process::translate_fchown_args(owner, group); 743 ret(c::fchownat( 744 borrowed_fd(dirfd), 745 c_str(path), 746 ow, 747 gr, 748 flags.bits(), 749 )) 750 } 751} 752 753#[cfg(not(any( 754 target_os = "ios", 755 target_os = "macos", 756 target_os = "redox", 757 target_os = "wasi", 758)))] 759pub(crate) fn mknodat( 760 dirfd: BorrowedFd<'_>, 761 path: &CStr, 762 file_type: FileType, 763 mode: Mode, 764 dev: Dev, 765) -> io::Result<()> { 766 unsafe { 767 ret(c::mknodat( 768 borrowed_fd(dirfd), 769 c_str(path), 770 (mode.bits() | file_type.as_raw_mode()) as c::mode_t, 771 dev.try_into().map_err(|_e| io::Errno::PERM)?, 772 )) 773 } 774} 775 776#[cfg(any(target_os = "android", target_os = "linux"))] 777pub(crate) fn copy_file_range( 778 fd_in: BorrowedFd<'_>, 779 off_in: Option<&mut u64>, 780 fd_out: BorrowedFd<'_>, 781 off_out: Option<&mut u64>, 782 len: u64, 783) -> io::Result<u64> { 784 assert_eq!(size_of::<c::loff_t>(), size_of::<u64>()); 785 786 let mut off_in_val: c::loff_t = 0; 787 let mut off_out_val: c::loff_t = 0; 788 // Silently cast; we'll get `EINVAL` if the value is negative. 789 let off_in_ptr = if let Some(off_in) = &off_in { 790 off_in_val = (**off_in) as i64; 791 &mut off_in_val 792 } else { 793 null_mut() 794 }; 795 let off_out_ptr = if let Some(off_out) = &off_out { 796 off_out_val = (**off_out) as i64; 797 &mut off_out_val 798 } else { 799 null_mut() 800 }; 801 let len: usize = len.try_into().unwrap_or(usize::MAX); 802 let copied = unsafe { 803 syscall_ret_ssize_t(c::syscall( 804 c::SYS_copy_file_range, 805 borrowed_fd(fd_in), 806 off_in_ptr, 807 borrowed_fd(fd_out), 808 off_out_ptr, 809 len, 810 0, // no flags are defined yet 811 ))? 812 }; 813 if let Some(off_in) = off_in { 814 *off_in = off_in_val as u64; 815 } 816 if let Some(off_out) = off_out { 817 *off_out = off_out_val as u64; 818 } 819 Ok(copied as u64) 820} 821 822#[cfg(not(any( 823 target_os = "dragonfly", 824 target_os = "haiku", 825 target_os = "illumos", 826 target_os = "ios", 827 target_os = "macos", 828 target_os = "netbsd", 829 target_os = "openbsd", 830 target_os = "redox", 831 target_os = "solaris", 832)))] 833pub(crate) fn fadvise(fd: BorrowedFd<'_>, offset: u64, len: u64, advice: Advice) -> io::Result<()> { 834 let offset = offset as i64; 835 let len = len as i64; 836 837 // FreeBSD returns `EINVAL` on invalid offsets; emulate the POSIX behavior. 838 #[cfg(target_os = "freebsd")] 839 let offset = if (offset as i64) < 0 { 840 i64::MAX 841 } else { 842 offset 843 }; 844 845 // FreeBSD returns `EINVAL` on overflow; emulate the POSIX behavior. 846 #[cfg(target_os = "freebsd")] 847 let len = if len > 0 && offset.checked_add(len).is_none() { 848 i64::MAX - offset 849 } else { 850 len 851 }; 852 853 let err = unsafe { libc_posix_fadvise(borrowed_fd(fd), offset, len, advice as c::c_int) }; 854 855 // `posix_fadvise` returns its error status rather than using `errno`. 856 if err == 0 { 857 Ok(()) 858 } else { 859 Err(io::Errno(err)) 860 } 861} 862 863pub(crate) fn fcntl_getfl(fd: BorrowedFd<'_>) -> io::Result<OFlags> { 864 unsafe { ret_c_int(c::fcntl(borrowed_fd(fd), c::F_GETFL)).map(OFlags::from_bits_truncate) } 865} 866 867pub(crate) fn fcntl_setfl(fd: BorrowedFd<'_>, flags: OFlags) -> io::Result<()> { 868 unsafe { ret(c::fcntl(borrowed_fd(fd), c::F_SETFL, flags.bits())) } 869} 870 871#[cfg(any( 872 target_os = "android", 873 target_os = "freebsd", 874 target_os = "fuchsia", 875 target_os = "linux", 876))] 877pub(crate) fn fcntl_get_seals(fd: BorrowedFd<'_>) -> io::Result<SealFlags> { 878 unsafe { 879 ret_c_int(c::fcntl(borrowed_fd(fd), c::F_GET_SEALS)) 880 .map(|flags| SealFlags::from_bits_unchecked(flags)) 881 } 882} 883 884#[cfg(any( 885 target_os = "android", 886 target_os = "freebsd", 887 target_os = "fuchsia", 888 target_os = "linux", 889))] 890pub(crate) fn fcntl_add_seals(fd: BorrowedFd<'_>, seals: SealFlags) -> io::Result<()> { 891 unsafe { ret(c::fcntl(borrowed_fd(fd), c::F_ADD_SEALS, seals.bits())) } 892} 893 894pub(crate) fn seek(fd: BorrowedFd<'_>, pos: SeekFrom) -> io::Result<u64> { 895 let (whence, offset): (c::c_int, libc_off_t) = match pos { 896 SeekFrom::Start(pos) => { 897 let pos: u64 = pos; 898 // Silently cast; we'll get `EINVAL` if the value is negative. 899 (c::SEEK_SET, pos as i64) 900 } 901 SeekFrom::End(offset) => (c::SEEK_END, offset), 902 SeekFrom::Current(offset) => (c::SEEK_CUR, offset), 903 }; 904 let offset = unsafe { ret_off_t(libc_lseek(borrowed_fd(fd), offset, whence))? }; 905 Ok(offset as u64) 906} 907 908pub(crate) fn tell(fd: BorrowedFd<'_>) -> io::Result<u64> { 909 let offset = unsafe { ret_off_t(libc_lseek(borrowed_fd(fd), 0, c::SEEK_CUR))? }; 910 Ok(offset as u64) 911} 912 913#[cfg(not(any(target_os = "android", target_os = "linux", target_os = "wasi")))] 914pub(crate) fn fchmod(fd: BorrowedFd<'_>, mode: Mode) -> io::Result<()> { 915 unsafe { ret(c::fchmod(borrowed_fd(fd), mode.bits())) } 916} 917 918#[cfg(any(target_os = "android", target_os = "linux"))] 919pub(crate) fn fchmod(fd: BorrowedFd<'_>, mode: Mode) -> io::Result<()> { 920 // Use `c::syscall` rather than `c::fchmod` because some libc 921 // implementations, such as musl, add extra logic to `fchmod` to emulate 922 // support for `O_PATH`, which uses `/proc` outside our control and 923 // interferes with our own use of `O_PATH`. 924 unsafe { 925 syscall_ret(c::syscall( 926 c::SYS_fchmod, 927 borrowed_fd(fd), 928 c::c_uint::from(mode.bits()), 929 )) 930 } 931} 932 933#[cfg(any(target_os = "android", target_os = "linux"))] 934pub(crate) fn fchown(fd: BorrowedFd<'_>, owner: Option<Uid>, group: Option<Gid>) -> io::Result<()> { 935 // Use `c::syscall` rather than `c::fchown` because some libc 936 // implementations, such as musl, add extra logic to `fchown` to emulate 937 // support for `O_PATH`, which uses `/proc` outside our control and 938 // interferes with our own use of `O_PATH`. 939 unsafe { 940 let (ow, gr) = crate::process::translate_fchown_args(owner, group); 941 syscall_ret(c::syscall(c::SYS_fchown, borrowed_fd(fd), ow, gr)) 942 } 943} 944 945#[cfg(not(any(target_os = "android", target_os = "linux", target_os = "wasi")))] 946pub(crate) fn fchown(fd: BorrowedFd<'_>, owner: Option<Uid>, group: Option<Gid>) -> io::Result<()> { 947 unsafe { 948 let (ow, gr) = crate::process::translate_fchown_args(owner, group); 949 ret(c::fchown(borrowed_fd(fd), ow, gr)) 950 } 951} 952 953#[cfg(not(any(target_os = "solaris", target_os = "wasi")))] 954pub(crate) fn flock(fd: BorrowedFd<'_>, operation: FlockOperation) -> io::Result<()> { 955 unsafe { ret(c::flock(borrowed_fd(fd), operation as c::c_int)) } 956} 957 958pub(crate) fn fstat(fd: BorrowedFd<'_>) -> io::Result<Stat> { 959 // 32-bit and mips64 Linux: `struct stat64` is not y2038 compatible; use 960 // `statx`. 961 #[cfg(all( 962 any(target_os = "android", target_os = "linux"), 963 any(target_pointer_width = "32", target_arch = "mips64"), 964 ))] 965 { 966 match statx(fd, cstr!(""), AtFlags::EMPTY_PATH, StatxFlags::BASIC_STATS) { 967 Ok(x) => statx_to_stat(x), 968 Err(io::Errno::NOSYS) => fstat_old(fd), 969 Err(err) => Err(err), 970 } 971 } 972 973 // Main version: libc is y2038 safe. Or, the platform is not y2038 safe and 974 // there's nothing practical we can do. 975 #[cfg(not(all( 976 any(target_os = "android", target_os = "linux"), 977 any(target_pointer_width = "32", target_arch = "mips64"), 978 )))] 979 unsafe { 980 let mut stat = MaybeUninit::<Stat>::uninit(); 981 ret(libc_fstat(borrowed_fd(fd), stat.as_mut_ptr()))?; 982 Ok(stat.assume_init()) 983 } 984} 985 986#[cfg(all( 987 any(target_os = "android", target_os = "linux"), 988 any(target_pointer_width = "32", target_arch = "mips64"), 989))] 990fn fstat_old(fd: BorrowedFd<'_>) -> io::Result<Stat> { 991 unsafe { 992 let mut result = MaybeUninit::<c::stat64>::uninit(); 993 ret(libc_fstat(borrowed_fd(fd), result.as_mut_ptr()))?; 994 stat64_to_stat(result.assume_init()) 995 } 996} 997 998#[cfg(not(any( 999 target_os = "haiku", 1000 target_os = "illumos", 1001 target_os = "netbsd", 1002 target_os = "redox", 1003 target_os = "solaris", 1004 target_os = "wasi", 1005)))] 1006pub(crate) fn fstatfs(fd: BorrowedFd<'_>) -> io::Result<StatFs> { 1007 let mut statfs = MaybeUninit::<StatFs>::uninit(); 1008 unsafe { 1009 ret(libc_fstatfs(borrowed_fd(fd), statfs.as_mut_ptr()))?; 1010 Ok(statfs.assume_init()) 1011 } 1012} 1013 1014#[cfg(not(any( 1015 target_os = "haiku", 1016 target_os = "illumos", 1017 target_os = "redox", 1018 target_os = "solaris", 1019 target_os = "wasi", 1020)))] 1021pub(crate) fn fstatvfs(fd: BorrowedFd<'_>) -> io::Result<StatVfs> { 1022 let mut statvfs = MaybeUninit::<libc_statvfs>::uninit(); 1023 unsafe { 1024 ret(libc_fstatvfs(borrowed_fd(fd), statvfs.as_mut_ptr()))?; 1025 Ok(libc_statvfs_to_statvfs(statvfs.assume_init())) 1026 } 1027} 1028 1029#[cfg(not(any( 1030 target_os = "haiku", 1031 target_os = "illumos", 1032 target_os = "redox", 1033 target_os = "solaris", 1034 target_os = "wasi" 1035)))] 1036fn libc_statvfs_to_statvfs(from: libc_statvfs) -> StatVfs { 1037 StatVfs { 1038 f_bsize: from.f_bsize as u64, 1039 f_frsize: from.f_frsize as u64, 1040 f_blocks: from.f_blocks as u64, 1041 f_bfree: from.f_bfree as u64, 1042 f_bavail: from.f_bavail as u64, 1043 f_files: from.f_files as u64, 1044 f_ffree: from.f_ffree as u64, 1045 f_favail: from.f_ffree as u64, 1046 f_fsid: from.f_fsid as u64, 1047 f_flag: unsafe { StatVfsMountFlags::from_bits_unchecked(from.f_flag as u64) }, 1048 f_namemax: from.f_namemax as u64, 1049 } 1050} 1051 1052pub(crate) fn futimens(fd: BorrowedFd<'_>, times: &Timestamps) -> io::Result<()> { 1053 // 32-bit gnu version: libc has `futimens` but it is not y2038 safe by default. 1054 #[cfg(all( 1055 any(target_arch = "arm", target_arch = "mips", target_arch = "x86"), 1056 target_env = "gnu", 1057 ))] 1058 unsafe { 1059 if let Some(libc_futimens) = __futimens64.get() { 1060 let libc_times: [LibcTimespec; 2] = [ 1061 times.last_access.clone().into(), 1062 times.last_modification.clone().into(), 1063 ]; 1064 1065 ret(libc_futimens(borrowed_fd(fd), libc_times.as_ptr())) 1066 } else { 1067 futimens_old(fd, times) 1068 } 1069 } 1070 1071 // Main version: libc is y2038 safe and has `futimens`. Or, the platform 1072 // is not y2038 safe and there's nothing practical we can do. 1073 #[cfg(not(any( 1074 target_os = "ios", 1075 target_os = "macos", 1076 all( 1077 any(target_arch = "arm", target_arch = "mips", target_arch = "x86"), 1078 target_env = "gnu", 1079 ) 1080 )))] 1081 unsafe { 1082 // Assert that `Timestamps` has the expected layout. 1083 let _ = core::mem::transmute::<Timestamps, [c::timespec; 2]>(times.clone()); 1084 1085 ret(c::futimens(borrowed_fd(fd), as_ptr(times).cast())) 1086 } 1087 1088 // `futimens` was introduced in macOS 10.13. 1089 #[cfg(any(target_os = "ios", target_os = "macos"))] 1090 unsafe { 1091 // ABI details. 1092 weak! { 1093 fn futimens(c::c_int, *const c::timespec) -> c::c_int 1094 } 1095 extern "C" { 1096 fn fsetattrlist( 1097 fd: c::c_int, 1098 attr_list: *const Attrlist, 1099 attr_buf: *const c::c_void, 1100 attr_buf_size: c::size_t, 1101 options: c::c_ulong, 1102 ) -> c::c_int; 1103 } 1104 1105 // If we have `futimens`, use it. 1106 if let Some(have_futimens) = futimens.get() { 1107 // Assert that `Timestamps` has the expected layout. 1108 let _ = core::mem::transmute::<Timestamps, [c::timespec; 2]>(times.clone()); 1109 1110 return ret(have_futimens(borrowed_fd(fd), as_ptr(times).cast())); 1111 } 1112 1113 // Otherwise use `fsetattrlist`. 1114 let (attrbuf_size, times, attrs) = times_to_attrlist(times); 1115 1116 ret(fsetattrlist( 1117 borrowed_fd(fd), 1118 &attrs, 1119 as_ptr(×).cast(), 1120 attrbuf_size, 1121 0, 1122 )) 1123 } 1124} 1125 1126#[cfg(all( 1127 any(target_arch = "arm", target_arch = "mips", target_arch = "x86"), 1128 target_env = "gnu", 1129))] 1130unsafe fn futimens_old(fd: BorrowedFd<'_>, times: &Timestamps) -> io::Result<()> { 1131 let old_times = [ 1132 c::timespec { 1133 tv_sec: times 1134 .last_access 1135 .tv_sec 1136 .try_into() 1137 .map_err(|_| io::Errno::OVERFLOW)?, 1138 tv_nsec: times.last_access.tv_nsec, 1139 }, 1140 c::timespec { 1141 tv_sec: times 1142 .last_modification 1143 .tv_sec 1144 .try_into() 1145 .map_err(|_| io::Errno::OVERFLOW)?, 1146 tv_nsec: times.last_modification.tv_nsec, 1147 }, 1148 ]; 1149 1150 ret(c::futimens(borrowed_fd(fd), old_times.as_ptr())) 1151} 1152 1153#[cfg(not(any( 1154 target_os = "aix", 1155 target_os = "dragonfly", 1156 target_os = "illumos", 1157 target_os = "ios", 1158 target_os = "macos", 1159 target_os = "netbsd", 1160 target_os = "openbsd", 1161 target_os = "redox", 1162 target_os = "solaris", 1163)))] 1164pub(crate) fn fallocate( 1165 fd: BorrowedFd<'_>, 1166 mode: FallocateFlags, 1167 offset: u64, 1168 len: u64, 1169) -> io::Result<()> { 1170 // Silently cast; we'll get `EINVAL` if the value is negative. 1171 let offset = offset as i64; 1172 let len = len as i64; 1173 1174 #[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))] 1175 unsafe { 1176 ret(libc_fallocate(borrowed_fd(fd), mode.bits(), offset, len)) 1177 } 1178 1179 #[cfg(not(any(target_os = "android", target_os = "fuchsia", target_os = "linux")))] 1180 { 1181 assert!(mode.is_empty()); 1182 let err = unsafe { libc_posix_fallocate(borrowed_fd(fd), offset, len) }; 1183 1184 // `posix_fallocate` returns its error status rather than using `errno`. 1185 if err == 0 { 1186 Ok(()) 1187 } else { 1188 Err(io::Errno(err)) 1189 } 1190 } 1191} 1192 1193#[cfg(any(target_os = "ios", target_os = "macos"))] 1194pub(crate) fn fallocate( 1195 fd: BorrowedFd<'_>, 1196 mode: FallocateFlags, 1197 offset: u64, 1198 len: u64, 1199) -> io::Result<()> { 1200 let offset: i64 = offset.try_into().map_err(|_e| io::Errno::INVAL)?; 1201 let len = len as i64; 1202 1203 assert!(mode.is_empty()); 1204 1205 let new_len = offset.checked_add(len).ok_or(io::Errno::FBIG)?; 1206 let mut store = c::fstore_t { 1207 fst_flags: c::F_ALLOCATECONTIG, 1208 fst_posmode: c::F_PEOFPOSMODE, 1209 fst_offset: 0, 1210 fst_length: new_len, 1211 fst_bytesalloc: 0, 1212 }; 1213 unsafe { 1214 if c::fcntl(borrowed_fd(fd), c::F_PREALLOCATE, &store) == -1 { 1215 store.fst_flags = c::F_ALLOCATEALL; 1216 let _ = ret_c_int(c::fcntl(borrowed_fd(fd), c::F_PREALLOCATE, &store))?; 1217 } 1218 ret(c::ftruncate(borrowed_fd(fd), new_len)) 1219 } 1220} 1221 1222pub(crate) fn fsync(fd: BorrowedFd<'_>) -> io::Result<()> { 1223 unsafe { ret(c::fsync(borrowed_fd(fd))) } 1224} 1225 1226#[cfg(not(any( 1227 target_os = "dragonfly", 1228 target_os = "haiku", 1229 target_os = "ios", 1230 target_os = "macos", 1231 target_os = "redox", 1232)))] 1233pub(crate) fn fdatasync(fd: BorrowedFd<'_>) -> io::Result<()> { 1234 unsafe { ret(c::fdatasync(borrowed_fd(fd))) } 1235} 1236 1237pub(crate) fn ftruncate(fd: BorrowedFd<'_>, length: u64) -> io::Result<()> { 1238 let length = length.try_into().map_err(|_overflow_err| io::Errno::FBIG)?; 1239 unsafe { ret(libc_ftruncate(borrowed_fd(fd), length)) } 1240} 1241 1242#[cfg(any(target_os = "android", target_os = "freebsd", target_os = "linux"))] 1243pub(crate) fn memfd_create(path: &CStr, flags: MemfdFlags) -> io::Result<OwnedFd> { 1244 #[cfg(target_os = "freebsd")] 1245 weakcall! { 1246 fn memfd_create( 1247 name: *const c::c_char, 1248 flags: c::c_uint 1249 ) -> c::c_int 1250 } 1251 1252 #[cfg(any(target_os = "android", target_os = "linux"))] 1253 weak_or_syscall! { 1254 fn memfd_create( 1255 name: *const c::c_char, 1256 flags: c::c_uint 1257 ) via SYS_memfd_create -> c::c_int 1258 } 1259 1260 unsafe { ret_owned_fd(memfd_create(c_str(path), flags.bits())) } 1261} 1262 1263#[cfg(any(target_os = "android", target_os = "linux"))] 1264pub(crate) fn openat2( 1265 dirfd: BorrowedFd<'_>, 1266 path: &CStr, 1267 oflags: OFlags, 1268 mode: Mode, 1269 resolve: ResolveFlags, 1270) -> io::Result<OwnedFd> { 1271 let oflags: i32 = oflags.bits(); 1272 let open_how = OpenHow { 1273 oflag: u64::from(oflags as u32), 1274 mode: u64::from(mode.bits()), 1275 resolve: resolve.bits(), 1276 }; 1277 1278 unsafe { 1279 syscall_ret_owned_fd(c::syscall( 1280 SYS_OPENAT2, 1281 borrowed_fd(dirfd), 1282 c_str(path), 1283 &open_how, 1284 SIZEOF_OPEN_HOW, 1285 )) 1286 } 1287} 1288#[cfg(all( 1289 target_pointer_width = "32", 1290 any(target_os = "android", target_os = "linux"), 1291))] 1292const SYS_OPENAT2: i32 = 437; 1293#[cfg(all( 1294 target_pointer_width = "64", 1295 any(target_os = "android", target_os = "linux"), 1296))] 1297const SYS_OPENAT2: i64 = 437; 1298 1299#[cfg(any(target_os = "android", target_os = "linux"))] 1300#[repr(C)] 1301#[derive(Debug)] 1302struct OpenHow { 1303 oflag: u64, 1304 mode: u64, 1305 resolve: u64, 1306} 1307#[cfg(any(target_os = "android", target_os = "linux"))] 1308const SIZEOF_OPEN_HOW: usize = size_of::<OpenHow>(); 1309 1310#[cfg(target_os = "linux")] 1311pub(crate) fn sendfile( 1312 out_fd: BorrowedFd<'_>, 1313 in_fd: BorrowedFd<'_>, 1314 offset: Option<&mut u64>, 1315 count: usize, 1316) -> io::Result<usize> { 1317 unsafe { 1318 let nsent = ret_ssize_t(c::sendfile64( 1319 borrowed_fd(out_fd), 1320 borrowed_fd(in_fd), 1321 offset.map_or(null_mut(), crate::utils::as_mut_ptr).cast(), 1322 count, 1323 ))?; 1324 Ok(nsent as usize) 1325 } 1326} 1327 1328/// Convert from a Linux `statx` value to rustix's `Stat`. 1329#[cfg(all( 1330 any(target_os = "android", target_os = "linux"), 1331 target_pointer_width = "32", 1332))] 1333fn statx_to_stat(x: crate::fs::Statx) -> io::Result<Stat> { 1334 Ok(Stat { 1335 st_dev: crate::fs::makedev(x.stx_dev_major, x.stx_dev_minor).into(), 1336 st_mode: x.stx_mode.into(), 1337 st_nlink: x.stx_nlink.into(), 1338 st_uid: x.stx_uid.into(), 1339 st_gid: x.stx_gid.into(), 1340 st_rdev: crate::fs::makedev(x.stx_rdev_major, x.stx_rdev_minor).into(), 1341 st_size: x.stx_size.try_into().map_err(|_| io::Errno::OVERFLOW)?, 1342 st_blksize: x.stx_blksize.into(), 1343 st_blocks: x.stx_blocks.into(), 1344 st_atime: x 1345 .stx_atime 1346 .tv_sec 1347 .try_into() 1348 .map_err(|_| io::Errno::OVERFLOW)?, 1349 st_atime_nsec: x.stx_atime.tv_nsec as _, 1350 st_mtime: x 1351 .stx_mtime 1352 .tv_sec 1353 .try_into() 1354 .map_err(|_| io::Errno::OVERFLOW)?, 1355 st_mtime_nsec: x.stx_mtime.tv_nsec as _, 1356 st_ctime: x 1357 .stx_ctime 1358 .tv_sec 1359 .try_into() 1360 .map_err(|_| io::Errno::OVERFLOW)?, 1361 st_ctime_nsec: x.stx_ctime.tv_nsec as _, 1362 st_ino: x.stx_ino.into(), 1363 }) 1364} 1365 1366/// Convert from a Linux `statx` value to rustix's `Stat`. 1367/// 1368/// mips64' `struct stat64` in libc has private fields, and `stx_blocks` 1369#[cfg(all( 1370 any(target_os = "android", target_os = "linux"), 1371 target_arch = "mips64", 1372))] 1373fn statx_to_stat(x: crate::fs::Statx) -> io::Result<Stat> { 1374 let mut result: Stat = unsafe { core::mem::zeroed() }; 1375 1376 result.st_dev = crate::fs::makedev(x.stx_dev_major, x.stx_dev_minor); 1377 result.st_mode = x.stx_mode.into(); 1378 result.st_nlink = x.stx_nlink.into(); 1379 result.st_uid = x.stx_uid.into(); 1380 result.st_gid = x.stx_gid.into(); 1381 result.st_rdev = crate::fs::makedev(x.stx_rdev_major, x.stx_rdev_minor); 1382 result.st_size = x.stx_size.try_into().map_err(|_| io::Errno::OVERFLOW)?; 1383 result.st_blksize = x.stx_blksize.into(); 1384 result.st_blocks = x.stx_blocks.try_into().map_err(|_e| io::Errno::OVERFLOW)?; 1385 result.st_atime = x 1386 .stx_atime 1387 .tv_sec 1388 .try_into() 1389 .map_err(|_| io::Errno::OVERFLOW)?; 1390 result.st_atime_nsec = x.stx_atime.tv_nsec as _; 1391 result.st_mtime = x 1392 .stx_mtime 1393 .tv_sec 1394 .try_into() 1395 .map_err(|_| io::Errno::OVERFLOW)?; 1396 result.st_mtime_nsec = x.stx_mtime.tv_nsec as _; 1397 result.st_ctime = x 1398 .stx_ctime 1399 .tv_sec 1400 .try_into() 1401 .map_err(|_| io::Errno::OVERFLOW)?; 1402 result.st_ctime_nsec = x.stx_ctime.tv_nsec as _; 1403 result.st_ino = x.stx_ino.into(); 1404 1405 Ok(result) 1406} 1407 1408/// Convert from a Linux `stat64` value to rustix's `Stat`. 1409#[cfg(all( 1410 any(target_os = "android", target_os = "linux"), 1411 target_pointer_width = "32", 1412))] 1413fn stat64_to_stat(s64: c::stat64) -> io::Result<Stat> { 1414 Ok(Stat { 1415 st_dev: s64.st_dev.try_into().map_err(|_| io::Errno::OVERFLOW)?, 1416 st_mode: s64.st_mode.try_into().map_err(|_| io::Errno::OVERFLOW)?, 1417 st_nlink: s64.st_nlink.try_into().map_err(|_| io::Errno::OVERFLOW)?, 1418 st_uid: s64.st_uid.try_into().map_err(|_| io::Errno::OVERFLOW)?, 1419 st_gid: s64.st_gid.try_into().map_err(|_| io::Errno::OVERFLOW)?, 1420 st_rdev: s64.st_rdev.try_into().map_err(|_| io::Errno::OVERFLOW)?, 1421 st_size: s64.st_size.try_into().map_err(|_| io::Errno::OVERFLOW)?, 1422 st_blksize: s64.st_blksize.try_into().map_err(|_| io::Errno::OVERFLOW)?, 1423 st_blocks: s64.st_blocks.try_into().map_err(|_| io::Errno::OVERFLOW)?, 1424 st_atime: s64.st_atime.try_into().map_err(|_| io::Errno::OVERFLOW)?, 1425 st_atime_nsec: s64 1426 .st_atime_nsec 1427 .try_into() 1428 .map_err(|_| io::Errno::OVERFLOW)?, 1429 st_mtime: s64.st_mtime.try_into().map_err(|_| io::Errno::OVERFLOW)?, 1430 st_mtime_nsec: s64 1431 .st_mtime_nsec 1432 .try_into() 1433 .map_err(|_| io::Errno::OVERFLOW)?, 1434 st_ctime: s64.st_ctime.try_into().map_err(|_| io::Errno::OVERFLOW)?, 1435 st_ctime_nsec: s64 1436 .st_ctime_nsec 1437 .try_into() 1438 .map_err(|_| io::Errno::OVERFLOW)?, 1439 st_ino: s64.st_ino.try_into().map_err(|_| io::Errno::OVERFLOW)?, 1440 }) 1441} 1442 1443/// Convert from a Linux `stat64` value to rustix's `Stat`. 1444/// 1445/// mips64' `struct stat64` in libc has private fields, and `st_blocks` has 1446/// type `i64`. 1447#[cfg(all( 1448 any(target_os = "android", target_os = "linux"), 1449 target_arch = "mips64", 1450))] 1451fn stat64_to_stat(s64: c::stat64) -> io::Result<Stat> { 1452 let mut result: Stat = unsafe { core::mem::zeroed() }; 1453 1454 result.st_dev = s64.st_dev.try_into().map_err(|_| io::Errno::OVERFLOW)?; 1455 result.st_mode = s64.st_mode.try_into().map_err(|_| io::Errno::OVERFLOW)?; 1456 result.st_nlink = s64.st_nlink.try_into().map_err(|_| io::Errno::OVERFLOW)?; 1457 result.st_uid = s64.st_uid.try_into().map_err(|_| io::Errno::OVERFLOW)?; 1458 result.st_gid = s64.st_gid.try_into().map_err(|_| io::Errno::OVERFLOW)?; 1459 result.st_rdev = s64.st_rdev.try_into().map_err(|_| io::Errno::OVERFLOW)?; 1460 result.st_size = s64.st_size.try_into().map_err(|_| io::Errno::OVERFLOW)?; 1461 result.st_blksize = s64.st_blksize.try_into().map_err(|_| io::Errno::OVERFLOW)?; 1462 result.st_blocks = s64.st_blocks.try_into().map_err(|_| io::Errno::OVERFLOW)?; 1463 result.st_atime = s64.st_atime.try_into().map_err(|_| io::Errno::OVERFLOW)?; 1464 result.st_atime_nsec = s64 1465 .st_atime_nsec 1466 .try_into() 1467 .map_err(|_| io::Errno::OVERFLOW)?; 1468 result.st_mtime = s64.st_mtime.try_into().map_err(|_| io::Errno::OVERFLOW)?; 1469 result.st_mtime_nsec = s64 1470 .st_mtime_nsec 1471 .try_into() 1472 .map_err(|_| io::Errno::OVERFLOW)?; 1473 result.st_ctime = s64.st_ctime.try_into().map_err(|_| io::Errno::OVERFLOW)?; 1474 result.st_ctime_nsec = s64 1475 .st_ctime_nsec 1476 .try_into() 1477 .map_err(|_| io::Errno::OVERFLOW)?; 1478 result.st_ino = s64.st_ino.try_into().map_err(|_| io::Errno::OVERFLOW)?; 1479 1480 Ok(result) 1481} 1482 1483#[cfg(any(target_os = "android", target_os = "linux"))] 1484#[allow(non_upper_case_globals)] 1485mod sys { 1486 use super::{c, BorrowedFd, Statx}; 1487 1488 #[cfg(all(target_os = "android", target_arch = "arm"))] 1489 const SYS_statx: c::c_long = 397; 1490 #[cfg(all(target_os = "android", target_arch = "x86"))] 1491 const SYS_statx: c::c_long = 383; 1492 #[cfg(all(target_os = "android", target_arch = "aarch64"))] 1493 const SYS_statx: c::c_long = 291; 1494 #[cfg(all(target_os = "android", target_arch = "x86_64"))] 1495 const SYS_statx: c::c_long = 332; 1496 1497 weak_or_syscall! { 1498 pub(super) fn statx( 1499 pirfd: BorrowedFd<'_>, 1500 path: *const c::c_char, 1501 flags: c::c_int, 1502 mask: c::c_uint, 1503 buf: *mut Statx 1504 ) via SYS_statx -> c::c_int 1505 } 1506} 1507 1508#[cfg(any(target_os = "android", target_os = "linux"))] 1509#[allow(non_upper_case_globals)] 1510pub(crate) fn statx( 1511 dirfd: BorrowedFd<'_>, 1512 path: &CStr, 1513 flags: AtFlags, 1514 mask: StatxFlags, 1515) -> io::Result<Statx> { 1516 // If a future Linux kernel adds more fields to `struct statx` and users 1517 // passing flags unknown to rustix in `StatxFlags`, we could end up 1518 // writing outside of the buffer. To prevent this possibility, we mask off 1519 // any flags that we don't know about. 1520 // 1521 // This includes `STATX__RESERVED`, which has a value that we know, but 1522 // which could take on arbitrary new meaning in the future. Linux currently 1523 // rejects this flag with `EINVAL`, so we do the same. 1524 // 1525 // This doesn't rely on `STATX_ALL` because [it's deprecated] and already 1526 // doesn't represent all the known flags. 1527 // 1528 // [it's deprecated]: https://patchwork.kernel.org/project/linux-fsdevel/patch/20200505095915.11275-7-mszeredi@redhat.com/ 1529 #[cfg(not(any(target_os = "android", target_env = "musl")))] 1530 const STATX__RESERVED: u32 = libc::STATX__RESERVED as u32; 1531 #[cfg(any(target_os = "android", target_env = "musl"))] 1532 const STATX__RESERVED: u32 = linux_raw_sys::general::STATX__RESERVED; 1533 if (mask.bits() & STATX__RESERVED) == STATX__RESERVED { 1534 return Err(io::Errno::INVAL); 1535 } 1536 let mask = mask & StatxFlags::all(); 1537 1538 let mut statx_buf = MaybeUninit::<Statx>::uninit(); 1539 unsafe { 1540 ret(sys::statx( 1541 dirfd, 1542 c_str(path), 1543 flags.bits(), 1544 mask.bits(), 1545 statx_buf.as_mut_ptr(), 1546 ))?; 1547 Ok(statx_buf.assume_init()) 1548 } 1549} 1550 1551#[cfg(any(target_os = "android", target_os = "linux"))] 1552#[inline] 1553pub(crate) fn is_statx_available() -> bool { 1554 unsafe { 1555 // Call `statx` with null pointers so that if it fails for any reason 1556 // other than `EFAULT`, we know it's not supported. 1557 matches!( 1558 ret(sys::statx(cwd(), null(), 0, 0, null_mut())), 1559 Err(io::Errno::FAULT) 1560 ) 1561 } 1562} 1563 1564#[cfg(any(target_os = "ios", target_os = "macos"))] 1565pub(crate) unsafe fn fcopyfile( 1566 from: BorrowedFd<'_>, 1567 to: BorrowedFd<'_>, 1568 state: copyfile_state_t, 1569 flags: CopyfileFlags, 1570) -> io::Result<()> { 1571 extern "C" { 1572 fn fcopyfile( 1573 from: c::c_int, 1574 to: c::c_int, 1575 state: copyfile_state_t, 1576 flags: c::c_uint, 1577 ) -> c::c_int; 1578 } 1579 1580 nonnegative_ret(fcopyfile( 1581 borrowed_fd(from), 1582 borrowed_fd(to), 1583 state, 1584 flags.bits(), 1585 )) 1586} 1587 1588#[cfg(any(target_os = "ios", target_os = "macos"))] 1589pub(crate) fn copyfile_state_alloc() -> io::Result<copyfile_state_t> { 1590 extern "C" { 1591 fn copyfile_state_alloc() -> copyfile_state_t; 1592 } 1593 1594 let result = unsafe { copyfile_state_alloc() }; 1595 if result.0.is_null() { 1596 Err(io::Errno::last_os_error()) 1597 } else { 1598 Ok(result) 1599 } 1600} 1601 1602#[cfg(any(target_os = "ios", target_os = "macos"))] 1603pub(crate) unsafe fn copyfile_state_free(state: copyfile_state_t) -> io::Result<()> { 1604 extern "C" { 1605 fn copyfile_state_free(state: copyfile_state_t) -> c::c_int; 1606 } 1607 1608 nonnegative_ret(copyfile_state_free(state)) 1609} 1610 1611#[cfg(any(target_os = "ios", target_os = "macos"))] 1612const COPYFILE_STATE_COPIED: u32 = 8; 1613 1614#[cfg(any(target_os = "ios", target_os = "macos"))] 1615pub(crate) unsafe fn copyfile_state_get_copied(state: copyfile_state_t) -> io::Result<u64> { 1616 let mut copied = MaybeUninit::<u64>::uninit(); 1617 copyfile_state_get(state, COPYFILE_STATE_COPIED, copied.as_mut_ptr().cast())?; 1618 Ok(copied.assume_init()) 1619} 1620 1621#[cfg(any(target_os = "ios", target_os = "macos"))] 1622pub(crate) unsafe fn copyfile_state_get( 1623 state: copyfile_state_t, 1624 flag: u32, 1625 dst: *mut c::c_void, 1626) -> io::Result<()> { 1627 extern "C" { 1628 fn copyfile_state_get(state: copyfile_state_t, flag: u32, dst: *mut c::c_void) -> c::c_int; 1629 } 1630 1631 nonnegative_ret(copyfile_state_get(state, flag, dst)) 1632} 1633 1634#[cfg(any(target_os = "ios", target_os = "macos"))] 1635pub(crate) fn getpath(fd: BorrowedFd<'_>) -> io::Result<CString> { 1636 // The use of PATH_MAX is generally not encouraged, but it 1637 // is inevitable in this case because macOS defines `fcntl` with 1638 // `F_GETPATH` in terms of `MAXPATHLEN`, and there are no 1639 // alternatives. If a better method is invented, it should be used 1640 // instead. 1641 let mut buf = alloc::vec![0; c::PATH_MAX as usize]; 1642 1643 // From the [macOS `fcntl` man page]: 1644 // `F_GETPATH` - Get the path of the file descriptor `Fildes`. The argument 1645 // must be a buffer of size `MAXPATHLEN` or greater. 1646 // 1647 // [macOS `fcntl` man page]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/fcntl.2.html 1648 unsafe { 1649 ret(c::fcntl(borrowed_fd(fd), c::F_GETPATH, buf.as_mut_ptr()))?; 1650 } 1651 1652 let l = buf.iter().position(|&c| c == 0).unwrap(); 1653 buf.truncate(l); 1654 1655 // TODO: On Rust 1.56, we can use `shrink_to` here. 1656 //buf.shrink_to(l + 1); 1657 buf.shrink_to_fit(); 1658 1659 Ok(CString::new(buf).unwrap()) 1660} 1661 1662#[cfg(any(target_os = "ios", target_os = "macos"))] 1663pub(crate) fn fcntl_rdadvise(fd: BorrowedFd<'_>, offset: u64, len: u64) -> io::Result<()> { 1664 // From the [macOS `fcntl` man page]: 1665 // `F_RDADVISE` - Issue an advisory read async with no copy to user. 1666 // 1667 // The `F_RDADVISE` command operates on the following structure which holds 1668 // information passed from the user to the system: 1669 // 1670 // ```c 1671 // struct radvisory { 1672 // off_t ra_offset; /* offset into the file */ 1673 // int ra_count; /* size of the read */ 1674 // }; 1675 // ``` 1676 // 1677 // [macOS `fcntl` man page]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/fcntl.2.html 1678 let ra_offset = match offset.try_into() { 1679 Ok(len) => len, 1680 // If this conversion fails, the user is providing an offset outside 1681 // any possible file extent, so just ignore it. 1682 Err(_) => return Ok(()), 1683 }; 1684 let ra_count = match len.try_into() { 1685 Ok(len) => len, 1686 // If this conversion fails, the user is providing a dubiously large 1687 // hint which is unlikely to improve performance. 1688 Err(_) => return Ok(()), 1689 }; 1690 unsafe { 1691 let radvisory = c::radvisory { 1692 ra_offset, 1693 ra_count, 1694 }; 1695 ret(c::fcntl(borrowed_fd(fd), c::F_RDADVISE, &radvisory)) 1696 } 1697} 1698 1699#[cfg(any(target_os = "ios", target_os = "macos"))] 1700pub(crate) fn fcntl_fullfsync(fd: BorrowedFd<'_>) -> io::Result<()> { 1701 unsafe { ret(c::fcntl(borrowed_fd(fd), c::F_FULLFSYNC)) } 1702} 1703 1704/// Convert `times` from a `futimens`/`utimensat` argument into `setattrlist` 1705/// arguments. 1706#[cfg(any(target_os = "ios", target_os = "macos"))] 1707fn times_to_attrlist(times: &Timestamps) -> (c::size_t, [c::timespec; 2], Attrlist) { 1708 // ABI details. 1709 const ATTR_CMN_MODTIME: u32 = 0x0000_0400; 1710 const ATTR_CMN_ACCTIME: u32 = 0x0000_1000; 1711 const ATTR_BIT_MAP_COUNT: u16 = 5; 1712 1713 let mut times = times.clone(); 1714 1715 // If we have any `UTIME_NOW` elements, replace them with the current time. 1716 if times.last_access.tv_nsec == c::UTIME_NOW || times.last_modification.tv_nsec == c::UTIME_NOW 1717 { 1718 let now = { 1719 let mut tv = c::timeval { 1720 tv_sec: 0, 1721 tv_usec: 0, 1722 }; 1723 unsafe { 1724 let r = c::gettimeofday(&mut tv, null_mut()); 1725 assert_eq!(r, 0); 1726 } 1727 c::timespec { 1728 tv_sec: tv.tv_sec, 1729 tv_nsec: (tv.tv_usec * 1000) as _, 1730 } 1731 }; 1732 if times.last_access.tv_nsec == c::UTIME_NOW { 1733 times.last_access = now; 1734 } 1735 if times.last_modification.tv_nsec == c::UTIME_NOW { 1736 times.last_modification = now; 1737 } 1738 } 1739 1740 // Pack the return values following the rules for [`getattrlist`]. 1741 // 1742 // [`getattrlist`]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/getattrlist.2.html 1743 let mut times_size = 0; 1744 let mut attrs = Attrlist { 1745 bitmapcount: ATTR_BIT_MAP_COUNT, 1746 reserved: 0, 1747 commonattr: 0, 1748 volattr: 0, 1749 dirattr: 0, 1750 fileattr: 0, 1751 forkattr: 0, 1752 }; 1753 let mut return_times = [c::timespec { 1754 tv_sec: 0, 1755 tv_nsec: 0, 1756 }; 2]; 1757 let mut times_index = 0; 1758 if times.last_modification.tv_nsec != c::UTIME_OMIT { 1759 attrs.commonattr |= ATTR_CMN_MODTIME; 1760 return_times[times_index] = times.last_modification; 1761 times_index += 1; 1762 times_size += size_of::<c::timespec>(); 1763 } 1764 if times.last_access.tv_nsec != c::UTIME_OMIT { 1765 attrs.commonattr |= ATTR_CMN_ACCTIME; 1766 return_times[times_index] = times.last_access; 1767 times_size += size_of::<c::timespec>(); 1768 } 1769 1770 (times_size, return_times, attrs) 1771} 1772 1773/// Support type for `Attrlist`. 1774#[cfg(any(target_os = "ios", target_os = "macos"))] 1775type Attrgroup = u32; 1776 1777/// Attribute list for use with `setattrlist`. 1778#[cfg(any(target_os = "ios", target_os = "macos"))] 1779#[repr(C)] 1780struct Attrlist { 1781 bitmapcount: u16, 1782 reserved: u16, 1783 commonattr: Attrgroup, 1784 volattr: Attrgroup, 1785 dirattr: Attrgroup, 1786 fileattr: Attrgroup, 1787 forkattr: Attrgroup, 1788} 1789 1790#[cfg(any(target_os = "android", target_os = "linux"))] 1791pub(crate) fn mount( 1792 source: Option<&CStr>, 1793 target: &CStr, 1794 file_system_type: Option<&CStr>, 1795 flags: super::types::MountFlagsArg, 1796 data: Option<&CStr>, 1797) -> io::Result<()> { 1798 unsafe { 1799 ret(c::mount( 1800 source.map_or_else(null, CStr::as_ptr), 1801 target.as_ptr(), 1802 file_system_type.map_or_else(null, CStr::as_ptr), 1803 flags.0, 1804 data.map_or_else(null, CStr::as_ptr).cast(), 1805 )) 1806 } 1807} 1808