commit c63d5b114163f56899e75153ee25b475bb623beb
parent 9a8e88d6229ff3848547d754326e06482bb5546b
Author: Zack Newman <zack@philomathiclife.com>
Date: Tue, 30 Sep 2025 17:43:27 -0600
make no_std. only support certain platforms. add supp groups support
Diffstat:
| M | Cargo.toml | | | 34 | ++++++++++++++++++++++++++++------ |
| M | README.md | | | 13 | +++++++++++++ |
| M | src/c.rs | | | 94 | +++++++++++++++++++++++++++++++++++++------------------------------------------ |
| A | src/err.rs | | | 2264 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| M | src/lib.rs | | | 750 | +++++++++++++++++++++++++++++++++++++++++++------------------------------------ |
| M | src/openbsd.rs | | | 728 | +++++++++++++++++++++++++++++++++++++++++-------------------------------------- |
6 files changed, 3134 insertions(+), 749 deletions(-)
diff --git a/Cargo.toml b/Cargo.toml
@@ -1,7 +1,7 @@
[package]
authors = ["Zack Newman <zack@philomathiclife.com>"]
-categories = ["external-ffi-bindings", "os::unix-apis"]
-description = "FFI for setresuid(2), setresgid(2), chroot(2), pledge(2), and unveil(2)."
+categories = ["external-ffi-bindings", "no-std::no-alloc", "os::unix-apis"]
+description = "FFI for setgroups(2), setresuid(2), setresgid(2), chroot(2), pledge(2), and unveil(2)."
documentation = "https://docs.rs/priv_sep/latest/priv_sep/"
edition = "2024"
keywords = ["ffi", "openbsd", "privsep", "security", "unix"]
@@ -10,7 +10,7 @@ name = "priv_sep"
readme = "README.md"
repository = "https://git.philomathiclife.com/repos/priv_sep/"
rust-version = "1.86.0"
-version = "3.0.0-alpha.1.3"
+version = "3.0.0-alpha.2.0"
[lints.rust]
ambiguous_negative_literals = { level = "deny", priority = -1 }
@@ -23,7 +23,7 @@ future_incompatible = { level = "deny", priority = -1 }
impl_trait_redundant_captures = { level = "deny", priority = -1 }
keyword_idents = { level = "deny", priority = -1 }
let_underscore = { level = "deny", priority = -1 }
-#linker_messages = { level = "deny", priority = -1 }
+linker_messages = { level = "deny", priority = -1 }
#lossy_provenance_casts = { level = "deny", priority = -1 }
macro_use_extern_crate = { level = "deny", priority = -1 }
meta_variable_misuse = { level = "deny", priority = -1 }
@@ -44,9 +44,11 @@ rust_2021_compatibility = { level = "deny", priority = -1 }
rust_2024_compatibility = { level = "deny", priority = -1 }
single_use_lifetimes = { level = "deny", priority = -1 }
#supertrait_item_shadowing_definition = { level = "deny", priority = -1 }
+#supertrait_item_shadowing_usage = { level = "deny", priority = -1 }
trivial_casts = { level = "deny", priority = -1 }
trivial_numeric_casts = { level = "deny", priority = -1 }
unit_bindings = { level = "deny", priority = -1 }
+unknown-or-malformed-diagnostic-attributes = { level = "deny", priority = -1 }
unnameable_types = { level = "deny", priority = -1 }
#unqualified_local_imports = { level = "deny", priority = -1 }
unreachable_pub = { level = "deny", priority = -1 }
@@ -62,7 +64,6 @@ variant_size_differences = { level = "deny", priority = -1 }
warnings = { level = "deny", priority = -1 }
[lints.clippy]
-all = { level = "deny", priority = -1 }
cargo = { level = "deny", priority = -1 }
complexity = { level = "deny", priority = -1 }
correctness = { level = "deny", priority = -1 }
@@ -81,14 +82,35 @@ implicit_return = "allow"
min_ident_chars = "allow"
missing_trait_methods = "allow"
pub_with_shorthand = "allow"
+question_mark_used = "allow"
redundant_pub_crate = "allow"
ref_patterns = "allow"
return_and_then = "allow"
single_char_lifetime_names = "allow"
+semicolon_inside_block = "allow"
[package.metadata.docs.rs]
all-features = true
-rustdoc-args = ["--cfg", "docsrs"]
+cargo-args = ["-Zbuild-std=std"]
+default-target = "x86_64-unknown-openbsd"
+targets = [
+ "aarch64-apple-darwin",
+ "aarch64-unknown-linux-gnu",
+ "i686-unknown-linux-gnu",
+ "x86_64-unknown-dragonfly",
+ "x86_64-unknown-freebsd",
+ "x86_64-unknown-linux-gnu",
+ "x86_64-unknown-netbsd"
+]
[dev-dependencies]
tokio = { version = "1.47.1", default-features = false, features = ["macros", "net", "rt"] }
+
+
+### FEATURES #################################################################
+
+[features]
+default = ["std"]
+
+# Provide std support.
+std = []
diff --git a/README.md b/README.md
@@ -6,6 +6,17 @@
`priv_sep` is a library that uses the system's libc to perform privilege separation and privilege reduction.
+Note the only platforms that are currently supported are platforms that correspond to the following
+`target_os` values:
+
+* `dragonfly`
+* `freebsd`
+* `linux`
+* `netbsd`
+* `openbsd`
+
+or the `apple` `target_vendor`.
+
## `priv_sep` in action for OpenBSD
```rust
@@ -25,6 +36,7 @@ async fn main() -> Result<Infallible, PrivDropErr<Error>> {
// `chroot(2)` to `/path/chroot/` and `chdir(2)` to `/`.
// `pledge(2)` `id`, `inet`, `rpath`, `stdio`, and `unveil`.
// Bind to TCP `[::1]:443` as root.
+ // `setgroups(2)` to drop all supplementary groups.
// `setresgid(2)` to the group ID associated with nobody.
// `setresuid(2)` to the user ID associated with nobody.
// Remove `id` from our `pledge(2)`d promises.
@@ -71,6 +83,7 @@ async fn main() -> Result<Infallible, PrivDropErr<Error>> {
// Get the user ID and group ID for nobody from `passwd(5)`.
// `chroot(2)` to `/path/chroot/` and `chdir(2)` to `/`.
// Bind to TCP `[::1]:443` as root.
+ // `setgroups(2)` to drop all supplementary groups.
// `setresgid(2)` to the group ID associated with nobody.
// `setresuid(2)` to the user ID associated with nobody.
let listener = UserInfo::chroot_then_priv_drop_async("nobody", "/path/chroot/", false, async || {
diff --git a/src/c.rs b/src/c.rs
@@ -1,31 +1,15 @@
use super::{Gid, Uid, UserInfo};
-#[cfg(any(target_os = "espidf", target_os = "horizon", target_os = "vita"))]
-use core::ffi::c_ushort;
use core::ffi::{c_char, c_int};
/// Error code when a libc call is successful.
pub(crate) const SUCCESS: c_int = 0;
-/// `uid_t` and `gid_t`.
-#[cfg(any(target_os = "espidf", target_os = "horizon", target_os = "vita"))]
-pub type IdT = c_ushort;
-/// `uid_t` and `gid_t`.
-#[cfg(target_os = "nto")]
-pub type IdT = i32;
-/// `uid_t` and `gid_t`.
-#[cfg(not(any(
- target_os = "espidf",
- target_os = "horizon",
- target_os = "nto",
- target_os = "vita"
-)))]
-pub(crate) type IdT = u32;
/// `time_t`.
#[cfg(all(
any(
target_os = "dragonfly",
target_os = "freebsd",
- target_os = "macos",
target_os = "netbsd",
- target_os = "openbsd"
+ target_os = "openbsd",
+ target_vendor = "apple"
),
target_arch = "aarch64",
target_pointer_width = "32"
@@ -36,9 +20,9 @@ type TimeT = i32;
any(
target_os = "dragonfly",
target_os = "freebsd",
- target_os = "macos",
target_os = "netbsd",
- target_os = "openbsd"
+ target_os = "openbsd",
+ target_vendor = "apple"
),
not(all(target_arch = "aarch64", target_pointer_width = "32"))
))]
@@ -51,25 +35,25 @@ pub(crate) struct Passwd {
/// User password.
pass: *mut c_char,
/// User ID.
- uid: IdT,
+ uid: u32,
/// Group ID.
- gid: IdT,
+ gid: u32,
/// Password change time.
#[cfg(any(
target_os = "dragonfly",
target_os = "freebsd",
- target_os = "macos",
target_os = "netbsd",
- target_os = "openbsd"
+ target_os = "openbsd",
+ target_vendor = "apple"
))]
change: TimeT,
/// User access class.
#[cfg(any(
target_os = "dragonfly",
target_os = "freebsd",
- target_os = "macos",
target_os = "netbsd",
- target_os = "openbsd"
+ target_os = "openbsd",
+ target_vendor = "apple"
))]
class: *mut c_char,
/// User information.
@@ -82,13 +66,13 @@ pub(crate) struct Passwd {
#[cfg(any(
target_os = "dragonfly",
target_os = "freebsd",
- target_os = "macos",
target_os = "netbsd",
- target_os = "openbsd"
+ target_os = "openbsd",
+ target_vendor = "apple"
))]
expire: TimeT,
/// Internal fields.
- #[cfg(any(target_os = "dragonfly", target_os = "freebsd", target_os = "macos"))]
+ #[cfg(any(target_os = "dragonfly", target_os = "freebsd"))]
fields: c_int,
}
impl Passwd {
@@ -103,13 +87,13 @@ impl Passwd {
#[expect(unsafe_code, reason = "FFI requires unsafe")]
unsafe extern "C" {
/// [`getuid`](https://pubs.opengroup.org/onlinepubs/9799919799.2024edition/functions/getuid.html).
- pub(crate) safe fn getuid() -> IdT;
+ pub(crate) safe fn getuid() -> u32;
/// [`geteuid`](https://pubs.opengroup.org/onlinepubs/9799919799.2024edition/functions/geteuid.html).
- pub(crate) safe fn geteuid() -> IdT;
+ pub(crate) safe fn geteuid() -> u32;
/// [`getgid`](https://pubs.opengroup.org/onlinepubs/9799919799.2024edition/functions/getgid.html).
- pub(crate) safe fn getgid() -> IdT;
+ pub(crate) safe fn getgid() -> u32;
/// [`getegid`](https://pubs.opengroup.org/onlinepubs/9799919799.2024edition/functions/getegid.html).
- pub(crate) safe fn getegid() -> IdT;
+ pub(crate) safe fn getegid() -> u32;
/// [`setresuid`](https://pubs.opengroup.org/onlinepubs/9799919799.2024edition/functions/setresuid.html).
#[cfg(any(
target_os = "dragonfly",
@@ -117,15 +101,10 @@ unsafe extern "C" {
target_os = "linux",
target_os = "openbsd"
))]
- pub(crate) safe fn setresuid(ruid: IdT, euid: IdT, suid: IdT) -> c_int;
+ pub(crate) safe fn setresuid(ruid: u32, euid: u32, suid: u32) -> c_int;
/// [`setuid`](https://pubs.opengroup.org/onlinepubs/9799919799.2024edition/functions/setuid.html#).
- #[cfg(not(any(
- target_os = "dragonfly",
- target_os = "freebsd",
- target_os = "linux",
- target_os = "openbsd"
- )))]
- pub(crate) safe fn setuid(uid: IdT) -> c_int;
+ #[cfg(any(target_os = "netbsd", target_vendor = "apple"))]
+ pub(crate) safe fn setuid(uid: u32) -> c_int;
/// [`setresgid`](https://pubs.opengroup.org/onlinepubs/9799919799.2024edition/functions/setresgid.html).
#[cfg(any(
target_os = "dragonfly",
@@ -133,15 +112,10 @@ unsafe extern "C" {
target_os = "linux",
target_os = "openbsd"
))]
- pub(crate) safe fn setresgid(rgid: IdT, egid: IdT, sgid: IdT) -> c_int;
+ pub(crate) safe fn setresgid(rgid: u32, egid: u32, sgid: u32) -> c_int;
/// [`setgid`](https://pubs.opengroup.org/onlinepubs/9799919799.2024edition/functions/setgid.html#).
- #[cfg(not(any(
- target_os = "dragonfly",
- target_os = "freebsd",
- target_os = "linux",
- target_os = "openbsd"
- )))]
- pub(crate) safe fn setgid(gid: IdT) -> c_int;
+ #[cfg(any(target_os = "netbsd", target_vendor = "apple"))]
+ pub(crate) safe fn setgid(gid: u32) -> c_int;
/// [`chroot(2)`](https://manned.org/chroot.2).
pub(crate) fn chroot(path: *const c_char) -> c_int;
/// [`chdir`](https://pubs.opengroup.org/onlinepubs/9799919799.2024edition/functions/chdir.html).
@@ -156,10 +130,30 @@ unsafe extern "C" {
) -> c_int;
/// [`getpwuid_r`](https://pubs.opengroup.org/onlinepubs/9799919799.2024edition/functions/getpwuid_r.html).
pub(crate) fn getpwuid_r(
- uid: IdT,
+ uid: u32,
pwd: *mut Passwd,
buf: *mut c_char,
size: usize,
result: *mut *mut Passwd,
) -> c_int;
+ /// [`setgroups(2)`](https://man.openbsd.org/setgroups.2).
+ #[cfg(any(
+ target_os = "dragonfly",
+ target_os = "freebsd",
+ target_os = "netbsd",
+ target_os = "openbsd",
+ ))]
+ pub(crate) fn setgroups(size: c_int, gids: *const u32) -> c_int;
+ /// [`setgroups(2)`](https://www.man7.org/linux/man-pages/man2/setgroups.2.html).
+ #[cfg(target_os = "linux")]
+ pub(crate) fn setgroups(size: usize, gids: *const u32) -> c_int;
+ /// [`__errno`](https://github.com/openbsd/src/blob/master/include/errno.h#L54).
+ #[cfg(any(target_os = "netbsd", target_os = "openbsd"))]
+ pub(crate) fn __errno() -> *mut c_int;
+ /// [`__error`](https://github.com/freebsd/freebsd-src/blob/main/sys/sys/errno.h#L43).
+ #[cfg(any(target_os = "freebsd", target_vendor = "apple"))]
+ pub(crate) fn __error() -> *mut c_int;
+ /// [`__errno_location`](https://sourceware.org/git/?p=glibc.git;a=blob;f=include/errno.h;h=f0ccaa74dd8bebed83e4adfbf301339efeae3cdb;hb=HEAD#l37).
+ #[cfg(any(target_os = "dragonfly", target_os = "linux"))]
+ pub(crate) fn __errno_location() -> *mut c_int;
}
diff --git a/src/err.rs b/src/err.rs
@@ -0,0 +1,2264 @@
+use super::c;
+use core::{
+ error::Error,
+ ffi::c_int,
+ fmt::{self, Display, Formatter},
+};
+#[cfg(feature = "std")]
+use std::io::Error as StdErr;
+/// `newtype` around a [`c_int`] that is used to contain unknown error numbers in the [`Errno::Other`]
+/// variant.
+///
+/// Note this cannot be constructed directly since the purpose is to only contain unknown error codes
+/// ensuring that any non-[`Errno::Other`] is not equal to an `Errno::Other`.
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+pub struct OtherErrno(c_int);
+impl OtherErrno {
+ /// Returns the contained error number.
+ #[inline]
+ #[must_use]
+ pub const fn into_raw(self) -> c_int {
+ self.0
+ }
+}
+impl From<OtherErrno> for c_int {
+ #[inline]
+ fn from(value: OtherErrno) -> Self {
+ value.0
+ }
+}
+/// Error returned from calls to the system's libc.
+///
+/// Note this contains variants associated with all supported targets; thus for a given target,
+/// it's likely a non-empty subset of the variants will never be returned.
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+pub enum Errno {
+ /// Operation not permitted.
+ EPERM,
+ /// No such file or directory.
+ ENOENT,
+ /// No such process.
+ ESRCH,
+ /// Interrupted system call.
+ EINTR,
+ /// Input/output error.
+ EIO,
+ /// Device not configured.
+ ENXIO,
+ /// Argument list too long.
+ E2BIG,
+ /// Exec format error.
+ ENOEXEC,
+ /// Bad file descriptor.
+ EBADF,
+ /// No child processes.
+ ECHILD,
+ /// Resource deadlock avoided.
+ EDEADLK,
+ /// Cannot allocate memory.
+ ENOMEM,
+ /// Permission denied.
+ EACCES,
+ /// Bad address.
+ EFAULT,
+ /// Block device required.
+ ENOTBLK,
+ /// Device busy.
+ EBUSY,
+ /// File exists.
+ EEXIST,
+ /// Cross-device link.
+ EXDEV,
+ /// Operation not supported by device.
+ ENODEV,
+ /// Not a directory.
+ ENOTDIR,
+ /// Is a directory.
+ EISDIR,
+ /// Invalid argument.
+ EINVAL,
+ /// Too many open files in system.
+ ENFILE,
+ /// Too many open files.
+ EMFILE,
+ /// Inappropriate ioctl for device.
+ ENOTTY,
+ /// Text file busy.
+ ETXTBSY,
+ /// File too large.
+ EFBIG,
+ /// No space left on device.
+ ENOSPC,
+ /// Illegal seek.
+ ESPIPE,
+ /// Read-only file system.
+ EROFS,
+ /// Too many links.
+ EMLINK,
+ /// Broken pipe.
+ EPIPE,
+ /// Numerical argument out of domain.
+ EDOM,
+ /// Result too large.
+ ERANGE,
+ /// Operation would block.
+ EWOULDBLOCK,
+ /// Operation now in progress.
+ EINPROGRESS,
+ /// Operation already in progress.
+ EALREADY,
+ /// Socket operation on non-socket.
+ ENOTSOCK,
+ /// Destination address required.
+ EDESTADDRREQ,
+ /// Message too long.
+ EMSGSIZE,
+ /// Protocol wrong type for socket.
+ EPROTOTYPE,
+ /// Protocol not available.
+ ENOPROTOOPT,
+ /// Protocol not supported.
+ EPROTONOSUPPORT,
+ /// Socket type not supported.
+ ESOCKTNOSUPPORT,
+ /// Operation not supported.
+ EOPNOTSUPP,
+ /// Protocol family not supported.
+ EPFNOSUPPORT,
+ /// Address family not supported by protocol family.
+ EAFNOSUPPORT,
+ /// Address already in use.
+ EADDRINUSE,
+ /// Can't assign requested address.
+ EADDRNOTAVAIL,
+ /// Network is down.
+ ENETDOWN,
+ /// Network is unreachable.
+ ENETUNREACH,
+ /// Network dropped connection on reset.
+ ENETRESET,
+ /// Software caused connection abort.
+ ECONNABORTED,
+ /// Connection reset by peer.
+ ECONNRESET,
+ /// No buffer space available.
+ ENOBUFS,
+ /// Socket is already connected.
+ EISCONN,
+ /// Socket is not connected.
+ ENOTCONN,
+ /// Can't send after socket shutdown.
+ ESHUTDOWN,
+ /// Too many references: can't splice.
+ ETOOMANYREFS,
+ /// Operation timed out.
+ ETIMEDOUT,
+ /// Connection refused.
+ ECONNREFUSED,
+ /// Too many levels of symbolic links.
+ ELOOP,
+ /// File name too long.
+ ENAMETOOLONG,
+ /// Host is down.
+ EHOSTDOWN,
+ /// No route to host.
+ EHOSTUNREACH,
+ /// Directory not empty.
+ ENOTEMPTY,
+ /// Too many processes.
+ EPROCLIM,
+ /// Too many users.
+ EUSERS,
+ /// Disk quota exceeded.
+ EDQUOT,
+ /// Stale NFS file handle.
+ ESTALE,
+ /// Too many levels of remote in path.
+ EREMOTE,
+ /// RPC struct is bad.
+ EBADRPC,
+ /// RPC version wrong.
+ ERPCMISMATCH,
+ /// RPC program not available.
+ EPROGUNAVAIL,
+ /// Program version wrong.
+ EPROGMISMATCH,
+ /// Bad procedure for program.
+ EPROCUNAVAIL,
+ /// No locks available.
+ ENOLCK,
+ /// Function not implemented.
+ ENOSYS,
+ /// Inappropriate file type or format.
+ EFTYPE,
+ /// Authentication error.
+ EAUTH,
+ /// Need authenticator.
+ ENEEDAUTH,
+ /// IPsec processing failure.
+ #[expect(clippy::doc_markdown, reason = "false positive")]
+ EIPSEC,
+ /// Attribute not found.
+ ENOATTR,
+ /// Illegal byte sequence.
+ EILSEQ,
+ /// No medium found.
+ ENOMEDIUM,
+ /// Wrong medium type.
+ EMEDIUMTYPE,
+ /// Value too large to be stored in data type.
+ EOVERFLOW,
+ /// Operation canceled.
+ ECANCELED,
+ /// Identifier removed.
+ EIDRM,
+ /// No message of desired type.
+ ENOMSG,
+ /// Not supported.
+ ENOTSUP,
+ /// Bad message.
+ EBADMSG,
+ /// State not recoverable.
+ ENOTRECOVERABLE,
+ /// Previous owner died.
+ EOWNERDEAD,
+ /// Protocol error.
+ EPROTO,
+ /// Programming error.
+ EDOOFUS,
+ /// Multihop attempted.
+ EMULTIHOP,
+ /// Link has been severed.
+ ENOLINK,
+ /// Capabilities insufficient.
+ ENOTCAPABLE,
+ /// Not permitted in capability mode.
+ ECAPMODE,
+ /// Integrity check failed.
+ EINTEGRITY,
+ /// Channel number out of range.
+ ECHRNG,
+ /// Level 2 not synchronized.
+ EL2NSYNC,
+ /// Level 3 halted.
+ EL3HLT,
+ /// Level 3 reset.
+ EL3RST,
+ /// Link number out of range.
+ ELNRNG,
+ /// Protocol driver not attached.
+ EUNATCH,
+ /// No CSI structure available.
+ ENOCSI,
+ /// Level 2 halted.
+ EL2HLT,
+ /// Invalid exchange.
+ EBADE,
+ /// Invalid request descriptor.
+ EBADR,
+ /// Exchange full.
+ EXFULL,
+ /// No anode.
+ ENOANO,
+ /// Invalid request code.
+ EBADRQC,
+ /// Invalid slot.
+ EBADSLT,
+ /// Bad font file format.
+ EBFONT,
+ /// Device not a stream.
+ ENOSTR,
+ /// No data available.
+ ENODATA,
+ /// Timer expired.
+ ETIME,
+ /// Out of streams resources.
+ ENOSR,
+ /// Machine is not on the network.
+ ENONET,
+ /// Package not installed.
+ ENOPKG,
+ /// Advertise error.
+ EADV,
+ /// Srmount error.
+ ESRMNT,
+ /// Communication error on send.
+ ECOMM,
+ /// RFS specific error.
+ EDOTDOT,
+ /// Name not unique on network.
+ ENOTUNIQ,
+ /// File descriptor in bad state.
+ EBADFD,
+ /// Remote address changed.
+ EREMCHG,
+ /// Can not access a needed shared library.
+ ELIBACC,
+ /// Accessing a corrupted shared library.
+ ELIBBAD,
+ /// .lib section in a.out corrupted.
+ ELIBSCN,
+ /// Attempting to link in too many shared libraries.
+ ELIBMAX,
+ /// Cannot exec a shared library directly.
+ ELIBEXEC,
+ /// Interrupted system call should be restarted.
+ ERESTART,
+ /// Streams pipe error.
+ ESTRPIPE,
+ /// Structure needs cleaning.
+ EUCLEAN,
+ /// Not a XENIX named type file.
+ ENOTNAM,
+ /// No XENIX semaphores available.
+ ENAVAIL,
+ /// Is a named type file.
+ EISNAM,
+ /// Remote I/O error.
+ EREMOTEIO,
+ /// Required key not available.
+ ENOKEY,
+ /// Key has expired.
+ EKEYEXPIRED,
+ /// Key has been revoked.
+ EKEYREVOKED,
+ /// Key was rejected by service.
+ EKEYREJECTED,
+ /// Operation not possible due to RF-kill.
+ ERFKILL,
+ /// Memory page has hardware error.
+ EHWPOISON,
+ /// Device power is off.
+ EPWROFF,
+ /// Device error, e.g. paper out.
+ EDEVERR,
+ /// Bad executable.
+ EBADEXEC,
+ /// Bad CPU type in executable.
+ EBADARCH,
+ /// Shared library version mismatch.
+ ESHLIBVERS,
+ /// Malformed Macho file.
+ EBADMACHO,
+ /// No such policy registered.
+ ENOPOLICY,
+ /// Interface output queue is full.
+ EQFULL,
+ /// All other error codes.
+ ///
+ /// Note this is guaranteed to contain an error code that is not the same as any of the above
+ /// variants.
+ Other(OtherErrno),
+}
+impl Errno {
+ /// Returns a `Self` equivalent to `code`.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// # use priv_sep::Errno;
+ /// assert_eq!(Errno::from_raw(1), Errno::EPERM);
+ /// ```
+ #[expect(clippy::too_many_lines, reason = "large match expression")]
+ #[inline]
+ #[must_use]
+ pub const fn from_raw(code: c_int) -> Self {
+ #[cfg(target_os = "dragonfly")]
+ match code {
+ 1 => Self::EPERM,
+ 2 => Self::ENOENT,
+ 3 => Self::ESRCH,
+ 4 => Self::EINTR,
+ 5 => Self::EIO,
+ 6 => Self::ENXIO,
+ 7 => Self::E2BIG,
+ 8 => Self::ENOEXEC,
+ 9 => Self::EBADF,
+ 10 => Self::ECHILD,
+ 11 => Self::EDEADLK,
+ 12 => Self::ENOMEM,
+ 13 => Self::EACCES,
+ 14 => Self::EFAULT,
+ 15 => Self::ENOTBLK,
+ 16 => Self::EBUSY,
+ 17 => Self::EEXIST,
+ 18 => Self::EXDEV,
+ 19 => Self::ENODEV,
+ 20 => Self::ENOTDIR,
+ 21 => Self::EISDIR,
+ 22 => Self::EINVAL,
+ 23 => Self::ENFILE,
+ 24 => Self::EMFILE,
+ 25 => Self::ENOTTY,
+ 26 => Self::ETXTBSY,
+ 27 => Self::EFBIG,
+ 28 => Self::ENOSPC,
+ 29 => Self::ESPIPE,
+ 30 => Self::EROFS,
+ 31 => Self::EMLINK,
+ 32 => Self::EPIPE,
+ 33 => Self::EDOM,
+ 34 => Self::ERANGE,
+ 35 => Self::EWOULDBLOCK,
+ 36 => Self::EINPROGRESS,
+ 37 => Self::EALREADY,
+ 38 => Self::ENOTSOCK,
+ 39 => Self::EDESTADDRREQ,
+ 40 => Self::EMSGSIZE,
+ 41 => Self::EPROTOTYPE,
+ 42 => Self::ENOPROTOOPT,
+ 43 => Self::EPROTONOSUPPORT,
+ 44 => Self::ESOCKTNOSUPPORT,
+ 45 => Self::EOPNOTSUPP,
+ 46 => Self::EPFNOSUPPORT,
+ 47 => Self::EAFNOSUPPORT,
+ 48 => Self::EADDRINUSE,
+ 49 => Self::EADDRNOTAVAIL,
+ 50 => Self::ENETDOWN,
+ 51 => Self::ENETUNREACH,
+ 52 => Self::ENETRESET,
+ 53 => Self::ECONNABORTED,
+ 54 => Self::ECONNRESET,
+ 55 => Self::ENOBUFS,
+ 56 => Self::EISCONN,
+ 57 => Self::ENOTCONN,
+ 58 => Self::ESHUTDOWN,
+ 59 => Self::ETOOMANYREFS,
+ 60 => Self::ETIMEDOUT,
+ 61 => Self::ECONNREFUSED,
+ 62 => Self::ELOOP,
+ 63 => Self::ENAMETOOLONG,
+ 64 => Self::EHOSTDOWN,
+ 65 => Self::EHOSTUNREACH,
+ 66 => Self::ENOTEMPTY,
+ 67 => Self::EPROCLIM,
+ 68 => Self::EUSERS,
+ 69 => Self::EDQUOT,
+ 70 => Self::ESTALE,
+ 71 => Self::EREMOTE,
+ 72 => Self::EBADRPC,
+ 73 => Self::ERPCMISMATCH,
+ 74 => Self::EPROGUNAVAIL,
+ 75 => Self::EPROGMISMATCH,
+ 76 => Self::EPROCUNAVAIL,
+ 77 => Self::ENOLCK,
+ 78 => Self::ENOSYS,
+ 79 => Self::EFTYPE,
+ 80 => Self::EAUTH,
+ 81 => Self::ENEEDAUTH,
+ 82 => Self::EIDRM,
+ 83 => Self::ENOMSG,
+ 84 => Self::EOVERFLOW,
+ 85 => Self::ECANCELED,
+ 86 => Self::EILSEQ,
+ 87 => Self::ENOATTR,
+ 88 => Self::EDOOFUS,
+ 89 => Self::EBADMSG,
+ 90 => Self::EMULTIHOP,
+ 91 => Self::ENOLINK,
+ 92 => Self::EPROTO,
+ 93 => Self::ENOMEDIUM,
+ 94 => Self::ENOTRECOVERABLE,
+ 95 => Self::EOWNERDEAD,
+ _ => Self::Other(OtherErrno(code)),
+ }
+ #[cfg(target_os = "freebsd")]
+ match code {
+ 1 => Self::EPERM,
+ 2 => Self::ENOENT,
+ 3 => Self::ESRCH,
+ 4 => Self::EINTR,
+ 5 => Self::EIO,
+ 6 => Self::ENXIO,
+ 7 => Self::E2BIG,
+ 8 => Self::ENOEXEC,
+ 9 => Self::EBADF,
+ 10 => Self::ECHILD,
+ 11 => Self::EDEADLK,
+ 12 => Self::ENOMEM,
+ 13 => Self::EACCES,
+ 14 => Self::EFAULT,
+ 15 => Self::ENOTBLK,
+ 16 => Self::EBUSY,
+ 17 => Self::EEXIST,
+ 18 => Self::EXDEV,
+ 19 => Self::ENODEV,
+ 20 => Self::ENOTDIR,
+ 21 => Self::EISDIR,
+ 22 => Self::EINVAL,
+ 23 => Self::ENFILE,
+ 24 => Self::EMFILE,
+ 25 => Self::ENOTTY,
+ 26 => Self::ETXTBSY,
+ 27 => Self::EFBIG,
+ 28 => Self::ENOSPC,
+ 29 => Self::ESPIPE,
+ 30 => Self::EROFS,
+ 31 => Self::EMLINK,
+ 32 => Self::EPIPE,
+ 33 => Self::EDOM,
+ 34 => Self::ERANGE,
+ 35 => Self::EWOULDBLOCK,
+ 36 => Self::EINPROGRESS,
+ 37 => Self::EALREADY,
+ 38 => Self::ENOTSOCK,
+ 39 => Self::EDESTADDRREQ,
+ 40 => Self::EMSGSIZE,
+ 41 => Self::EPROTOTYPE,
+ 42 => Self::ENOPROTOOPT,
+ 43 => Self::EPROTONOSUPPORT,
+ 44 => Self::ESOCKTNOSUPPORT,
+ 45 => Self::EOPNOTSUPP,
+ 46 => Self::EPFNOSUPPORT,
+ 47 => Self::EAFNOSUPPORT,
+ 48 => Self::EADDRINUSE,
+ 49 => Self::EADDRNOTAVAIL,
+ 50 => Self::ENETDOWN,
+ 51 => Self::ENETUNREACH,
+ 52 => Self::ENETRESET,
+ 53 => Self::ECONNABORTED,
+ 54 => Self::ECONNRESET,
+ 55 => Self::ENOBUFS,
+ 56 => Self::EISCONN,
+ 57 => Self::ENOTCONN,
+ 58 => Self::ESHUTDOWN,
+ 59 => Self::ETOOMANYREFS,
+ 60 => Self::ETIMEDOUT,
+ 61 => Self::ECONNREFUSED,
+ 62 => Self::ELOOP,
+ 63 => Self::ENAMETOOLONG,
+ 64 => Self::EHOSTDOWN,
+ 65 => Self::EHOSTUNREACH,
+ 66 => Self::ENOTEMPTY,
+ 67 => Self::EPROCLIM,
+ 68 => Self::EUSERS,
+ 69 => Self::EDQUOT,
+ 70 => Self::ESTALE,
+ 71 => Self::EREMOTE,
+ 72 => Self::EBADRPC,
+ 73 => Self::ERPCMISMATCH,
+ 74 => Self::EPROGUNAVAIL,
+ 75 => Self::EPROGMISMATCH,
+ 76 => Self::EPROCUNAVAIL,
+ 77 => Self::ENOLCK,
+ 78 => Self::ENOSYS,
+ 79 => Self::EFTYPE,
+ 80 => Self::EAUTH,
+ 81 => Self::ENEEDAUTH,
+ 82 => Self::EIDRM,
+ 83 => Self::ENOMSG,
+ 84 => Self::EOVERFLOW,
+ 85 => Self::ECANCELED,
+ 86 => Self::EILSEQ,
+ 87 => Self::ENOATTR,
+ 88 => Self::EDOOFUS,
+ 89 => Self::EBADMSG,
+ 90 => Self::EMULTIHOP,
+ 91 => Self::ENOLINK,
+ 92 => Self::EPROTO,
+ 93 => Self::ENOTCAPABLE,
+ 94 => Self::ECAPMODE,
+ 95 => Self::ENOTRECOVERABLE,
+ 96 => Self::EOWNERDEAD,
+ 97 => Self::EINTEGRITY,
+ _ => Self::Other(OtherErrno(code)),
+ }
+ #[cfg(target_vendor = "apple")]
+ match code {
+ 1 => Self::EPERM,
+ 2 => Self::ENOENT,
+ 3 => Self::ESRCH,
+ 4 => Self::EINTR,
+ 5 => Self::EIO,
+ 6 => Self::ENXIO,
+ 7 => Self::E2BIG,
+ 8 => Self::ENOEXEC,
+ 9 => Self::EBADF,
+ 10 => Self::ECHILD,
+ 11 => Self::EDEADLK,
+ 12 => Self::ENOMEM,
+ 13 => Self::EACCES,
+ 14 => Self::EFAULT,
+ 15 => Self::ENOTBLK,
+ 16 => Self::EBUSY,
+ 17 => Self::EEXIST,
+ 18 => Self::EXDEV,
+ 19 => Self::ENODEV,
+ 20 => Self::ENOTDIR,
+ 21 => Self::EISDIR,
+ 22 => Self::EINVAL,
+ 23 => Self::ENFILE,
+ 24 => Self::EMFILE,
+ 25 => Self::ENOTTY,
+ 26 => Self::ETXTBSY,
+ 27 => Self::EFBIG,
+ 28 => Self::ENOSPC,
+ 29 => Self::ESPIPE,
+ 30 => Self::EROFS,
+ 31 => Self::EMLINK,
+ 32 => Self::EPIPE,
+ 33 => Self::EDOM,
+ 34 => Self::ERANGE,
+ 35 => Self::EWOULDBLOCK,
+ 36 => Self::EINPROGRESS,
+ 37 => Self::EALREADY,
+ 38 => Self::ENOTSOCK,
+ 39 => Self::EDESTADDRREQ,
+ 40 => Self::EMSGSIZE,
+ 41 => Self::EPROTOTYPE,
+ 42 => Self::ENOPROTOOPT,
+ 43 => Self::EPROTONOSUPPORT,
+ 44 => Self::ESOCKTNOSUPPORT,
+ 45 => Self::ENOTSUPP,
+ 46 => Self::EPFNOSUPPORT,
+ 47 => Self::EAFNOSUPPORT,
+ 48 => Self::EADDRINUSE,
+ 49 => Self::EADDRNOTAVAIL,
+ 50 => Self::ENETDOWN,
+ 51 => Self::ENETUNREACH,
+ 52 => Self::ENETRESET,
+ 53 => Self::ECONNABORTED,
+ 54 => Self::ECONNRESET,
+ 55 => Self::ENOBUFS,
+ 56 => Self::EISCONN,
+ 57 => Self::ENOTCONN,
+ 58 => Self::ESHUTDOWN,
+ 59 => Self::ETOOMANYREFS,
+ 60 => Self::ETIMEDOUT,
+ 61 => Self::ECONNREFUSED,
+ 62 => Self::ELOOP,
+ 63 => Self::ENAMETOOLONG,
+ 64 => Self::EHOSTDOWN,
+ 65 => Self::EHOSTUNREACH,
+ 66 => Self::ENOTEMPTY,
+ 67 => Self::EPROCLIM,
+ 68 => Self::EUSERS,
+ 69 => Self::EDQUOT,
+ 70 => Self::ESTALE,
+ 71 => Self::EREMOTE,
+ 72 => Self::EBADRPC,
+ 73 => Self::ERPCMISMATCH,
+ 74 => Self::EPROGUNAVAIL,
+ 75 => Self::EPROGMISMATCH,
+ 76 => Self::EPROCUNAVAIL,
+ 77 => Self::ENOLCK,
+ 78 => Self::ENOSYS,
+ 79 => Self::EFTYPE,
+ 80 => Self::EAUTH,
+ 81 => Self::ENEEDAUTH,
+ 82 => Self::EPWROFF,
+ 83 => Self::EDEVERR,
+ 84 => Self::EOVERFLOW,
+ 85 => Self::EBADEXEC,
+ 86 => Self::EBADARCH,
+ 87 => Self::ESHLIBVERS,
+ 88 => Self::EBADMACHO,
+ 89 => Self::ECANCELED,
+ 90 => Self::EIDRM,
+ 91 => Self::ENOMSG,
+ 92 => Self::EILSEQ,
+ 93 => Self::ENOATTR,
+ 94 => Self::EBADMSG,
+ 95 => Self::EMULTIHOP,
+ 96 => Self::ENODATA,
+ 97 => Self::ENOLINK,
+ 98 => Self::ENOSR,
+ 99 => Self::ENOSTR,
+ 100 => Self::EPROTO,
+ 101 => Self::ETIME,
+ 102 => Self::EOPNOTSUPP,
+ 103 => Self::ENOPOLICY,
+ 104 => Self::ENOTRECOVERABLE,
+ 105 => Self::EOWNERDEAD,
+ 106 => Self::EQFULL,
+ _ => Self::Other(OtherErrno(code)),
+ }
+ #[cfg(target_os = "netbsd")]
+ match code {
+ 1 => Self::EPERM,
+ 2 => Self::ENOENT,
+ 3 => Self::ESRCH,
+ 4 => Self::EINTR,
+ 5 => Self::EIO,
+ 6 => Self::ENXIO,
+ 7 => Self::E2BIG,
+ 8 => Self::ENOEXEC,
+ 9 => Self::EBADF,
+ 10 => Self::ECHILD,
+ 11 => Self::EDEADLK,
+ 12 => Self::ENOMEM,
+ 13 => Self::EACCES,
+ 14 => Self::EFAULT,
+ 15 => Self::ENOTBLK,
+ 16 => Self::EBUSY,
+ 17 => Self::EEXIST,
+ 18 => Self::EXDEV,
+ 19 => Self::ENODEV,
+ 20 => Self::ENOTDIR,
+ 21 => Self::EISDIR,
+ 22 => Self::EINVAL,
+ 23 => Self::ENFILE,
+ 24 => Self::EMFILE,
+ 25 => Self::ENOTTY,
+ 26 => Self::ETXTBSY,
+ 27 => Self::EFBIG,
+ 28 => Self::ENOSPC,
+ 29 => Self::ESPIPE,
+ 30 => Self::EROFS,
+ 31 => Self::EMLINK,
+ 32 => Self::EPIPE,
+ 33 => Self::EDOM,
+ 34 => Self::ERANGE,
+ 35 => Self::EWOULDBLOCK,
+ 36 => Self::EINPROGRESS,
+ 37 => Self::EALREADY,
+ 38 => Self::ENOTSOCK,
+ 39 => Self::EDESTADDRREQ,
+ 40 => Self::EMSGSIZE,
+ 41 => Self::EPROTOTYPE,
+ 42 => Self::ENOPROTOOPT,
+ 43 => Self::EPROTONOSUPPORT,
+ 44 => Self::ESOCKTNOSUPPORT,
+ 45 => Self::EOPNOTSUPP,
+ 46 => Self::EPFNOSUPPORT,
+ 47 => Self::EAFNOSUPPORT,
+ 48 => Self::EADDRINUSE,
+ 49 => Self::EADDRNOTAVAIL,
+ 50 => Self::ENETDOWN,
+ 51 => Self::ENETUNREACH,
+ 52 => Self::ENETRESET,
+ 53 => Self::ECONNABORTED,
+ 54 => Self::ECONNRESET,
+ 55 => Self::ENOBUFS,
+ 56 => Self::EISCONN,
+ 57 => Self::ENOTCONN,
+ 58 => Self::ESHUTDOWN,
+ 59 => Self::ETOOMANYREFS,
+ 60 => Self::ETIMEDOUT,
+ 61 => Self::ECONNREFUSED,
+ 62 => Self::ELOOP,
+ 63 => Self::ENAMETOOLONG,
+ 64 => Self::EHOSTDOWN,
+ 65 => Self::EHOSTUNREACH,
+ 66 => Self::ENOTEMPTY,
+ 67 => Self::EPROCLIM,
+ 68 => Self::EUSERS,
+ 69 => Self::EDQUOT,
+ 70 => Self::ESTALE,
+ 71 => Self::EREMOTE,
+ 72 => Self::EBADRPC,
+ 73 => Self::ERPCMISMATCH,
+ 74 => Self::EPROGUNAVAIL,
+ 75 => Self::EPROGMISMATCH,
+ 76 => Self::EPROCUNAVAIL,
+ 77 => Self::ENOLCK,
+ 78 => Self::ENOSYS,
+ 79 => Self::EFTYPE,
+ 80 => Self::EAUTH,
+ 81 => Self::ENEEDAUTH,
+ 82 => Self::EIDRM,
+ 83 => Self::ENOMSG,
+ 84 => Self::EOVERFLOW,
+ 85 => Self::EILSEQ,
+ 86 => Self::ENOTSUP,
+ 87 => Self::ECANCELED,
+ 88 => Self::EBADMSG,
+ 89 => Self::ENODATA,
+ 90 => Self::ENOSR,
+ 91 => Self::ENOSTR,
+ 92 => Self::ETIME,
+ 93 => Self::ENOATTR,
+ 94 => Self::EMULTIHOP,
+ 95 => Self::ENOLINK,
+ 96 => Self::EPROTO,
+ 97 => Self::EOWNERDEAD,
+ 98 => Self::ENOTRECOVERABLE,
+ _ => Self::Other(OtherErrno(code)),
+ }
+ #[cfg(target_os = "openbsd")]
+ match code {
+ 1 => Self::EPERM,
+ 2 => Self::ENOENT,
+ 3 => Self::ESRCH,
+ 4 => Self::EINTR,
+ 5 => Self::EIO,
+ 6 => Self::ENXIO,
+ 7 => Self::E2BIG,
+ 8 => Self::ENOEXEC,
+ 9 => Self::EBADF,
+ 10 => Self::ECHILD,
+ 11 => Self::EDEADLK,
+ 12 => Self::ENOMEM,
+ 13 => Self::EACCES,
+ 14 => Self::EFAULT,
+ 15 => Self::ENOTBLK,
+ 16 => Self::EBUSY,
+ 17 => Self::EEXIST,
+ 18 => Self::EXDEV,
+ 19 => Self::ENODEV,
+ 20 => Self::ENOTDIR,
+ 21 => Self::EISDIR,
+ 22 => Self::EINVAL,
+ 23 => Self::ENFILE,
+ 24 => Self::EMFILE,
+ 25 => Self::ENOTTY,
+ 26 => Self::ETXTBSY,
+ 27 => Self::EFBIG,
+ 28 => Self::ENOSPC,
+ 29 => Self::ESPIPE,
+ 30 => Self::EROFS,
+ 31 => Self::EMLINK,
+ 32 => Self::EPIPE,
+ 33 => Self::EDOM,
+ 34 => Self::ERANGE,
+ 35 => Self::EWOULDBLOCK,
+ 36 => Self::EINPROGRESS,
+ 37 => Self::EALREADY,
+ 38 => Self::ENOTSOCK,
+ 39 => Self::EDESTADDRREQ,
+ 40 => Self::EMSGSIZE,
+ 41 => Self::EPROTOTYPE,
+ 42 => Self::ENOPROTOOPT,
+ 43 => Self::EPROTONOSUPPORT,
+ 44 => Self::ESOCKTNOSUPPORT,
+ 45 => Self::EOPNOTSUPP,
+ 46 => Self::EPFNOSUPPORT,
+ 47 => Self::EAFNOSUPPORT,
+ 48 => Self::EADDRINUSE,
+ 49 => Self::EADDRNOTAVAIL,
+ 50 => Self::ENETDOWN,
+ 51 => Self::ENETUNREACH,
+ 52 => Self::ENETRESET,
+ 53 => Self::ECONNABORTED,
+ 54 => Self::ECONNRESET,
+ 55 => Self::ENOBUFS,
+ 56 => Self::EISCONN,
+ 57 => Self::ENOTCONN,
+ 58 => Self::ESHUTDOWN,
+ 59 => Self::ETOOMANYREFS,
+ 60 => Self::ETIMEDOUT,
+ 61 => Self::ECONNREFUSED,
+ 62 => Self::ELOOP,
+ 63 => Self::ENAMETOOLONG,
+ 64 => Self::EHOSTDOWN,
+ 65 => Self::EHOSTUNREACH,
+ 66 => Self::ENOTEMPTY,
+ 67 => Self::EPROCLIM,
+ 68 => Self::EUSERS,
+ 69 => Self::EDQUOT,
+ 70 => Self::ESTALE,
+ 71 => Self::EREMOTE,
+ 72 => Self::EBADRPC,
+ 73 => Self::ERPCMISMATCH,
+ 74 => Self::EPROGUNAVAIL,
+ 75 => Self::EPROGMISMATCH,
+ 76 => Self::EPROCUNAVAIL,
+ 77 => Self::ENOLCK,
+ 78 => Self::ENOSYS,
+ 79 => Self::EFTYPE,
+ 80 => Self::EAUTH,
+ 81 => Self::ENEEDAUTH,
+ 82 => Self::EIPSEC,
+ 83 => Self::ENOATTR,
+ 84 => Self::EILSEQ,
+ 85 => Self::ENOMEDIUM,
+ 86 => Self::EMEDIUMTYPE,
+ 87 => Self::EOVERFLOW,
+ 88 => Self::ECANCELED,
+ 89 => Self::EIDRM,
+ 90 => Self::ENOMSG,
+ 91 => Self::ENOTSUP,
+ 92 => Self::EBADMSG,
+ 93 => Self::ENOTRECOVERABLE,
+ 94 => Self::EOWNERDEAD,
+ 95 => Self::EPROTO,
+ _ => Self::Other(OtherErrno(code)),
+ }
+ #[cfg(target_os = "linux")]
+ match code {
+ 1 => Self::EPERM,
+ 2 => Self::ENOENT,
+ 3 => Self::ESRCH,
+ 4 => Self::EINTR,
+ 5 => Self::EIO,
+ 6 => Self::ENXIO,
+ 7 => Self::E2BIG,
+ 8 => Self::ENOEXEC,
+ 9 => Self::EBADF,
+ 10 => Self::ECHILD,
+ 11 => Self::EWOULDBLOCK,
+ 12 => Self::ENOMEM,
+ 13 => Self::EACCES,
+ 14 => Self::EFAULT,
+ 15 => Self::ENOTBLK,
+ 16 => Self::EBUSY,
+ 17 => Self::EEXIST,
+ 18 => Self::EXDEV,
+ 19 => Self::ENODEV,
+ 20 => Self::ENOTDIR,
+ 21 => Self::EISDIR,
+ 22 => Self::EINVAL,
+ 23 => Self::ENFILE,
+ 24 => Self::EMFILE,
+ 25 => Self::ENOTTY,
+ 26 => Self::ETXTBSY,
+ 27 => Self::EFBIG,
+ 28 => Self::ENOSPC,
+ 29 => Self::ESPIPE,
+ 30 => Self::EROFS,
+ 31 => Self::EMLINK,
+ 32 => Self::EPIPE,
+ 33 => Self::EDOM,
+ 34 => Self::ERANGE,
+ 35 => Self::EDEADLK,
+ 36 => Self::ENAMETOOLONG,
+ 37 => Self::ENOLCK,
+ 38 => Self::ENOSYS,
+ 39 => Self::ENOTEMPTY,
+ 40 => Self::ELOOP,
+ 42 => Self::ENOMSG,
+ 43 => Self::EIDRM,
+ 44 => Self::ECHRNG,
+ 45 => Self::EL2NSYNC,
+ 46 => Self::EL3HLT,
+ 47 => Self::EL3RST,
+ 48 => Self::ELNRNG,
+ 49 => Self::EUNATCH,
+ 50 => Self::ENOCSI,
+ 51 => Self::EL2HLT,
+ 52 => Self::EBADE,
+ 53 => Self::EBADR,
+ 54 => Self::EXFULL,
+ 55 => Self::ENOANO,
+ 56 => Self::EBADRQC,
+ 57 => Self::EBADSLT,
+ 59 => Self::EBFONT,
+ 60 => Self::ENOSTR,
+ 61 => Self::ENODATA,
+ 62 => Self::ETIME,
+ 63 => Self::ENOSR,
+ 64 => Self::ENONET,
+ 65 => Self::ENOPKG,
+ 66 => Self::EREMOTE,
+ 67 => Self::ENOLINK,
+ 68 => Self::EADV,
+ 69 => Self::ESRMNT,
+ 70 => Self::ECOMM,
+ 71 => Self::EPROTO,
+ 72 => Self::EMULTIHOP,
+ 73 => Self::EDOTDOT,
+ 74 => Self::EBADMSG,
+ 75 => Self::EOVERFLOW,
+ 76 => Self::ENOTUNIQ,
+ 77 => Self::EBADFD,
+ 78 => Self::EREMCHG,
+ 79 => Self::ELIBACC,
+ 80 => Self::ELIBBAD,
+ 81 => Self::ELIBSCN,
+ 82 => Self::ELIBMAX,
+ 83 => Self::ELIBEXEC,
+ 84 => Self::EILSEQ,
+ 85 => Self::ERESTART,
+ 86 => Self::ESTRPIPE,
+ 87 => Self::EUSERS,
+ 88 => Self::ENOTSOCK,
+ 89 => Self::EDESTADDRREQ,
+ 90 => Self::EMSGSIZE,
+ 91 => Self::EPROTOTYPE,
+ 92 => Self::ENOPROTOOPT,
+ 93 => Self::EPROTONOSUPPORT,
+ 94 => Self::ESOCKTNOSUPPORT,
+ 95 => Self::EOPNOTSUPP,
+ 96 => Self::EPFNOSUPPORT,
+ 97 => Self::EAFNOSUPPORT,
+ 98 => Self::EADDRINUSE,
+ 99 => Self::EADDRNOTAVAIL,
+ 100 => Self::ENETDOWN,
+ 101 => Self::ENETUNREACH,
+ 102 => Self::ENETRESET,
+ 103 => Self::ECONNABORTED,
+ 104 => Self::ECONNRESET,
+ 105 => Self::ENOBUFS,
+ 106 => Self::EISCONN,
+ 107 => Self::ENOTCONN,
+ 108 => Self::ESHUTDOWN,
+ 109 => Self::ETOOMANYREFS,
+ 110 => Self::ETIMEDOUT,
+ 111 => Self::ECONNREFUSED,
+ 112 => Self::EHOSTDOWN,
+ 113 => Self::EHOSTUNREACH,
+ 114 => Self::EALREADY,
+ 115 => Self::EINPROGRESS,
+ 116 => Self::ESTALE,
+ 117 => Self::EUCLEAN,
+ 118 => Self::ENOTNAM,
+ 119 => Self::ENAVAIL,
+ 120 => Self::EISNAM,
+ 121 => Self::EREMOTEIO,
+ 122 => Self::EDQUOT,
+ 123 => Self::ENOMEDIUM,
+ 124 => Self::EMEDIUMTYPE,
+ 125 => Self::ECANCELED,
+ 126 => Self::ENOKEY,
+ 127 => Self::EKEYEXPIRED,
+ 128 => Self::EKEYREVOKED,
+ 129 => Self::EKEYREJECTED,
+ 130 => Self::EOWNERDEAD,
+ 131 => Self::ENOTRECOVERABLE,
+ 132 => Self::ERFKILL,
+ 133 => Self::EHWPOISON,
+ _ => Self::Other(OtherErrno(code)),
+ }
+ }
+ /// Returns the raw error code `self` is equivalent to.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// # use priv_sep::Errno;
+ /// assert_eq!(Errno::EPERM.into_raw(), 1);
+ /// ```
+ #[expect(clippy::too_many_lines, reason = "large match expression")]
+ #[inline]
+ #[must_use]
+ pub const fn into_raw(self) -> c_int {
+ /// Essentially `unreachable` but `const`.
+ macro_rules! impossible {
+ () => {
+ panic!("there is a bug in priv_sep::Errno. Impossible variant for the target.")
+ };
+ }
+ #[cfg(target_os = "dragonfly")]
+ match self {
+ Self::EPERM => 1,
+ Self::ENOENT => 2,
+ Self::ESRCH => 3,
+ Self::EINTR => 4,
+ Self::EIO => 5,
+ Self::ENXIO => 6,
+ Self::E2BIG => 7,
+ Self::ENOEXEC => 8,
+ Self::EBADF => 9,
+ Self::ECHILD => 10,
+ Self::EDEADLK => 11,
+ Self::ENOMEM => 12,
+ Self::EACCES => 13,
+ Self::EFAULT => 14,
+ Self::ENOTBLK => 15,
+ Self::EBUSY => 16,
+ Self::EEXIST => 17,
+ Self::EXDEV => 18,
+ Self::ENODEV => 19,
+ Self::ENOTDIR => 20,
+ Self::EISDIR => 21,
+ Self::EINVAL => 22,
+ Self::ENFILE => 23,
+ Self::EMFILE => 24,
+ Self::ENOTTY => 25,
+ Self::ETXTBSY => 26,
+ Self::EFBIG => 27,
+ Self::ENOSPC => 28,
+ Self::ESPIPE => 29,
+ Self::EROFS => 30,
+ Self::EMLINK => 31,
+ Self::EPIPE => 32,
+ Self::EDOM => 33,
+ Self::ERANGE => 34,
+ Self::EWOULDBLOCK => 35,
+ Self::EINPROGRESS => 36,
+ Self::EALREADY => 37,
+ Self::ENOTSOCK => 38,
+ Self::EDESTADDRREQ => 39,
+ Self::EMSGSIZE => 40,
+ Self::EPROTOTYPE => 41,
+ Self::ENOPROTOOPT => 42,
+ Self::EPROTONOSUPPORT => 43,
+ Self::ESOCKTNOSUPPORT => 44,
+ Self::EOPNOTSUPP => 45,
+ Self::EPFNOSUPPORT => 46,
+ Self::EAFNOSUPPORT => 47,
+ Self::EADDRINUSE => 48,
+ Self::EADDRNOTAVAIL => 49,
+ Self::ENETDOWN => 50,
+ Self::ENETUNREACH => 51,
+ Self::ENETRESET => 52,
+ Self::ECONNABORTED => 53,
+ Self::ECONNRESET => 54,
+ Self::ENOBUFS => 55,
+ Self::EISCONN => 56,
+ Self::ENOTCONN => 57,
+ Self::ESHUTDOWN => 58,
+ Self::ETOOMANYREFS => 59,
+ Self::ETIMEDOUT => 60,
+ Self::ECONNREFUSED => 61,
+ Self::ELOOP => 62,
+ Self::ENAMETOOLONG => 63,
+ Self::EHOSTDOWN => 64,
+ Self::EHOSTUNREACH => 65,
+ Self::ENOTEMPTY => 66,
+ Self::EPROCLIM => 67,
+ Self::EUSERS => 68,
+ Self::EDQUOT => 69,
+ Self::ESTALE => 70,
+ Self::EREMOTE => 71,
+ Self::EBADRPC => 72,
+ Self::ERPCMISMATCH => 73,
+ Self::EPROGUNAVAIL => 74,
+ Self::EPROGMISMATCH => 75,
+ Self::EPROCUNAVAIL => 76,
+ Self::ENOLCK => 77,
+ Self::ENOSYS => 78,
+ Self::EFTYPE => 79,
+ Self::EAUTH => 80,
+ Self::ENEEDAUTH => 81,
+ Self::EIDRM => 82,
+ Self::ENOMSG => 83,
+ Self::EOVERFLOW => 84,
+ Self::ECANCELED => 85,
+ Self::EILSEQ => 86,
+ Self::ENOATTR => 87,
+ Self::EDOOFUS => 88,
+ Self::EBADMSG => 89,
+ Self::EMULTIHOP => 90,
+ Self::ENOLINK => 91,
+ Self::EPROTO => 92,
+ Self::ENOMEDIUM => 93,
+ Self::ENOTRECOVERABLE => 94,
+ Self::EOWNERDEAD => 95,
+ Self::Other(OtherErrno(code)) => code,
+ Self::EIPSEC
+ | Self::EMEDIUMTYPE
+ | Self::ENOTSUP
+ | Self::ENOTCAPABLE
+ | Self::ECAPMODE
+ | Self::EINTEGRITY
+ | Self::ECHRNG
+ | Self::EL2NSYNC
+ | Self::EL3HLT
+ | Self::EL3RST
+ | Self::ELNRNG
+ | Self::EUNATCH
+ | Self::ENOCSI
+ | Self::EL2HLT
+ | Self::EBADE
+ | Self::EBADR
+ | Self::EXFULL
+ | Self::ENOANO
+ | Self::EBADRQC
+ | Self::EBADSLT
+ | Self::EBFONT
+ | Self::ENOSTR
+ | Self::ENODATA
+ | Self::ETIME
+ | Self::ENOSR
+ | Self::ENONET
+ | Self::ENOPKG
+ | Self::EADV
+ | Self::ESRMNT
+ | Self::ECOMM
+ | Self::EDOTDOT
+ | Self::ENOTUNIQ
+ | Self::EBADFD
+ | Self::EREMCHG
+ | Self::ELIBACC
+ | Self::ELIBBAD
+ | Self::ELIBSCN
+ | Self::ELIBMAX
+ | Self::ELIBEXEC
+ | Self::ERESTART
+ | Self::ESTRPIPE
+ | Self::EUCLEAN
+ | Self::ENOTNAM
+ | Self::ENAVAIL
+ | Self::EISNAM
+ | Self::EREMOTEIO
+ | Self::ENOKEY
+ | Self::EKEYEXPIRED
+ | Self::EKEYREVOKED
+ | Self::EKEYREJECTED
+ | Self::ERFKILL
+ | Self::EHWPOISON
+ | Self::EPWROFF
+ | Self::EDEVERR
+ | Self::EBADEXEC
+ | Self::EBADARCH
+ | Self::ESHLIBVERS
+ | Self::EBADMACHO
+ | Self::ENOPOLICY
+ | Self::EQFULL => impossible!(),
+ }
+ #[cfg(target_os = "freebsd")]
+ match self {
+ Self::EPERM => 1,
+ Self::ENOENT => 2,
+ Self::ESRCH => 3,
+ Self::EINTR => 4,
+ Self::EIO => 5,
+ Self::ENXIO => 6,
+ Self::E2BIG => 7,
+ Self::ENOEXEC => 8,
+ Self::EBADF => 9,
+ Self::ECHILD => 10,
+ Self::EDEADLK => 11,
+ Self::ENOMEM => 12,
+ Self::EACCES => 13,
+ Self::EFAULT => 14,
+ Self::ENOTBLK => 15,
+ Self::EBUSY => 16,
+ Self::EEXIST => 17,
+ Self::EXDEV => 18,
+ Self::ENODEV => 19,
+ Self::ENOTDIR => 20,
+ Self::EISDIR => 21,
+ Self::EINVAL => 22,
+ Self::ENFILE => 23,
+ Self::EMFILE => 24,
+ Self::ENOTTY => 25,
+ Self::ETXTBSY => 26,
+ Self::EFBIG => 27,
+ Self::ENOSPC => 28,
+ Self::ESPIPE => 29,
+ Self::EROFS => 30,
+ Self::EMLINK => 31,
+ Self::EPIPE => 32,
+ Self::EDOM => 33,
+ Self::ERANGE => 34,
+ Self::EWOULDBLOCK => 35,
+ Self::EINPROGRESS => 36,
+ Self::EALREADY => 37,
+ Self::ENOTSOCK => 38,
+ Self::EDESTADDRREQ => 39,
+ Self::EMSGSIZE => 40,
+ Self::EPROTOTYPE => 41,
+ Self::ENOPROTOOPT => 42,
+ Self::EPROTONOSUPPORT => 43,
+ Self::ESOCKTNOSUPPORT => 44,
+ Self::EOPNOTSUPP => 45,
+ Self::EPFNOSUPPORT => 46,
+ Self::EAFNOSUPPORT => 47,
+ Self::EADDRINUSE => 48,
+ Self::EADDRNOTAVAIL => 49,
+ Self::ENETDOWN => 50,
+ Self::ENETUNREACH => 51,
+ Self::ENETRESET => 52,
+ Self::ECONNABORTED => 53,
+ Self::ECONNRESET => 54,
+ Self::ENOBUFS => 55,
+ Self::EISCONN => 56,
+ Self::ENOTCONN => 57,
+ Self::ESHUTDOWN => 58,
+ Self::ETOOMANYREFS => 59,
+ Self::ETIMEDOUT => 60,
+ Self::ECONNREFUSED => 61,
+ Self::ELOOP => 62,
+ Self::ENAMETOOLONG => 63,
+ Self::EHOSTDOWN => 64,
+ Self::EHOSTUNREACH => 65,
+ Self::ENOTEMPTY => 66,
+ Self::EPROCLIM => 67,
+ Self::EUSERS => 68,
+ Self::EDQUOT => 69,
+ Self::ESTALE => 70,
+ Self::EREMOTE => 71,
+ Self::EBADRPC => 72,
+ Self::ERPCMISMATCH => 73,
+ Self::EPROGUNAVAIL => 74,
+ Self::EPROGMISMATCH => 75,
+ Self::EPROCUNAVAIL => 76,
+ Self::ENOLCK => 77,
+ Self::ENOSYS => 78,
+ Self::EFTYPE => 79,
+ Self::EAUTH => 80,
+ Self::ENEEDAUTH => 81,
+ Self::EIDRM => 82,
+ Self::ENOMSG => 83,
+ Self::EOVERFLOW => 84,
+ Self::ECANCELED => 85,
+ Self::EILSEQ => 86,
+ Self::ENOATTR => 87,
+ Self::EDOOFUS => 88,
+ Self::EBADMSG => 89,
+ Self::EMULTIHOP => 90,
+ Self::ENOLINK => 91,
+ Self::EPROTO => 92,
+ Self::ENOTCAPABLE => 93,
+ Self::ECAPMODE => 94,
+ Self::ENOTRECOVERABLE => 95,
+ Self::EOWNERDEAD => 96,
+ Self::EINTEGRITY => 97,
+ Self::Other(OtherErrno(code)) => code,
+ Self::EIPSEC
+ | Self::ENOMEDIUM
+ | Self::EMEDIUMTYPE
+ | Self::ENOTSUP
+ | Self::ECHRNG
+ | Self::EL2NSYNC
+ | Self::EL3HLT
+ | Self::EL3RST
+ | Self::ELNRNG
+ | Self::EUNATCH
+ | Self::ENOCSI
+ | Self::EL2HLT
+ | Self::EBADE
+ | Self::EBADR
+ | Self::EXFULL
+ | Self::ENOANO
+ | Self::EBADRQC
+ | Self::EBADSLT
+ | Self::EBFONT
+ | Self::ENOSTR
+ | Self::ENODATA
+ | Self::ETIME
+ | Self::ENOSR
+ | Self::ENONET
+ | Self::ENOPKG
+ | Self::EADV
+ | Self::ESRMNT
+ | Self::ECOMM
+ | Self::EDOTDOT
+ | Self::ENOTUNIQ
+ | Self::EBADFD
+ | Self::EREMCHG
+ | Self::ELIBACC
+ | Self::ELIBBAD
+ | Self::ELIBSCN
+ | Self::ELIBMAX
+ | Self::ELIBEXEC
+ | Self::ERESTART
+ | Self::ESTRPIPE
+ | Self::EUCLEAN
+ | Self::ENOTNAM
+ | Self::ENAVAIL
+ | Self::EISNAM
+ | Self::EREMOTEIO
+ | Self::ENOKEY
+ | Self::EKEYEXPIRED
+ | Self::EKEYREVOKED
+ | Self::EKEYREJECTED
+ | Self::ERFKILL
+ | Self::EHWPOISON
+ | Self::EPWROFF
+ | Self::EDEVERR
+ | Self::EBADEXEC
+ | Self::EBADARCH
+ | Self::ESHLIBVERS
+ | Self::EBADMACHO
+ | Self::ENOPOLICY
+ | Self::EQFULL => impossible!(),
+ }
+ #[cfg(target_vendor = "apple")]
+ match self {
+ Self::EPERM => 1,
+ Self::ENOENT => 2,
+ Self::ESRCH => 3,
+ Self::EINTR => 4,
+ Self::EIO => 5,
+ Self::ENXIO => 6,
+ Self::E2BIG => 7,
+ Self::ENOEXEC => 8,
+ Self::EBADF => 9,
+ Self::ECHILD => 10,
+ Self::EDEADLK => 11,
+ Self::ENOMEM => 12,
+ Self::EACCES => 13,
+ Self::EFAULT => 14,
+ Self::ENOTBLK => 15,
+ Self::EBUSY => 16,
+ Self::EEXIST => 17,
+ Self::EXDEV => 18,
+ Self::ENODEV => 19,
+ Self::ENOTDIR => 20,
+ Self::EISDIR => 21,
+ Self::EINVAL => 22,
+ Self::ENFILE => 23,
+ Self::EMFILE => 24,
+ Self::ENOTTY => 25,
+ Self::ETXTBSY => 26,
+ Self::EFBIG => 27,
+ Self::ENOSPC => 28,
+ Self::ESPIPE => 29,
+ Self::EROFS => 30,
+ Self::EMLINK => 31,
+ Self::EPIPE => 32,
+ Self::EDOM => 33,
+ Self::ERANGE => 34,
+ Self::EWOULDBLOCK => 35,
+ Self::EINPROGRESS => 36,
+ Self::EALREADY => 37,
+ Self::ENOTSOCK => 38,
+ Self::EDESTADDRREQ => 39,
+ Self::EMSGSIZE => 40,
+ Self::EPROTOTYPE => 41,
+ Self::ENOPROTOOPT => 42,
+ Self::EPROTONOSUPPORT => 43,
+ Self::ESOCKTNOSUPPORT => 44,
+ Self::ENOTSUPP => 45,
+ Self::EPFNOSUPPORT => 46,
+ Self::EAFNOSUPPORT => 47,
+ Self::EADDRINUSE => 48,
+ Self::EADDRNOTAVAIL => 49,
+ Self::ENETDOWN => 50,
+ Self::ENETUNREACH => 51,
+ Self::ENETRESET => 52,
+ Self::ECONNABORTED => 53,
+ Self::ECONNRESET => 54,
+ Self::ENOBUFS => 55,
+ Self::EISCONN => 56,
+ Self::ENOTCONN => 57,
+ Self::ESHUTDOWN => 58,
+ Self::ETOOMANYREFS => 59,
+ Self::ETIMEDOUT => 60,
+ Self::ECONNREFUSED => 61,
+ Self::ELOOP => 62,
+ Self::ENAMETOOLONG => 63,
+ Self::EHOSTDOWN => 64,
+ Self::EHOSTUNREACH => 65,
+ Self::ENOTEMPTY => 66,
+ Self::EPROCLIM => 67,
+ Self::EUSERS => 68,
+ Self::EDQUOT => 69,
+ Self::ESTALE => 70,
+ Self::EREMOTE => 71,
+ Self::EBADRPC => 72,
+ Self::ERPCMISMATCH => 73,
+ Self::EPROGUNAVAIL => 74,
+ Self::EPROGMISMATCH => 75,
+ Self::EPROCUNAVAIL => 76,
+ Self::ENOLCK => 77,
+ Self::ENOSYS => 78,
+ Self::EFTYPE => 79,
+ Self::EAUTH => 80,
+ Self::ENEEDAUTH => 81,
+ Self::EPWROFF => 82,
+ Self::EDEVERR => 83,
+ Self::EOVERFLOW => 84,
+ Self::EBADEXEC => 85,
+ Self::EBADARCH => 86,
+ Self::ESHLIBVERS => 87,
+ Self::EBADMACHO => 88,
+ Self::ECANCELED => 89,
+ Self::EIDRM => 90,
+ Self::ENOMSG => 91,
+ Self::EILSEQ => 92,
+ Self::ENOATTR => 93,
+ Self::EBADMSG => 94,
+ Self::EMULTIHOP => 95,
+ Self::ENODATA => 96,
+ Self::ENOLINK => 97,
+ Self::ENOSR => 98,
+ Self::ENOSTR => 99,
+ Self::EPROTO => 100,
+ Self::ETIME => 101,
+ Self::EOPNOTSUPP => 102,
+ Self::ENOPOLICY => 103,
+ Self::ENOTRECOVERABLE => 104,
+ Self::EOWNERDEAD => 105,
+ Self::EQFULL => 106,
+ Self::Other(OtherErrno(code)) => code,
+ Self::EIPSEC
+ | Self::ENOMEDIUM
+ | Self::EMEDIUMTYPE
+ | Self::EDOOFUS
+ | Self::ENOTCAPABLE
+ | Self::ECAPMODE
+ | Self::EINTEGRITY
+ | Self::ECHRNG
+ | Self::EL2NSYNC
+ | Self::EL3HLT
+ | Self::EL3RST
+ | Self::ELNRNG
+ | Self::EUNATCH
+ | Self::ENOCSI
+ | Self::EL2HLT
+ | Self::EBADE
+ | Self::EBADR
+ | Self::EXFULL
+ | Self::ENOANO
+ | Self::EBADRQC
+ | Self::EBADSLT
+ | Self::EBFONT
+ | Self::ENONET
+ | Self::ENOPKG
+ | Self::EADV
+ | Self::ESRMNT
+ | Self::ECOMM
+ | Self::EDOTDOT
+ | Self::ENOTUNIQ
+ | Self::EBADFD
+ | Self::EREMCHG
+ | Self::ELIBACC
+ | Self::ELIBBAD
+ | Self::ELIBSCN
+ | Self::ELIBMAX
+ | Self::ELIBEXEC
+ | Self::ERESTART
+ | Self::ESTRPIPE
+ | Self::EUCLEAN
+ | Self::ENOTNAM
+ | Self::ENAVAIL
+ | Self::EISNAM
+ | Self::EREMOTEIO
+ | Self::ENOKEY
+ | Self::EKEYEXPIRED
+ | Self::EKEYREVOKED
+ | Self::EKEYREJECTED
+ | Self::ERFKILL
+ | Self::EHWPOISON => impossible!(),
+ }
+ #[cfg(target_os = "netbsd")]
+ match self {
+ Self::EPERM => 1,
+ Self::ENOENT => 2,
+ Self::ESRCH => 3,
+ Self::EINTR => 4,
+ Self::EIO => 5,
+ Self::ENXIO => 6,
+ Self::E2BIG => 7,
+ Self::ENOEXEC => 8,
+ Self::EBADF => 9,
+ Self::ECHILD => 10,
+ Self::EDEADLK => 11,
+ Self::ENOMEM => 12,
+ Self::EACCES => 13,
+ Self::EFAULT => 14,
+ Self::ENOTBLK => 15,
+ Self::EBUSY => 16,
+ Self::EEXIST => 17,
+ Self::EXDEV => 18,
+ Self::ENODEV => 19,
+ Self::ENOTDIR => 20,
+ Self::EISDIR => 21,
+ Self::EINVAL => 22,
+ Self::ENFILE => 23,
+ Self::EMFILE => 24,
+ Self::ENOTTY => 25,
+ Self::ETXTBSY => 26,
+ Self::EFBIG => 27,
+ Self::ENOSPC => 28,
+ Self::ESPIPE => 29,
+ Self::EROFS => 30,
+ Self::EMLINK => 31,
+ Self::EPIPE => 32,
+ Self::EDOM => 33,
+ Self::ERANGE => 34,
+ Self::EWOULDBLOCK => 35,
+ Self::EINPROGRESS => 36,
+ Self::EALREADY => 37,
+ Self::ENOTSOCK => 38,
+ Self::EDESTADDRREQ => 39,
+ Self::EMSGSIZE => 40,
+ Self::EPROTOTYPE => 41,
+ Self::ENOPROTOOPT => 42,
+ Self::EPROTONOSUPPORT => 43,
+ Self::ESOCKTNOSUPPORT => 44,
+ Self::EOPNOTSUPP => 45,
+ Self::EPFNOSUPPORT => 46,
+ Self::EAFNOSUPPORT => 47,
+ Self::EADDRINUSE => 48,
+ Self::EADDRNOTAVAIL => 49,
+ Self::ENETDOWN => 50,
+ Self::ENETUNREACH => 51,
+ Self::ENETRESET => 52,
+ Self::ECONNABORTED => 53,
+ Self::ECONNRESET => 54,
+ Self::ENOBUFS => 55,
+ Self::EISCONN => 56,
+ Self::ENOTCONN => 57,
+ Self::ESHUTDOWN => 58,
+ Self::ETOOMANYREFS => 59,
+ Self::ETIMEDOUT => 60,
+ Self::ECONNREFUSED => 61,
+ Self::ELOOP => 62,
+ Self::ENAMETOOLONG => 63,
+ Self::EHOSTDOWN => 64,
+ Self::EHOSTUNREACH => 65,
+ Self::ENOTEMPTY => 66,
+ Self::EPROCLIM => 67,
+ Self::EUSERS => 68,
+ Self::EDQUOT => 69,
+ Self::ESTALE => 70,
+ Self::EREMOTE => 71,
+ Self::EBADRPC => 72,
+ Self::ERPCMISMATCH => 73,
+ Self::EPROGUNAVAIL => 74,
+ Self::EPROGMISMATCH => 75,
+ Self::EPROCUNAVAIL => 76,
+ Self::ENOLCK => 77,
+ Self::ENOSYS => 78,
+ Self::EFTYPE => 79,
+ Self::EAUTH => 80,
+ Self::ENEEDAUTH => 81,
+ Self::EIDRM => 82,
+ Self::ENOMSG => 83,
+ Self::EOVERFLOW => 84,
+ Self::EILSEQ => 85,
+ Self::ENOTSUP => 86,
+ Self::ECANCELED => 87,
+ Self::EBADMSG => 88,
+ Self::ENODATA => 89,
+ Self::ENOSR => 90,
+ Self::ENOSTR => 91,
+ Self::ETIME => 92,
+ Self::ENOATTR => 93,
+ Self::EMULTIHOP => 94,
+ Self::ENOLINK => 95,
+ Self::EPROTO => 96,
+ Self::EOWNERDEAD => 97,
+ Self::ENOTRECOVERABLE => 98,
+ Self::Other(OtherErrno(code)) => code,
+ Self::EIPSEC
+ | Self::ENOMEDIUM
+ | Self::EMEDIUMTYPE
+ | Self::EDOOFUS
+ | Self::ENOTCAPABLE
+ | Self::ECAPMODE
+ | Self::EINTEGRITY
+ | Self::ECHRNG
+ | Self::EL2NSYNC
+ | Self::EL3HLT
+ | Self::EL3RST
+ | Self::ELNRNG
+ | Self::EUNATCH
+ | Self::ENOCSI
+ | Self::EL2HLT
+ | Self::EBADE
+ | Self::EBADR
+ | Self::EXFULL
+ | Self::ENOANO
+ | Self::EBADRQC
+ | Self::EBADSLT
+ | Self::EBFONT
+ | Self::ENONET
+ | Self::ENOPKG
+ | Self::EADV
+ | Self::ESRMNT
+ | Self::ECOMM
+ | Self::EDOTDOT
+ | Self::ENOTUNIQ
+ | Self::EBADFD
+ | Self::EREMCHG
+ | Self::ELIBACC
+ | Self::ELIBBAD
+ | Self::ELIBSCN
+ | Self::ELIBMAX
+ | Self::ELIBEXEC
+ | Self::ERESTART
+ | Self::ESTRPIPE
+ | Self::EUCLEAN
+ | Self::ENOTNAM
+ | Self::ENAVAIL
+ | Self::EISNAM
+ | Self::EREMOTEIO
+ | Self::ENOKEY
+ | Self::EKEYEXPIRED
+ | Self::EKEYREVOKED
+ | Self::EKEYREJECTED
+ | Self::ERFKILL
+ | Self::EHWPOISON
+ | Self::EPWROFF
+ | Self::EDEVERR
+ | Self::EBADEXEC
+ | Self::EBADARCH
+ | Self::ESHLIBVERS
+ | Self::EBADMACHO
+ | Self::ENOPOLICY
+ | Self::EQFULL => impossible!(),
+ }
+ #[cfg(target_os = "openbsd")]
+ match self {
+ Self::EPERM => 1,
+ Self::ENOENT => 2,
+ Self::ESRCH => 3,
+ Self::EINTR => 4,
+ Self::EIO => 5,
+ Self::ENXIO => 6,
+ Self::E2BIG => 7,
+ Self::ENOEXEC => 8,
+ Self::EBADF => 9,
+ Self::ECHILD => 10,
+ Self::EDEADLK => 11,
+ Self::ENOMEM => 12,
+ Self::EACCES => 13,
+ Self::EFAULT => 14,
+ Self::ENOTBLK => 15,
+ Self::EBUSY => 16,
+ Self::EEXIST => 17,
+ Self::EXDEV => 18,
+ Self::ENODEV => 19,
+ Self::ENOTDIR => 20,
+ Self::EISDIR => 21,
+ Self::EINVAL => 22,
+ Self::ENFILE => 23,
+ Self::EMFILE => 24,
+ Self::ENOTTY => 25,
+ Self::ETXTBSY => 26,
+ Self::EFBIG => 27,
+ Self::ENOSPC => 28,
+ Self::ESPIPE => 29,
+ Self::EROFS => 30,
+ Self::EMLINK => 31,
+ Self::EPIPE => 32,
+ Self::EDOM => 33,
+ Self::ERANGE => 34,
+ Self::EWOULDBLOCK => 35,
+ Self::EINPROGRESS => 36,
+ Self::EALREADY => 37,
+ Self::ENOTSOCK => 38,
+ Self::EDESTADDRREQ => 39,
+ Self::EMSGSIZE => 40,
+ Self::EPROTOTYPE => 41,
+ Self::ENOPROTOOPT => 42,
+ Self::EPROTONOSUPPORT => 43,
+ Self::ESOCKTNOSUPPORT => 44,
+ Self::EOPNOTSUPP => 45,
+ Self::EPFNOSUPPORT => 46,
+ Self::EAFNOSUPPORT => 47,
+ Self::EADDRINUSE => 48,
+ Self::EADDRNOTAVAIL => 49,
+ Self::ENETDOWN => 50,
+ Self::ENETUNREACH => 51,
+ Self::ENETRESET => 52,
+ Self::ECONNABORTED => 53,
+ Self::ECONNRESET => 54,
+ Self::ENOBUFS => 55,
+ Self::EISCONN => 56,
+ Self::ENOTCONN => 57,
+ Self::ESHUTDOWN => 58,
+ Self::ETOOMANYREFS => 59,
+ Self::ETIMEDOUT => 60,
+ Self::ECONNREFUSED => 61,
+ Self::ELOOP => 62,
+ Self::ENAMETOOLONG => 63,
+ Self::EHOSTDOWN => 64,
+ Self::EHOSTUNREACH => 65,
+ Self::ENOTEMPTY => 66,
+ Self::EPROCLIM => 67,
+ Self::EUSERS => 68,
+ Self::EDQUOT => 69,
+ Self::ESTALE => 70,
+ Self::EREMOTE => 71,
+ Self::EBADRPC => 72,
+ Self::ERPCMISMATCH => 73,
+ Self::EPROGUNAVAIL => 74,
+ Self::EPROGMISMATCH => 75,
+ Self::EPROCUNAVAIL => 76,
+ Self::ENOLCK => 77,
+ Self::ENOSYS => 78,
+ Self::EFTYPE => 79,
+ Self::EAUTH => 80,
+ Self::ENEEDAUTH => 81,
+ Self::EIPSEC => 82,
+ Self::ENOATTR => 83,
+ Self::EILSEQ => 84,
+ Self::ENOMEDIUM => 85,
+ Self::EMEDIUMTYPE => 86,
+ Self::EOVERFLOW => 87,
+ Self::ECANCELED => 88,
+ Self::EIDRM => 89,
+ Self::ENOMSG => 90,
+ Self::ENOTSUP => 91,
+ Self::EBADMSG => 92,
+ Self::ENOTRECOVERABLE => 93,
+ Self::EOWNERDEAD => 94,
+ Self::EPROTO => 95,
+ Self::Other(OtherErrno(code)) => code,
+ Self::EDOOFUS
+ | Self::EMULTIHOP
+ | Self::ENOLINK
+ | Self::ENOTCAPABLE
+ | Self::ECAPMODE
+ | Self::EINTEGRITY
+ | Self::ECHRNG
+ | Self::EL2NSYNC
+ | Self::EL3HLT
+ | Self::EL3RST
+ | Self::ELNRNG
+ | Self::EUNATCH
+ | Self::ENOCSI
+ | Self::EL2HLT
+ | Self::EBADE
+ | Self::EBADR
+ | Self::EXFULL
+ | Self::ENOANO
+ | Self::EBADRQC
+ | Self::EBADSLT
+ | Self::EBFONT
+ | Self::ENOSTR
+ | Self::ENODATA
+ | Self::ETIME
+ | Self::ENOSR
+ | Self::ENONET
+ | Self::ENOPKG
+ | Self::EADV
+ | Self::ESRMNT
+ | Self::ECOMM
+ | Self::EDOTDOT
+ | Self::ENOTUNIQ
+ | Self::EBADFD
+ | Self::EREMCHG
+ | Self::ELIBACC
+ | Self::ELIBBAD
+ | Self::ELIBSCN
+ | Self::ELIBMAX
+ | Self::ELIBEXEC
+ | Self::ERESTART
+ | Self::ESTRPIPE
+ | Self::EUCLEAN
+ | Self::ENOTNAM
+ | Self::ENAVAIL
+ | Self::EISNAM
+ | Self::EREMOTEIO
+ | Self::ENOKEY
+ | Self::EKEYEXPIRED
+ | Self::EKEYREVOKED
+ | Self::EKEYREJECTED
+ | Self::ERFKILL
+ | Self::EHWPOISON
+ | Self::EPWROFF
+ | Self::EDEVERR
+ | Self::EBADEXEC
+ | Self::EBADARCH
+ | Self::ESHLIBVERS
+ | Self::EBADMACHO
+ | Self::ENOPOLICY
+ | Self::EQFULL => impossible!(),
+ }
+ #[cfg(target_os = "linux")]
+ match self {
+ Self::EPERM => 1,
+ Self::ENOENT => 2,
+ Self::ESRCH => 3,
+ Self::EINTR => 4,
+ Self::EIO => 5,
+ Self::ENXIO => 6,
+ Self::E2BIG => 7,
+ Self::ENOEXEC => 8,
+ Self::EBADF => 9,
+ Self::ECHILD => 10,
+ Self::EWOULDBLOCK => 11,
+ Self::ENOMEM => 12,
+ Self::EACCES => 13,
+ Self::EFAULT => 14,
+ Self::ENOTBLK => 15,
+ Self::EBUSY => 16,
+ Self::EEXIST => 17,
+ Self::EXDEV => 18,
+ Self::ENODEV => 19,
+ Self::ENOTDIR => 20,
+ Self::EISDIR => 21,
+ Self::EINVAL => 22,
+ Self::ENFILE => 23,
+ Self::EMFILE => 24,
+ Self::ENOTTY => 25,
+ Self::ETXTBSY => 26,
+ Self::EFBIG => 27,
+ Self::ENOSPC => 28,
+ Self::ESPIPE => 29,
+ Self::EROFS => 30,
+ Self::EMLINK => 31,
+ Self::EPIPE => 32,
+ Self::EDOM => 33,
+ Self::ERANGE => 34,
+ Self::EDEADLK => 35,
+ Self::ENAMETOOLONG => 36,
+ Self::ENOLCK => 37,
+ Self::ENOSYS => 38,
+ Self::ENOTEMPTY => 39,
+ Self::ELOOP => 40,
+ Self::ENOMSG => 42,
+ Self::EIDRM => 43,
+ Self::ECHRNG => 44,
+ Self::EL2NSYNC => 45,
+ Self::EL3HLT => 46,
+ Self::EL3RST => 47,
+ Self::ELNRNG => 48,
+ Self::EUNATCH => 49,
+ Self::ENOCSI => 50,
+ Self::EL2HLT => 51,
+ Self::EBADE => 52,
+ Self::EBADR => 53,
+ Self::EXFULL => 54,
+ Self::ENOANO => 55,
+ Self::EBADRQC => 56,
+ Self::EBADSLT => 57,
+ Self::EBFONT => 59,
+ Self::ENOSTR => 60,
+ Self::ENODATA => 61,
+ Self::ETIME => 62,
+ Self::ENOSR => 63,
+ Self::ENONET => 64,
+ Self::ENOPKG => 65,
+ Self::EREMOTE => 66,
+ Self::ENOLINK => 67,
+ Self::EADV => 68,
+ Self::ESRMNT => 69,
+ Self::ECOMM => 70,
+ Self::EPROTO => 71,
+ Self::EMULTIHOP => 72,
+ Self::EDOTDOT => 73,
+ Self::EBADMSG => 74,
+ Self::EOVERFLOW => 75,
+ Self::ENOTUNIQ => 76,
+ Self::EBADFD => 77,
+ Self::EREMCHG => 78,
+ Self::ELIBACC => 79,
+ Self::ELIBBAD => 80,
+ Self::ELIBSCN => 81,
+ Self::ELIBMAX => 82,
+ Self::ELIBEXEC => 83,
+ Self::EILSEQ => 84,
+ Self::ERESTART => 85,
+ Self::ESTRPIPE => 86,
+ Self::EUSERS => 87,
+ Self::ENOTSOCK => 88,
+ Self::EDESTADDRREQ => 89,
+ Self::EMSGSIZE => 90,
+ Self::EPROTOTYPE => 91,
+ Self::ENOPROTOOPT => 92,
+ Self::EPROTONOSUPPORT => 93,
+ Self::ESOCKTNOSUPPORT => 94,
+ Self::EOPNOTSUPP => 95,
+ Self::EPFNOSUPPORT => 96,
+ Self::EAFNOSUPPORT => 97,
+ Self::EADDRINUSE => 98,
+ Self::EADDRNOTAVAIL => 99,
+ Self::ENETDOWN => 100,
+ Self::ENETUNREACH => 101,
+ Self::ENETRESET => 102,
+ Self::ECONNABORTED => 103,
+ Self::ECONNRESET => 104,
+ Self::ENOBUFS => 105,
+ Self::EISCONN => 106,
+ Self::ENOTCONN => 107,
+ Self::ESHUTDOWN => 108,
+ Self::ETOOMANYREFS => 109,
+ Self::ETIMEDOUT => 110,
+ Self::ECONNREFUSED => 111,
+ Self::EHOSTDOWN => 112,
+ Self::EHOSTUNREACH => 113,
+ Self::EALREADY => 114,
+ Self::EINPROGRESS => 115,
+ Self::ESTALE => 116,
+ Self::EUCLEAN => 117,
+ Self::ENOTNAM => 118,
+ Self::ENAVAIL => 119,
+ Self::EISNAM => 120,
+ Self::EREMOTEIO => 121,
+ Self::EDQUOT => 122,
+ Self::ENOMEDIUM => 123,
+ Self::EMEDIUMTYPE => 124,
+ Self::ECANCELED => 125,
+ Self::ENOKEY => 126,
+ Self::EKEYEXPIRED => 127,
+ Self::EKEYREVOKED => 128,
+ Self::EKEYREJECTED => 129,
+ Self::EOWNERDEAD => 130,
+ Self::ENOTRECOVERABLE => 131,
+ Self::ERFKILL => 132,
+ Self::EHWPOISON => 133,
+ Self::Other(OtherErrno(code)) => code,
+ Self::EPROCLIM
+ | Self::EBADRPC
+ | Self::ERPCMISMATCH
+ | Self::EPROGUNAVAIL
+ | Self::EPROGMISMATCH
+ | Self::EPROCUNAVAIL
+ | Self::EFTYPE
+ | Self::EAUTH
+ | Self::ENEEDAUTH
+ | Self::EIPSEC
+ | Self::ENOATTR
+ | Self::ENOTSUP
+ | Self::EDOOFUS
+ | Self::ENOTCAPABLE
+ | Self::ECAPMODE
+ | Self::EINTEGRITY
+ | Self::EPWROFF
+ | Self::EDEVERR
+ | Self::EBADEXEC
+ | Self::EBADARCH
+ | Self::ESHLIBVERS
+ | Self::EBADMACHO
+ | Self::ENOPOLICY
+ | Self::EQFULL => impossible!(),
+ }
+ }
+ /// Returns the error pointer.
+ #[expect(unsafe_code, reason = "safety comment justifies correctness")]
+ fn get_raw_err_ptr() -> *mut c_int {
+ #[cfg(any(target_os = "netbsd", target_os = "openbsd"))]
+ // SAFETY:
+ // Safe because errno is a thread-local variable.
+ unsafe {
+ c::__errno()
+ }
+ #[cfg(any(target_os = "freebsd", target_vendor = "apple"))]
+ // SAFETY:
+ // Safe because errno is a thread-local variable.
+ unsafe {
+ c::__error()
+ }
+ #[cfg(any(target_os = "dragonfly", target_os = "linux"))]
+ // SAFETY:
+ // Safe because errno is a thread-local variable.
+ unsafe {
+ c::__errno_location()
+ }
+ }
+ /// Sets the platform error to `self`.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// # use priv_sep::Errno;
+ /// Errno::ENOENT.set();
+ /// ```
+ #[expect(unsafe_code, reason = "safety comment justifies correctness")]
+ #[inline]
+ pub fn set(self) {
+ let ptr = Self::get_raw_err_ptr();
+ debug_assert!(
+ !ptr.is_null() && ptr.is_aligned(),
+ "libc errno returned a pointer that was either null or not aligned. Something is terribly wrong with your system"
+ );
+ let code = self.into_raw();
+ // SAFETY:
+ // Verified above that `ptr` is not null and aligned. Note while we only verify
+ // this on non-release builds, the situation is so dire that one could argue we are
+ // already in "undefined" territory.
+ unsafe { *ptr = code };
+ }
+ /// Returns the current platform error.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// # use priv_sep::Errno;
+ /// Errno::ENOENT.set();
+ /// assert_eq!(Errno::last(), Errno::ENOENT);
+ /// ```
+ #[expect(unsafe_code, reason = "safety comment justifies correctness")]
+ #[inline]
+ #[must_use]
+ pub fn last() -> Self {
+ let ptr = Self::get_raw_err_ptr();
+ debug_assert!(
+ !ptr.is_null() && ptr.is_aligned(),
+ "libc errno returned a pointer that was either null or not aligned. Something is terribly wrong with your system"
+ );
+ // SAFETY:
+ // Verified above that `ptr` is not null and aligned. Note while we only verify
+ // this on non-release builds, the situation is so dire that one could argue we are
+ // already in "undefined" territory.
+ Self::from_raw(unsafe { *ptr })
+ }
+ /// Clears any error.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// # use priv_sep::Errno;
+ /// Errno::clear();
+ /// assert!(matches!(Errno::last(), Errno::Other(other) if other.into_raw() == 0));
+ /// ```
+ #[inline]
+ pub fn clear() {
+ Self::Other(OtherErrno(0)).set();
+ }
+}
+impl From<Errno> for c_int {
+ #[inline]
+ fn from(value: Errno) -> Self {
+ value.into_raw()
+ }
+}
+impl From<c_int> for Errno {
+ #[inline]
+ fn from(value: c_int) -> Self {
+ Self::from_raw(value)
+ }
+}
+impl Display for Errno {
+ #[expect(clippy::too_many_lines, reason = "large match expression")]
+ #[inline]
+ fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+ match *self {
+ Self::EPERM => f.write_str("Operation not permitted"),
+ Self::ENOENT => f.write_str("No such file or directory"),
+ Self::ESRCH => f.write_str("No such process"),
+ Self::EINTR => f.write_str("Interrupted system call"),
+ Self::EIO => f.write_str("Input/output error"),
+ Self::ENXIO => f.write_str("Device not configured"),
+ Self::E2BIG => f.write_str("Argument list too long"),
+ Self::ENOEXEC => f.write_str("Exec format error"),
+ Self::EBADF => f.write_str("Bad file descriptor"),
+ Self::ECHILD => f.write_str("No child processes"),
+ Self::EDEADLK => f.write_str("Resource deadlock avoided"),
+ Self::ENOMEM => f.write_str("Cannot allocate memory"),
+ Self::EACCES => f.write_str("Permission denied"),
+ Self::EFAULT => f.write_str("Bad address"),
+ Self::ENOTBLK => f.write_str("Block device required"),
+ Self::EBUSY => f.write_str("Device busy"),
+ Self::EEXIST => f.write_str("File exists"),
+ Self::EXDEV => f.write_str("Cross-device link"),
+ Self::ENODEV => f.write_str("Operation not supported by device"),
+ Self::ENOTDIR => f.write_str("Not a directory"),
+ Self::EISDIR => f.write_str("Is a directory"),
+ Self::EINVAL => f.write_str("Invalid argument"),
+ Self::ENFILE => f.write_str("Too many open files in system"),
+ Self::EMFILE => f.write_str("Too many open files"),
+ Self::ENOTTY => f.write_str("Inappropriate ioctl for device"),
+ Self::ETXTBSY => f.write_str("Text file busy"),
+ Self::EFBIG => f.write_str("File too large"),
+ Self::ENOSPC => f.write_str("No space left on device"),
+ Self::ESPIPE => f.write_str("Illegal seek"),
+ Self::EROFS => f.write_str("Read-only file system"),
+ Self::EMLINK => f.write_str("Too many links"),
+ Self::EPIPE => f.write_str("Broken pipe"),
+ Self::EDOM => f.write_str("Numerical argument out of domain"),
+ Self::ERANGE => f.write_str("Result too large"),
+ Self::EWOULDBLOCK => f.write_str("Operation would block"),
+ Self::EINPROGRESS => f.write_str("Operation now in progress"),
+ Self::EALREADY => f.write_str("Operation already in progress"),
+ Self::ENOTSOCK => f.write_str("Socket operation on non-socket"),
+ Self::EDESTADDRREQ => f.write_str("Destination address required"),
+ Self::EMSGSIZE => f.write_str("Message too long"),
+ Self::EPROTOTYPE => f.write_str("Protocol wrong type for socket"),
+ Self::ENOPROTOOPT => f.write_str("Protocol not available"),
+ Self::EPROTONOSUPPORT => f.write_str("Protocol not supported"),
+ Self::ESOCKTNOSUPPORT => f.write_str("Socket type not supported"),
+ Self::EOPNOTSUPP => f.write_str("Operation not supported"),
+ Self::EPFNOSUPPORT => f.write_str("Protocol family not supported"),
+ Self::EAFNOSUPPORT => f.write_str("Address family not supported by protocol family"),
+ Self::EADDRINUSE => f.write_str("Address already in use"),
+ Self::EADDRNOTAVAIL => f.write_str("Can't assign requested address"),
+ Self::ENETDOWN => f.write_str("Network is down"),
+ Self::ENETUNREACH => f.write_str("Network is unreachable"),
+ Self::ENETRESET => f.write_str("Network dropped connection on reset"),
+ Self::ECONNABORTED => f.write_str("Software caused connection abort"),
+ Self::ECONNRESET => f.write_str("Connection reset by peer"),
+ Self::ENOBUFS => f.write_str("No buffer space available"),
+ Self::EISCONN => f.write_str("Socket is already connected"),
+ Self::ENOTCONN => f.write_str("Socket is not connected"),
+ Self::ESHUTDOWN => f.write_str("Can't send after socket shutdown"),
+ Self::ETOOMANYREFS => f.write_str("Too many references: can't splice"),
+ Self::ETIMEDOUT => f.write_str("Operation timed out"),
+ Self::ECONNREFUSED => f.write_str("Connection refused"),
+ Self::ELOOP => f.write_str("Too many levels of symbolic links"),
+ Self::ENAMETOOLONG => f.write_str("File name too long"),
+ Self::EHOSTDOWN => f.write_str("Host is down"),
+ Self::EHOSTUNREACH => f.write_str("No route to host"),
+ Self::ENOTEMPTY => f.write_str("Directory not empty"),
+ Self::EPROCLIM => f.write_str("Too many processes"),
+ Self::EUSERS => f.write_str("Too many users"),
+ Self::EDQUOT => f.write_str("Disk quota exceeded"),
+ Self::ESTALE => f.write_str("Stale NFS file handle"),
+ Self::EREMOTE => f.write_str("Too many levels of remote in path"),
+ Self::EBADRPC => f.write_str("RPC struct is bad"),
+ Self::ERPCMISMATCH => f.write_str("RPC version wrong"),
+ Self::EPROGUNAVAIL => f.write_str("RPC program not available"),
+ Self::EPROGMISMATCH => f.write_str("Program version wrong"),
+ Self::EPROCUNAVAIL => f.write_str("Bad procedure for program"),
+ Self::ENOLCK => f.write_str("No locks available"),
+ Self::ENOSYS => f.write_str("Function not implemented"),
+ Self::EFTYPE => f.write_str("Inappropriate file type or format"),
+ Self::EAUTH => f.write_str("Authentication error"),
+ Self::ENEEDAUTH => f.write_str("Need authenticator"),
+ Self::EIPSEC => f.write_str("IPsec processing failure"),
+ Self::ENOATTR => f.write_str("Attribute not found"),
+ Self::EILSEQ => f.write_str("Illegal byte sequence"),
+ Self::ENOMEDIUM => f.write_str("No medium found"),
+ Self::EMEDIUMTYPE => f.write_str("Wrong medium type"),
+ Self::EOVERFLOW => f.write_str("Value too large to be stored in data type"),
+ Self::ECANCELED => f.write_str("Operation canceled"),
+ Self::EIDRM => f.write_str("Identifier removed"),
+ Self::ENOMSG => f.write_str("No message of desired type"),
+ Self::ENOTSUP => f.write_str("Not supported"),
+ Self::EBADMSG => f.write_str("Bad message"),
+ Self::ENOTRECOVERABLE => f.write_str("State not recoverable"),
+ Self::EOWNERDEAD => f.write_str("Previous owner died"),
+ Self::EPROTO => f.write_str("Protocol error"),
+ Self::EDOOFUS => f.write_str("Programming error"),
+ Self::EMULTIHOP => f.write_str("Multihop attempted"),
+ Self::ENOLINK => f.write_str("Link has been severed"),
+ Self::ENOTCAPABLE => f.write_str("Capabilities insufficient"),
+ Self::ECAPMODE => f.write_str("Not permitted in capability mode"),
+ Self::EINTEGRITY => f.write_str("Integrity check failed"),
+ Self::ECHRNG => f.write_str("Channel number out of range"),
+ Self::EL2NSYNC => f.write_str("Level 2 not synchronized"),
+ Self::EL3HLT => f.write_str("Level 3 halted"),
+ Self::EL3RST => f.write_str("Level 3 reset"),
+ Self::ELNRNG => f.write_str("Link number out of range"),
+ Self::EUNATCH => f.write_str("Protocol driver not attached"),
+ Self::ENOCSI => f.write_str("No CSI structure available"),
+ Self::EL2HLT => f.write_str("Level 2 halted"),
+ Self::EBADE => f.write_str("Invalid exchange"),
+ Self::EBADR => f.write_str("Invalid request descriptor"),
+ Self::EXFULL => f.write_str("Exchange full"),
+ Self::ENOANO => f.write_str("No anode"),
+ Self::EBADRQC => f.write_str("Invalid request code"),
+ Self::EBADSLT => f.write_str("Invalid slot"),
+ Self::EBFONT => f.write_str("Bad font file format"),
+ Self::ENOSTR => f.write_str("Device not a stream"),
+ Self::ENODATA => f.write_str("No data available"),
+ Self::ETIME => f.write_str("Timer expired"),
+ Self::ENOSR => f.write_str("Out of streams resources"),
+ Self::ENONET => f.write_str("Machine is not on the network"),
+ Self::ENOPKG => f.write_str("Package not installed"),
+ Self::EADV => f.write_str("Advertise error"),
+ Self::ESRMNT => f.write_str("Srmount error"),
+ Self::ECOMM => f.write_str("Communication error on send"),
+ Self::EDOTDOT => f.write_str("RFS specific error"),
+ Self::ENOTUNIQ => f.write_str("Name not unique on network"),
+ Self::EBADFD => f.write_str("File descriptor in bad state"),
+ Self::EREMCHG => f.write_str("Remote address changed"),
+ Self::ELIBACC => f.write_str("Can not access a needed shared library"),
+ Self::ELIBBAD => f.write_str("Accessing a corrupted shared library"),
+ Self::ELIBSCN => f.write_str(".lib section in a.out corrupted"),
+ Self::ELIBMAX => f.write_str("Attempting to link in too many shared libraries"),
+ Self::ELIBEXEC => f.write_str("Cannot exec a shared library directly"),
+ Self::ERESTART => f.write_str("Interrupted system call should be restarted"),
+ Self::ESTRPIPE => f.write_str("Streams pipe error"),
+ Self::EUCLEAN => f.write_str("Structure needs cleaning"),
+ Self::ENOTNAM => f.write_str("Not a XENIX named type file"),
+ Self::ENAVAIL => f.write_str("No XENIX semaphores available"),
+ Self::EISNAM => f.write_str("Is a named type file"),
+ Self::EREMOTEIO => f.write_str("Remote I/O error"),
+ Self::ENOKEY => f.write_str("Required key not available"),
+ Self::EKEYEXPIRED => f.write_str("Key has expired"),
+ Self::EKEYREVOKED => f.write_str("Key has been revoked"),
+ Self::EKEYREJECTED => f.write_str("Key was rejected by service"),
+ Self::ERFKILL => f.write_str("Operation not possible due to RF-kill"),
+ Self::EHWPOISON => f.write_str("Memory page has hardware error"),
+ Self::EPWROFF => f.write_str("Device power is off"),
+ Self::EDEVERR => f.write_str("Device error, e.g. paper out"),
+ Self::EBADEXEC => f.write_str("Bad executable"),
+ Self::EBADARCH => f.write_str("Bad CPU type in executable"),
+ Self::ESHLIBVERS => f.write_str("Shared library version mismatch"),
+ Self::EBADMACHO => f.write_str("Malformed Macho file"),
+ Self::ENOPOLICY => f.write_str("No such policy registered"),
+ Self::EQFULL => f.write_str("Interface output queue is full"),
+ Self::Other(other) => write!(f, "Unknown error code: {}", other.0),
+ }
+ }
+}
+impl Error for Errno {}
+#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
+#[cfg(feature = "std")]
+impl From<Errno> for StdErr {
+ #[inline]
+ fn from(value: Errno) -> Self {
+ // All platforms we support have `RawOsError` as `c_int`; thus no conversion is needed.
+ Self::from_raw_os_error(value.into_raw())
+ }
+}
+#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
+#[cfg(feature = "std")]
+impl TryFrom<StdErr> for Errno {
+ type Error = StdErr;
+ #[inline]
+ fn try_from(value: StdErr) -> Result<Self, Self::Error> {
+ // All platforms we support have `RawOsError` as `c_int`; thus no conversion is needed.
+ value.raw_os_error().ok_or(value).map(Self::from_raw)
+ }
+}
diff --git a/src/lib.rs b/src/lib.rs
@@ -4,11 +4,25 @@
//! [crates-io]: https://img.shields.io/badge/crates.io-fc8d62?style=for-the-badge&labelColor=555555&logo=rust
//! [docs-rs]: https://img.shields.io/badge/docs.rs-66c2a5?style=for-the-badge&labelColor=555555&logo=docs.rs
//!
-//! `priv_sep` is a library that uses the system's libc to perform privilege separation and privilege reduction.
+//! `priv_sep` is a library that uses the system's libc to perform privilege separation and privilege reduction
+//! for Unix-like platforms.
+//!
+//! Note the only platforms that are currently supported are platforms that correspond to the following
+//! `target_os` values:
+//!
+//! * `dragonfly`
+//! * `freebsd`
+//! * `linux`
+//! * `netbsd`
+//! * `openbsd`
+//!
+//! or the `apple` `target_vendor`.
//!
//! ## `priv_sep` in action for OpenBSD
//!
//! ```no_run
+//! # #[cfg(target_os = "openbsd")]
+//! use core::ffi::CStr;
//! use core::convert::Infallible;
//! # #[cfg(target_os = "openbsd")]
//! use priv_sep::{Permissions, PrivDropErr, Promise, Promises};
@@ -24,17 +38,18 @@
//! #[tokio::main(flavor = "current_thread")]
//! async fn main() -> Result<Infallible, PrivDropErr<Error>> {
//! /// Config file.
-//! const CONFIG: &str = "config";
+//! const CONFIG: &CStr = c"config";
//! // Get the user ID and group ID for nobody from `passwd(5)`.
//! // `chroot(2)` to `/path/chroot/` and `chdir(2)` to `/`.
//! // `pledge(2)` `id`, `inet`, `rpath`, `stdio`, and `unveil`.
//! // Bind to TCP `[::1]:443` as root.
+//! // `setgroups(2)` to drop all supplementary groups.
//! // `setresgid(2)` to the group ID associated with nobody.
//! // `setresuid(2)` to the user ID associated with nobody.
//! // Remove `id` from our `pledge(2)`d promises.
//! let (listener, mut promises) = Promises::new_chroot_then_priv_drop_async(
-//! "nobody",
-//! "/path/chroot/",
+//! c"nobody",
+//! c"/path/chroot/",
//! [Promise::Inet, Promise::Rpath, Promise::Unveil],
//! false,
//! async || TcpListener::bind(SocketAddrV6::new(Ipv6Addr::LOCALHOST, 443, 0, 0)).await,
@@ -45,7 +60,7 @@
//! // Read `config`.
//! // This will of course fail if the file does not exist or nobody does not
//! // have read permissions.
-//! let config = fs::read(CONFIG)?;
+//! let config = fs::read(CONFIG.to_str().unwrap_or_else(|_e| unreachable!("only contains UTF-8"))).map_err(PrivDropErr::Other)?;
//! // Remove file system access.
//! Permissions::NONE.unveil(CONFIG)?;
//! // Remove `rpath` and `unveil` from our `pledge(2)`d promises
@@ -75,9 +90,10 @@
//! // Get the user ID and group ID for nobody from `passwd(5)`.
//! // `chroot(2)` to `/path/chroot/` and `chdir(2)` to `/`.
//! // Bind to TCP `[::1]:443` as root.
+//! // `setgroups(2)` to drop all supplementary groups.
//! // `setresgid(2)` to the group ID associated with nobody.
//! // `setresuid(2)` to the user ID associated with nobody.
-//! let listener = UserInfo::chroot_then_priv_drop_async("nobody", "/path/chroot/", false, async || {
+//! let listener = UserInfo::chroot_then_priv_drop_async(c"nobody", c"/path/chroot/", false, async || {
//! TcpListener::bind(SocketAddrV6::new(Ipv6Addr::LOCALHOST, 443, 0, 0)).await
//! }).await?;
//! // At this point, the process is running under nobody.
@@ -90,15 +106,29 @@
//! }
//! ```
#![cfg_attr(docsrs, feature(doc_cfg))]
-#![allow(clippy::pub_use, reason = "don't want openbsd types in a module")]
-extern crate alloc;
+#![no_std]
+#![cfg(any(
+ target_os = "dragonfly",
+ target_os = "freebsd",
+ target_os = "linux",
+ target_os = "netbsd",
+ target_os = "openbsd",
+ target_vendor = "apple"
+))]
+#![allow(
+ clippy::pub_use,
+ reason = "don't want Errno nor openbsd types in a module"
+)]
+#[cfg(feature = "std")]
+extern crate std;
/// C FFI.
mod c;
+/// Errno.
+mod err;
/// OpenBSD
#[cfg(any(doc, target_os = "openbsd"))]
mod openbsd;
-use alloc::ffi::{CString, NulError};
-use c::{IdT, SUCCESS};
+use c::SUCCESS;
use core::{
error::Error as CoreErr,
ffi::{CStr, c_char, c_int},
@@ -106,13 +136,14 @@ use core::{
mem::MaybeUninit,
ptr,
};
+pub use err::{Errno, OtherErrno};
#[cfg_attr(docsrs, doc(cfg(target_os = "openbsd")))]
#[cfg(any(doc, target_os = "openbsd"))]
pub use openbsd::{Permission, Permissions, Promise, Promises};
-use std::{io::Error, os::unix::ffi::OsStrExt as _, path::Path};
/// [`uid_t`](https://pubs.opengroup.org/onlinepubs/9799919799.2024edition/basedefs/sys_types.h.html).
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
-pub struct Uid(pub IdT);
+#[repr(transparent)]
+pub struct Uid(pub u32);
impl Uid {
/// The root user ID (i.e., 0).
pub const ROOT: Self = Self(0);
@@ -171,7 +202,7 @@ impl Uid {
/// assert!(Uid(1000).setresuid().is_ok());
/// ```
#[inline]
- pub fn setresuid(self) -> Result<(), Error> {
+ pub fn setresuid(self) -> Result<(), Errno> {
#[cfg(any(
target_os = "dragonfly",
target_os = "freebsd",
@@ -179,17 +210,12 @@ impl Uid {
target_os = "openbsd"
))]
let code = c::setresuid(self.0, self.0, self.0);
- #[cfg(not(any(
- target_os = "dragonfly",
- target_os = "freebsd",
- target_os = "linux",
- target_os = "openbsd"
- )))]
+ #[cfg(any(target_os = "netbsd", target_vendor = "apple"))]
let code = c::setuid(self.0);
if code == SUCCESS {
Ok(())
} else {
- Err(Error::last_os_error())
+ Err(Errno::last())
}
}
}
@@ -205,27 +231,28 @@ impl PartialEq<Uid> for &Uid {
**self == *other
}
}
-impl PartialEq<IdT> for Uid {
+impl PartialEq<u32> for Uid {
#[inline]
- fn eq(&self, other: &IdT) -> bool {
+ fn eq(&self, other: &u32) -> bool {
self.0 == *other
}
}
-impl From<Uid> for IdT {
+impl From<Uid> for u32 {
#[inline]
fn from(value: Uid) -> Self {
value.0
}
}
-impl From<IdT> for Uid {
+impl From<u32> for Uid {
#[inline]
- fn from(value: IdT) -> Self {
+ fn from(value: u32) -> Self {
Self(value)
}
}
/// [`gid_t`](https://pubs.opengroup.org/onlinepubs/9799919799.2024edition/basedefs/sys_types.h.html).
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
-pub struct Gid(pub IdT);
+#[repr(transparent)]
+pub struct Gid(pub u32);
impl Gid {
/// [`getgid`](https://pubs.opengroup.org/onlinepubs/9799919799.2024edition/functions/getgid.html).
///
@@ -269,7 +296,7 @@ impl Gid {
/// assert!(Gid(1000).setresgid().is_ok());
/// ```
#[inline]
- pub fn setresgid(self) -> Result<(), Error> {
+ pub fn setresgid(self) -> Result<(), Errno> {
#[cfg(any(
target_os = "dragonfly",
target_os = "freebsd",
@@ -277,17 +304,12 @@ impl Gid {
target_os = "openbsd"
))]
let code = c::setresgid(self.0, self.0, self.0);
- #[cfg(not(any(
- target_os = "dragonfly",
- target_os = "freebsd",
- target_os = "linux",
- target_os = "openbsd"
- )))]
+ #[cfg(any(target_os = "netbsd", target_vendor = "apple"))]
let code = c::setgid(self.0);
if code == SUCCESS {
Ok(())
} else {
- Err(Error::last_os_error())
+ Err(Errno::last())
}
}
}
@@ -303,113 +325,75 @@ impl PartialEq<Gid> for &Gid {
**self == *other
}
}
-impl PartialEq<IdT> for Gid {
+impl PartialEq<u32> for Gid {
#[inline]
- fn eq(&self, other: &IdT) -> bool {
+ fn eq(&self, other: &u32) -> bool {
self.0 == *other
}
}
-impl From<Gid> for IdT {
+impl From<Gid> for u32 {
#[inline]
fn from(value: Gid) -> Self {
value.0
}
}
-impl From<IdT> for Gid {
+impl From<u32> for Gid {
#[inline]
- fn from(value: IdT) -> Self {
+ fn from(value: u32) -> Self {
Self(value)
}
}
-/// Error when [`CString::new`] errors or an I/O error occurs due to a libc call.
-#[derive(Debug)]
-pub enum NulOrIoErr {
- /// Error returned from [`CString::new`].
- Nul(NulError),
- /// Generic I/O error returned from a libc call.
- Io(Error),
-}
-impl Display for NulOrIoErr {
- #[inline]
- fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
- match *self {
- Self::Nul(ref err) => write!(f, "CString could not be created: {err}"),
- Self::Io(ref err) => write!(f, "libc I/O error: {err}"),
- }
- }
-}
-impl CoreErr for NulOrIoErr {}
-impl From<NulError> for NulOrIoErr {
- #[inline]
- fn from(value: NulError) -> Self {
- Self::Nul(value)
- }
-}
-impl From<Error> for NulOrIoErr {
- #[inline]
- fn from(value: Error) -> Self {
- Self::Io(value)
- }
-}
/// [`chroot(2)`](https://manned.org/chroot.2).
///
/// # Errors
///
-/// Returns [`NulError`] iff [`CString::new`] does.
-/// Returns [`Error`] iff `chroot(2)` errors.
+/// Errors iff `chroot(2)` does.
///
/// # Examples
///
/// ```no_run
-/// assert!(priv_sep::chroot("./").is_ok());
+/// assert!(priv_sep::chroot(c"./").is_ok());
/// ```
#[expect(unsafe_code, reason = "chroot(2) takes a pointer")]
#[inline]
-pub fn chroot<P: AsRef<Path>>(path: P) -> Result<(), NulOrIoErr> {
- CString::new(path.as_ref().as_os_str().as_bytes())
- .map_err(NulOrIoErr::Nul)
- .and_then(|c_path| {
- let ptr = c_path.as_ptr();
- // SAFETY:
- // `ptr` is valid and not null.
- if unsafe { c::chroot(ptr) } == SUCCESS {
- Ok(())
- } else {
- Err(NulOrIoErr::Io(Error::last_os_error()))
- }
- })
+pub fn chroot(path: &CStr) -> Result<(), Errno> {
+ let ptr = path.as_ptr();
+ // SAFETY:
+ // `ptr` is valid and not null.
+ if unsafe { c::chroot(ptr) } == SUCCESS {
+ Ok(())
+ } else {
+ Err(Errno::last())
+ }
}
/// [`chdir`](https://pubs.opengroup.org/onlinepubs/9799919799.2024edition/functions/chdir.html).
///
/// This function MUST only be called by `chdir` and `chroot_then_chdir`.
#[expect(unsafe_code, reason = "chdir(2) takes a pointer")]
-fn private_chdir(path: *const c_char) -> Result<(), Error> {
+fn private_chdir(path: *const c_char) -> Result<(), Errno> {
// SAFETY:
// `path` is valid and not null as can be seen in the only functions that call this function:
// `chdir` and `chroot_then_chdir`.
if unsafe { c::chdir(path) } == SUCCESS {
Ok(())
} else {
- Err(Error::last_os_error())
+ Err(Errno::last())
}
}
/// [`chdir`](https://pubs.opengroup.org/onlinepubs/9799919799.2024edition/functions/chdir.html).
///
/// # Errors
///
-/// Returns [`NulError`] iff [`CString::new`] does.
-/// Returns [`Error`] iff `chdir` errors.
+/// Errors iff `chdir` does.
///
/// # Examples
///
/// ```no_run
-/// assert!(priv_sep::chdir("/").is_ok());
+/// assert!(priv_sep::chdir(c"/").is_ok());
/// ```
#[inline]
-pub fn chdir<P: AsRef<Path>>(path: P) -> Result<(), NulOrIoErr> {
- CString::new(path.as_ref().as_os_str().as_bytes())
- .map_err(NulOrIoErr::Nul)
- .and_then(|c_path| private_chdir(c_path.as_ptr()).map_err(NulOrIoErr::Io))
+pub fn chdir(path: &CStr) -> Result<(), Errno> {
+ private_chdir(path.as_ptr())
}
/// Calls [`chroot`] on `path` followed by a call to [`chdir`] on `"/"`.
///
@@ -420,21 +404,19 @@ pub fn chdir<P: AsRef<Path>>(path: P) -> Result<(), NulOrIoErr> {
/// # Examples
///
/// ```no_run
-/// assert!(priv_sep::chroot_then_chdir("./").is_ok());
+/// assert!(priv_sep::chroot_then_chdir(c"./").is_ok());
/// ```
#[inline]
-pub fn chroot_then_chdir<P: AsRef<Path>>(path: P) -> Result<(), NulOrIoErr> {
+pub fn chroot_then_chdir(path: &CStr) -> Result<(), Errno> {
/// Root directory.
const ROOT: *const c_char = c"/".as_ptr();
- chroot(path).and_then(|()| private_chdir(ROOT).map_err(NulOrIoErr::Io))
+ chroot(path).and_then(|()| private_chdir(ROOT))
}
/// Error returned when dropping privileges.
-#[derive(Debug)]
+#[derive(Clone, Copy, Debug)]
pub enum PrivDropErr<E> {
- /// Error when [`CString::new`] errors.
- Nul(NulError),
- /// Error when an I/O error occurs from a libc call.
- Io(Error),
+ /// Error when an error occurs from a libc call.
+ Libc(Errno),
/// Error when there is no entry in the user database corresponding to the passed username.
NoPasswdEntry,
/// Error when [`UserInfo::is_root`].
@@ -446,11 +428,7 @@ impl<E: Display> Display for PrivDropErr<E> {
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match *self {
- Self::Nul(ref err) => write!(
- f,
- "CString could not be created from the username to drop privileges to: {err}"
- ),
- Self::Io(ref err) => write!(f, "libc I/O error when dropping privileges: {err}"),
+ Self::Libc(err) => write!(f, "libc error when dropping privileges: {err}"),
Self::NoPasswdEntry => f.write_str("no passwd(5) entry to drop privileges to"),
Self::RootEntry => f.write_str(
"setresuid(2) is not allowed to be called on uid 0 when dropping privileges",
@@ -463,32 +441,17 @@ impl<E: Display> Display for PrivDropErr<E> {
}
}
impl<E: CoreErr> CoreErr for PrivDropErr<E> {}
-impl<E> From<NulError> for PrivDropErr<E> {
- #[inline]
- fn from(value: NulError) -> Self {
- Self::Nul(value)
- }
-}
-impl<E> From<Error> for PrivDropErr<E> {
- #[inline]
- fn from(value: Error) -> Self {
- Self::Io(value)
- }
-}
-impl<E> From<NulOrIoErr> for PrivDropErr<E> {
+impl<E> From<Errno> for PrivDropErr<E> {
#[inline]
- fn from(value: NulOrIoErr) -> Self {
- match value {
- NulOrIoErr::Nul(e) => Self::Nul(e),
- NulOrIoErr::Io(e) => Self::Io(e),
- }
+ fn from(value: Errno) -> Self {
+ Self::Libc(value)
}
}
/// Error returned from [`UserInfo::setresid_if_valid`].
-#[derive(Debug)]
+#[derive(Clone, Copy, Debug)]
pub enum SetresidErr {
- /// Error when an I/O error occurs from a libc call.
- Io(Error),
+ /// Error when an error occurs from a libc call.
+ Libc(Errno),
/// Error when there is no entry in the user database corresponding to [`UserInfo::uid`].
NoPasswdEntry,
/// Error when the entry in the user database has a different gid than [`UserInfo::gid`].
@@ -498,17 +461,17 @@ impl Display for SetresidErr {
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match *self {
- Self::Io(ref err) => write!(f, "libc I/O error when dropping privileges: {err}"),
+ Self::Libc(err) => write!(f, "libc error when dropping privileges: {err}"),
Self::NoPasswdEntry => f.write_str("no passwd(5) entry to drop privileges to"),
Self::GidMismatch => f.write_str("gid in passwd(5) does match the expected gid"),
}
}
}
impl CoreErr for SetresidErr {}
-impl From<Error> for SetresidErr {
+impl From<Errno> for SetresidErr {
#[inline]
- fn from(value: Error) -> Self {
- Self::Io(value)
+ fn from(value: Errno) -> Self {
+ Self::Libc(value)
}
}
/// Used by [`UserInfo::getpw_entry`].
@@ -576,6 +539,8 @@ pub struct UserInfo {
pub gid: Gid,
}
impl UserInfo {
+ /// The buffer size we use to read a `passwd` entry.
+ const PW_ENT_BUF_LEN: usize = 1024;
/// Returns `true` iff [`Uid::is_root`].
///
/// # Examples
@@ -589,132 +554,83 @@ impl UserInfo {
pub const fn is_root(self) -> bool {
self.uid.is_root()
}
+ /// Helper for [`Self::with_buffer`], [`Self::new`], and [`Self::setresid_if_exists`].
+ #[expect(
+ unsafe_code,
+ reason = "getpwnam_r(3) and getpwuid_r(3) take in pointers"
+ )]
+ fn getpw_entry<P: Copy + PwEntry>(u: P, buffer: &mut [c_char]) -> Result<Option<Self>, Errno> {
+ let mut pwd = MaybeUninit::<c::Passwd>::uninit();
+ let pwd_ptr = pwd.as_mut_ptr();
+ let buf_ptr = buffer.as_mut_ptr();
+ let len = buffer.len();
+ let mut result = ptr::null_mut();
+ let res_ptr = &mut result;
+ // SAFETY:
+ // `pwd_ptr` is only written to; thus the fact `pwd` is unitialized is fine.
+ // `buf_ptr` is valid, initialized, and not null.
+ // `len` is the length of `buf_ptr`.
+ // `res_ptr` is valid, initialized, and not null.
+ // `result` is valid, initialized, and allowed to be null.
+ let code = unsafe { u.getpw(pwd_ptr, buf_ptr, len, res_ptr) };
+ if code == SUCCESS {
+ if result.is_null() {
+ Ok(None)
+ } else {
+ debug_assert!(
+ result.is_aligned(),
+ "libc getpwnam_r or getpwuid_r result was not aligned. Something is terribly wrong with your system"
+ );
+ // SAFETY:
+ // Verified above that `result` is not null and aligned. Note while we only verify the pointer
+ // is aligned on non-release builds, the situation is so dire that one could argue we are
+ // already in "undefined" territory.
+ // When `result` is not null, the platform is supposed to have written to `pwd`.
+ Ok(Some(unsafe { pwd.assume_init() }.into_user_info()))
+ }
+ } else {
+ Err(Errno::from_raw(code))
+ }
+ }
/// [`getpwnam_r`](https://pubs.opengroup.org/onlinepubs/9799919799.2024edition/functions/getpwnam_r.html).
///
/// Uses `buffer` to write the user database entry into returning `None` iff there is no entry; otherwise
/// returns `Self`.
///
- /// Note it is the caller's responsibility to ensure `buffer` is large enough; otherwise an [`Error`] will
+ /// Note it is the caller's responsibility to ensure `buffer` is large enough; otherwise an [`Errno`] will
/// be returned.
///
/// # Errors
///
- /// Returns [`NulError`] iff [`CString::new`] does.
- /// Returns [`Error`] iff `getpwnam_r` errors.
+ /// Errors iff `getpwnam_r` does.
///
/// # Examples
///
/// ```no_run
/// # use priv_sep::{Uid, UserInfo};
- /// assert!(UserInfo::with_buffer("root", [0; 128].as_mut_slice())?.map_or(false, |info| info.is_root()));
- /// # Ok::<_, priv_sep::NulOrIoErr>(())
+ /// assert!(UserInfo::with_buffer(c"root", [0; 128].as_mut_slice())?.map_or(false, |info| info.is_root()));
+ /// # Ok::<_, priv_sep::Errno>(())
/// ```
- #[expect(unsafe_code, reason = "getpwnam_r(3) takes in pointers")]
#[inline]
- pub fn with_buffer<T: Into<Vec<u8>>>(
- name: T,
- buffer: &mut [c_char],
- ) -> Result<Option<Self>, NulOrIoErr> {
- CString::new(name).map_err(NulOrIoErr::Nul).and_then(|n| {
- let ptr = n.as_ptr();
- let mut pwd = MaybeUninit::<c::Passwd>::uninit();
- let pwd_ptr = pwd.as_mut_ptr();
- let buf_ptr = buffer.as_mut_ptr();
- let len = buffer.len();
- let mut result = ptr::null_mut();
- let res_ptr = &mut result;
- // SAFETY:
- // `pwd_ptr` is only written to; thus the fact `pwd` is unitialized is fine.
- // `buf_ptr` is valid, initialized, and not null.
- // `len` is the length of `buf_ptr`.
- // `res_ptr` is valid, initialized, and not null.
- // `result` is valid, initialized, and allowed to be null.
- let code = unsafe { c::getpwnam_r(ptr, pwd_ptr, buf_ptr, len, res_ptr) };
- if code == SUCCESS {
- if result.is_null() {
- Ok(None)
- } else {
- // SAFETY:
- // `c::getpwnam_r` writes to `pwd` iff `result` is not null.
- Ok(Some(unsafe { pwd.assume_init() }.into_user_info()))
- }
- } else {
- Err(NulOrIoErr::Io(Error::from_raw_os_error(code)))
- }
- })
+ pub fn with_buffer(name: &CStr, buffer: &mut [c_char]) -> Result<Option<Self>, Errno> {
+ Self::getpw_entry(CStrWrapper(name), buffer)
}
- /// Helper for [`Self::new`] and [`Self::setresid_if_exists`].
- #[expect(
- unsafe_code,
- reason = "getpwnam_r(3) and getpwuid_r(3) take in pointers"
- )]
- fn getpw_entry<P: Copy + PwEntry>(u: P) -> Result<Option<Self>, Error> {
- /// Initial buffer size.
- const INIT_CAP: usize = 128;
- // `2 * (MAX_CAP - 1) <= isize::MAX` MUST be true.
- /// Maximum buffer size.
- const MAX_CAP: usize = 0x4000;
- /// [`ERANGE`](https://man.openbsd.org/errno#Result).
- const ERANGE: c_int = 34;
- let mut buffer = Vec::with_capacity(INIT_CAP);
- let mut cap = buffer.capacity();
- let mut pwd = MaybeUninit::<c::Passwd>::uninit();
- let mut result = ptr::null_mut();
- let mut pwd_ptr;
- let mut res_ptr;
- let mut code;
- let mut buf_ptr;
- loop {
- pwd_ptr = pwd.as_mut_ptr();
- res_ptr = &mut result;
- buf_ptr = buffer.as_mut_ptr();
- // SAFETY:
- // `pwd_ptr` is only written to; thus the fact `pwd` is unitialized is fine.
- // `buf_ptr` is valid, initialized, and not null.
- // `cap` is the length of `buf_ptr`.
- // `res_ptr` is valid, initialized, and not null.
- // `result` is valid, initialized, and allowed to be null.
- code = unsafe { u.getpw(pwd_ptr, buf_ptr, cap, res_ptr) };
- if code == SUCCESS {
- return Ok(if result.is_null() {
- None
- } else {
- // SAFETY:
- // `CStrWrapper::getpw` writes to `pwd` iff `result` is not null.
- Some(unsafe { pwd.assume_init() }.into_user_info())
- });
- } else if code == ERANGE {
- if cap >= MAX_CAP {
- return Err(Error::from_raw_os_error(code));
- }
- // `cap < MAX_CAP` and
- // `2 * (MAX_CAP - 1) < isize::MAX`, so overflow is not possible.
- buffer.reserve(cap << 1);
- cap = buffer.capacity();
- } else {
- return Err(Error::from_raw_os_error(code));
- }
- }
- }
- /// Same as [`Self::with_buffer`] except repeated attempts are made with progressively larger buffers up to
- /// 16 KiB.
+ /// Same as [`Self::with_buffer`] except a stack-allocated buffer of 1024 bytes is used.
///
/// # Errors
///
- /// Errors iff [`Self::with_buffer`] does for a 16 KiB buffer.
+ /// Errors iff [`Self::with_buffer`] does for a 1024-byte buffer.
///
/// # Examples
///
/// ```no_run
/// # use priv_sep::UserInfo;
- /// assert!(UserInfo::new("root")?.map_or(false, |info| info.is_root()));
- /// # Ok::<_, priv_sep::NulOrIoErr>(())
+ /// assert!(UserInfo::new(c"root")?.map_or(false, |info| info.is_root()));
+ /// # Ok::<_, priv_sep::Errno>(())
/// ```
#[inline]
- pub fn new<T: Into<Vec<u8>>>(name: T) -> Result<Option<Self>, NulOrIoErr> {
- CString::new(name)
- .map_err(NulOrIoErr::Nul)
- .and_then(|n| Self::getpw_entry(CStrWrapper(n.as_c_str())).map_err(NulOrIoErr::Io))
+ pub fn new(name: &CStr) -> Result<Option<Self>, Errno> {
+ Self::with_buffer(name, &mut [0; Self::PW_ENT_BUF_LEN])
}
/// Calls [`Gid::setresgid`] and [`Uid::setresuid`].
///
@@ -726,13 +642,13 @@ impl UserInfo {
///
/// ```no_run
/// # use priv_sep::UserInfo;
- /// if let Some(user) = UserInfo::new("nobody")? {
+ /// if let Some(user) = UserInfo::new(c"nobody")? {
/// user.setresid()?;
/// }
- /// # Ok::<_, priv_sep::NulOrIoErr>(())
+ /// # Ok::<_, priv_sep::Errno>(())
/// ```
#[inline]
- pub fn setresid(self) -> Result<(), Error> {
+ pub fn setresid(self) -> Result<(), Errno> {
self.gid.setresgid().and_then(|()| self.uid.setresuid())
}
/// Same as [`Self::setresid`] except
@@ -758,29 +674,49 @@ impl UserInfo {
/// ```
#[inline]
pub fn setresid_if_valid(self) -> Result<(), SetresidErr> {
- Self::getpw_entry(self.uid)
- .map_err(SetresidErr::Io)
+ Self::getpw_entry(self.uid, &mut [0; Self::PW_ENT_BUF_LEN])
+ .map_err(SetresidErr::Libc)
.and_then(|opt| {
opt.ok_or(SetresidErr::NoPasswdEntry).and_then(|info| {
if info.gid == self.gid {
- self.setresid().map_err(SetresidErr::Io)
+ self.setresid().map_err(SetresidErr::Libc)
} else {
Err(SetresidErr::GidMismatch)
}
})
})
}
- /// Calls [`Self::new`], invokes `f`, then calls [`Self::setresid`].
+ /// Helper to unify targets that don't support `setgroups(2)`.
+ ///
+ /// No-op.
+ #[cfg(target_vendor = "apple")]
+ const fn drop_sup_groups() -> Result<(), Errno> {
+ Ok(())
+ }
+ /// Helper to unify targets that don't support `setgroups(2)`.
+ #[cfg(any(
+ target_os = "dragonfly",
+ target_os = "freebsd",
+ target_os = "linux",
+ target_os = "netbsd",
+ target_os = "openbsd"
+ ))]
+ fn drop_sup_groups() -> Result<(), Errno> {
+ drop_supplementary_groups()
+ }
+ /// Calls [`Self::new`], invokes `f`, calls [`drop_supplementary_groups`] (if the platform supports
+ /// `setgroups(2)`), then calls [`Self::setresid`].
///
/// Dropping privileges is necessary when needing to perform certain actions as root before no longer needing
/// such abilities; at which point, one calls
- /// [`setresgid`](https://pubs.opengroup.org/onlinepubs/9799919799.2024edition/functions/setresgid.html) and
+ /// [`setgroups(2)`](https://www.man7.org/linux/man-pages/man2/setgroups.2.html),
+ /// [`setresgid`](https://pubs.opengroup.org/onlinepubs/9799919799.2024edition/functions/setresgid.html), and
/// [`setresuid`](https://pubs.opengroup.org/onlinepubs/9799919799.2024edition/functions/setresuid.html)
/// using a lesser privileged gid and uid.
///
/// # Errors
///
- /// Errors iff [`Self::new`], `f`, or [`Self::setresid`] do or there is no entry in the user database
+ /// Errors iff [`Self::new`], `f`, [`drop_supplementary_groups`], or [`Self::setresid`] do or there is no entry in the user database
/// corresponding to `name` or the entry has uid 0.
///
/// # Examples
@@ -789,24 +725,27 @@ impl UserInfo {
/// # use core::net::{Ipv6Addr, SocketAddrV6};
/// # use priv_sep::{PrivDropErr, UserInfo};
/// # use std::{io::Error, net::TcpListener};
- /// let listener = UserInfo::priv_drop("nobody", || {
+ /// let listener = UserInfo::priv_drop(c"nobody", || {
/// TcpListener::bind(SocketAddrV6::new(Ipv6Addr::LOCALHOST, 443, 0, 0))
/// })?;
/// # Ok::<_, PrivDropErr<Error>>(())
/// ```
#[inline]
- pub fn priv_drop<U: Into<Vec<u8>>, T, E, F: FnOnce() -> Result<T, E>>(
- name: U,
+ pub fn priv_drop<T, E, F: FnOnce() -> Result<T, E>>(
+ name: &CStr,
f: F,
) -> Result<T, PrivDropErr<E>> {
- Self::new(name).map_err(PrivDropErr::from).and_then(|opt| {
+ Self::new(name).map_err(PrivDropErr::Libc).and_then(|opt| {
opt.ok_or_else(|| PrivDropErr::NoPasswdEntry)
.and_then(|info| {
if info.is_root() {
Err(PrivDropErr::RootEntry)
} else {
- f().map_err(PrivDropErr::Other)
- .and_then(|res| info.setresid().map_err(PrivDropErr::Io).map(|()| res))
+ f().map_err(PrivDropErr::Other).and_then(|res| {
+ Self::drop_sup_groups()
+ .and_then(|()| info.setresid().map(|()| res))
+ .map_err(PrivDropErr::Libc)
+ })
}
})
})
@@ -823,13 +762,13 @@ impl UserInfo {
/// # use core::net::{Ipv6Addr, SocketAddrV6};
/// # use priv_sep::UserInfo;
/// # use tokio::net::TcpListener;
- /// let listener_fut = UserInfo::priv_drop_async("nobody", async || {
+ /// let listener_fut = UserInfo::priv_drop_async(c"nobody", async || {
/// TcpListener::bind(SocketAddrV6::new(Ipv6Addr::LOCALHOST, 443, 0, 0)).await
/// });
/// ```
#[inline]
- pub async fn priv_drop_async<U: Into<Vec<u8>>, T, E, F: AsyncFnOnce() -> Result<T, E>>(
- name: U,
+ pub async fn priv_drop_async<T, E, F: AsyncFnOnce() -> Result<T, E>>(
+ name: &CStr,
f: F,
) -> Result<T, PrivDropErr<E>> {
match Self::new(name) {
@@ -839,13 +778,15 @@ impl UserInfo {
if info.is_root() {
Err(PrivDropErr::RootEntry)
} else {
- f().await
- .map_err(PrivDropErr::Other)
- .and_then(|res| info.setresid().map_err(PrivDropErr::Io).map(|()| res))
+ f().await.map_err(PrivDropErr::Other).and_then(|res| {
+ Self::drop_sup_groups()
+ .and_then(|()| info.setresid().map(|()| res))
+ .map_err(PrivDropErr::Libc)
+ })
}
}
},
- Err(err) => Err(PrivDropErr::from(err)),
+ Err(err) => Err(PrivDropErr::Libc(err)),
}
}
/// Same as [`Self::priv_drop`] except [`chroot_then_chdir`] is called before or after invoking `f` based on
@@ -861,25 +802,19 @@ impl UserInfo {
/// # use core::net::{Ipv6Addr, SocketAddrV6};
/// # use priv_sep::{PrivDropErr, UserInfo};
/// # use std::{io::Error, net::TcpListener};
- /// let listener = UserInfo::chroot_then_priv_drop("nobody", "./", false, || {
+ /// let listener = UserInfo::chroot_then_priv_drop(c"nobody", c"./", false, || {
/// TcpListener::bind(SocketAddrV6::new(Ipv6Addr::LOCALHOST, 443, 0, 0))
/// })?;
/// # Ok::<_, PrivDropErr<Error>>(())
/// ```
#[inline]
- pub fn chroot_then_priv_drop<
- U: Into<Vec<u8>>,
- P: AsRef<Path>,
- T,
- E,
- F: FnOnce() -> Result<T, E>,
- >(
- name: U,
- path: P,
+ pub fn chroot_then_priv_drop<T, E, F: FnOnce() -> Result<T, E>>(
+ name: &CStr,
+ path: &CStr,
chroot_after_f: bool,
f: F,
) -> Result<T, PrivDropErr<E>> {
- Self::new(name).map_err(PrivDropErr::from).and_then(|opt| {
+ Self::new(name).map_err(PrivDropErr::Libc).and_then(|opt| {
opt.ok_or_else(|| PrivDropErr::NoPasswdEntry)
.and_then(|info| {
if info.is_root() {
@@ -887,15 +822,19 @@ impl UserInfo {
} else if chroot_after_f {
f().map_err(PrivDropErr::Other).and_then(|res| {
chroot_then_chdir(path)
- .map_err(PrivDropErr::from)
+ .map_err(PrivDropErr::Libc)
.map(|()| res)
})
} else {
chroot_then_chdir(path)
- .map_err(PrivDropErr::from)
+ .map_err(PrivDropErr::Libc)
.and_then(|()| f().map_err(PrivDropErr::Other))
}
- .and_then(|res| info.setresid().map_err(PrivDropErr::Io).map(|()| res))
+ .and_then(|res| {
+ Self::drop_sup_groups()
+ .and_then(|()| info.setresid().map(|()| res))
+ .map_err(PrivDropErr::Libc)
+ })
})
})
}
@@ -911,20 +850,14 @@ impl UserInfo {
/// # use core::net::{Ipv6Addr, SocketAddrV6};
/// # use priv_sep::UserInfo;
/// # use tokio::net::TcpListener;
- /// let listener_fut = UserInfo::chroot_then_priv_drop_async("nobody", "./", false, async || {
+ /// let listener_fut = UserInfo::chroot_then_priv_drop_async(c"nobody", c"./", false, async || {
/// TcpListener::bind(SocketAddrV6::new(Ipv6Addr::LOCALHOST, 443, 0, 0)).await
/// });
/// ```
#[inline]
- pub async fn chroot_then_priv_drop_async<
- U: Into<Vec<u8>>,
- P: AsRef<Path>,
- T,
- E,
- F: AsyncFnOnce() -> Result<T, E>,
- >(
- name: U,
- path: P,
+ pub async fn chroot_then_priv_drop_async<T, E, F: AsyncFnOnce() -> Result<T, E>>(
+ name: &CStr,
+ path: &CStr,
chroot_after_f: bool,
f: F,
) -> Result<T, PrivDropErr<E>> {
@@ -936,18 +869,22 @@ impl UserInfo {
} else if chroot_after_f {
f().await.map_err(PrivDropErr::Other).and_then(|res| {
chroot_then_chdir(path)
- .map_err(PrivDropErr::from)
+ .map_err(PrivDropErr::Libc)
.map(|()| res)
})
} else {
match chroot_then_chdir(path) {
Ok(()) => f().await.map_err(PrivDropErr::Other),
- Err(err) => Err(PrivDropErr::from(err)),
+ Err(err) => Err(PrivDropErr::Libc(err)),
}
}
- .and_then(|res| info.setresid().map_err(PrivDropErr::Io).map(|()| res)),
+ .and_then(|res| {
+ Self::drop_sup_groups()
+ .and_then(|()| info.setresid().map(|()| res))
+ .map_err(PrivDropErr::Libc)
+ }),
},
- Err(err) => Err(PrivDropErr::from(err)),
+ Err(err) => Err(PrivDropErr::Libc(err)),
}
}
}
@@ -963,15 +900,126 @@ impl PartialEq<UserInfo> for &UserInfo {
**self == *other
}
}
+/// [`setgroups(2)`](https://www.man7.org/linux/man-pages/man2/setgroups.2.html).
+///
+/// # Errors
+///
+/// Errors iff `setgroups` does.
+#[cfg_attr(
+ docsrs,
+ doc(cfg(any(
+ target_os = "dragonfly",
+ target_os = "freebsd",
+ target_os = "linux",
+ target_os = "netbsd",
+ target_os = "openbsd"
+ )))
+)]
+#[cfg(any(
+ target_os = "dragonfly",
+ target_os = "freebsd",
+ target_os = "linux",
+ target_os = "netbsd",
+ target_os = "openbsd"
+))]
+#[expect(unsafe_code, reason = "setgroups(2) takes a pointer")]
+#[inline]
+pub fn setgroups(groups: &[Gid]) -> Result<(), Errno> {
+ #[cfg(target_os = "linux")]
+ let size = groups.len();
+ #[cfg(any(
+ target_os = "dragonfly",
+ target_os = "freebsd",
+ target_os = "netbsd",
+ target_os = "openbsd"
+ ))]
+ let size = c_int::try_from(groups.len()).map_err(|_e| Errno::EINVAL)?;
+ let gids = groups.as_ptr().cast();
+ // SAFETY:
+ // `size` is the length of `gids`, and `gids` is a valid, non-null, alligned pointer to
+ // `u32`s. Note that `Gid` is a `newtype` around `u32` with `repr(transparent)`; thus the above
+ // pointer cast is fine.
+ let code = unsafe { c::setgroups(size, gids) };
+ if code == SUCCESS {
+ Ok(())
+ } else {
+ Err(Errno::last())
+ }
+}
+/// [`setgroups(2)`](https://www.man7.org/linux/man-pages/man2/setgroups.2.html) passing in
+/// 0 and a null pointer.
+///
+/// If successful, this drops all supplementary groups.
+///
+/// # Errors
+///
+/// Errors iff `setgroups` does.
+///
+/// # Examples
+///
+/// ```no_run
+/// assert!(priv_sep::drop_supplementary_groups().is_ok());
+/// ```
+#[cfg_attr(
+ docsrs,
+ doc(cfg(any(
+ target_os = "dragonfly",
+ target_os = "freebsd",
+ target_os = "linux",
+ target_os = "netbsd",
+ target_os = "openbsd"
+ )))
+)]
+#[cfg(any(
+ target_os = "dragonfly",
+ target_os = "freebsd",
+ target_os = "linux",
+ target_os = "netbsd",
+ target_os = "openbsd"
+))]
+#[expect(unsafe_code, reason = "setgroups(2) takes a pointer")]
+#[inline]
+pub fn drop_supplementary_groups() -> Result<(), Errno> {
+ #[cfg(target_os = "linux")]
+ let size: usize = 0;
+ #[cfg(any(
+ target_os = "dragonfly",
+ target_os = "freebsd",
+ target_os = "netbsd",
+ target_os = "openbsd"
+ ))]
+ let size: c_int = 0;
+ let gids = ptr::null();
+ // SAFETY:
+ // Passing in 0 and a null pointer is valid and causes all supplementary groups to be dropped.
+ let code = unsafe { c::setgroups(size, gids) };
+ if code == SUCCESS {
+ Ok(())
+ } else {
+ Err(Errno::last())
+ }
+}
#[cfg(test)]
mod tests {
- use super::{Gid, NulOrIoErr, PrivDropErr, SetresidErr, Uid, UserInfo};
- #[cfg(target_os = "openbsd")]
+ #[cfg(feature = "std")]
+ use super::{CStr, PrivDropErr};
+ use super::{Errno, Gid, SetresidErr, Uid, UserInfo};
+ #[cfg(all(feature = "std", target_os = "openbsd"))]
use super::{Permissions, Promise, Promises};
+ #[cfg(feature = "std")]
use core::net::{Ipv6Addr, SocketAddrV6};
+ #[cfg(all(feature = "std", target_os = "openbsd"))]
+ use std::{format, print};
+ #[cfg(feature = "std")]
use std::{fs, io::Error, net::TcpListener};
use tokio as _;
- const README: &str = "README.md";
+ #[cfg(feature = "std")]
+ const README: &CStr = c"README.md";
+ #[cfg(feature = "std")]
+ const README_STR: &str = match README.to_str() {
+ Ok(val) => val,
+ Err(_) => panic!("not possible"),
+ };
#[test]
fn test_getuid() {
_ = Uid::getuid();
@@ -989,29 +1037,29 @@ mod tests {
_ = Gid::getegid();
}
#[test]
- fn test_setresuid() -> Result<(), Error> {
+ fn test_setresuid() -> Result<(), Errno> {
Uid::geteuid().setresuid()
}
#[test]
- fn test_setresgid() -> Result<(), Error> {
+ fn test_setresgid() -> Result<(), Errno> {
Gid::getegid().setresgid()
}
#[test]
- fn test_user_info_new() -> Result<(), NulOrIoErr> {
- if let Some(user) = UserInfo::new("root")? {
+ fn test_user_info_new() -> Result<(), Errno> {
+ if let Some(user) = UserInfo::new(c"root")? {
assert!(user.is_root());
}
Ok(())
}
#[test]
- fn test_user_info_with_buffer() -> Result<(), NulOrIoErr> {
- if let Some(user) = UserInfo::with_buffer("root", [0; 512].as_mut_slice())? {
+ fn test_user_info_with_buffer() -> Result<(), Errno> {
+ if let Some(user) = UserInfo::with_buffer(c"root", [0; 512].as_mut_slice())? {
assert!(user.is_root());
}
Ok(())
}
#[test]
- fn test_user_info_setresid() -> Result<(), Error> {
+ fn test_user_info_setresid() -> Result<(), Errno> {
UserInfo {
uid: Uid::geteuid(),
gid: Gid::getegid(),
@@ -1037,11 +1085,12 @@ mod tests {
.map_or_else(|e| matches!(e, SetresidErr::GidMismatch), |_| false)
);
}
+ #[cfg(feature = "std")]
#[test]
#[ignore]
fn test_priv_drop() -> Result<(), PrivDropErr<Error>> {
if Uid::geteuid().is_root() {
- UserInfo::priv_drop("zack", || {
+ UserInfo::priv_drop(c"nobody", || {
TcpListener::bind(SocketAddrV6::new(Ipv6Addr::LOCALHOST, 443, 0, 0))
})
.map(|_| {
@@ -1051,49 +1100,63 @@ mod tests {
})
} else {
assert!(
- UserInfo::priv_drop("root", || Ok::<_, Error>(()))
+ UserInfo::priv_drop(c"root", || Ok::<_, Error>(()))
.map_or_else(|e| matches!(e, PrivDropErr::RootEntry), |_| false)
);
Ok(())
}
}
+ #[cfg(feature = "std")]
#[test]
#[ignore]
fn test_chroot_priv_drop() -> Result<(), PrivDropErr<Error>> {
if Uid::geteuid().is_root() {
- UserInfo::chroot_then_priv_drop("zack", "./", false, || {
- TcpListener::bind(SocketAddrV6::new(Ipv6Addr::LOCALHOST, 443, 0, 0))
+ UserInfo::chroot_then_priv_drop(c"nobody", c"./", false, || {
+ TcpListener::bind(SocketAddrV6::new(Ipv6Addr::LOCALHOST, 987, 0, 0))
})
.and_then(|_| {
- fs::exists(README).map_err(PrivDropErr::Io).map(|exists| {
- assert!(exists);
- assert!(
- TcpListener::bind(SocketAddrV6::new(Ipv6Addr::LOCALHOST, 80, 0, 0))
- .is_err()
- );
- })
+ fs::exists(README_STR)
+ .map_err(PrivDropErr::Other)
+ .map(|exists| {
+ assert!(exists);
+ assert!(
+ TcpListener::bind(SocketAddrV6::new(Ipv6Addr::LOCALHOST, 82, 0, 0))
+ .is_err()
+ );
+ })
})
} else {
Ok(())
}
}
- #[cfg(target_os = "openbsd")]
+ #[cfg(all(feature = "std", target_os = "openbsd"))]
#[test]
#[ignore]
fn test_pledge_unveil() {
- const FILE_EXISTS: &str = "/home/zack/foo.txt";
- _ = fs::metadata(FILE_EXISTS)
- .expect(format!("{FILE_EXISTS} does not exist, so unit testing cannot occur").as_str());
- const FILE_NOT_EXISTS: &str = "/home/zack/aadkjfasj3s23";
- drop(fs::metadata(FILE_NOT_EXISTS).expect_err(
- format!("{FILE_NOT_EXISTS} exists, so unit testing cannot occur").as_str(),
- ));
- const DIR_NOT_EXISTS: &str = "/home/zack/aadkjfasj3s23/";
- drop(
- fs::metadata(DIR_NOT_EXISTS).expect_err(
- format!("{DIR_NOT_EXISTS} exists, so unit testing cannot occur").as_str(),
- ),
+ const FILE_EXISTS: &CStr = c"/home/zack/foo.txt";
+ const FILE_EXISTS_STR: &str = match FILE_EXISTS.to_str() {
+ Ok(val) => val,
+ Err(_) => panic!("not possible"),
+ };
+ _ = fs::metadata(FILE_EXISTS_STR).expect(
+ format!("{FILE_EXISTS_STR} does not exist, so unit testing cannot occur").as_str(),
);
+ const FILE_NOT_EXISTS: &CStr = c"/home/zack/aadkjfasj3s23";
+ const FILE_NOT_EXISTS_STR: &str = match FILE_NOT_EXISTS.to_str() {
+ Ok(val) => val,
+ Err(_) => panic!("not possible"),
+ };
+ drop(fs::metadata(FILE_NOT_EXISTS_STR).expect_err(
+ format!("{FILE_NOT_EXISTS_STR} exists, so unit testing cannot occur").as_str(),
+ ));
+ const DIR_NOT_EXISTS: &CStr = c"/home/zack/aadkjfasj3s23/";
+ const DIR_NOT_EXISTS_STR: &str = match DIR_NOT_EXISTS.to_str() {
+ Ok(val) => val,
+ Err(_) => panic!("not possible"),
+ };
+ drop(fs::metadata(DIR_NOT_EXISTS_STR).expect_err(
+ format!("{DIR_NOT_EXISTS_STR} exists, so unit testing cannot occur").as_str(),
+ ));
// This tests that a NULL `promise` does nothing.
assert!(Promises::pledge_none().is_ok());
print!("");
@@ -1125,11 +1188,11 @@ mod tests {
assert!(initial_promises.pledge().is_ok());
// This tests unveil with no permissions.
assert!(Permissions::NONE.unveil(FILE_EXISTS).is_ok());
- assert!(fs::metadata(FILE_EXISTS).is_err());
+ assert!(fs::metadata(FILE_EXISTS_STR).is_err());
// This tests unveil with read permissions,
// and one can unveil more permissions (unlike pledge which can only remove promises).
assert!(Permissions::READ.unveil(FILE_EXISTS).is_ok());
- assert!(fs::metadata(FILE_EXISTS).is_ok());
+ assert!(fs::metadata(FILE_EXISTS_STR).is_ok());
// This tests that calls to unveil on missing files don't error.
assert!(Permissions::NONE.unveil(FILE_NOT_EXISTS).is_ok());
// This tests that calls to unveil on missing directories error.
@@ -1137,7 +1200,7 @@ mod tests {
// This tests that unveil can no longer be called.
assert!(Permissions::unveil_no_more().is_ok());
assert!(Permissions::NONE.unveil(FILE_EXISTS).is_err());
- assert!(fs::metadata(FILE_EXISTS).is_ok());
+ assert!(fs::metadata(FILE_EXISTS_STR).is_ok());
// The below tests that Promises can only be removed and not added.
initial_promises.remove_promises([Promise::Unveil]);
assert_eq!(initial_promises.len(), 2);
@@ -1150,43 +1213,38 @@ mod tests {
assert!(Promises::new([Promise::Rpath]).pledge().is_err());
// If the below is uncommented, the program should crash since the above
// call to pledge no longer allows access to the file system.
- // drop(fs::metadata(FILE_EXISTS));
+ // drop(fs::metadata(FILE_EXISTS_STR));
}
- #[cfg(target_os = "openbsd")]
+ #[cfg(all(feature = "std", target_os = "openbsd"))]
#[test]
#[ignore]
fn test_pledge_priv_drop() -> Result<(), PrivDropErr<Error>> {
if Uid::geteuid().is_root() {
Promises::new_priv_drop(
- "zack",
+ c"nobody",
[Promise::Inet, Promise::Rpath, Promise::Unveil],
false,
- || TcpListener::bind(SocketAddrV6::new(Ipv6Addr::LOCALHOST, 443, 0, 0)),
+ || TcpListener::bind(SocketAddrV6::new(Ipv6Addr::LOCALHOST, 47, 0, 0)),
)
- .and_then(|(_, mut promises)| {
- Permissions::READ
- .unveil(README)
- .map_err(PrivDropErr::from)
+ .and_then(|(_, _)| {
+ Permissions::unveil_raw(README, c"r")
+ .map_err(PrivDropErr::Libc)
.and_then(|()| {
- fs::exists(README)
- .map_err(PrivDropErr::Io)
+ fs::exists(README_STR)
+ .map_err(PrivDropErr::Other)
.and_then(|exists| {
Permissions::NONE
.unveil(README)
- .map_err(PrivDropErr::from)
+ .map_err(PrivDropErr::Libc)
.and_then(|()| {
- promises
- .remove_promises_then_pledge([
- Promise::Rpath,
- Promise::Unveil,
- ])
- .map_err(PrivDropErr::Io)
+ Promises::pledge_raw(c"inet stdio")
+ .map_err(PrivDropErr::Libc)
.map(|()| {
assert!(exists);
assert!(
TcpListener::bind(SocketAddrV6::new(
Ipv6Addr::LOCALHOST,
- 80,
+ 792,
0,
0
))
@@ -1201,42 +1259,42 @@ mod tests {
Ok(())
}
}
- #[cfg(target_os = "openbsd")]
+ #[cfg(all(feature = "std", target_os = "openbsd"))]
#[test]
#[ignore]
fn test_pledge_chroot_priv_drop() -> Result<(), PrivDropErr<Error>> {
if Uid::geteuid().is_root() {
Promises::new_chroot_then_priv_drop(
- "zack",
- "./",
+ c"nobody",
+ c"./",
[Promise::Inet, Promise::Rpath, Promise::Unveil],
false,
- || TcpListener::bind(SocketAddrV6::new(Ipv6Addr::LOCALHOST, 443, 0, 0)),
+ || TcpListener::bind(SocketAddrV6::new(Ipv6Addr::LOCALHOST, 382, 0, 0)),
)
.and_then(|(_, mut promises)| {
Permissions::READ
.unveil(README)
- .map_err(PrivDropErr::from)
+ .map_err(PrivDropErr::Libc)
.and_then(|()| {
- fs::exists(README)
- .map_err(PrivDropErr::Io)
+ fs::exists(README_STR)
+ .map_err(PrivDropErr::Other)
.and_then(|exists| {
Permissions::NONE
.unveil(README)
- .map_err(PrivDropErr::from)
+ .map_err(PrivDropErr::Libc)
.and_then(|()| {
promises
.remove_promises_then_pledge([
Promise::Rpath,
Promise::Unveil,
])
- .map_err(PrivDropErr::Io)
+ .map_err(PrivDropErr::Libc)
.map(|()| {
assert!(exists);
assert!(
TcpListener::bind(SocketAddrV6::new(
Ipv6Addr::LOCALHOST,
- 80,
+ 588,
0,
0
))
diff --git a/src/openbsd.rs b/src/openbsd.rs
@@ -1,16 +1,12 @@
#[cfg(doc)]
use super::chroot_then_chdir;
-use super::{NulOrIoErr, PrivDropErr, SUCCESS, UserInfo};
-use alloc::ffi::CString;
-#[cfg(doc)]
-use alloc::ffi::NulError;
+use super::{Errno, PrivDropErr, SUCCESS, UserInfo};
use core::{
- ffi::{c_char, c_int},
+ ffi::{CStr, c_char, c_int},
fmt::{self, Display, Formatter},
ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Not},
ptr,
};
-use std::{io::Error, os::unix::ffi::OsStrExt as _, path::Path};
#[expect(unsafe_code, reason = "FFI requires unsafe")]
unsafe extern "C" {
/// [`pledge(2)`](https://man.openbsd.org/pledge.2).
@@ -287,20 +283,14 @@ impl Promises {
/// # use priv_sep::PrivDropErr;
/// # use std::{io::Error, net::TcpListener};
/// # #[cfg(target_os = "openbsd")]
- /// let (listener, promises) = Promises::new_priv_drop("nobody", [Promise::Inet], false, || {
+ /// let (listener, promises) = Promises::new_priv_drop(c"nobody", [Promise::Inet], false, || {
/// TcpListener::bind(SocketAddrV6::new(Ipv6Addr::LOCALHOST, 443, 0, 0))
/// })?;
/// # Ok::<_, PrivDropErr<Error>>(())
/// ```
#[inline]
- pub fn new_priv_drop<
- U: Into<Vec<u8>>,
- Prom: AsRef<[Promise]>,
- T,
- E,
- F: FnOnce() -> Result<T, E>,
- >(
- name: U,
+ pub fn new_priv_drop<Prom: AsRef<[Promise]>, T, E, F: FnOnce() -> Result<T, E>>(
+ name: &CStr,
initial: Prom,
retain_id_promise: bool,
f: F,
@@ -310,22 +300,22 @@ impl Promises {
|val, promise| val.add(*promise),
);
UserInfo::new(name)
- .map_err(PrivDropErr::from)
+ .map_err(PrivDropErr::Libc)
.and_then(|opt| {
opt.ok_or_else(|| PrivDropErr::NoPasswdEntry)
.and_then(|info| {
if info.is_root() {
Err(PrivDropErr::RootEntry)
} else {
- promises.pledge().map_err(PrivDropErr::Io).and_then(|()| {
+ promises.pledge().map_err(PrivDropErr::Libc).and_then(|()| {
f().map_err(PrivDropErr::Other).and_then(|res| {
- info.setresid().map_err(PrivDropErr::Io).and_then(|()| {
+ info.setresid().map_err(PrivDropErr::Libc).and_then(|()| {
if retain_id_promise {
Ok(())
} else {
promises
.remove_then_pledge(Promise::Id)
- .map_err(PrivDropErr::Io)
+ .map_err(PrivDropErr::Libc)
}
.map(|()| (res, promises))
})
@@ -349,19 +339,18 @@ impl Promises {
/// # use core::net::{Ipv6Addr, SocketAddrV6};
/// # use tokio::net::TcpListener;
/// # #[cfg(target_os = "openbsd")]
- /// let listener_fut = Promises::new_priv_drop_async("nobody", [Promise::Inet], false, async || {
+ /// let listener_fut = Promises::new_priv_drop_async(c"nobody", [Promise::Inet], false, async || {
/// TcpListener::bind(SocketAddrV6::new(Ipv6Addr::LOCALHOST, 443, 0, 0)).await
/// });
/// ```
#[inline]
pub async fn new_priv_drop_async<
- U: Into<Vec<u8>>,
Prom: AsRef<[Promise]>,
T,
E,
F: AsyncFnOnce() -> Result<T, E>,
>(
- name: U,
+ name: &CStr,
initial: Prom,
retain_id_promise: bool,
f: F,
@@ -379,23 +368,23 @@ impl Promises {
} else {
match promises.pledge() {
Ok(()) => f().await.map_err(PrivDropErr::Other).and_then(|res| {
- info.setresid().map_err(PrivDropErr::Io).and_then(|()| {
+ info.setresid().map_err(PrivDropErr::Libc).and_then(|()| {
if retain_id_promise {
Ok(())
} else {
promises
.remove_then_pledge(Promise::Id)
- .map_err(PrivDropErr::Io)
+ .map_err(PrivDropErr::Libc)
}
.map(|()| (res, promises))
})
}),
- Err(err) => Err(PrivDropErr::Io(err)),
+ Err(err) => Err(PrivDropErr::Libc(err)),
}
}
}
},
- Err(err) => Err(PrivDropErr::from(err)),
+ Err(err) => Err(PrivDropErr::Libc(err)),
}
}
/// Same as [`Self::new_priv_drop`] except [`chroot_then_chdir`] is called before [`Self::pledge`]ing `initial`.
@@ -413,22 +402,15 @@ impl Promises {
/// # use priv_sep::PrivDropErr;
/// # use std::{io::Error, net::TcpListener};
/// # #[cfg(target_os = "openbsd")]
- /// let (listener, promises) = Promises::new_chroot_then_priv_drop("nobody", "./", [Promise::Inet], false, || {
+ /// let (listener, promises) = Promises::new_chroot_then_priv_drop(c"nobody", c"./", [Promise::Inet], false, || {
/// TcpListener::bind(SocketAddrV6::new(Ipv6Addr::LOCALHOST, 443, 0, 0))
/// })?;
/// # Ok::<_, PrivDropErr<Error>>(())
/// ```
#[inline]
- pub fn new_chroot_then_priv_drop<
- U: Into<Vec<u8>>,
- P: AsRef<Path>,
- Prom: AsRef<[Promise]>,
- T,
- E,
- F: FnOnce() -> Result<T, E>,
- >(
- name: U,
- path: P,
+ pub fn new_chroot_then_priv_drop<Prom: AsRef<[Promise]>, T, E, F: FnOnce() -> Result<T, E>>(
+ name: &CStr,
+ path: &CStr,
initial: Prom,
retain_id_promise: bool,
f: F,
@@ -438,7 +420,7 @@ impl Promises {
|val, promise| val.add(*promise),
);
UserInfo::new(name)
- .map_err(PrivDropErr::from)
+ .map_err(PrivDropErr::Libc)
.and_then(|opt| {
opt.ok_or_else(|| PrivDropErr::NoPasswdEntry)
.and_then(|info| {
@@ -446,18 +428,18 @@ impl Promises {
Err(PrivDropErr::RootEntry)
} else {
super::chroot_then_chdir(path)
- .map_err(PrivDropErr::from)
+ .map_err(PrivDropErr::Libc)
.and_then(|()| {
- promises.pledge().map_err(PrivDropErr::Io).and_then(|()| {
+ promises.pledge().map_err(PrivDropErr::Libc).and_then(|()| {
f().map_err(PrivDropErr::Other).and_then(|res| {
- info.setresid().map_err(PrivDropErr::Io).and_then(
+ info.setresid().map_err(PrivDropErr::Libc).and_then(
|()| {
if retain_id_promise {
Ok(())
} else {
promises
.remove_then_pledge(Promise::Id)
- .map_err(PrivDropErr::Io)
+ .map_err(PrivDropErr::Libc)
}
.map(|()| (res, promises))
},
@@ -483,21 +465,19 @@ impl Promises {
/// # use core::net::{Ipv6Addr, SocketAddrV6};
/// # use tokio::net::TcpListener;
/// # #[cfg(target_os = "openbsd")]
- /// let listener_fut = Promises::new_chroot_then_priv_drop_async("nobody", "./", [Promise::Inet], false, async || {
+ /// let listener_fut = Promises::new_chroot_then_priv_drop_async(c"nobody", c"./", [Promise::Inet], false, async || {
/// TcpListener::bind(SocketAddrV6::new(Ipv6Addr::LOCALHOST, 443, 0, 0)).await
/// });
/// ```
#[inline]
pub async fn new_chroot_then_priv_drop_async<
- U: Into<Vec<u8>>,
- P: AsRef<Path>,
Prom: AsRef<[Promise]>,
T,
E,
F: AsyncFnOnce() -> Result<T, E>,
>(
- name: U,
- path: P,
+ name: &CStr,
+ path: &CStr,
initial: Prom,
retain_id_promise: bool,
f: F,
@@ -516,25 +496,25 @@ impl Promises {
match super::chroot_then_chdir(path) {
Ok(()) => match promises.pledge() {
Ok(()) => f().await.map_err(PrivDropErr::Other).and_then(|res| {
- info.setresid().map_err(PrivDropErr::Io).and_then(|()| {
+ info.setresid().map_err(PrivDropErr::Libc).and_then(|()| {
if retain_id_promise {
Ok(())
} else {
promises
.remove_then_pledge(Promise::Id)
- .map_err(PrivDropErr::Io)
+ .map_err(PrivDropErr::Libc)
}
.map(|()| (res, promises))
})
}),
- Err(err) => Err(PrivDropErr::Io(err)),
+ Err(err) => Err(PrivDropErr::Libc(err)),
},
- Err(err) => Err(PrivDropErr::from(err)),
+ Err(err) => Err(PrivDropErr::Libc(err)),
}
}
}
},
- Err(err) => Err(PrivDropErr::from(err)),
+ Err(err) => Err(PrivDropErr::Libc(err)),
}
}
/// Returns the number of [`Promise`]s.
@@ -621,7 +601,7 @@ impl Promises {
///
/// # Errors
///
- /// Returns [`Error`] iff `pledge` does.
+ /// Errors iff `pledge(2)` does.
///
/// # Examples
///
@@ -632,7 +612,7 @@ impl Promises {
/// assert!(Promises::new([Promise::Rpath, Promise::Stdio]).retain_then_pledge([Promise::Rpath]).is_ok());
/// ```
#[inline]
- pub fn retain_then_pledge<P: AsRef<[Promise]>>(&mut self, promises: P) -> Result<(), Error> {
+ pub fn retain_then_pledge<P: AsRef<[Promise]>>(&mut self, promises: P) -> Result<(), Errno> {
// We opt for the easy way by copying `self`. This should be the fastest for the
// typical case since `self` likely has very few `Promise`s, and it makes for less code.
let cur = Self(self.0);
@@ -672,7 +652,7 @@ impl Promises {
///
/// # Errors
///
- /// Returns [`Error`] iff `pledge` does.
+ /// Errors iff `pledge(2)` does.
///
/// # Examples
///
@@ -686,7 +666,7 @@ impl Promises {
pub fn remove_promises_then_pledge<P: AsRef<[Promise]>>(
&mut self,
promises: P,
- ) -> Result<(), Error> {
+ ) -> Result<(), Errno> {
// We opt for the easy way by copying `self`. This should be the fastest for the
// typical case since `self` likely has very few `Promise`s, and it makes for less code.
let cur = Self(self.0);
@@ -721,7 +701,7 @@ impl Promises {
///
/// # Errors
///
- /// Returns [`Error`] iff `pledge` does.
+ /// Errors iff `pledge(2)` does.
///
/// # Examples
///
@@ -732,7 +712,7 @@ impl Promises {
/// assert!(Promises::new([Promise::Rpath, Promise::Stdio]).remove_then_pledge(Promise::Rpath).is_ok());
/// ```
#[inline]
- pub fn remove_then_pledge(&mut self, promise: Promise) -> Result<(), Error> {
+ pub fn remove_then_pledge(&mut self, promise: Promise) -> Result<(), Errno> {
// We opt for the easy way by copying `self`. This should be the fastest for the
// typical case since `self` likely has very few `Promise`s, and it makes for less code.
let cur = Self(self.0);
@@ -745,9 +725,9 @@ impl Promises {
}
/// Invokes `pledge(2)` always passing `NULL` for `execpromises` and `promises` for `promises`.
///
- /// This function MUST only be called by [`Self::pledge`] and [`Self::pledge_none`].
+ /// This function MUST only be called by [`Self::pledge`], [`Self::pledge_none`] and [`Self::pledge_raw`].
#[expect(unsafe_code, reason = "pledge(2) takes in pointers")]
- fn inner_pledge(promises: *const c_char) -> Result<(), Error> {
+ fn inner_pledge(promises: *const c_char) -> Result<(), Errno> {
// SAFETY:
// `promises` is either null or valid as can be seen in the only functions that call this
// function:
@@ -755,7 +735,7 @@ impl Promises {
if unsafe { pledge(promises, ptr::null()) } == SUCCESS {
Ok(())
} else {
- Err(Error::last_os_error())
+ Err(Errno::last())
}
}
/// Invokes [`pledge(2)`](https://man.openbsd.org/pledge.2) always passing in
@@ -763,7 +743,7 @@ impl Promises {
///
/// # Errors
///
- /// Returns [`Error`] iff `pledge(2)` errors.
+ /// Errors iff `pledge(2)` does.
///
/// # Examples
///
@@ -773,146 +753,329 @@ impl Promises {
/// # #[cfg(target_os = "openbsd")]
/// assert!(Promises::new([Promise::Stdio]).pledge().is_ok());
/// ```
- #[expect(
- unsafe_code,
- reason = "we manually construct a valid CString and ensure its safety"
- )]
+ #[expect(unsafe_code, reason = "comment justifies correctness")]
#[expect(
clippy::arithmetic_side_effects,
clippy::indexing_slicing,
- reason = "we replace a trailing space with 0 to ensure CString is correctly constructed"
+ reason = "comments justify correctness"
)]
#[expect(
clippy::cognitive_complexity,
clippy::too_many_lines,
- reason = "many if blocks to handle each Promise"
+ reason = "a lot of Promises to check"
)]
#[inline]
- pub fn pledge(&self) -> Result<(), Error> {
- let mut p = Vec::new();
+ pub fn pledge(&self) -> Result<(), Errno> {
+ // Most of this code was programmatically generated.
+ /// b"audio ".
+ const AUDIO: &[u8; 6] = b"audio ";
+ /// b"bpf ".
+ const BPF: &[u8; 4] = b"bpf ";
+ /// b"chown ".
+ const CHOWN: &[u8; 6] = b"chown ";
+ /// b"cpath ".
+ const CPATH: &[u8; 6] = b"cpath ";
+ /// b"disklabel ".
+ const DISKLABEL: &[u8; 10] = b"disklabel ";
+ /// b"dns ".
+ const DNS: &[u8; 4] = b"dns ";
+ /// b"dpath ".
+ const DPATH: &[u8; 6] = b"dpath ";
+ /// b"drm ".
+ const DRM: &[u8; 4] = b"drm ";
+ /// b"error ".
+ const ERROR: &[u8; 6] = b"error ";
+ /// b"exec ".
+ const EXEC: &[u8; 5] = b"exec ";
+ /// b"fattr ".
+ const FATTR: &[u8; 6] = b"fattr ";
+ /// b"flock ".
+ const FLOCK: &[u8; 6] = b"flock ";
+ /// b"getpw ".
+ const GETPW: &[u8; 6] = b"getpw ";
+ /// b"id ".
+ const ID: &[u8; 3] = b"id ";
+ /// b"inet ".
+ const INET: &[u8; 5] = b"inet ";
+ /// b"mcast ".
+ const MCAST: &[u8; 6] = b"mcast ";
+ /// b"pf ".
+ const PF: &[u8; 3] = b"pf ";
+ /// b"proc ".
+ const PROC: &[u8; 5] = b"proc ";
+ /// b"prot_exec ".
+ #[expect(clippy::doc_markdown, reason = "false positive")]
+ const PROT_EXEC: &[u8; 10] = b"prot_exec ";
+ /// b"ps ".
+ const PS: &[u8; 3] = b"ps ";
+ /// b"recvfd ".
+ const RECVFD: &[u8; 7] = b"recvfd ";
+ /// b"route ".
+ const ROUTE: &[u8; 6] = b"route ";
+ /// b"rpath ".
+ const RPATH: &[u8; 6] = b"rpath ";
+ /// b"sendfd ".
+ const SENDFD: &[u8; 7] = b"sendfd ";
+ /// b"settime ".
+ const SETTIME: &[u8; 8] = b"settime ";
+ /// b"stdio ".
+ const STDIO: &[u8; 6] = b"stdio ";
+ /// b"tape ".
+ const TAPE: &[u8; 5] = b"tape ";
+ /// b"tmppath ".
+ const TMPPATH: &[u8; 8] = b"tmppath ";
+ /// b"tty ".
+ const TTY: &[u8; 4] = b"tty ";
+ /// b"unix ".
+ const UNIX: &[u8; 5] = b"unix ";
+ /// b"unveil ".
+ const UNVEIL: &[u8; 7] = b"unveil ";
+ /// b"video ".
+ const VIDEO: &[u8; 6] = b"video ";
+ /// b"vminfo ".
+ const VMINFO: &[u8; 7] = b"vminfo ";
+ /// b"vmm ".
+ const VMM: &[u8; 4] = b"vmm ";
+ /// b"wpath ".
+ const WPATH: &[u8; 6] = b"wpath ";
+ /// b"wroute ".
+ const WROUTE: &[u8; 7] = b"wroute ";
+ /// Maximum length needed to pledge all promises.
+ const MAX_LEN: usize = AUDIO.len()
+ + BPF.len()
+ + CHOWN.len()
+ + CPATH.len()
+ + DISKLABEL.len()
+ + DNS.len()
+ + DPATH.len()
+ + DRM.len()
+ + ERROR.len()
+ + EXEC.len()
+ + FATTR.len()
+ + FLOCK.len()
+ + GETPW.len()
+ + ID.len()
+ + INET.len()
+ + MCAST.len()
+ + PF.len()
+ + PROC.len()
+ + PROT_EXEC.len()
+ + PS.len()
+ + RECVFD.len()
+ + ROUTE.len()
+ + RPATH.len()
+ + SENDFD.len()
+ + SETTIME.len()
+ + STDIO.len()
+ + TAPE.len()
+ + TMPPATH.len()
+ + TTY.len()
+ + UNIX.len()
+ + UNVEIL.len()
+ + VIDEO.len()
+ + VMINFO.len()
+ + VMM.len()
+ + WPATH.len()
+ + WROUTE.len();
+ let mut max_buffer = [0; MAX_LEN];
+ let mut idx = 0;
+ let mut len = 0;
+ // Our additions are all free from overflow since a compilation error would have occurred for
+ // `MAX_LEN`.
+ // Our indexing is correct since `len - idx` is always the length of the value we copy from.
if self.contains(Promise::Audio) {
- p.extend_from_slice(b"audio ");
+ len = AUDIO.len();
+ max_buffer[..len].copy_from_slice(AUDIO);
+ idx = len;
}
if self.contains(Promise::Bpf) {
- p.extend_from_slice(b"bpf ");
+ len += BPF.len();
+ max_buffer[idx..len].copy_from_slice(BPF);
+ idx = len;
}
if self.contains(Promise::Chown) {
- p.extend_from_slice(b"chown ");
+ len += CHOWN.len();
+ max_buffer[idx..len].copy_from_slice(CHOWN);
+ idx = len;
}
if self.contains(Promise::Cpath) {
- p.extend_from_slice(b"cpath ");
+ len += CPATH.len();
+ max_buffer[idx..len].copy_from_slice(CPATH);
+ idx = len;
}
if self.contains(Promise::Disklabel) {
- p.extend_from_slice(b"disklabel ");
+ len += DISKLABEL.len();
+ max_buffer[idx..len].copy_from_slice(DISKLABEL);
+ idx = len;
}
if self.contains(Promise::Dns) {
- p.extend_from_slice(b"dns ");
+ len += DNS.len();
+ max_buffer[idx..len].copy_from_slice(DNS);
+ idx = len;
}
if self.contains(Promise::Dpath) {
- p.extend_from_slice(b"dpath ");
+ len += DPATH.len();
+ max_buffer[idx..len].copy_from_slice(DPATH);
+ idx = len;
}
if self.contains(Promise::Drm) {
- p.extend_from_slice(b"drm ");
+ len += DRM.len();
+ max_buffer[idx..len].copy_from_slice(DRM);
+ idx = len;
}
if self.contains(Promise::Error) {
- p.extend_from_slice(b"error ");
+ len += ERROR.len();
+ max_buffer[idx..len].copy_from_slice(ERROR);
+ idx = len;
}
if self.contains(Promise::Exec) {
- p.extend_from_slice(b"exec ");
+ len += EXEC.len();
+ max_buffer[idx..len].copy_from_slice(EXEC);
+ idx = len;
}
if self.contains(Promise::Fattr) {
- p.extend_from_slice(b"fattr ");
+ len += FATTR.len();
+ max_buffer[idx..len].copy_from_slice(FATTR);
+ idx = len;
}
if self.contains(Promise::Flock) {
- p.extend_from_slice(b"flock ");
+ len += FLOCK.len();
+ max_buffer[idx..len].copy_from_slice(FLOCK);
+ idx = len;
}
if self.contains(Promise::Getpw) {
- p.extend_from_slice(b"getpw ");
+ len += GETPW.len();
+ max_buffer[idx..len].copy_from_slice(GETPW);
+ idx = len;
}
if self.contains(Promise::Id) {
- p.extend_from_slice(b"id ");
+ len += ID.len();
+ max_buffer[idx..len].copy_from_slice(ID);
+ idx = len;
}
if self.contains(Promise::Inet) {
- p.extend_from_slice(b"inet ");
+ len += INET.len();
+ max_buffer[idx..len].copy_from_slice(INET);
+ idx = len;
}
if self.contains(Promise::Mcast) {
- p.extend_from_slice(b"mcast ");
+ len += MCAST.len();
+ max_buffer[idx..len].copy_from_slice(MCAST);
+ idx = len;
}
if self.contains(Promise::Pf) {
- p.extend_from_slice(b"pf ");
+ len += PF.len();
+ max_buffer[idx..len].copy_from_slice(PF);
+ idx = len;
}
if self.contains(Promise::Proc) {
- p.extend_from_slice(b"proc ");
+ len += PROC.len();
+ max_buffer[idx..len].copy_from_slice(PROC);
+ idx = len;
}
if self.contains(Promise::ProtExec) {
- p.extend_from_slice(b"prot_exec ");
+ len += PROT_EXEC.len();
+ max_buffer[idx..len].copy_from_slice(PROT_EXEC);
+ idx = len;
}
if self.contains(Promise::Ps) {
- p.extend_from_slice(b"ps ");
+ len += PS.len();
+ max_buffer[idx..len].copy_from_slice(PS);
+ idx = len;
}
if self.contains(Promise::Recvfd) {
- p.extend_from_slice(b"recvfd ");
+ len += RECVFD.len();
+ max_buffer[idx..len].copy_from_slice(RECVFD);
+ idx = len;
}
if self.contains(Promise::Route) {
- p.extend_from_slice(b"route ");
+ len += ROUTE.len();
+ max_buffer[idx..len].copy_from_slice(ROUTE);
+ idx = len;
}
if self.contains(Promise::Rpath) {
- p.extend_from_slice(b"rpath ");
+ len += RPATH.len();
+ max_buffer[idx..len].copy_from_slice(RPATH);
+ idx = len;
}
if self.contains(Promise::Sendfd) {
- p.extend_from_slice(b"sendfd ");
+ len += SENDFD.len();
+ max_buffer[idx..len].copy_from_slice(SENDFD);
+ idx = len;
}
if self.contains(Promise::Settime) {
- p.extend_from_slice(b"settime ");
+ len += SETTIME.len();
+ max_buffer[idx..len].copy_from_slice(SETTIME);
+ idx = len;
}
if self.contains(Promise::Stdio) {
- p.extend_from_slice(b"stdio ");
+ len += STDIO.len();
+ max_buffer[idx..len].copy_from_slice(STDIO);
+ idx = len;
}
if self.contains(Promise::Tape) {
- p.extend_from_slice(b"tape ");
+ len += TAPE.len();
+ max_buffer[idx..len].copy_from_slice(TAPE);
+ idx = len;
}
if self.contains(Promise::Tmppath) {
- p.extend_from_slice(b"tmppath ");
+ len += TMPPATH.len();
+ max_buffer[idx..len].copy_from_slice(TMPPATH);
+ idx = len;
}
if self.contains(Promise::Tty) {
- p.extend_from_slice(b"tty ");
+ len += TTY.len();
+ max_buffer[idx..len].copy_from_slice(TTY);
+ idx = len;
}
if self.contains(Promise::Unix) {
- p.extend_from_slice(b"unix ");
+ len += UNIX.len();
+ max_buffer[idx..len].copy_from_slice(UNIX);
+ idx = len;
}
if self.contains(Promise::Unveil) {
- p.extend_from_slice(b"unveil ");
+ len += UNVEIL.len();
+ max_buffer[idx..len].copy_from_slice(UNVEIL);
+ idx = len;
}
if self.contains(Promise::Video) {
- p.extend_from_slice(b"video ");
+ len += VIDEO.len();
+ max_buffer[idx..len].copy_from_slice(VIDEO);
+ idx = len;
}
if self.contains(Promise::Vminfo) {
- p.extend_from_slice(b"vminfo ");
+ len += VMINFO.len();
+ max_buffer[idx..len].copy_from_slice(VMINFO);
+ idx = len;
}
if self.contains(Promise::Vmm) {
- p.extend_from_slice(b"vmm ");
+ len += VMM.len();
+ max_buffer[idx..len].copy_from_slice(VMM);
+ idx = len;
}
if self.contains(Promise::Wpath) {
- p.extend_from_slice(b"wpath ");
+ len += WPATH.len();
+ max_buffer[idx..len].copy_from_slice(WPATH);
+ idx = len;
}
if self.contains(Promise::Wroute) {
- p.extend_from_slice(b"wroute ");
+ len += WROUTE.len();
+ max_buffer[idx..len].copy_from_slice(WROUTE);
}
- let idx = p.len();
- if idx == 0 {
- p.push(0);
+ if len == 0 {
+ len += 1;
} else {
// All promises have a space after them which means
// we must replace the last promise's space with
// 0/nul byte.
- // `idx` is at least 1 based on the above check.
- p[idx - 1] = 0;
+ // `len` is at least 1 based on the above check.
+ max_buffer[len - 1] = 0;
}
// SAFETY:
- // `p` was populated above with correct ASCII-encoding of the literal
+ // `max_buffer[..len]` was populated above with correct ASCII-encoding of the literal
// values all of which do not have 0 bytes.
- // `p` ends with a 0/nul byte.
- let arg = unsafe { CString::from_vec_with_nul_unchecked(p) };
+ // `max_buffer[..len]` ends with a 0/nul byte.
+ let arg = unsafe { CStr::from_bytes_with_nul_unchecked(&max_buffer[..len]) };
Self::inner_pledge(arg.as_ptr())
}
/// Invokes [`pledge(2)`](https://man.openbsd.org/pledge.2) with `NULL` for both `promises` and
@@ -920,7 +1083,7 @@ impl Promises {
///
/// # Errors
///
- /// Returns [`Error`] iff `pledge(2)` errors.
+ /// Errors iff `pledge(2)` does.
///
/// # Examples
///
@@ -931,10 +1094,31 @@ impl Promises {
/// assert!(Promises::pledge_none().is_ok());
/// ```
#[inline]
- pub fn pledge_none() -> Result<(), Error> {
+ pub fn pledge_none() -> Result<(), Errno> {
// `NULL` is always valid for `promises`.
Self::inner_pledge(ptr::null())
}
+ /// Invokes [`pledge(2)`](https://man.openbsd.org/pledge.2) with [`CStr::as_ptr`] from `promises` for
+ /// `promises` and `NULL` for `execpromises`.
+ ///
+ /// Exposes an API that allows one to `pledge(2)` "directly".
+ ///
+ /// # Errors
+ ///
+ /// Errors iff `pledge(2)` does.
+ ///
+ /// # Examples
+ ///
+ /// ```no_run
+ /// # #[cfg(target_os = "openbsd")]
+ /// # use priv_sep::Promises;
+ /// # #[cfg(target_os = "openbsd")]
+ /// assert!(Promises::pledge_raw(c"rpath stdio").is_ok());
+ /// ```
+ #[inline]
+ pub fn pledge_raw(promises: &CStr) -> Result<(), Errno> {
+ Self::inner_pledge(promises.as_ptr())
+ }
}
impl PartialEq<&Self> for Promises {
#[inline]
@@ -1033,7 +1217,7 @@ impl Permissions {
///
/// This function MUST only be called by the functions [`Self::unveil`] and [`Self::unveil_no_more`].
#[expect(unsafe_code, reason = "unveil(2) takes in pointers")]
- fn inner_unveil(path: *const c_char, permissions: *const c_char) -> Result<(), Error> {
+ fn inner_unveil(path: *const c_char, permissions: *const c_char) -> Result<(), Errno> {
// SAFETY:
// `path` and `permissions` are either null or valid as can be seen in the only functions that call this
// function:
@@ -1041,7 +1225,7 @@ impl Permissions {
if unsafe { unveil(path, permissions) } == SUCCESS {
Ok(())
} else {
- Err(Error::last_os_error())
+ Err(Errno::last())
}
}
/// Invokes [`unveil(2)`](https://man.openbsd.org/unveil.2)
@@ -1049,76 +1233,78 @@ impl Permissions {
///
/// # Errors
///
- /// Returns [`NulError`] iff [`CString::new`] does.
- /// Returns [`Error`] iff `unveil(2)` errors.
+ /// Errors iff `unveil(2)` does.
///
/// # Examples
///
/// ```no_run
/// # #[cfg(target_os = "openbsd")]
- /// # use std::io::ErrorKind;
- /// # #[cfg(target_os = "openbsd")]
- /// # use priv_sep::{Permissions, NulOrIoErr};
+ /// # use priv_sep::{Errno, Permissions};
/// # #[cfg(target_os = "openbsd")]
- /// assert!(Permissions::READ.unveil("/path/to/read").is_ok());
+ /// assert!(Permissions::READ.unveil(c"/path/to/read").is_ok());
/// # #[cfg(target_os = "openbsd")]
- /// assert!(Permissions::READ.unveil("/path/does/not/exist").map_or_else(
- /// |err| match err {
- /// NulOrIoErr::Io(e) => e.kind() == ErrorKind::NotFound,
- /// NulOrIoErr::Nul(_) => false,
- /// },
+ /// assert!(Permissions::READ.unveil(c"/path/does/not/exist").map_or_else(
+ /// |err| err == Errno::ENOENT,
/// |()| false
/// ));
/// ```
- #[expect(
- unsafe_code,
- reason = "we manually construct a valid CString and ensure its safety"
- )]
- #[expect(
- clippy::arithmetic_side_effects,
- reason = "we pre-allocate a Vec with the exact capacity which is capped at 5"
- )]
#[inline]
- pub fn unveil<P: AsRef<Path>>(self, path: P) -> Result<(), NulOrIoErr> {
- CString::new(path.as_ref().as_os_str().as_bytes()).map_or_else(
- |e| Err(NulOrIoErr::Nul(e)),
- |path_c| {
- // The max sum is 5, so overflow is not possible.
- let mut vec = Vec::with_capacity(
- usize::from(self.is_enabled(Permission::Create))
- + usize::from(self.is_enabled(Permission::Execute))
- + usize::from(self.is_enabled(Permission::Read))
- + usize::from(self.is_enabled(Permission::Write))
- + 1,
- );
- if self.is_enabled(Permission::Create) {
- vec.push(b'c');
- }
- if self.is_enabled(Permission::Execute) {
- vec.push(b'x');
- }
+ pub fn unveil(self, path: &CStr) -> Result<(), Errno> {
+ let perms = if self.is_enabled(Permission::Create) {
+ if self.is_enabled(Permission::Execute) {
if self.is_enabled(Permission::Read) {
- vec.push(b'r');
+ if self.is_enabled(Permission::Write) {
+ c"crwx"
+ } else {
+ c"crx"
+ }
+ } else if self.is_enabled(Permission::Write) {
+ c"cwx"
+ } else {
+ c"cx"
}
+ } else if self.is_enabled(Permission::Read) {
if self.is_enabled(Permission::Write) {
- vec.push(b'w');
+ c"crw"
+ } else {
+ c"cr"
}
- vec.push(0);
- // SAFETY:
- // `vec` was populated above with correct ASCII-encoding of the literal
- // values all of which do not have 0 bytes.
- // `vec` ends with a 0/nul byte.
- let perm_c = unsafe { CString::from_vec_with_nul_unchecked(vec) };
- Self::inner_unveil(path_c.as_ptr(), perm_c.as_ptr()).map_err(NulOrIoErr::Io)
- },
- )
+ } else if self.is_enabled(Permission::Write) {
+ c"cw"
+ } else {
+ c"c"
+ }
+ } else if self.is_enabled(Permission::Execute) {
+ if self.is_enabled(Permission::Read) {
+ if self.is_enabled(Permission::Write) {
+ c"rwx"
+ } else {
+ c"rx"
+ }
+ } else if self.is_enabled(Permission::Write) {
+ c"wx"
+ } else {
+ c"x"
+ }
+ } else if self.is_enabled(Permission::Read) {
+ if self.is_enabled(Permission::Write) {
+ c"rw"
+ } else {
+ c"r"
+ }
+ } else if self.is_enabled(Permission::Write) {
+ c"w"
+ } else {
+ c""
+ };
+ Self::inner_unveil(path.as_ptr(), perms.as_ptr())
}
/// Invokes [`unveil(2)`](https://man.openbsd.org/unveil.2) by passing `NULL` for both `path` and
/// `permissions`.
///
/// # Errors
///
- /// Returns [`Error`] when a problem occurs.
+ /// Errors iff `unveil(2)` does.
///
/// # Examples
///
@@ -1129,10 +1315,37 @@ impl Permissions {
/// assert!(Permissions::unveil_no_more().is_ok());
/// ```
#[inline]
- pub fn unveil_no_more() -> Result<(), Error> {
+ pub fn unveil_no_more() -> Result<(), Errno> {
// `NULL` is valid for both `path` and `permissions`.
Self::inner_unveil(ptr::null(), ptr::null())
}
+ /// Invokes [`unveil(2)`](https://man.openbsd.org/unveil.2)
+ /// passing [`CStr::as_ptr`] from `path` for `path` and `permissions.as_ptr()` for `permissions`.
+ ///
+ /// Exposes an API that allows one to `unveil(2)` "directly".
+ ///
+ /// # Errors
+ ///
+ /// Errors iff `unveil(2)` does.
+ ///
+ /// # Examples
+ ///
+ /// ```no_run
+ /// # #[cfg(target_os = "openbsd")]
+ /// # use priv_sep::{Errno, Permissions};
+ /// # #[cfg(target_os = "openbsd")]
+ /// assert!(Permissions::unveil_raw(c"/path/to/read", c"r").is_ok());
+ /// # #[cfg(target_os = "openbsd")]
+ /// assert!(Permissions::unveil_raw(c"/path/does/not/exist", c"r").map_or_else(
+ /// |err| err == Errno::ENOENT,
+ /// |()| false
+ /// ));
+ /// ```
+ #[inline]
+ pub fn unveil_raw(path: &CStr, permissions: &CStr) -> Result<(), Errno> {
+ // `NULL` is valid for both `path` and `permissions`.
+ Self::inner_unveil(path.as_ptr(), permissions.as_ptr())
+ }
}
impl Display for Permissions {
#[inline]
@@ -1221,182 +1434,3 @@ impl PartialEq<Permissions> for &Permissions {
**self == *other
}
}
-#[cfg(test)]
-mod tests {
- use super::{Permissions, Promise, Promises};
- use crate::{PrivDropErr, Uid};
- use core::net::{Ipv6Addr, SocketAddrV6};
- use std::{fs, io::Error, net::TcpListener};
- const README: &str = "README.md";
- #[test]
- #[ignore]
- fn test_pledge_unveil() {
- const FILE_EXISTS: &str = "/home/zack/foo.txt";
- _ = fs::metadata(FILE_EXISTS)
- .expect(format!("{FILE_EXISTS} does not exist, so unit testing cannot occur").as_str());
- const FILE_NOT_EXISTS: &str = "/home/zack/aadkjfasj3s23";
- drop(fs::metadata(FILE_NOT_EXISTS).expect_err(
- format!("{FILE_NOT_EXISTS} exists, so unit testing cannot occur").as_str(),
- ));
- const DIR_NOT_EXISTS: &str = "/home/zack/aadkjfasj3s23/";
- drop(
- fs::metadata(DIR_NOT_EXISTS).expect_err(
- format!("{DIR_NOT_EXISTS} exists, so unit testing cannot occur").as_str(),
- ),
- );
- // This tests that a NULL `promise` does nothing.
- assert!(Promises::pledge_none().is_ok());
- print!("");
- assert!(Promises::ALL.pledge().is_ok());
- // This tests that duplicates are ignored as well as the implementation of PartialEq.
- let mut initial_promises = Promises::new([
- Promise::Stdio,
- Promise::Unveil,
- Promise::Rpath,
- Promise::Stdio,
- ]);
- assert!(initial_promises.len() == 3);
- assert!(
- initial_promises == Promises::new([Promise::Rpath, Promise::Stdio, Promise::Unveil])
- );
- // Test retain.
- assert!({
- let mut vals = Promises::new([
- Promise::Audio,
- Promise::Bpf,
- Promise::Chown,
- Promise::Cpath,
- Promise::Error,
- Promise::Exec,
- ]);
- vals.retain([Promise::Error, Promise::Chown]);
- vals.len() == 2 && vals.contains(Promise::Chown) && vals.contains(Promise::Error)
- });
- assert!(initial_promises.pledge().is_ok());
- // This tests unveil with no permissions.
- assert!(Permissions::NONE.unveil(FILE_EXISTS).is_ok());
- assert!(fs::metadata(FILE_EXISTS).is_err());
- // This tests unveil with read permissions,
- // and one can unveil more permissions (unlike pledge which can only remove promises).
- assert!(Permissions::READ.unveil(FILE_EXISTS).is_ok());
- assert!(fs::metadata(FILE_EXISTS).is_ok());
- // This tests that calls to unveil on missing files don't error.
- assert!(Permissions::NONE.unveil(FILE_NOT_EXISTS).is_ok());
- // This tests that calls to unveil on missing directories error.
- assert!(Permissions::NONE.unveil(DIR_NOT_EXISTS).is_err());
- // This tests that unveil can no longer be called.
- assert!(Permissions::unveil_no_more().is_ok());
- assert!(Permissions::NONE.unveil(FILE_EXISTS).is_err());
- assert!(fs::metadata(FILE_EXISTS).is_ok());
- // The below tests that Promises can only be removed and not added.
- initial_promises.remove_promises([Promise::Unveil]);
- assert_eq!(initial_promises.len(), 2);
- initial_promises.remove(Promise::Rpath);
- assert_eq!(initial_promises.len(), 1);
- initial_promises.remove(Promise::Rpath);
- assert_eq!(initial_promises.len(), 1);
- assert!(initial_promises.pledge().is_ok());
- print!("");
- assert!(Promises::new([Promise::Rpath]).pledge().is_err());
- // If the below is uncommented, the program should crash since the above
- // call to pledge no longer allows access to the file system.
- // drop(fs::metadata(FILE_EXISTS));
- }
- #[test]
- #[ignore]
- fn test_pledge_priv_drop() -> Result<(), PrivDropErr<Error>> {
- if Uid::geteuid().is_root() {
- Promises::new_priv_drop(
- "zack",
- [Promise::Inet, Promise::Rpath, Promise::Unveil],
- false,
- || TcpListener::bind(SocketAddrV6::new(Ipv6Addr::LOCALHOST, 443, 0, 0)),
- )
- .and_then(|(_, mut promises)| {
- Permissions::READ
- .unveil(README)
- .map_err(PrivDropErr::from)
- .and_then(|()| {
- fs::exists(README)
- .map_err(PrivDropErr::Io)
- .and_then(|exists| {
- Permissions::NONE
- .unveil(README)
- .map_err(PrivDropErr::from)
- .and_then(|()| {
- promises
- .remove_promises_then_pledge([
- Promise::Rpath,
- Promise::Unveil,
- ])
- .map_err(PrivDropErr::Io)
- .map(|()| {
- assert!(exists);
- assert!(
- TcpListener::bind(SocketAddrV6::new(
- Ipv6Addr::LOCALHOST,
- 80,
- 0,
- 0
- ))
- .is_err()
- );
- })
- })
- })
- })
- })
- } else {
- Ok(())
- }
- }
- #[test]
- #[ignore]
- fn test_pledge_chroot_priv_drop() -> Result<(), PrivDropErr<Error>> {
- if Uid::geteuid().is_root() {
- Promises::new_chroot_then_priv_drop(
- "zack",
- "./",
- [Promise::Inet, Promise::Rpath, Promise::Unveil],
- false,
- || TcpListener::bind(SocketAddrV6::new(Ipv6Addr::LOCALHOST, 443, 0, 0)),
- )
- .and_then(|(_, mut promises)| {
- Permissions::READ
- .unveil(README)
- .map_err(PrivDropErr::from)
- .and_then(|()| {
- fs::exists(README)
- .map_err(PrivDropErr::Io)
- .and_then(|exists| {
- Permissions::NONE
- .unveil(README)
- .map_err(PrivDropErr::from)
- .and_then(|()| {
- promises
- .remove_promises_then_pledge([
- Promise::Rpath,
- Promise::Unveil,
- ])
- .map_err(PrivDropErr::Io)
- .map(|()| {
- assert!(exists);
- assert!(
- TcpListener::bind(SocketAddrV6::new(
- Ipv6Addr::LOCALHOST,
- 80,
- 0,
- 0
- ))
- .is_err()
- );
- })
- })
- })
- })
- })
- } else {
- Ok(())
- }
- }
-}