diff --git a/src/capi/core.rs b/src/capi/core.rs index 7b0f65f5..be35af56 100644 --- a/src/capi/core.rs +++ b/src/capi/core.rs @@ -22,10 +22,9 @@ use crate::{ ret::IntoCReturn, utils::{self, CBorrowedFd}, }, - error::{Error, ErrorImpl}, + error::{Error, ErrorExt, ErrorImpl}, flags::{OpenFlags, RenameFlags}, procfs::PROCFS_HANDLE, - syscalls, utils::FdExt, InodeType, Root, RootRef, }; @@ -90,13 +89,10 @@ pub extern "C" fn pathrs_reopen(fd: CBorrowedFd<'_>, flags: c_int) -> RawFd { fd.try_as_borrowed_fd()? .reopen(&PROCFS_HANDLE, flags) .and_then(|file| { - // Rust sets O_CLOEXEC by default, without an opt-out. We need to - // disable it if we weren't asked to do O_CLOEXEC. + // Rust sets O_CLOEXEC by default, without an opt-out. We need + // to disable it if we weren't asked to do O_CLOEXEC. if !flags.contains(OpenFlags::O_CLOEXEC) { - syscalls::fcntl_unset_cloexec(&file).map_err(|err| ErrorImpl::RawOsError { - operation: "clear O_CLOEXEC on fd".into(), - source: err, - })?; + utils::fcntl_unset_cloexec(&file).wrap("clear O_CLOEXEC on reopened fd")?; } Ok(file) }) diff --git a/src/capi/error.rs b/src/capi/error.rs new file mode 100644 index 00000000..efeaeb02 --- /dev/null +++ b/src/capi/error.rs @@ -0,0 +1,191 @@ +/* + * libpathrs: safe path resolution on Linux + * Copyright (C) 2019-2024 Aleksa Sarai + * Copyright (C) 2019-2024 SUSE LLC + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +use crate::{ + capi::{ret::CReturn, utils::Leakable}, + error::{Error, ErrorKind}, +}; + +use std::{ + collections::{hash_map::Entry as HashMapEntry, HashMap}, + error::Error as StdError, + ffi::CString, + ptr, + sync::Mutex, +}; + +use libc::{c_char, c_int}; +use rand::{self, Rng}; + +// TODO: Switch this to using a slab or similar structure, possibly using a less +// heavy-weight lock? Maybe sharded-slab? +// MSRV(1.70): Use OnceLock. +// MSRV(1.80): Use LazyLock. +lazy_static! { + static ref ERROR_MAP: Mutex> = Mutex::new(HashMap::new()); +} + +pub(crate) fn store_error(err: Error) -> CReturn { + let mut err_map = ERROR_MAP.lock().unwrap(); + + // Try to find a negative error value we can use. We avoid using anything in + // 0..4096 to avoid users interpreting the return value as an -errno (at the + // moment, the largest errno is ~150 but the kernel currently reserves + // 4096 values as possible ERR_PTR values). + let mut g = rand::thread_rng(); + loop { + let idx = g.gen_range(CReturn::MIN..=-4096); + match err_map.entry(idx) { + HashMapEntry::Occupied(_) => continue, + HashMapEntry::Vacant(slot) => { + slot.insert(err); + return idx; + } + } + } +} + +/// Attempts to represent a Rust Error type in C. This structure must be freed +/// using pathrs_errorinfo_free(). +// NOTE: This API is exposed to library users in a read-only manner with memory +// management done by libpathrs -- so you may only ever append to it. +#[repr(align(8), C)] +pub struct CError { + // TODO: Put a version or size here so that C users can tell what fields are + // valid if we add fields in the future. + // + /// Raw errno(3) value of the underlying error (or 0 if the source of the + /// error was not due to a syscall error). + // We can't call this field "errno" because glibc defines errno(3) as a + // macro, causing all sorts of problems if you have a struct with an "errno" + // field. Best to avoid those headaches. + pub saved_errno: u64, + + /// Textual description of the error. + pub description: *const c_char, +} + +impl Leakable for CError {} + +impl From<&Error> for CError { + /// Construct a new CError struct based on the given error. The description + /// is pretty-printed in a C-like manner (causes are appended to one another + /// with separating colons). In addition, if the root-cause of the error is + /// an IOError then errno is populated with that value. + fn from(err: &Error) -> Self { + // TODO: Switch to Error::chain() once it's stabilised. + // + let desc = { + let mut desc = err.to_string(); + let mut err: &(dyn StdError) = err; + while let Some(next) = err.source() { + desc.push_str(": "); + desc.push_str(&next.to_string()); + err = next; + } + // Create a C-compatible string for CError.description. + CString::new(desc).expect("CString::new(description) failed in CError generation") + }; + + // TODO: We should probably convert some of our internal errors into + // equivalent POSIX-style errors (InvalidArgument => -EINVAL, for + // instance). + let errno = match err.kind() { + ErrorKind::OsError(Some(err)) => err.abs(), + _ => 0, + }; + + CError { + saved_errno: errno.try_into().unwrap_or(0), + description: desc.into_raw(), + } + } +} + +impl Drop for CError { + fn drop(&mut self) { + if !self.description.is_null() { + let description = self.description as *mut c_char; + // Clear the pointer to avoid double-frees. + self.description = ptr::null_mut(); + // SAFETY: CString::from_raw is safe because the C caller guarantees + // that the pointer we get is the same one we gave them. + let _ = unsafe { CString::from_raw(description) }; + // drop the CString + } + } +} + +/// Retrieve error information about an error id returned by a pathrs operation. +/// +/// Whenever an error occurs with libpathrs, a negative number describing that +/// error (the error id) is returned. pathrs_errorinfo() is used to retrieve +/// that information: +/// +/// ```c +/// fd = pathrs_resolve(root, "/foo/bar"); +/// if (fd < 0) { +/// // fd is an error id +/// pathrs_error_t *error = pathrs_errorinfo(fd); +/// // ... print the error information ... +/// pathrs_errorinfo_free(error); +/// } +/// ``` +/// +/// Once pathrs_errorinfo() is called for a particular error id, that error id +/// is no longer valid and should not be used for subsequent pathrs_errorinfo() +/// calls. +/// +/// Error ids are only unique from one another until pathrs_errorinfo() is +/// called, at which point the id can be re-used for subsequent errors. The +/// precise format of error ids is completely opaque and they should never be +/// compared directly or used for anything other than with pathrs_errorinfo(). +/// +/// Error ids are not thread-specific and thus pathrs_errorinfo() can be called +/// on a different thread to the thread where the operation failed (this is of +/// particular note to green-thread language bindings like Go, where this is +/// important). +/// +/// # Return Value +/// +/// If there was a saved error with the provided id, a pathrs_error_t is +/// returned describing the error. Use pathrs_errorinfo_free() to free the +/// associated memory once you are done with the error. +#[no_mangle] +pub extern "C" fn pathrs_errorinfo(err_id: c_int) -> Option<&'static mut CError> { + let mut err_map = ERROR_MAP.lock().unwrap(); + + err_map + .remove(&err_id) + .as_ref() + .map(CError::from) + .map(Leakable::leak) +} + +/// Free the pathrs_error_t object returned by pathrs_errorinfo(). +#[no_mangle] +pub extern "C" fn pathrs_errorinfo_free(ptr: *mut CError) { + if ptr.is_null() { + return; + } + + // SAFETY: The C caller guarantees that the pointer is of the correct type + // and that this isn't a double-free. + unsafe { (*ptr).free() } +} diff --git a/src/capi/mod.rs b/src/capi/mod.rs index 0c32e920..7eeee46d 100644 --- a/src/capi/mod.rs +++ b/src/capi/mod.rs @@ -31,7 +31,10 @@ pub mod core; /// procfs-related function wrappers. pub mod procfs; -/// Helpers for converting Result<...> into C-style int returns. +/// C-friendly [`Error`](crate::error::Error) representation and helpers. +pub mod error; + +/// Helpers for converting [`Result`] into C-style int returns. pub mod ret; mod utils; diff --git a/src/capi/ret.rs b/src/capi/ret.rs index a0517415..f2a988a4 100644 --- a/src/capi/ret.rs +++ b/src/capi/ret.rs @@ -17,55 +17,18 @@ * along with this program. If not, see . */ -use crate::{ - capi::utils::{CError, Leakable}, - error::Error, - Handle, Root, -}; +use crate::{capi::error as capi_error, error::Error, Handle, Root}; -use std::{ - collections::{hash_map::Entry as HashMapEntry, HashMap}, - os::unix::io::{IntoRawFd, OwnedFd}, - sync::Mutex, -}; +use std::os::unix::io::{IntoRawFd, OwnedFd}; use libc::c_int; -use rand::{self, Rng}; -type CReturn = c_int; +pub(super) type CReturn = c_int; pub(super) trait IntoCReturn { fn into_c_return(self) -> CReturn; } -// TODO: Switch this to using a slab or similar structure, possibly using a less -// heavy-weight lock? Maybe sharded-slab? -// MSRV(1.70): Use OnceLock. -// MSRV(1.80): Use LazyLock. -lazy_static! { - static ref ERROR_MAP: Mutex> = Mutex::new(HashMap::new()); -} - -fn store_error(err: Error) -> CReturn { - let mut err_map = ERROR_MAP.lock().unwrap(); - - // Try to find a negative error value we can use. We avoid using anything in - // 0..4096 to avoid users interpreting the return value as an -errno (at the - // moment, the largest errno is ~150 but the kernel currently reserves - // 4096 values as possible ERR_PTR values). - let mut g = rand::thread_rng(); - loop { - let idx = g.gen_range(CReturn::MIN..=-4096); - match err_map.entry(idx) { - HashMapEntry::Occupied(_) => continue, - HashMapEntry::Vacant(slot) => { - slot.insert(err); - return idx; - } - } - } -} - // TODO: Is it possible for us to return an actual OwnedFd through FFI when we // need to? Unfortunately, CReturn may end up returning -1 which is an // invalid OwnedFd/BorrowedFd value... @@ -109,65 +72,7 @@ where // self.map_or_else(store_error, IntoCReturn::into_c_return) match self { Ok(ok) => ok.into_c_return(), - Err(err) => store_error(err), + Err(err) => capi_error::store_error(err), } } } - -/// Retrieve error information about an error id returned by a pathrs operation. -/// -/// Whenever an error occurs with libpathrs, a negative number describing that -/// error (the error id) is returned. pathrs_errorinfo() is used to retrieve -/// that information: -/// -/// ```c -/// fd = pathrs_resolve(root, "/foo/bar"); -/// if (fd < 0) { -/// // fd is an error id -/// pathrs_error_t *error = pathrs_errorinfo(fd); -/// // ... print the error information ... -/// pathrs_errorinfo_free(error); -/// } -/// ``` -/// -/// Once pathrs_errorinfo() is called for a particular error id, that error id -/// is no longer valid and should not be used for subsequent pathrs_errorinfo() -/// calls. -/// -/// Error ids are only unique from one another until pathrs_errorinfo() is -/// called, at which point the id can be re-used for subsequent errors. The -/// precise format of error ids is completely opaque and they should never be -/// compared directly or used for anything other than with pathrs_errorinfo(). -/// -/// Error ids are not thread-specific and thus pathrs_errorinfo() can be called -/// on a different thread to the thread where the operation failed (this is of -/// particular note to green-thread language bindings like Go, where this is -/// important). -/// -/// # Return Value -/// -/// If there was a saved error with the provided id, a pathrs_error_t is -/// returned describing the error. Use pathrs_errorinfo_free() to free the -/// associated memory once you are done with the error. -#[no_mangle] -pub extern "C" fn pathrs_errorinfo(err_id: c_int) -> Option<&'static mut CError> { - let mut err_map = ERROR_MAP.lock().unwrap(); - - err_map - .remove(&err_id) - .as_ref() - .map(CError::from) - .map(Leakable::leak) -} - -/// Free the pathrs_error_t object returned by pathrs_errorinfo(). -#[no_mangle] -pub extern "C" fn pathrs_errorinfo_free(ptr: *mut CError) { - if ptr.is_null() { - return; - } - - // SAFETY: The C caller guarantees that the pointer is of the correct type - // and that this isn't a double-free. - unsafe { (*ptr).free() } -} diff --git a/src/capi/utils.rs b/src/capi/utils.rs index ecd330b5..e84e4ed1 100644 --- a/src/capi/utils.rs +++ b/src/capi/utils.rs @@ -17,7 +17,7 @@ * along with this program. If not, see . */ -use crate::error::{Error, ErrorImpl, ErrorKind}; +use crate::error::{Error, ErrorImpl}; use std::{ cmp, @@ -25,13 +25,14 @@ use std::{ marker::PhantomData, os::unix::{ ffi::OsStrExt, - io::{BorrowedFd, RawFd}, + io::{AsFd, BorrowedFd, RawFd}, }, path::Path, ptr, }; use libc::{c_char, c_int, size_t}; +use rustix::io as rustix_io; /// Equivalent to [`BorrowedFd`], except that there are no restrictions on what /// value the inner [`RawFd`] can take. This is necessary because C callers @@ -50,8 +51,9 @@ pub struct CBorrowedFd<'fd> { } impl<'fd> CBorrowedFd<'fd> { - /// Take a [`CBorrowedFd`] from C FFI and convert it to a proper [`BorrowedFd`] - /// after making sure that it has a valid value (ie. is not negative). + /// Take a [`CBorrowedFd`] from C FFI and convert it to a proper + /// [`BorrowedFd`] after making sure that it has a valid value (ie. is not + /// negative). pub(crate) fn try_as_borrowed_fd(&self) -> Result, Error> { // TODO: We might want to support AT_FDCWD in the future. The // openat2 resolver handles it correctly, but the O_PATH @@ -74,6 +76,30 @@ impl<'fd> CBorrowedFd<'fd> { } } +/// Wrapper for `fcntl(F_GETFD)` followed by `fcntl(F_SETFD)`, clearing the +/// `FD_CLOEXEC` bit. +/// +/// This is required because Rust automatically sets `O_CLOEXEC` on all new +/// files, so we need to manually unset it when we return certain fds to the C +/// FFI (in fairness, `O_CLOEXEC` is a good default). +pub(crate) fn fcntl_unset_cloexec(fd: Fd) -> Result<(), Error> { + let fd = fd.as_fd(); + + let old = rustix_io::fcntl_getfd(fd).map_err(|err| ErrorImpl::OsError { + operation: "fcntl(F_GETFD)".into(), + source: err.into(), + })?; + let new = old.difference(rustix_io::FdFlags::CLOEXEC); + + rustix_io::fcntl_setfd(fd, new).map_err(|err| { + ErrorImpl::OsError { + operation: "fcntl(F_SETFD)".into(), + source: err.into(), + } + .into() + }) +} + pub(crate) fn parse_path<'a>(path: *const c_char) -> Result<&'a Path, Error> { if path.is_null() { Err(ErrorImpl::InvalidArgument { @@ -110,125 +136,29 @@ pub(crate) fn copy_path_into_buffer>( Ok(path.to_bytes().len() as c_int) } -pub(crate) trait Leakable { +pub(crate) trait Leakable: Sized { /// Leak a structure such that it can be passed through C-FFI. - fn leak(self) -> &'static mut Self; + fn leak(self) -> &'static mut Self { + Box::leak(Box::new(self)) + } /// Given a structure leaked through Leakable::leak, un-leak it. /// /// SAFETY: Callers must be sure to only ever call this once on a given /// pointer (otherwise memory corruption will occur). - unsafe fn unleak(&'static mut self) -> Self; + unsafe fn unleak(&'static mut self) -> Self { + // SAFETY: Box::from_raw is safe because the caller guarantees that + // the pointer we get is the same one we gave them, and it will only + // ever be called once with the same pointer. + *unsafe { Box::from_raw(self as *mut Self) } + } /// Shorthand for `std::mem::drop(self.unleak())`. /// /// SAFETY: Same unsafety issue as `self.unleak()`. - unsafe fn free(&'static mut self); -} - -/// A macro to implement the trivial methods of Leakable -- due to a restriction -/// of the Rust compiler (you cannot have default trait methods that use Self -/// directly, because the size of Self is not known by the trait). -/// -/// ```ignore -/// leakable!{ impl Leakable for CError; } -/// leakable!{ impl Leakable for CVec; } -/// ``` -macro_rules! leakable { - // Inner implementation. - (...) => { - fn leak(self) -> &'static mut Self { - Box::leak(Box::new(self)) - } - - unsafe fn unleak(&'static mut self) -> Self { - // SAFETY: Box::from_raw is safe because the caller guarantees that - // the pointer we get is the same one we gave them, and it will only - // ever be called once with the same pointer. - *unsafe { Box::from_raw(self as *mut Self) } - } - - unsafe fn free(&'static mut self) { - // SAFETY: Caller guarantees this is safe to do. - let _ = unsafe { self.unleak() }; - // drop Self - } - }; - - (impl Leakable for $type:ty ;) => { - impl Leakable for $type { - leakable!(...); - } - }; - - (impl<$($generics:tt),+> Leakable for $type:ty ;) => { - impl<$($generics),+> Leakable for $type { - leakable!(...); - } - }; -} - -/// Attempts to represent a Rust Error type in C. This structure must be freed -/// using pathrs_errorinfo_free(). -// NOTE: This API is exposed to library users in a read-only manner with memory -// management done by libpathrs -- so you may only ever append to it. -#[repr(align(8), C)] -pub struct CError { - /// Raw errno(3) value of the underlying error (or 0 if the source of the - /// error was not due to a syscall error). - // We can't call this field "errno" because glibc defines errno(3) as a - // macro, causing all sorts of problems if you have a struct with an "errno" - // field. Best to avoid those headaches. - pub saved_errno: u64, - - /// Textual description of the error. - pub description: *const c_char, -} - -leakable! { - impl Leakable for CError; -} - -impl From<&Error> for CError { - /// Construct a new CError struct based on the given error. The description - /// is pretty-printed in a C-like manner (causes are appended to one another - /// with separating colons). In addition, if the root-cause of the error is - /// an IOError then errno is populated with that value. - fn from(err: &Error) -> Self { - let desc = err.iter_chain_hotfix().fold(String::new(), |mut s, next| { - if !s.is_empty() { - s.push_str(": "); - } - s.push_str(&next.to_string()); - s - }); - - // Create a C-compatible string for CError.description. - let desc = - CString::new(desc).expect("CString::new(description) failed in CError generation"); - - let errno = match err.kind() { - ErrorKind::OsError(Some(err)) => err.abs(), - _ => 0, - }; - - CError { - saved_errno: errno.try_into().unwrap_or(0), - description: desc.into_raw(), - } - } -} - -impl Drop for CError { - fn drop(&mut self) { - if !self.description.is_null() { - let description = self.description as *mut c_char; - // Clear the pointer to avoid double-frees. - self.description = ptr::null_mut(); - // SAFETY: CString::from_raw is safe because the C caller guarantees - // that the pointer we get is the same one we gave them. - let _ = unsafe { CString::from_raw(description) }; - // drop the CString - } + unsafe fn free(&'static mut self) { + // SAFETY: Caller guarantees this is safe to do. + let _ = unsafe { self.unleak() }; + // drop Self } } diff --git a/src/error.rs b/src/error.rs index e23064c9..71d35001 100644 --- a/src/error.rs +++ b/src/error.rs @@ -29,7 +29,7 @@ use crate::{resolvers::opath::SymlinkStackError, syscalls::Error as SyscallError}; -use std::{borrow::Cow, error::Error as StdError, io::Error as IOError}; +use std::{borrow::Cow, io::Error as IOError}; // TODO: Add a backtrace to Error. We would just need to add an automatic // Backtrace::capture() in From. But it's not clear whether we want to @@ -169,35 +169,3 @@ impl ErrorExt for Result { self.map_err(|err| err.with_wrap(context_fn)) } } - -/// A backport of the nightly-only [`Chain`]. This method -/// will be removed as soon as that is stabilised. -/// -/// [`Chain`]: https://doc.rust-lang.org/nightly/std/error/struct.Chain.html -// XXX: https://github.com/rust-lang/rust/issues/58520 -pub(crate) struct Chain<'a> { - current: Option<&'a (dyn StdError + 'static)>, -} - -impl<'a> Iterator for Chain<'a> { - type Item = &'a (dyn StdError + 'static); - - fn next(&mut self) -> Option { - let current = self.current; - self.current = self.current.and_then(StdError::source); - current - } -} - -impl Error { - /// A backport of the nightly-only [`Error::chain`]. This method - /// will be removed as soon as that is stabilised. - /// - /// [`Error::chain`]: https://doc.rust-lang.org/nightly/std/error/trait.Error.html#method.chain - // XXX: https://github.com/rust-lang/rust/issues/58520 - pub(crate) fn iter_chain_hotfix(&self) -> Chain { - Chain { - current: Some(self), - } - } -} diff --git a/src/root.rs b/src/root.rs index e892275f..203ecd72 100644 --- a/src/root.rs +++ b/src/root.rs @@ -44,7 +44,7 @@ use std::{ }; use libc::dev_t; -use rustix::fs::{self, Dir, SeekFrom}; +use rustix::fs::{self as rustix_fs, Dir, SeekFrom}; /// An inode type to be created with [`Root::create`]. #[derive(Clone, Debug)] @@ -927,7 +927,7 @@ impl RootRef<'_> { // half-read directory iterator. We have to do this manually rather // than using Dir::rewind() because Dir::rewind() just marks the // iterator so it is rewinded when the next iteration happens. - fs::seek(&next, SeekFrom::Start(0)).map_err(|err| ErrorImpl::OsError { + rustix_fs::seek(&next, SeekFrom::Start(0)).map_err(|err| ErrorImpl::OsError { operation: "reset offset of directory handle".into(), source: err.into(), })?; diff --git a/src/syscalls.rs b/src/syscalls.rs index e33941d0..a90a5969 100644 --- a/src/syscalls.rs +++ b/src/syscalls.rs @@ -104,16 +104,6 @@ impl fmt::Display for FrozenFd { /// [`Error`]: crate::error::Error #[derive(thiserror::Error, Debug)] pub(crate) enum Error { - #[error("fcntl({fd}, F_GETFD)")] - FcntlGetFlags { fd: FrozenFd, source: IOError }, - - #[error("fcntl({fd}, F_SETFD, 0x{flags:x})")] - FcntlSetFlags { - fd: FrozenFd, - flags: i32, - source: IOError, - }, - #[error("openat({dirfd}, {path}, 0x{flags:x}, 0o{mode:o})")] Openat { dirfd: FrozenFd, @@ -261,8 +251,6 @@ impl Error { pub(crate) fn root_cause(&self) -> &IOError { // XXX: This should probably be a macro... match self { - Error::FcntlGetFlags { source, .. } => source, - Error::FcntlSetFlags { source, .. } => source, Error::Openat { source, .. } => source, Error::Openat2 { source, .. } => source, Error::Readlinkat { source, .. } => source, @@ -290,46 +278,6 @@ impl Error { // prefer these simpler C-like bindings. We also have the ability to check // for support of each syscall. -/// Wrapper for `fcntl(F_GETFD)` followed by `fcntl(F_SETFD)`, clearing the -/// `FD_CLOEXEC` bit. -/// -/// This is required because Rust automatically sets `O_CLOEXEC` on all new -/// files, so we need to manually unset it when we return certain fds to the C -/// FFI (in fairness, `O_CLOEXEC` is a good default). -pub(crate) fn fcntl_unset_cloexec(fd: Fd) -> Result<(), Error> { - let fd = fd.as_fd(); - - // SAFETY: Obviously safe-to-use Linux syscall. - let old = unsafe { libc::fcntl(fd.as_raw_fd(), libc::F_GETFD) }; - let err = IOError::last_os_error(); - - if old < 0 { - Err(Error::FcntlGetFlags { - fd: fd.into(), - source: err, - })? - } - - let new = old & !libc::FD_CLOEXEC; - if new == old { - return Ok(()); - } - - // SAFETY: Obviously safe-to-use Linux syscall. - let ret = unsafe { libc::fcntl(fd.as_raw_fd(), libc::F_SETFD, new) }; - let err = IOError::last_os_error(); - - if ret >= 0 { - Ok(()) - } else { - Err(Error::FcntlSetFlags { - fd: fd.into(), - flags: new, - source: err, - }) - } -} - /// Wrapper for `openat(2)` which auto-sets `O_CLOEXEC | O_NOCTTY`. /// /// This is needed because Rust doesn't provide a way to access the dirfd @@ -856,10 +804,8 @@ pub(crate) fn getegid() -> libc::gid_t { #[cfg(test)] pub(crate) fn getcwd() -> Result { - use rustix::process; - let buffer = Vec::with_capacity(libc::PATH_MAX as usize); - Ok(OsStr::from_bytes(process::getcwd(buffer)?.to_bytes()).into()) + Ok(OsStr::from_bytes(rustix::process::getcwd(buffer)?.to_bytes()).into()) } bitflags! {