1//! POSIX-style `*at` functions. 2//! 3//! The `dirfd` argument to these functions may be a file descriptor for a 4//! directory, or the special value returned by [`cwd`]. 5//! 6//! [`cwd`]: crate::fs::cwd 7 8use crate::fd::OwnedFd; 9use crate::ffi::{CStr, CString}; 10#[cfg(not(any(target_os = "illumos", target_os = "solaris")))] 11use crate::fs::Access; 12#[cfg(any(target_os = "ios", target_os = "macos"))] 13use crate::fs::CloneFlags; 14#[cfg(not(any(target_os = "ios", target_os = "macos", target_os = "wasi")))] 15use crate::fs::FileType; 16#[cfg(any(target_os = "android", target_os = "linux"))] 17use crate::fs::RenameFlags; 18use crate::fs::{AtFlags, Mode, OFlags, Stat, Timestamps}; 19use crate::path::SMALL_PATH_BUFFER_SIZE; 20#[cfg(not(target_os = "wasi"))] 21use crate::process::{Gid, Uid}; 22use crate::{backend, io, path}; 23use alloc::vec::Vec; 24use backend::fd::{AsFd, BorrowedFd}; 25use backend::time::types::Nsecs; 26 27pub use backend::fs::types::{Dev, RawMode}; 28 29/// `UTIME_NOW` for use with [`utimensat`]. 30/// 31/// [`utimensat`]: crate::fs::utimensat 32#[cfg(not(target_os = "redox"))] 33pub const UTIME_NOW: Nsecs = backend::fs::types::UTIME_NOW as Nsecs; 34 35/// `UTIME_OMIT` for use with [`utimensat`]. 36/// 37/// [`utimensat`]: crate::fs::utimensat 38#[cfg(not(target_os = "redox"))] 39pub const UTIME_OMIT: Nsecs = backend::fs::types::UTIME_OMIT as Nsecs; 40 41/// `openat(dirfd, path, oflags, mode)`—Opens a file. 42/// 43/// POSIX guarantees that `openat` will use the lowest unused file descriptor, 44/// however it is not safe in general to rely on this, as file descriptors may 45/// be unexpectedly allocated on other threads or in libraries. 46/// 47/// The `Mode` argument is only significant when creating a file. 48/// 49/// # References 50/// - [POSIX] 51/// - [Linux] 52/// 53/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/openat.html 54/// [Linux]: https://man7.org/linux/man-pages/man2/open.2.html 55#[inline] 56pub fn openat<P: path::Arg, Fd: AsFd>( 57 dirfd: Fd, 58 path: P, 59 oflags: OFlags, 60 create_mode: Mode, 61) -> io::Result<OwnedFd> { 62 path.into_with_c_str(|path| { 63 backend::fs::syscalls::openat(dirfd.as_fd(), path, oflags, create_mode) 64 }) 65} 66 67/// `readlinkat(fd, path)`—Reads the contents of a symlink. 68/// 69/// If `reuse` is non-empty, reuse its buffer to store the result if possible. 70/// 71/// # References 72/// - [POSIX] 73/// - [Linux] 74/// 75/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/readlinkat.html 76/// [Linux]: https://man7.org/linux/man-pages/man2/readlinkat.2.html 77#[inline] 78pub fn readlinkat<P: path::Arg, Fd: AsFd, B: Into<Vec<u8>>>( 79 dirfd: Fd, 80 path: P, 81 reuse: B, 82) -> io::Result<CString> { 83 path.into_with_c_str(|path| _readlinkat(dirfd.as_fd(), path, reuse.into())) 84} 85 86fn _readlinkat(dirfd: BorrowedFd<'_>, path: &CStr, mut buffer: Vec<u8>) -> io::Result<CString> { 87 // This code would benefit from having a better way to read into 88 // uninitialized memory, but that requires `unsafe`. 89 buffer.clear(); 90 buffer.reserve(SMALL_PATH_BUFFER_SIZE); 91 buffer.resize(buffer.capacity(), 0_u8); 92 93 loop { 94 let nread = backend::fs::syscalls::readlinkat(dirfd.as_fd(), path, &mut buffer)?; 95 96 let nread = nread as usize; 97 assert!(nread <= buffer.len()); 98 if nread < buffer.len() { 99 buffer.resize(nread, 0_u8); 100 return Ok(CString::new(buffer).unwrap()); 101 } 102 buffer.reserve(1); // use `Vec` reallocation strategy to grow capacity exponentially 103 buffer.resize(buffer.capacity(), 0_u8); 104 } 105} 106 107/// `mkdirat(fd, path, mode)`—Creates a directory. 108/// 109/// # References 110/// - [POSIX] 111/// - [Linux] 112/// 113/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/mkdirat.html 114/// [Linux]: https://man7.org/linux/man-pages/man2/mkdirat.2.html 115#[inline] 116pub fn mkdirat<P: path::Arg, Fd: AsFd>(dirfd: Fd, path: P, mode: Mode) -> io::Result<()> { 117 path.into_with_c_str(|path| backend::fs::syscalls::mkdirat(dirfd.as_fd(), path, mode)) 118} 119 120/// `linkat(old_dirfd, old_path, new_dirfd, new_path, flags)`—Creates a hard 121/// link. 122/// 123/// # References 124/// - [POSIX] 125/// - [Linux] 126/// 127/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/linkat.html 128/// [Linux]: https://man7.org/linux/man-pages/man2/linkat.2.html 129#[inline] 130pub fn linkat<P: path::Arg, Q: path::Arg, PFd: AsFd, QFd: AsFd>( 131 old_dirfd: PFd, 132 old_path: P, 133 new_dirfd: QFd, 134 new_path: Q, 135 flags: AtFlags, 136) -> io::Result<()> { 137 old_path.into_with_c_str(|old_path| { 138 new_path.into_with_c_str(|new_path| { 139 backend::fs::syscalls::linkat( 140 old_dirfd.as_fd(), 141 old_path, 142 new_dirfd.as_fd(), 143 new_path, 144 flags, 145 ) 146 }) 147 }) 148} 149 150/// `unlinkat(fd, path, flags)`—Unlinks a file or remove a directory. 151/// 152/// With the [`REMOVEDIR`] flag, this removes a directory. This is in place 153/// of a `rmdirat` function. 154/// 155/// # References 156/// - [POSIX] 157/// - [Linux] 158/// 159/// [`REMOVEDIR`]: AtFlags::REMOVEDIR 160/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/unlinkat.html 161/// [Linux]: https://man7.org/linux/man-pages/man2/unlinkat.2.html 162#[inline] 163pub fn unlinkat<P: path::Arg, Fd: AsFd>(dirfd: Fd, path: P, flags: AtFlags) -> io::Result<()> { 164 path.into_with_c_str(|path| backend::fs::syscalls::unlinkat(dirfd.as_fd(), path, flags)) 165} 166 167/// `renameat(old_dirfd, old_path, new_dirfd, new_path)`—Renames a file or 168/// directory. 169/// 170/// # References 171/// - [POSIX] 172/// - [Linux] 173/// 174/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/renameat.html 175/// [Linux]: https://man7.org/linux/man-pages/man2/renameat.2.html 176#[inline] 177pub fn renameat<P: path::Arg, Q: path::Arg, PFd: AsFd, QFd: AsFd>( 178 old_dirfd: PFd, 179 old_path: P, 180 new_dirfd: QFd, 181 new_path: Q, 182) -> io::Result<()> { 183 old_path.into_with_c_str(|old_path| { 184 new_path.into_with_c_str(|new_path| { 185 backend::fs::syscalls::renameat( 186 old_dirfd.as_fd(), 187 old_path, 188 new_dirfd.as_fd(), 189 new_path, 190 ) 191 }) 192 }) 193} 194 195/// `renameat2(old_dirfd, old_path, new_dirfd, new_path, flags)`—Renames a 196/// file or directory. 197/// 198/// # References 199/// - [Linux] 200/// 201/// [Linux]: https://man7.org/linux/man-pages/man2/renameat2.2.html 202#[cfg(any(target_os = "android", target_os = "linux"))] 203#[inline] 204#[doc(alias = "renameat2")] 205pub fn renameat_with<P: path::Arg, Q: path::Arg, PFd: AsFd, QFd: AsFd>( 206 old_dirfd: PFd, 207 old_path: P, 208 new_dirfd: QFd, 209 new_path: Q, 210 flags: RenameFlags, 211) -> io::Result<()> { 212 old_path.into_with_c_str(|old_path| { 213 new_path.into_with_c_str(|new_path| { 214 backend::fs::syscalls::renameat2( 215 old_dirfd.as_fd(), 216 old_path, 217 new_dirfd.as_fd(), 218 new_path, 219 flags, 220 ) 221 }) 222 }) 223} 224 225/// `symlinkat(old_path, new_dirfd, new_path)`—Creates a symlink. 226/// 227/// # References 228/// - [POSIX] 229/// - [Linux] 230/// 231/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/symlinkat.html 232/// [Linux]: https://man7.org/linux/man-pages/man2/symlinkat.2.html 233#[inline] 234pub fn symlinkat<P: path::Arg, Q: path::Arg, Fd: AsFd>( 235 old_path: P, 236 new_dirfd: Fd, 237 new_path: Q, 238) -> io::Result<()> { 239 old_path.into_with_c_str(|old_path| { 240 new_path.into_with_c_str(|new_path| { 241 backend::fs::syscalls::symlinkat(old_path, new_dirfd.as_fd(), new_path) 242 }) 243 }) 244} 245 246/// `fstatat(dirfd, path, flags)`—Queries metadata for a file or directory. 247/// 248/// [`Mode::from_raw_mode`] and [`FileType::from_raw_mode`] may be used to 249/// interpret the `st_mode` field. 250/// 251/// # References 252/// - [POSIX] 253/// - [Linux] 254/// 255/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/fstatat.html 256/// [Linux]: https://man7.org/linux/man-pages/man2/fstatat.2.html 257/// [`Mode::from_raw_mode`]: crate::fs::Mode::from_raw_mode 258/// [`FileType::from_raw_mode`]: crate::fs::FileType::from_raw_mode 259#[inline] 260#[doc(alias = "fstatat")] 261pub fn statat<P: path::Arg, Fd: AsFd>(dirfd: Fd, path: P, flags: AtFlags) -> io::Result<Stat> { 262 path.into_with_c_str(|path| backend::fs::syscalls::statat(dirfd.as_fd(), path, flags)) 263} 264 265/// `faccessat(dirfd, path, access, flags)`—Tests permissions for a file or 266/// directory. 267/// 268/// # References 269/// - [POSIX] 270/// - [Linux] 271/// 272/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/faccessat.html 273/// [Linux]: https://man7.org/linux/man-pages/man2/faccessat.2.html 274#[cfg(not(any(target_os = "illumos", target_os = "solaris")))] 275#[inline] 276#[doc(alias = "faccessat")] 277pub fn accessat<P: path::Arg, Fd: AsFd>( 278 dirfd: Fd, 279 path: P, 280 access: Access, 281 flags: AtFlags, 282) -> io::Result<()> { 283 path.into_with_c_str(|path| backend::fs::syscalls::accessat(dirfd.as_fd(), path, access, flags)) 284} 285 286/// `utimensat(dirfd, path, times, flags)`—Sets file or directory timestamps. 287/// 288/// # References 289/// - [POSIX] 290/// - [Linux] 291/// 292/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/utimensat.html 293/// [Linux]: https://man7.org/linux/man-pages/man2/utimensat.2.html 294#[inline] 295pub fn utimensat<P: path::Arg, Fd: AsFd>( 296 dirfd: Fd, 297 path: P, 298 times: &Timestamps, 299 flags: AtFlags, 300) -> io::Result<()> { 301 path.into_with_c_str(|path| backend::fs::syscalls::utimensat(dirfd.as_fd(), path, times, flags)) 302} 303 304/// `fchmodat(dirfd, path, mode, 0)`—Sets file or directory permissions. 305/// 306/// The flags argument is fixed to 0, so `AT_SYMLINK_NOFOLLOW` is not 307/// supported. <details>Platform support for this flag varies widely.</details> 308/// 309/// This implementation does not support `O_PATH` file descriptors, even on 310/// platforms where the host libc emulates it. 311/// 312/// # References 313/// - [POSIX] 314/// - [Linux] 315/// 316/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/fchmodat.html 317/// [Linux]: https://man7.org/linux/man-pages/man2/fchmodat.2.html 318#[cfg(not(target_os = "wasi"))] 319#[inline] 320#[doc(alias = "fchmodat")] 321pub fn chmodat<P: path::Arg, Fd: AsFd>(dirfd: Fd, path: P, mode: Mode) -> io::Result<()> { 322 path.into_with_c_str(|path| backend::fs::syscalls::chmodat(dirfd.as_fd(), path, mode)) 323} 324 325/// `fclonefileat(src, dst_dir, dst, flags)`—Efficiently copies between files. 326/// 327/// # References 328/// - [Apple] 329/// 330/// [Apple]: https://opensource.apple.com/source/xnu/xnu-3789.21.4/bsd/man/man2/clonefile.2.auto.html 331#[cfg(any(target_os = "ios", target_os = "macos"))] 332#[inline] 333pub fn fclonefileat<Fd: AsFd, DstFd: AsFd, P: path::Arg>( 334 src: Fd, 335 dst_dir: DstFd, 336 dst: P, 337 flags: CloneFlags, 338) -> io::Result<()> { 339 dst.into_with_c_str(|dst| { 340 backend::fs::syscalls::fclonefileat(src.as_fd(), dst_dir.as_fd(), dst, flags) 341 }) 342} 343 344/// `mknodat(dirfd, path, mode, dev)`—Creates special or normal files. 345/// 346/// # References 347/// - [POSIX] 348/// - [Linux] 349/// 350/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/mknodat.html 351/// [Linux]: https://man7.org/linux/man-pages/man2/mknodat.2.html 352#[cfg(not(any(target_os = "ios", target_os = "macos", target_os = "wasi")))] 353#[inline] 354pub fn mknodat<P: path::Arg, Fd: AsFd>( 355 dirfd: Fd, 356 path: P, 357 file_type: FileType, 358 mode: Mode, 359 dev: Dev, 360) -> io::Result<()> { 361 path.into_with_c_str(|path| { 362 backend::fs::syscalls::mknodat(dirfd.as_fd(), path, file_type, mode, dev) 363 }) 364} 365 366/// `fchownat(dirfd, path, owner, group, flags)`—Sets file or directory 367/// ownership. 368/// 369/// # References 370/// - [POSIX] 371/// - [Linux] 372/// 373/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/fchownat.html 374/// [Linux]: https://man7.org/linux/man-pages/man2/fchownat.2.html 375#[cfg(not(target_os = "wasi"))] 376#[inline] 377#[doc(alias = "fchownat")] 378pub fn chownat<P: path::Arg, Fd: AsFd>( 379 dirfd: Fd, 380 path: P, 381 owner: Option<Uid>, 382 group: Option<Gid>, 383 flags: AtFlags, 384) -> io::Result<()> { 385 path.into_with_c_str(|path| { 386 backend::fs::syscalls::chownat(dirfd.as_fd(), path, owner, group, flags) 387 }) 388} 389