| 1 | //! file control options |
| 2 | use crate::errno::Errno; |
| 3 | #[cfg (all(target_os = "freebsd" , target_arch = "x86_64" ))] |
| 4 | use core::slice; |
| 5 | use libc::{c_int, c_uint, size_t, ssize_t}; |
| 6 | #[cfg (any( |
| 7 | target_os = "netbsd" , |
| 8 | apple_targets, |
| 9 | target_os = "dragonfly" , |
| 10 | all(target_os = "freebsd" , target_arch = "x86_64" ), |
| 11 | ))] |
| 12 | use std::ffi::CStr; |
| 13 | use std::ffi::OsString; |
| 14 | #[cfg (not(any(target_os = "redox" , target_os = "solaris" )))] |
| 15 | use std::ops::{Deref, DerefMut}; |
| 16 | #[cfg (not(target_os = "redox" ))] |
| 17 | use std::os::raw; |
| 18 | use std::os::unix::ffi::OsStringExt; |
| 19 | use std::os::unix::io::RawFd; |
| 20 | #[cfg (not(any(target_os = "redox" , target_os = "solaris" )))] |
| 21 | use std::os::unix::io::{AsRawFd, OwnedFd}; |
| 22 | #[cfg (any( |
| 23 | target_os = "netbsd" , |
| 24 | apple_targets, |
| 25 | target_os = "dragonfly" , |
| 26 | all(target_os = "freebsd" , target_arch = "x86_64" ), |
| 27 | ))] |
| 28 | use std::path::PathBuf; |
| 29 | #[cfg (any(linux_android, target_os = "freebsd" ))] |
| 30 | use std::{os::unix::io::AsFd, ptr}; |
| 31 | |
| 32 | #[cfg (feature = "fs" )] |
| 33 | use crate::{sys::stat::Mode, NixPath, Result}; |
| 34 | |
| 35 | #[cfg (any( |
| 36 | linux_android, |
| 37 | target_os = "emscripten" , |
| 38 | target_os = "fuchsia" , |
| 39 | target_os = "wasi" , |
| 40 | target_env = "uclibc" , |
| 41 | target_os = "freebsd" |
| 42 | ))] |
| 43 | #[cfg (feature = "fs" )] |
| 44 | pub use self::posix_fadvise::{posix_fadvise, PosixFadviseAdvice}; |
| 45 | |
| 46 | #[cfg (not(target_os = "redox" ))] |
| 47 | #[cfg (any(feature = "fs" , feature = "process" , feature = "user" ))] |
| 48 | libc_bitflags! { |
| 49 | /// Flags that control how the various *at syscalls behave. |
| 50 | #[cfg_attr (docsrs, doc(cfg(any(feature = "fs" , feature = "process" ))))] |
| 51 | pub struct AtFlags: c_int { |
| 52 | #[allow (missing_docs)] |
| 53 | #[doc (hidden)] |
| 54 | // Should not be used by the public API, but only internally. |
| 55 | AT_REMOVEDIR; |
| 56 | /// Used with [`linkat`](crate::unistd::linkat`) to create a link to a symbolic link's |
| 57 | /// target, instead of to the symbolic link itself. |
| 58 | AT_SYMLINK_FOLLOW; |
| 59 | /// Used with functions like [`fstatat`](crate::sys::stat::fstatat`) to operate on a link |
| 60 | /// itself, instead of the symbolic link's target. |
| 61 | AT_SYMLINK_NOFOLLOW; |
| 62 | /// Don't automount the terminal ("basename") component of pathname if it is a directory |
| 63 | /// that is an automount point. |
| 64 | #[cfg (linux_android)] |
| 65 | AT_NO_AUTOMOUNT; |
| 66 | /// If the provided path is an empty string, operate on the provided directory file |
| 67 | /// descriptor instead. |
| 68 | #[cfg (any(linux_android, target_os = "freebsd" , target_os = "hurd" ))] |
| 69 | AT_EMPTY_PATH; |
| 70 | /// Used with [`faccessat`](crate::unistd::faccessat), the checks for accessibility are |
| 71 | /// performed using the effective user and group IDs instead of the real user and group ID |
| 72 | #[cfg (not(target_os = "android" ))] |
| 73 | AT_EACCESS; |
| 74 | } |
| 75 | } |
| 76 | |
| 77 | #[cfg (any( |
| 78 | feature = "fs" , |
| 79 | feature = "term" , |
| 80 | all(feature = "fanotify" , target_os = "linux" ) |
| 81 | ))] |
| 82 | libc_bitflags!( |
| 83 | /// Configuration options for opened files. |
| 84 | #[cfg_attr (docsrs, doc(cfg(any(feature = "fs" , feature = "term" , all(feature = "fanotify" , target_os = "linux" )))))] |
| 85 | pub struct OFlag: c_int { |
| 86 | /// Mask for the access mode of the file. |
| 87 | O_ACCMODE; |
| 88 | /// Use alternate I/O semantics. |
| 89 | #[cfg (target_os = "netbsd" )] |
| 90 | O_ALT_IO; |
| 91 | /// Open the file in append-only mode. |
| 92 | O_APPEND; |
| 93 | /// Generate a signal when input or output becomes possible. |
| 94 | #[cfg (not(any( |
| 95 | solarish, |
| 96 | target_os = "aix" , |
| 97 | target_os = "haiku" |
| 98 | )))] |
| 99 | O_ASYNC; |
| 100 | /// Closes the file descriptor once an `execve` call is made. |
| 101 | /// |
| 102 | /// Also sets the file offset to the beginning of the file. |
| 103 | O_CLOEXEC; |
| 104 | /// Create the file if it does not exist. |
| 105 | O_CREAT; |
| 106 | /// Try to minimize cache effects of the I/O for this file. |
| 107 | #[cfg (any( |
| 108 | freebsdlike, |
| 109 | linux_android, |
| 110 | solarish, |
| 111 | target_os = "netbsd" |
| 112 | ))] |
| 113 | O_DIRECT; |
| 114 | /// If the specified path isn't a directory, fail. |
| 115 | O_DIRECTORY; |
| 116 | /// Implicitly follow each `write()` with an `fdatasync()`. |
| 117 | #[cfg (any(linux_android, apple_targets, target_os = "freebsd" , netbsdlike))] |
| 118 | O_DSYNC; |
| 119 | /// Error out if a file was not created. |
| 120 | O_EXCL; |
| 121 | /// Open for execute only. |
| 122 | #[cfg (target_os = "freebsd" )] |
| 123 | O_EXEC; |
| 124 | /// Open with an exclusive file lock. |
| 125 | #[cfg (any(bsd, target_os = "redox" ))] |
| 126 | O_EXLOCK; |
| 127 | /// Same as `O_SYNC`. |
| 128 | #[cfg (any(bsd, |
| 129 | all(target_os = "linux" , not(target_env = "musl" ), not(target_env = "ohos" )), |
| 130 | target_os = "redox" ))] |
| 131 | O_FSYNC; |
| 132 | /// Allow files whose sizes can't be represented in an `off_t` to be opened. |
| 133 | #[cfg (linux_android)] |
| 134 | O_LARGEFILE; |
| 135 | /// Do not update the file last access time during `read(2)`s. |
| 136 | #[cfg (linux_android)] |
| 137 | O_NOATIME; |
| 138 | /// Don't attach the device as the process' controlling terminal. |
| 139 | #[cfg (not(target_os = "redox" ))] |
| 140 | O_NOCTTY; |
| 141 | /// Same as `O_NONBLOCK`. |
| 142 | #[cfg (not(any(target_os = "redox" , target_os = "haiku" )))] |
| 143 | O_NDELAY; |
| 144 | /// `open()` will fail if the given path is a symbolic link. |
| 145 | O_NOFOLLOW; |
| 146 | /// When possible, open the file in nonblocking mode. |
| 147 | O_NONBLOCK; |
| 148 | /// Don't deliver `SIGPIPE`. |
| 149 | #[cfg (target_os = "netbsd" )] |
| 150 | O_NOSIGPIPE; |
| 151 | /// Obtain a file descriptor for low-level access. |
| 152 | /// |
| 153 | /// The file itself is not opened and other file operations will fail. |
| 154 | #[cfg (any(linux_android, target_os = "redox" , target_os = "freebsd" , target_os = "fuchsia" ))] |
| 155 | O_PATH; |
| 156 | /// Only allow reading. |
| 157 | /// |
| 158 | /// This should not be combined with `O_WRONLY` or `O_RDWR`. |
| 159 | O_RDONLY; |
| 160 | /// Allow both reading and writing. |
| 161 | /// |
| 162 | /// This should not be combined with `O_WRONLY` or `O_RDONLY`. |
| 163 | O_RDWR; |
| 164 | /// Similar to `O_DSYNC` but applies to `read`s instead. |
| 165 | #[cfg (any(target_os = "linux" , netbsdlike))] |
| 166 | O_RSYNC; |
| 167 | /// Open directory for search only. Skip search permission checks on |
| 168 | /// later `openat()` calls using the obtained file descriptor. |
| 169 | #[cfg (any( |
| 170 | apple_targets, |
| 171 | solarish, |
| 172 | target_os = "netbsd" , |
| 173 | target_os = "freebsd" , |
| 174 | target_os = "fuchsia" , |
| 175 | target_os = "emscripten" , |
| 176 | target_os = "aix" , |
| 177 | target_os = "wasi" |
| 178 | ))] |
| 179 | O_SEARCH; |
| 180 | /// Open with a shared file lock. |
| 181 | #[cfg (any(bsd, target_os = "redox" ))] |
| 182 | O_SHLOCK; |
| 183 | /// Implicitly follow each `write()` with an `fsync()`. |
| 184 | #[cfg (not(target_os = "redox" ))] |
| 185 | O_SYNC; |
| 186 | /// Create an unnamed temporary file. |
| 187 | #[cfg (linux_android)] |
| 188 | O_TMPFILE; |
| 189 | /// Truncate an existing regular file to 0 length if it allows writing. |
| 190 | O_TRUNC; |
| 191 | /// Restore default TTY attributes. |
| 192 | #[cfg (target_os = "freebsd" )] |
| 193 | O_TTY_INIT; |
| 194 | /// Only allow writing. |
| 195 | /// |
| 196 | /// This should not be combined with `O_RDONLY` or `O_RDWR`. |
| 197 | O_WRONLY; |
| 198 | } |
| 199 | ); |
| 200 | |
| 201 | /// Computes the raw fd consumed by a function of the form `*at`. |
| 202 | #[cfg (any( |
| 203 | all(feature = "fs" , not(target_os = "redox" )), |
| 204 | all(feature = "process" , linux_android), |
| 205 | all(feature = "fanotify" , target_os = "linux" ) |
| 206 | ))] |
| 207 | pub(crate) fn at_rawfd(fd: Option<RawFd>) -> raw::c_int { |
| 208 | fd.unwrap_or(default:libc::AT_FDCWD) |
| 209 | } |
| 210 | |
| 211 | feature! { |
| 212 | #![feature = "fs" ] |
| 213 | |
| 214 | /// open or create a file for reading, writing or executing |
| 215 | /// |
| 216 | /// # See Also |
| 217 | /// [`open`](https://pubs.opengroup.org/onlinepubs/9699919799/functions/open.html) |
| 218 | // The conversion is not identical on all operating systems. |
| 219 | #[allow (clippy::useless_conversion)] |
| 220 | pub fn open<P: ?Sized + NixPath>( |
| 221 | path: &P, |
| 222 | oflag: OFlag, |
| 223 | mode: Mode, |
| 224 | ) -> Result<RawFd> { |
| 225 | let fd = path.with_nix_path(|cstr| unsafe { |
| 226 | libc::open(cstr.as_ptr(), oflag.bits(), mode.bits() as c_uint) |
| 227 | })?; |
| 228 | |
| 229 | Errno::result(fd) |
| 230 | } |
| 231 | |
| 232 | /// open or create a file for reading, writing or executing |
| 233 | /// |
| 234 | /// The `openat` function is equivalent to the [`open`] function except in the case where the path |
| 235 | /// specifies a relative path. In that case, the file to be opened is determined relative to the |
| 236 | /// directory associated with the file descriptor `fd`. |
| 237 | /// |
| 238 | /// # See Also |
| 239 | /// [`openat`](https://pubs.opengroup.org/onlinepubs/9699919799/functions/openat.html) |
| 240 | // The conversion is not identical on all operating systems. |
| 241 | #[allow (clippy::useless_conversion)] |
| 242 | #[cfg (not(target_os = "redox" ))] |
| 243 | pub fn openat<P: ?Sized + NixPath>( |
| 244 | dirfd: Option<RawFd>, |
| 245 | path: &P, |
| 246 | oflag: OFlag, |
| 247 | mode: Mode, |
| 248 | ) -> Result<RawFd> { |
| 249 | let fd = path.with_nix_path(|cstr| unsafe { |
| 250 | libc::openat(at_rawfd(dirfd), cstr.as_ptr(), oflag.bits(), mode.bits() as c_uint) |
| 251 | })?; |
| 252 | Errno::result(fd) |
| 253 | } |
| 254 | |
| 255 | cfg_if::cfg_if! { |
| 256 | if #[cfg(target_os = "linux" )] { |
| 257 | libc_bitflags! { |
| 258 | /// Path resolution flags. |
| 259 | /// |
| 260 | /// See [path resolution(7)](https://man7.org/linux/man-pages/man7/path_resolution.7.html) |
| 261 | /// for details of the resolution process. |
| 262 | pub struct ResolveFlag: libc::c_ulonglong { |
| 263 | /// Do not permit the path resolution to succeed if any component of |
| 264 | /// the resolution is not a descendant of the directory indicated by |
| 265 | /// dirfd. This causes absolute symbolic links (and absolute values of |
| 266 | /// pathname) to be rejected. |
| 267 | RESOLVE_BENEATH; |
| 268 | |
| 269 | /// Treat the directory referred to by dirfd as the root directory |
| 270 | /// while resolving pathname. |
| 271 | RESOLVE_IN_ROOT; |
| 272 | |
| 273 | /// Disallow all magic-link resolution during path resolution. Magic |
| 274 | /// links are symbolic link-like objects that are most notably found |
| 275 | /// in proc(5); examples include `/proc/[pid]/exe` and `/proc/[pid]/fd/*`. |
| 276 | /// |
| 277 | /// See symlink(7) for more details. |
| 278 | RESOLVE_NO_MAGICLINKS; |
| 279 | |
| 280 | /// Disallow resolution of symbolic links during path resolution. This |
| 281 | /// option implies RESOLVE_NO_MAGICLINKS. |
| 282 | RESOLVE_NO_SYMLINKS; |
| 283 | |
| 284 | /// Disallow traversal of mount points during path resolution (including |
| 285 | /// all bind mounts). |
| 286 | RESOLVE_NO_XDEV; |
| 287 | } |
| 288 | } |
| 289 | |
| 290 | /// Specifies how [openat2] should open a pathname. |
| 291 | /// |
| 292 | /// See <https://man7.org/linux/man-pages/man2/open_how.2type.html> |
| 293 | #[repr (transparent)] |
| 294 | #[derive (Clone, Copy, Debug)] |
| 295 | pub struct OpenHow(libc::open_how); |
| 296 | |
| 297 | impl OpenHow { |
| 298 | /// Create a new zero-filled `open_how`. |
| 299 | pub fn new() -> Self { |
| 300 | // safety: according to the man page, open_how MUST be zero-initialized |
| 301 | // on init so that unknown fields are also zeroed. |
| 302 | Self(unsafe { |
| 303 | std::mem::MaybeUninit::zeroed().assume_init() |
| 304 | }) |
| 305 | } |
| 306 | |
| 307 | /// Set the open flags used to open a file, completely overwriting any |
| 308 | /// existing flags. |
| 309 | pub fn flags(mut self, flags: OFlag) -> Self { |
| 310 | let flags = flags.bits() as libc::c_ulonglong; |
| 311 | self.0.flags = flags; |
| 312 | self |
| 313 | } |
| 314 | |
| 315 | /// Set the file mode new files will be created with, overwriting any |
| 316 | /// existing flags. |
| 317 | pub fn mode(mut self, mode: Mode) -> Self { |
| 318 | let mode = mode.bits() as libc::c_ulonglong; |
| 319 | self.0.mode = mode; |
| 320 | self |
| 321 | } |
| 322 | |
| 323 | /// Set resolve flags, completely overwriting any existing flags. |
| 324 | /// |
| 325 | /// See [ResolveFlag] for more detail. |
| 326 | pub fn resolve(mut self, resolve: ResolveFlag) -> Self { |
| 327 | let resolve = resolve.bits(); |
| 328 | self.0.resolve = resolve; |
| 329 | self |
| 330 | } |
| 331 | } |
| 332 | |
| 333 | // safety: default isn't derivable because libc::open_how must be zeroed |
| 334 | impl Default for OpenHow { |
| 335 | fn default() -> Self { |
| 336 | Self::new() |
| 337 | } |
| 338 | } |
| 339 | |
| 340 | /// Open or create a file for reading, writing or executing. |
| 341 | /// |
| 342 | /// `openat2` is an extension of the [`openat`] function that allows the caller |
| 343 | /// to control how path resolution happens. |
| 344 | /// |
| 345 | /// # See also |
| 346 | /// |
| 347 | /// [openat2](https://man7.org/linux/man-pages/man2/openat2.2.html) |
| 348 | pub fn openat2<P: ?Sized + NixPath>( |
| 349 | dirfd: RawFd, |
| 350 | path: &P, |
| 351 | mut how: OpenHow, |
| 352 | ) -> Result<RawFd> { |
| 353 | let fd = path.with_nix_path(|cstr| unsafe { |
| 354 | libc::syscall( |
| 355 | libc::SYS_openat2, |
| 356 | dirfd, |
| 357 | cstr.as_ptr(), |
| 358 | &mut how as *mut OpenHow, |
| 359 | std::mem::size_of::<libc::open_how>(), |
| 360 | ) |
| 361 | })?; |
| 362 | |
| 363 | Errno::result(fd as RawFd) |
| 364 | } |
| 365 | } |
| 366 | } |
| 367 | |
| 368 | /// Change the name of a file. |
| 369 | /// |
| 370 | /// The `renameat` function is equivalent to `rename` except in the case where either `old_path` |
| 371 | /// or `new_path` specifies a relative path. In such cases, the file to be renamed (or the its new |
| 372 | /// name, respectively) is located relative to `old_dirfd` or `new_dirfd`, respectively |
| 373 | /// |
| 374 | /// # See Also |
| 375 | /// [`renameat`](https://pubs.opengroup.org/onlinepubs/9699919799/functions/rename.html) |
| 376 | #[cfg (not(target_os = "redox" ))] |
| 377 | pub fn renameat<P1: ?Sized + NixPath, P2: ?Sized + NixPath>( |
| 378 | old_dirfd: Option<RawFd>, |
| 379 | old_path: &P1, |
| 380 | new_dirfd: Option<RawFd>, |
| 381 | new_path: &P2, |
| 382 | ) -> Result<()> { |
| 383 | let res = old_path.with_nix_path(|old_cstr| { |
| 384 | new_path.with_nix_path(|new_cstr| unsafe { |
| 385 | libc::renameat( |
| 386 | at_rawfd(old_dirfd), |
| 387 | old_cstr.as_ptr(), |
| 388 | at_rawfd(new_dirfd), |
| 389 | new_cstr.as_ptr(), |
| 390 | ) |
| 391 | }) |
| 392 | })??; |
| 393 | Errno::result(res).map(drop) |
| 394 | } |
| 395 | } |
| 396 | |
| 397 | #[cfg (all(target_os = "linux" , target_env = "gnu" ))] |
| 398 | #[cfg (feature = "fs" )] |
| 399 | libc_bitflags! { |
| 400 | /// Flags for use with [`renameat2`]. |
| 401 | #[cfg_attr (docsrs, doc(cfg(feature = "fs" )))] |
| 402 | pub struct RenameFlags: u32 { |
| 403 | /// Atomically exchange `old_path` and `new_path`. |
| 404 | RENAME_EXCHANGE; |
| 405 | /// Don't overwrite `new_path` of the rename. Return an error if `new_path` already |
| 406 | /// exists. |
| 407 | RENAME_NOREPLACE; |
| 408 | /// creates a "whiteout" object at the source of the rename at the same time as performing |
| 409 | /// the rename. |
| 410 | /// |
| 411 | /// This operation makes sense only for overlay/union filesystem implementations. |
| 412 | RENAME_WHITEOUT; |
| 413 | } |
| 414 | } |
| 415 | |
| 416 | feature! { |
| 417 | #![feature = "fs" ] |
| 418 | /// Like [`renameat`], but with an additional `flags` argument. |
| 419 | /// |
| 420 | /// A `renameat2` call with an empty flags argument is equivalent to `renameat`. |
| 421 | /// |
| 422 | /// # See Also |
| 423 | /// * [`rename`](https://man7.org/linux/man-pages/man2/rename.2.html) |
| 424 | #[cfg (all(target_os = "linux" , target_env = "gnu" ))] |
| 425 | pub fn renameat2<P1: ?Sized + NixPath, P2: ?Sized + NixPath>( |
| 426 | old_dirfd: Option<RawFd>, |
| 427 | old_path: &P1, |
| 428 | new_dirfd: Option<RawFd>, |
| 429 | new_path: &P2, |
| 430 | flags: RenameFlags, |
| 431 | ) -> Result<()> { |
| 432 | let res = old_path.with_nix_path(|old_cstr| { |
| 433 | new_path.with_nix_path(|new_cstr| unsafe { |
| 434 | libc::renameat2( |
| 435 | at_rawfd(old_dirfd), |
| 436 | old_cstr.as_ptr(), |
| 437 | at_rawfd(new_dirfd), |
| 438 | new_cstr.as_ptr(), |
| 439 | flags.bits(), |
| 440 | ) |
| 441 | }) |
| 442 | })??; |
| 443 | Errno::result(res).map(drop) |
| 444 | } |
| 445 | |
| 446 | fn wrap_readlink_result(mut v: Vec<u8>, len: ssize_t) -> Result<OsString> { |
| 447 | unsafe { v.set_len(len as usize) } |
| 448 | v.shrink_to_fit(); |
| 449 | Ok(OsString::from_vec(v.to_vec())) |
| 450 | } |
| 451 | |
| 452 | fn readlink_maybe_at<P: ?Sized + NixPath>( |
| 453 | dirfd: Option<RawFd>, |
| 454 | path: &P, |
| 455 | v: &mut Vec<u8>, |
| 456 | ) -> Result<libc::ssize_t> { |
| 457 | path.with_nix_path(|cstr| unsafe { |
| 458 | match dirfd { |
| 459 | #[cfg (target_os = "redox" )] |
| 460 | Some(_) => unreachable!(), |
| 461 | #[cfg (not(target_os = "redox" ))] |
| 462 | Some(dirfd) => libc::readlinkat( |
| 463 | dirfd, |
| 464 | cstr.as_ptr(), |
| 465 | v.as_mut_ptr().cast(), |
| 466 | v.capacity() as size_t, |
| 467 | ), |
| 468 | None => libc::readlink( |
| 469 | cstr.as_ptr(), |
| 470 | v.as_mut_ptr().cast(), |
| 471 | v.capacity() as size_t, |
| 472 | ), |
| 473 | } |
| 474 | }) |
| 475 | } |
| 476 | |
| 477 | fn inner_readlink<P: ?Sized + NixPath>( |
| 478 | dirfd: Option<RawFd>, |
| 479 | path: &P, |
| 480 | ) -> Result<OsString> { |
| 481 | #[cfg (not(target_os = "hurd" ))] |
| 482 | const PATH_MAX: usize = libc::PATH_MAX as usize; |
| 483 | #[cfg (target_os = "hurd" )] |
| 484 | const PATH_MAX: usize = 1024; // Hurd does not define a hard limit, so try a guess first |
| 485 | let mut v = Vec::with_capacity(PATH_MAX); |
| 486 | |
| 487 | { |
| 488 | // simple case: result is strictly less than `PATH_MAX` |
| 489 | let res = readlink_maybe_at(dirfd, path, &mut v)?; |
| 490 | let len = Errno::result(res)?; |
| 491 | debug_assert!(len >= 0); |
| 492 | if (len as usize) < v.capacity() { |
| 493 | return wrap_readlink_result(v, res); |
| 494 | } |
| 495 | } |
| 496 | |
| 497 | // Uh oh, the result is too long... |
| 498 | // Let's try to ask lstat how many bytes to allocate. |
| 499 | let mut try_size = { |
| 500 | let reported_size = match dirfd { |
| 501 | #[cfg (target_os = "redox" )] |
| 502 | Some(_) => unreachable!(), |
| 503 | #[cfg (any(linux_android, target_os = "freebsd" , target_os = "hurd" ))] |
| 504 | Some(dirfd) => { |
| 505 | let flags = if path.is_empty() { |
| 506 | AtFlags::AT_EMPTY_PATH |
| 507 | } else { |
| 508 | AtFlags::empty() |
| 509 | }; |
| 510 | super::sys::stat::fstatat( |
| 511 | Some(dirfd), |
| 512 | path, |
| 513 | flags | AtFlags::AT_SYMLINK_NOFOLLOW, |
| 514 | ) |
| 515 | } |
| 516 | #[cfg (not(any( |
| 517 | linux_android, |
| 518 | target_os = "redox" , |
| 519 | target_os = "freebsd" , |
| 520 | target_os = "hurd" |
| 521 | )))] |
| 522 | Some(dirfd) => super::sys::stat::fstatat( |
| 523 | Some(dirfd), |
| 524 | path, |
| 525 | AtFlags::AT_SYMLINK_NOFOLLOW, |
| 526 | ), |
| 527 | None => super::sys::stat::lstat(path), |
| 528 | } |
| 529 | .map(|x| x.st_size) |
| 530 | .unwrap_or(0); |
| 531 | |
| 532 | if reported_size > 0 { |
| 533 | // Note: even if `lstat`'s apparently valid answer turns out to be |
| 534 | // wrong, we will still read the full symlink no matter what. |
| 535 | reported_size as usize + 1 |
| 536 | } else { |
| 537 | // If lstat doesn't cooperate, or reports an error, be a little less |
| 538 | // precise. |
| 539 | PATH_MAX.max(128) << 1 |
| 540 | } |
| 541 | }; |
| 542 | |
| 543 | loop { |
| 544 | { |
| 545 | v.reserve_exact(try_size); |
| 546 | let res = readlink_maybe_at(dirfd, path, &mut v)?; |
| 547 | let len = Errno::result(res)?; |
| 548 | debug_assert!(len >= 0); |
| 549 | if (len as usize) < v.capacity() { |
| 550 | return wrap_readlink_result(v, res); |
| 551 | } |
| 552 | } |
| 553 | |
| 554 | // Ugh! Still not big enough! |
| 555 | match try_size.checked_shl(1) { |
| 556 | Some(next_size) => try_size = next_size, |
| 557 | // It's absurd that this would happen, but handle it sanely |
| 558 | // anyway. |
| 559 | None => break Err(Errno::ENAMETOOLONG), |
| 560 | } |
| 561 | } |
| 562 | } |
| 563 | |
| 564 | /// Read value of a symbolic link |
| 565 | /// |
| 566 | /// # See Also |
| 567 | /// * [`readlink`](https://pubs.opengroup.org/onlinepubs/9699919799/functions/readlink.html) |
| 568 | pub fn readlink<P: ?Sized + NixPath>(path: &P) -> Result<OsString> { |
| 569 | inner_readlink(None, path) |
| 570 | } |
| 571 | |
| 572 | /// Read value of a symbolic link. |
| 573 | /// |
| 574 | /// Equivalent to [`readlink` ] except where `path` specifies a relative path. In that case, |
| 575 | /// interpret `path` relative to open file specified by `dirfd`. |
| 576 | /// |
| 577 | /// # See Also |
| 578 | /// * [`readlink`](https://pubs.opengroup.org/onlinepubs/9699919799/functions/readlink.html) |
| 579 | #[cfg (not(target_os = "redox" ))] |
| 580 | pub fn readlinkat<P: ?Sized + NixPath>( |
| 581 | dirfd: Option<RawFd>, |
| 582 | path: &P, |
| 583 | ) -> Result<OsString> { |
| 584 | let dirfd = at_rawfd(dirfd); |
| 585 | inner_readlink(Some(dirfd), path) |
| 586 | } |
| 587 | } |
| 588 | |
| 589 | #[cfg (any(linux_android, target_os = "freebsd" ))] |
| 590 | #[cfg (feature = "fs" )] |
| 591 | libc_bitflags!( |
| 592 | /// Additional flags for file sealing, which allows for limiting operations on a file. |
| 593 | #[cfg_attr (docsrs, doc(cfg(feature = "fs" )))] |
| 594 | pub struct SealFlag: c_int { |
| 595 | /// Prevents further calls to `fcntl()` with `F_ADD_SEALS`. |
| 596 | F_SEAL_SEAL; |
| 597 | /// The file cannot be reduced in size. |
| 598 | F_SEAL_SHRINK; |
| 599 | /// The size of the file cannot be increased. |
| 600 | F_SEAL_GROW; |
| 601 | /// The file contents cannot be modified. |
| 602 | F_SEAL_WRITE; |
| 603 | /// The file contents cannot be modified, except via shared writable mappings that were |
| 604 | /// created prior to the seal being set. Since Linux 5.1. |
| 605 | #[cfg (linux_android)] |
| 606 | F_SEAL_FUTURE_WRITE; |
| 607 | } |
| 608 | ); |
| 609 | |
| 610 | #[cfg (feature = "fs" )] |
| 611 | libc_bitflags!( |
| 612 | /// Additional configuration flags for `fcntl`'s `F_SETFD`. |
| 613 | #[cfg_attr (docsrs, doc(cfg(feature = "fs" )))] |
| 614 | pub struct FdFlag: c_int { |
| 615 | /// The file descriptor will automatically be closed during a successful `execve(2)`. |
| 616 | FD_CLOEXEC; |
| 617 | } |
| 618 | ); |
| 619 | |
| 620 | feature! { |
| 621 | #![feature = "fs" ] |
| 622 | |
| 623 | /// Commands for use with [`fcntl`]. |
| 624 | #[cfg (not(target_os = "redox" ))] |
| 625 | #[derive (Debug, Eq, Hash, PartialEq)] |
| 626 | #[non_exhaustive ] |
| 627 | pub enum FcntlArg<'a> { |
| 628 | /// Duplicate the provided file descriptor |
| 629 | F_DUPFD(RawFd), |
| 630 | /// Duplicate the provided file descriptor and set the `FD_CLOEXEC` flag on it. |
| 631 | F_DUPFD_CLOEXEC(RawFd), |
| 632 | /// Get the close-on-exec flag associated with the file descriptor |
| 633 | F_GETFD, |
| 634 | /// Set the close-on-exec flag associated with the file descriptor |
| 635 | F_SETFD(FdFlag), // FD_FLAGS |
| 636 | /// Get descriptor status flags |
| 637 | F_GETFL, |
| 638 | /// Set descriptor status flags |
| 639 | F_SETFL(OFlag), // O_NONBLOCK |
| 640 | /// Set or clear a file segment lock |
| 641 | F_SETLK(&'a libc::flock), |
| 642 | /// Like [`F_SETLK`](FcntlArg::F_SETLK) except that if a shared or exclusive lock is blocked by |
| 643 | /// other locks, the process waits until the request can be satisfied. |
| 644 | F_SETLKW(&'a libc::flock), |
| 645 | /// Get the first lock that blocks the lock description |
| 646 | F_GETLK(&'a mut libc::flock), |
| 647 | /// Acquire or release an open file description lock |
| 648 | #[cfg (linux_android)] |
| 649 | F_OFD_SETLK(&'a libc::flock), |
| 650 | /// Like [`F_OFD_SETLK`](FcntlArg::F_OFD_SETLK) except that if a conflicting lock is held on |
| 651 | /// the file, then wait for that lock to be released. |
| 652 | #[cfg (linux_android)] |
| 653 | F_OFD_SETLKW(&'a libc::flock), |
| 654 | /// Determine whether it would be possible to create the given lock. If not, return details |
| 655 | /// about one existing lock that would prevent it. |
| 656 | #[cfg (linux_android)] |
| 657 | F_OFD_GETLK(&'a mut libc::flock), |
| 658 | /// Add seals to the file |
| 659 | #[cfg (any( |
| 660 | linux_android, |
| 661 | target_os = "freebsd" |
| 662 | ))] |
| 663 | F_ADD_SEALS(SealFlag), |
| 664 | /// Get seals associated with the file |
| 665 | #[cfg (any( |
| 666 | linux_android, |
| 667 | target_os = "freebsd" |
| 668 | ))] |
| 669 | F_GET_SEALS, |
| 670 | /// Asks the drive to flush all buffered data to permanent storage. |
| 671 | #[cfg (apple_targets)] |
| 672 | F_FULLFSYNC, |
| 673 | /// fsync + issue barrier to drive |
| 674 | #[cfg (apple_targets)] |
| 675 | F_BARRIERFSYNC, |
| 676 | /// Return the capacity of a pipe |
| 677 | #[cfg (linux_android)] |
| 678 | F_GETPIPE_SZ, |
| 679 | /// Change the capacity of a pipe |
| 680 | #[cfg (linux_android)] |
| 681 | F_SETPIPE_SZ(c_int), |
| 682 | /// Look up the path of an open file descriptor, if possible. |
| 683 | #[cfg (any( |
| 684 | target_os = "netbsd" , |
| 685 | target_os = "dragonfly" , |
| 686 | apple_targets, |
| 687 | ))] |
| 688 | F_GETPATH(&'a mut PathBuf), |
| 689 | /// Look up the path of an open file descriptor, if possible. |
| 690 | #[cfg (all(target_os = "freebsd" , target_arch = "x86_64" ))] |
| 691 | F_KINFO(&'a mut PathBuf), |
| 692 | /// Return the full path without firmlinks of the fd. |
| 693 | #[cfg (apple_targets)] |
| 694 | F_GETPATH_NOFIRMLINK(&'a mut PathBuf), |
| 695 | // TODO: Rest of flags |
| 696 | } |
| 697 | |
| 698 | /// Commands for use with [`fcntl`]. |
| 699 | #[cfg (target_os = "redox" )] |
| 700 | #[derive (Debug, Clone, Copy, Eq, Hash, PartialEq)] |
| 701 | #[non_exhaustive ] |
| 702 | pub enum FcntlArg { |
| 703 | /// Duplicate the provided file descriptor |
| 704 | F_DUPFD(RawFd), |
| 705 | /// Duplicate the provided file descriptor and set the `FD_CLOEXEC` flag on it. |
| 706 | F_DUPFD_CLOEXEC(RawFd), |
| 707 | /// Get the close-on-exec flag associated with the file descriptor |
| 708 | F_GETFD, |
| 709 | /// Set the close-on-exec flag associated with the file descriptor |
| 710 | F_SETFD(FdFlag), // FD_FLAGS |
| 711 | /// Get descriptor status flags |
| 712 | F_GETFL, |
| 713 | /// Set descriptor status flags |
| 714 | F_SETFL(OFlag), // O_NONBLOCK |
| 715 | } |
| 716 | pub use self::FcntlArg::*; |
| 717 | |
| 718 | /// Perform various operations on open file descriptors. |
| 719 | /// |
| 720 | /// # See Also |
| 721 | /// * [`fcntl`](https://pubs.opengroup.org/onlinepubs/9699919799/functions/fcntl.html) |
| 722 | // TODO: Figure out how to handle value fcntl returns |
| 723 | pub fn fcntl(fd: RawFd, arg: FcntlArg) -> Result<c_int> { |
| 724 | let res = unsafe { |
| 725 | match arg { |
| 726 | F_DUPFD(rawfd) => libc::fcntl(fd, libc::F_DUPFD, rawfd), |
| 727 | F_DUPFD_CLOEXEC(rawfd) => { |
| 728 | libc::fcntl(fd, libc::F_DUPFD_CLOEXEC, rawfd) |
| 729 | } |
| 730 | F_GETFD => libc::fcntl(fd, libc::F_GETFD), |
| 731 | F_SETFD(flag) => libc::fcntl(fd, libc::F_SETFD, flag.bits()), |
| 732 | F_GETFL => libc::fcntl(fd, libc::F_GETFL), |
| 733 | F_SETFL(flag) => libc::fcntl(fd, libc::F_SETFL, flag.bits()), |
| 734 | #[cfg (not(target_os = "redox" ))] |
| 735 | F_SETLK(flock) => libc::fcntl(fd, libc::F_SETLK, flock), |
| 736 | #[cfg (not(target_os = "redox" ))] |
| 737 | F_SETLKW(flock) => libc::fcntl(fd, libc::F_SETLKW, flock), |
| 738 | #[cfg (not(target_os = "redox" ))] |
| 739 | F_GETLK(flock) => libc::fcntl(fd, libc::F_GETLK, flock), |
| 740 | #[cfg (linux_android)] |
| 741 | F_OFD_SETLK(flock) => libc::fcntl(fd, libc::F_OFD_SETLK, flock), |
| 742 | #[cfg (linux_android)] |
| 743 | F_OFD_SETLKW(flock) => libc::fcntl(fd, libc::F_OFD_SETLKW, flock), |
| 744 | #[cfg (linux_android)] |
| 745 | F_OFD_GETLK(flock) => libc::fcntl(fd, libc::F_OFD_GETLK, flock), |
| 746 | #[cfg (any( |
| 747 | linux_android, |
| 748 | target_os = "freebsd" |
| 749 | ))] |
| 750 | F_ADD_SEALS(flag) => { |
| 751 | libc::fcntl(fd, libc::F_ADD_SEALS, flag.bits()) |
| 752 | } |
| 753 | #[cfg (any( |
| 754 | linux_android, |
| 755 | target_os = "freebsd" |
| 756 | ))] |
| 757 | F_GET_SEALS => libc::fcntl(fd, libc::F_GET_SEALS), |
| 758 | #[cfg (apple_targets)] |
| 759 | F_FULLFSYNC => libc::fcntl(fd, libc::F_FULLFSYNC), |
| 760 | #[cfg (apple_targets)] |
| 761 | F_BARRIERFSYNC => libc::fcntl(fd, libc::F_BARRIERFSYNC), |
| 762 | #[cfg (linux_android)] |
| 763 | F_GETPIPE_SZ => libc::fcntl(fd, libc::F_GETPIPE_SZ), |
| 764 | #[cfg (linux_android)] |
| 765 | F_SETPIPE_SZ(size) => libc::fcntl(fd, libc::F_SETPIPE_SZ, size), |
| 766 | #[cfg (any( |
| 767 | target_os = "dragonfly" , |
| 768 | target_os = "netbsd" , |
| 769 | apple_targets, |
| 770 | ))] |
| 771 | F_GETPATH(path) => { |
| 772 | let mut buffer = vec![0; libc::PATH_MAX as usize]; |
| 773 | let res = libc::fcntl(fd, libc::F_GETPATH, buffer.as_mut_ptr()); |
| 774 | let ok_res = Errno::result(res)?; |
| 775 | let optr = CStr::from_bytes_until_nul(&buffer).unwrap(); |
| 776 | *path = PathBuf::from(OsString::from(optr.to_str().unwrap())); |
| 777 | return Ok(ok_res) |
| 778 | }, |
| 779 | #[cfg (all(target_os = "freebsd" , target_arch = "x86_64" ))] |
| 780 | F_KINFO(path) => { |
| 781 | let mut info: libc::kinfo_file = std::mem::zeroed(); |
| 782 | info.kf_structsize = std::mem::size_of::<libc::kinfo_file>() as i32; |
| 783 | let res = libc::fcntl(fd, libc::F_KINFO, &mut info); |
| 784 | let ok_res = Errno::result(res)?; |
| 785 | let p = info.kf_path; |
| 786 | let u8_slice = slice::from_raw_parts(p.as_ptr().cast(), p.len()); |
| 787 | let optr = CStr::from_bytes_until_nul(u8_slice).unwrap(); |
| 788 | *path = PathBuf::from(OsString::from(optr.to_str().unwrap())); |
| 789 | return Ok(ok_res) |
| 790 | }, |
| 791 | #[cfg (apple_targets)] |
| 792 | F_GETPATH_NOFIRMLINK(path) => { |
| 793 | let mut buffer = vec![0; libc::PATH_MAX as usize]; |
| 794 | let res = libc::fcntl(fd, libc::F_GETPATH_NOFIRMLINK, buffer.as_mut_ptr()); |
| 795 | let ok_res = Errno::result(res)?; |
| 796 | let optr = CStr::from_bytes_until_nul(&buffer).unwrap(); |
| 797 | *path = PathBuf::from(OsString::from(optr.to_str().unwrap())); |
| 798 | return Ok(ok_res) |
| 799 | }, |
| 800 | } |
| 801 | }; |
| 802 | |
| 803 | Errno::result(res) |
| 804 | } |
| 805 | |
| 806 | /// Operations for use with [`Flock::lock`]. |
| 807 | #[cfg (not(any(target_os = "redox" , target_os = "solaris" )))] |
| 808 | #[derive (Clone, Copy, Debug, Eq, Hash, PartialEq)] |
| 809 | #[non_exhaustive ] |
| 810 | pub enum FlockArg { |
| 811 | /// shared file lock |
| 812 | LockShared, |
| 813 | /// exclusive file lock |
| 814 | LockExclusive, |
| 815 | /// Unlock file |
| 816 | Unlock, |
| 817 | /// Shared lock. Do not block when locking. |
| 818 | LockSharedNonblock, |
| 819 | /// Exclusive lock. Do not block when locking. |
| 820 | LockExclusiveNonblock, |
| 821 | #[allow (missing_docs)] |
| 822 | #[deprecated (since = "0.28.0" , note = "Use FlockArg::Unlock instead" )] |
| 823 | UnlockNonblock, |
| 824 | } |
| 825 | |
| 826 | #[allow (missing_docs)] |
| 827 | #[cfg (not(any(target_os = "redox" , target_os = "solaris" )))] |
| 828 | #[deprecated (since = "0.28.0" , note = "`fcntl::Flock` should be used instead." )] |
| 829 | pub fn flock(fd: RawFd, arg: FlockArg) -> Result<()> { |
| 830 | use self::FlockArg::*; |
| 831 | |
| 832 | let res = unsafe { |
| 833 | match arg { |
| 834 | LockShared => libc::flock(fd, libc::LOCK_SH), |
| 835 | LockExclusive => libc::flock(fd, libc::LOCK_EX), |
| 836 | Unlock => libc::flock(fd, libc::LOCK_UN), |
| 837 | LockSharedNonblock => { |
| 838 | libc::flock(fd, libc::LOCK_SH | libc::LOCK_NB) |
| 839 | } |
| 840 | LockExclusiveNonblock => { |
| 841 | libc::flock(fd, libc::LOCK_EX | libc::LOCK_NB) |
| 842 | } |
| 843 | #[allow (deprecated)] |
| 844 | UnlockNonblock => libc::flock(fd, libc::LOCK_UN | libc::LOCK_NB), |
| 845 | } |
| 846 | }; |
| 847 | |
| 848 | Errno::result(res).map(drop) |
| 849 | } |
| 850 | |
| 851 | /// Represents valid types for flock. |
| 852 | /// |
| 853 | /// # Safety |
| 854 | /// Types implementing this must not be `Clone`. |
| 855 | #[cfg (not(any(target_os = "redox" , target_os = "solaris" )))] |
| 856 | pub unsafe trait Flockable: AsRawFd {} |
| 857 | |
| 858 | /// Represents an owned flock, which unlocks on drop. |
| 859 | /// |
| 860 | /// See [flock(2)](https://linux.die.net/man/2/flock) for details on locking semantics. |
| 861 | #[cfg (not(any(target_os = "redox" , target_os = "solaris" )))] |
| 862 | #[derive (Debug)] |
| 863 | pub struct Flock<T: Flockable>(T); |
| 864 | |
| 865 | #[cfg (not(any(target_os = "redox" , target_os = "solaris" )))] |
| 866 | impl<T: Flockable> Drop for Flock<T> { |
| 867 | fn drop(&mut self) { |
| 868 | let res = Errno::result(unsafe { libc::flock(self.0.as_raw_fd(), libc::LOCK_UN) }); |
| 869 | if res.is_err() && !std::thread::panicking() { |
| 870 | panic!("Failed to remove flock: {}" , res.unwrap_err()); |
| 871 | } |
| 872 | } |
| 873 | } |
| 874 | |
| 875 | #[cfg (not(any(target_os = "redox" , target_os = "solaris" )))] |
| 876 | impl<T: Flockable> Deref for Flock<T> { |
| 877 | type Target = T; |
| 878 | |
| 879 | fn deref(&self) -> &Self::Target { |
| 880 | &self.0 |
| 881 | } |
| 882 | } |
| 883 | #[cfg (not(any(target_os = "redox" , target_os = "solaris" )))] |
| 884 | impl<T: Flockable> DerefMut for Flock<T> { |
| 885 | fn deref_mut(&mut self) -> &mut Self::Target { |
| 886 | &mut self.0 |
| 887 | } |
| 888 | } |
| 889 | |
| 890 | #[cfg (not(any(target_os = "redox" , target_os = "solaris" )))] |
| 891 | impl<T: Flockable> Flock<T> { |
| 892 | /// Obtain a/an flock. |
| 893 | /// |
| 894 | /// # Example |
| 895 | /// ``` |
| 896 | /// # use std::io::Write; |
| 897 | /// # use std::fs::File; |
| 898 | /// # use nix::fcntl::{Flock, FlockArg}; |
| 899 | /// # fn do_stuff(file: File) { |
| 900 | /// let mut file = match Flock::lock(file, FlockArg::LockExclusive) { |
| 901 | /// Ok(l) => l, |
| 902 | /// Err(_) => return, |
| 903 | /// }; |
| 904 | /// |
| 905 | /// // Do stuff |
| 906 | /// let data = "Foo bar"; |
| 907 | /// _ = file.write(data.as_bytes()); |
| 908 | /// _ = file.sync_data(); |
| 909 | /// # } |
| 910 | pub fn lock(t: T, args: FlockArg) -> std::result::Result<Self, (T, Errno)> { |
| 911 | let flags = match args { |
| 912 | FlockArg::LockShared => libc::LOCK_SH, |
| 913 | FlockArg::LockExclusive => libc::LOCK_EX, |
| 914 | FlockArg::LockSharedNonblock => libc::LOCK_SH | libc::LOCK_NB, |
| 915 | FlockArg::LockExclusiveNonblock => libc::LOCK_EX | libc::LOCK_NB, |
| 916 | #[allow (deprecated)] |
| 917 | FlockArg::Unlock | FlockArg::UnlockNonblock => return Err((t, Errno::EINVAL)), |
| 918 | }; |
| 919 | match Errno::result(unsafe { libc::flock(t.as_raw_fd(), flags) }) { |
| 920 | Ok(_) => Ok(Self(t)), |
| 921 | Err(errno) => Err((t, errno)), |
| 922 | } |
| 923 | } |
| 924 | |
| 925 | /// Remove the lock and return the object wrapped within. |
| 926 | /// |
| 927 | /// # Example |
| 928 | /// ``` |
| 929 | /// # use std::fs::File; |
| 930 | /// # use nix::fcntl::{Flock, FlockArg}; |
| 931 | /// fn do_stuff(file: File) -> nix::Result<()> { |
| 932 | /// let mut lock = match Flock::lock(file, FlockArg::LockExclusive) { |
| 933 | /// Ok(l) => l, |
| 934 | /// Err((_,e)) => return Err(e), |
| 935 | /// }; |
| 936 | /// |
| 937 | /// // Do critical section |
| 938 | /// |
| 939 | /// // Unlock |
| 940 | /// let file = match lock.unlock() { |
| 941 | /// Ok(f) => f, |
| 942 | /// Err((_, e)) => return Err(e), |
| 943 | /// }; |
| 944 | /// |
| 945 | /// // Do anything else |
| 946 | /// |
| 947 | /// Ok(()) |
| 948 | /// } |
| 949 | pub fn unlock(self) -> std::result::Result<T, (Self, Errno)> { |
| 950 | let inner = unsafe { match Errno::result(libc::flock(self.0.as_raw_fd(), libc::LOCK_UN)) { |
| 951 | Ok(_) => std::ptr::read(&self.0), |
| 952 | Err(errno) => return Err((self, errno)), |
| 953 | }}; |
| 954 | |
| 955 | std::mem::forget(self); |
| 956 | Ok(inner) |
| 957 | } |
| 958 | |
| 959 | /// Relock the file. This can upgrade or downgrade the lock type. |
| 960 | /// |
| 961 | /// # Example |
| 962 | /// ``` |
| 963 | /// # use std::fs::File; |
| 964 | /// # use nix::fcntl::{Flock, FlockArg}; |
| 965 | /// # use tempfile::tempfile; |
| 966 | /// let f: std::fs::File = tempfile().unwrap(); |
| 967 | /// let locked_file = Flock::lock(f, FlockArg::LockExclusive).unwrap(); |
| 968 | /// // Do stuff, then downgrade the lock |
| 969 | /// locked_file.relock(FlockArg::LockShared).unwrap(); |
| 970 | /// ``` |
| 971 | pub fn relock(&self, arg: FlockArg) -> Result<()> { |
| 972 | let flags = match arg { |
| 973 | FlockArg::LockShared => libc::LOCK_SH, |
| 974 | FlockArg::LockExclusive => libc::LOCK_EX, |
| 975 | FlockArg::LockSharedNonblock => libc::LOCK_SH | libc::LOCK_NB, |
| 976 | FlockArg::LockExclusiveNonblock => libc::LOCK_EX | libc::LOCK_NB, |
| 977 | #[allow (deprecated)] |
| 978 | FlockArg::Unlock | FlockArg::UnlockNonblock => return Err(Errno::EINVAL), |
| 979 | }; |
| 980 | Errno::result(unsafe { libc::flock(self.as_raw_fd(), flags) }).map(drop) |
| 981 | } |
| 982 | } |
| 983 | |
| 984 | // Safety: `File` is not [std::clone::Clone]. |
| 985 | #[cfg (not(any(target_os = "redox" , target_os = "solaris" )))] |
| 986 | unsafe impl Flockable for std::fs::File {} |
| 987 | |
| 988 | // Safety: `OwnedFd` is not [std::clone::Clone]. |
| 989 | #[cfg (not(any(target_os = "redox" , target_os = "solaris" )))] |
| 990 | unsafe impl Flockable for OwnedFd {} |
| 991 | } |
| 992 | |
| 993 | #[cfg (linux_android)] |
| 994 | #[cfg (feature = "zerocopy" )] |
| 995 | libc_bitflags! { |
| 996 | /// Additional flags to `splice` and friends. |
| 997 | #[cfg_attr(docsrs, doc(cfg(feature = "zerocopy" )))] |
| 998 | pub struct SpliceFFlags: c_uint { |
| 999 | /// Request that pages be moved instead of copied. |
| 1000 | /// |
| 1001 | /// Not applicable to `vmsplice`. |
| 1002 | SPLICE_F_MOVE; |
| 1003 | /// Do not block on I/O. |
| 1004 | SPLICE_F_NONBLOCK; |
| 1005 | /// Hint that more data will be coming in a subsequent splice. |
| 1006 | /// |
| 1007 | /// Not applicable to `vmsplice`. |
| 1008 | SPLICE_F_MORE; |
| 1009 | /// Gift the user pages to the kernel. |
| 1010 | /// |
| 1011 | /// Not applicable to `splice`. |
| 1012 | SPLICE_F_GIFT; |
| 1013 | } |
| 1014 | } |
| 1015 | |
| 1016 | feature! { |
| 1017 | #![feature = "zerocopy" ] |
| 1018 | |
| 1019 | /// Copy a range of data from one file to another |
| 1020 | /// |
| 1021 | /// The `copy_file_range` system call performs an in-kernel copy between |
| 1022 | /// file descriptors `fd_in` and `fd_out` without the additional cost of |
| 1023 | /// transferring data from the kernel to user space and back again. There may be |
| 1024 | /// additional optimizations for specific file systems. It copies up to `len` |
| 1025 | /// bytes of data from file descriptor `fd_in` to file descriptor `fd_out`, |
| 1026 | /// overwriting any data that exists within the requested range of the target |
| 1027 | /// file. |
| 1028 | /// |
| 1029 | /// If the `off_in` and/or `off_out` arguments are used, the values |
| 1030 | /// will be mutated to reflect the new position within the file after |
| 1031 | /// copying. If they are not used, the relevant file descriptors will be seeked |
| 1032 | /// to the new position. |
| 1033 | /// |
| 1034 | /// On successful completion the number of bytes actually copied will be |
| 1035 | /// returned. |
| 1036 | // Note: FreeBSD defines the offset argument as "off_t". Linux and Android |
| 1037 | // define it as "loff_t". But on both OSes, on all supported platforms, those |
| 1038 | // are 64 bits. So Nix uses i64 to make the docs simple and consistent. |
| 1039 | #[cfg (any(linux_android, target_os = "freebsd" ))] |
| 1040 | pub fn copy_file_range<Fd1: AsFd, Fd2: AsFd>( |
| 1041 | fd_in: Fd1, |
| 1042 | off_in: Option<&mut i64>, |
| 1043 | fd_out: Fd2, |
| 1044 | off_out: Option<&mut i64>, |
| 1045 | len: usize, |
| 1046 | ) -> Result<usize> { |
| 1047 | let off_in = off_in |
| 1048 | .map(|offset| offset as *mut i64) |
| 1049 | .unwrap_or(ptr::null_mut()); |
| 1050 | let off_out = off_out |
| 1051 | .map(|offset| offset as *mut i64) |
| 1052 | .unwrap_or(ptr::null_mut()); |
| 1053 | |
| 1054 | cfg_if::cfg_if! { |
| 1055 | if #[cfg(target_os = "freebsd" )] { |
| 1056 | let ret = unsafe { |
| 1057 | libc::copy_file_range( |
| 1058 | fd_in.as_fd().as_raw_fd(), |
| 1059 | off_in, |
| 1060 | fd_out.as_fd().as_raw_fd(), |
| 1061 | off_out, |
| 1062 | len, |
| 1063 | 0, |
| 1064 | ) |
| 1065 | }; |
| 1066 | } else { |
| 1067 | // May Linux distros still don't include copy_file_range in their |
| 1068 | // libc implementations, so we need to make a direct syscall. |
| 1069 | let ret = unsafe { |
| 1070 | libc::syscall( |
| 1071 | libc::SYS_copy_file_range, |
| 1072 | fd_in.as_fd().as_raw_fd(), |
| 1073 | off_in, |
| 1074 | fd_out.as_fd().as_raw_fd(), |
| 1075 | off_out, |
| 1076 | len, |
| 1077 | 0, |
| 1078 | ) |
| 1079 | }; |
| 1080 | } |
| 1081 | } |
| 1082 | Errno::result(ret).map(|r| r as usize) |
| 1083 | } |
| 1084 | |
| 1085 | /// Splice data to/from a pipe |
| 1086 | /// |
| 1087 | /// # See Also |
| 1088 | /// *[`splice`](https://man7.org/linux/man-pages/man2/splice.2.html) |
| 1089 | #[cfg (linux_android)] |
| 1090 | pub fn splice<Fd1: AsFd, Fd2: AsFd>( |
| 1091 | fd_in: Fd1, |
| 1092 | off_in: Option<&mut libc::loff_t>, |
| 1093 | fd_out: Fd2, |
| 1094 | off_out: Option<&mut libc::loff_t>, |
| 1095 | len: usize, |
| 1096 | flags: SpliceFFlags, |
| 1097 | ) -> Result<usize> { |
| 1098 | let off_in = off_in |
| 1099 | .map(|offset| offset as *mut libc::loff_t) |
| 1100 | .unwrap_or(ptr::null_mut()); |
| 1101 | let off_out = off_out |
| 1102 | .map(|offset| offset as *mut libc::loff_t) |
| 1103 | .unwrap_or(ptr::null_mut()); |
| 1104 | |
| 1105 | let ret = unsafe { |
| 1106 | libc::splice(fd_in.as_fd().as_raw_fd(), off_in, fd_out.as_fd().as_raw_fd(), off_out, len, flags.bits()) |
| 1107 | }; |
| 1108 | Errno::result(ret).map(|r| r as usize) |
| 1109 | } |
| 1110 | |
| 1111 | /// Duplicate pipe content |
| 1112 | /// |
| 1113 | /// # See Also |
| 1114 | /// *[`tee`](https://man7.org/linux/man-pages/man2/tee.2.html) |
| 1115 | #[cfg (linux_android)] |
| 1116 | pub fn tee<Fd1: AsFd, Fd2: AsFd>( |
| 1117 | fd_in: Fd1, |
| 1118 | fd_out: Fd2, |
| 1119 | len: usize, |
| 1120 | flags: SpliceFFlags, |
| 1121 | ) -> Result<usize> { |
| 1122 | let ret = unsafe { libc::tee(fd_in.as_fd().as_raw_fd(), fd_out.as_fd().as_raw_fd(), len, flags.bits()) }; |
| 1123 | Errno::result(ret).map(|r| r as usize) |
| 1124 | } |
| 1125 | |
| 1126 | /// Splice user pages to/from a pipe |
| 1127 | /// |
| 1128 | /// # See Also |
| 1129 | /// *[`vmsplice`](https://man7.org/linux/man-pages/man2/vmsplice.2.html) |
| 1130 | #[cfg (linux_android)] |
| 1131 | pub fn vmsplice<F: AsFd>( |
| 1132 | fd: F, |
| 1133 | iov: &[std::io::IoSlice<'_>], |
| 1134 | flags: SpliceFFlags, |
| 1135 | ) -> Result<usize> { |
| 1136 | let ret = unsafe { |
| 1137 | libc::vmsplice( |
| 1138 | fd.as_fd().as_raw_fd(), |
| 1139 | iov.as_ptr().cast(), |
| 1140 | iov.len(), |
| 1141 | flags.bits(), |
| 1142 | ) |
| 1143 | }; |
| 1144 | Errno::result(ret).map(|r| r as usize) |
| 1145 | } |
| 1146 | } |
| 1147 | |
| 1148 | #[cfg (target_os = "linux" )] |
| 1149 | #[cfg (feature = "fs" )] |
| 1150 | libc_bitflags!( |
| 1151 | /// Mode argument flags for fallocate determining operation performed on a given range. |
| 1152 | #[cfg_attr (docsrs, doc(cfg(feature = "fs" )))] |
| 1153 | pub struct FallocateFlags: c_int { |
| 1154 | /// File size is not changed. |
| 1155 | /// |
| 1156 | /// offset + len can be greater than file size. |
| 1157 | FALLOC_FL_KEEP_SIZE; |
| 1158 | /// Deallocates space by creating a hole. |
| 1159 | /// |
| 1160 | /// Must be ORed with FALLOC_FL_KEEP_SIZE. Byte range starts at offset and continues for len bytes. |
| 1161 | FALLOC_FL_PUNCH_HOLE; |
| 1162 | /// Removes byte range from a file without leaving a hole. |
| 1163 | /// |
| 1164 | /// Byte range to collapse starts at offset and continues for len bytes. |
| 1165 | FALLOC_FL_COLLAPSE_RANGE; |
| 1166 | /// Zeroes space in specified byte range. |
| 1167 | /// |
| 1168 | /// Byte range starts at offset and continues for len bytes. |
| 1169 | FALLOC_FL_ZERO_RANGE; |
| 1170 | /// Increases file space by inserting a hole within the file size. |
| 1171 | /// |
| 1172 | /// Does not overwrite existing data. Hole starts at offset and continues for len bytes. |
| 1173 | FALLOC_FL_INSERT_RANGE; |
| 1174 | /// Shared file data extants are made private to the file. |
| 1175 | /// |
| 1176 | /// Gaurantees that a subsequent write will not fail due to lack of space. |
| 1177 | FALLOC_FL_UNSHARE_RANGE; |
| 1178 | } |
| 1179 | ); |
| 1180 | |
| 1181 | feature! { |
| 1182 | #![feature = "fs" ] |
| 1183 | |
| 1184 | /// Manipulates file space. |
| 1185 | /// |
| 1186 | /// Allows the caller to directly manipulate the allocated disk space for the |
| 1187 | /// file referred to by fd. |
| 1188 | #[cfg (target_os = "linux" )] |
| 1189 | #[cfg (feature = "fs" )] |
| 1190 | pub fn fallocate( |
| 1191 | fd: RawFd, |
| 1192 | mode: FallocateFlags, |
| 1193 | offset: libc::off_t, |
| 1194 | len: libc::off_t, |
| 1195 | ) -> Result<()> { |
| 1196 | let res = unsafe { libc::fallocate(fd, mode.bits(), offset, len) }; |
| 1197 | Errno::result(res).map(drop) |
| 1198 | } |
| 1199 | |
| 1200 | /// Argument to [`fspacectl`] describing the range to zero. The first member is |
| 1201 | /// the file offset, and the second is the length of the region. |
| 1202 | #[cfg (any(target_os = "freebsd" ))] |
| 1203 | #[derive (Clone, Copy, Debug, Eq, PartialEq)] |
| 1204 | pub struct SpacectlRange(pub libc::off_t, pub libc::off_t); |
| 1205 | |
| 1206 | #[cfg (any(target_os = "freebsd" ))] |
| 1207 | impl SpacectlRange { |
| 1208 | /// Is the range empty? |
| 1209 | /// |
| 1210 | /// After a successful call to [`fspacectl`], A value of `true` for `SpacectlRange::is_empty` |
| 1211 | /// indicates that the operation is complete. |
| 1212 | #[inline ] |
| 1213 | pub fn is_empty(&self) -> bool { |
| 1214 | self.1 == 0 |
| 1215 | } |
| 1216 | |
| 1217 | /// Remaining length of the range |
| 1218 | #[inline ] |
| 1219 | pub fn len(&self) -> libc::off_t { |
| 1220 | self.1 |
| 1221 | } |
| 1222 | |
| 1223 | /// Next file offset to operate on |
| 1224 | #[inline ] |
| 1225 | pub fn offset(&self) -> libc::off_t { |
| 1226 | self.0 |
| 1227 | } |
| 1228 | } |
| 1229 | |
| 1230 | /// Punch holes in a file. |
| 1231 | /// |
| 1232 | /// `fspacectl` instructs the file system to deallocate a portion of a file. |
| 1233 | /// After a successful operation, this region of the file will return all zeroes |
| 1234 | /// if read. If the file system supports deallocation, then it may free the |
| 1235 | /// underlying storage, too. |
| 1236 | /// |
| 1237 | /// # Arguments |
| 1238 | /// |
| 1239 | /// - `fd` - File to operate on |
| 1240 | /// - `range.0` - File offset at which to begin deallocation |
| 1241 | /// - `range.1` - Length of the region to deallocate |
| 1242 | /// |
| 1243 | /// # Returns |
| 1244 | /// |
| 1245 | /// The operation may deallocate less than the entire requested region. On |
| 1246 | /// success, it returns the region that still remains to be deallocated. The |
| 1247 | /// caller should loop until the returned region is empty. |
| 1248 | /// |
| 1249 | /// # Example |
| 1250 | /// |
| 1251 | #[cfg_attr (fbsd14, doc = " ```" )] |
| 1252 | #[cfg_attr (not(fbsd14), doc = " ```no_run" )] |
| 1253 | /// # use std::io::Write; |
| 1254 | /// # use std::os::unix::fs::FileExt; |
| 1255 | /// # use std::os::unix::io::AsRawFd; |
| 1256 | /// # use nix::fcntl::*; |
| 1257 | /// # use tempfile::tempfile; |
| 1258 | /// const INITIAL: &[u8] = b"0123456789abcdef"; |
| 1259 | /// let mut f = tempfile().unwrap(); |
| 1260 | /// f.write_all(INITIAL).unwrap(); |
| 1261 | /// let mut range = SpacectlRange(3, 6); |
| 1262 | /// while (!range.is_empty()) { |
| 1263 | /// range = fspacectl(f.as_raw_fd(), range).unwrap(); |
| 1264 | /// } |
| 1265 | /// let mut buf = vec![0; INITIAL.len()]; |
| 1266 | /// f.read_exact_at(&mut buf, 0).unwrap(); |
| 1267 | /// assert_eq!(buf, b"012\0\0\0\0\0\09abcdef"); |
| 1268 | /// ``` |
| 1269 | #[cfg (target_os = "freebsd" )] |
| 1270 | #[inline ] // Delays codegen, preventing linker errors with dylibs and --no-allow-shlib-undefined |
| 1271 | pub fn fspacectl(fd: RawFd, range: SpacectlRange) -> Result<SpacectlRange> { |
| 1272 | let mut rqsr = libc::spacectl_range { |
| 1273 | r_offset: range.0, |
| 1274 | r_len: range.1, |
| 1275 | }; |
| 1276 | let res = unsafe { |
| 1277 | libc::fspacectl( |
| 1278 | fd, |
| 1279 | libc::SPACECTL_DEALLOC, // Only one command is supported ATM |
| 1280 | &rqsr, |
| 1281 | 0, // No flags are currently supported |
| 1282 | &mut rqsr, |
| 1283 | ) |
| 1284 | }; |
| 1285 | Errno::result(res).map(|_| SpacectlRange(rqsr.r_offset, rqsr.r_len)) |
| 1286 | } |
| 1287 | |
| 1288 | /// Like [`fspacectl`], but will never return incomplete. |
| 1289 | /// |
| 1290 | /// # Arguments |
| 1291 | /// |
| 1292 | /// - `fd` - File to operate on |
| 1293 | /// - `offset` - File offset at which to begin deallocation |
| 1294 | /// - `len` - Length of the region to deallocate |
| 1295 | /// |
| 1296 | /// # Returns |
| 1297 | /// |
| 1298 | /// Returns `()` on success. On failure, the region may or may not be partially |
| 1299 | /// deallocated. |
| 1300 | /// |
| 1301 | /// # Example |
| 1302 | /// |
| 1303 | #[cfg_attr (fbsd14, doc = " ```" )] |
| 1304 | #[cfg_attr (not(fbsd14), doc = " ```no_run" )] |
| 1305 | /// # use std::io::Write; |
| 1306 | /// # use std::os::unix::fs::FileExt; |
| 1307 | /// # use std::os::unix::io::AsRawFd; |
| 1308 | /// # use nix::fcntl::*; |
| 1309 | /// # use tempfile::tempfile; |
| 1310 | /// const INITIAL: &[u8] = b"0123456789abcdef"; |
| 1311 | /// let mut f = tempfile().unwrap(); |
| 1312 | /// f.write_all(INITIAL).unwrap(); |
| 1313 | /// fspacectl_all(f.as_raw_fd(), 3, 6).unwrap(); |
| 1314 | /// let mut buf = vec![0; INITIAL.len()]; |
| 1315 | /// f.read_exact_at(&mut buf, 0).unwrap(); |
| 1316 | /// assert_eq!(buf, b"012\0\0\0\0\0\09abcdef"); |
| 1317 | /// ``` |
| 1318 | #[cfg (target_os = "freebsd" )] |
| 1319 | #[inline ] // Delays codegen, preventing linker errors with dylibs and --no-allow-shlib-undefined |
| 1320 | pub fn fspacectl_all( |
| 1321 | fd: RawFd, |
| 1322 | offset: libc::off_t, |
| 1323 | len: libc::off_t, |
| 1324 | ) -> Result<()> { |
| 1325 | let mut rqsr = libc::spacectl_range { |
| 1326 | r_offset: offset, |
| 1327 | r_len: len, |
| 1328 | }; |
| 1329 | while rqsr.r_len > 0 { |
| 1330 | let res = unsafe { |
| 1331 | libc::fspacectl( |
| 1332 | fd, |
| 1333 | libc::SPACECTL_DEALLOC, // Only one command is supported ATM |
| 1334 | &rqsr, |
| 1335 | 0, // No flags are currently supported |
| 1336 | &mut rqsr, |
| 1337 | ) |
| 1338 | }; |
| 1339 | Errno::result(res)?; |
| 1340 | } |
| 1341 | Ok(()) |
| 1342 | } |
| 1343 | |
| 1344 | #[cfg (any( |
| 1345 | linux_android, |
| 1346 | target_os = "emscripten" , |
| 1347 | target_os = "fuchsia" , |
| 1348 | target_os = "wasi" , |
| 1349 | target_env = "uclibc" , |
| 1350 | target_os = "freebsd" |
| 1351 | ))] |
| 1352 | mod posix_fadvise { |
| 1353 | use crate::errno::Errno; |
| 1354 | use crate::Result; |
| 1355 | use std::os::unix::io::RawFd; |
| 1356 | |
| 1357 | #[cfg (feature = "fs" )] |
| 1358 | libc_enum! { |
| 1359 | /// The specific advice provided to [`posix_fadvise`]. |
| 1360 | #[repr (i32)] |
| 1361 | #[non_exhaustive ] |
| 1362 | #[cfg_attr (docsrs, doc(cfg(feature = "fs" )))] |
| 1363 | pub enum PosixFadviseAdvice { |
| 1364 | /// Revert to the default data access behavior. |
| 1365 | POSIX_FADV_NORMAL, |
| 1366 | /// The file data will be accessed sequentially. |
| 1367 | POSIX_FADV_SEQUENTIAL, |
| 1368 | /// A hint that file data will be accessed randomly, and prefetching is likely not |
| 1369 | /// advantageous. |
| 1370 | POSIX_FADV_RANDOM, |
| 1371 | /// The specified data will only be accessed once and then not reused. |
| 1372 | POSIX_FADV_NOREUSE, |
| 1373 | /// The specified data will be accessed in the near future. |
| 1374 | POSIX_FADV_WILLNEED, |
| 1375 | /// The specified data will not be accessed in the near future. |
| 1376 | POSIX_FADV_DONTNEED, |
| 1377 | } |
| 1378 | } |
| 1379 | |
| 1380 | feature! { |
| 1381 | #![feature = "fs" ] |
| 1382 | /// Allows a process to describe to the system its data access behavior for an open file |
| 1383 | /// descriptor. |
| 1384 | /// |
| 1385 | /// # See Also |
| 1386 | /// * [`posix_fadvise`](https://pubs.opengroup.org/onlinepubs/9699919799/functions/posix_fadvise.html) |
| 1387 | pub fn posix_fadvise( |
| 1388 | fd: RawFd, |
| 1389 | offset: libc::off_t, |
| 1390 | len: libc::off_t, |
| 1391 | advice: PosixFadviseAdvice, |
| 1392 | ) -> Result<()> { |
| 1393 | let res = unsafe { libc::posix_fadvise(fd, offset, len, advice as libc::c_int) }; |
| 1394 | |
| 1395 | if res == 0 { |
| 1396 | Ok(()) |
| 1397 | } else { |
| 1398 | Err(Errno::from_raw(res)) |
| 1399 | } |
| 1400 | } |
| 1401 | } |
| 1402 | } |
| 1403 | |
| 1404 | /// Pre-allocate storage for a range in a file |
| 1405 | /// |
| 1406 | /// # See Also |
| 1407 | /// * [`posix_fallocate`](https://pubs.opengroup.org/onlinepubs/9699919799/functions/posix_fallocate.html) |
| 1408 | #[cfg (any( |
| 1409 | linux_android, |
| 1410 | freebsdlike, |
| 1411 | target_os = "emscripten" , |
| 1412 | target_os = "fuchsia" , |
| 1413 | target_os = "wasi" , |
| 1414 | ))] |
| 1415 | pub fn posix_fallocate( |
| 1416 | fd: RawFd, |
| 1417 | offset: libc::off_t, |
| 1418 | len: libc::off_t, |
| 1419 | ) -> Result<()> { |
| 1420 | let res = unsafe { libc::posix_fallocate(fd, offset, len) }; |
| 1421 | match Errno::result(res) { |
| 1422 | Err(err) => Err(err), |
| 1423 | Ok(0) => Ok(()), |
| 1424 | Ok(errno) => Err(Errno::from_raw(errno)), |
| 1425 | } |
| 1426 | } |
| 1427 | } |
| 1428 | |