tuwunel_core/utils/sys.rs
1//! Operating-system resource and platform-integration utilities.
2//!
3//! Submodules expose compute, resource-limit, storage, and usage information.
4//! Top-level helpers normalize executable paths, parse device metadata, and
5//! classify socket file descriptors on Unix.
6
7pub mod compute;
8
9pub mod limits;
10
11pub mod storage;
12
13pub mod usage;
14
15#[cfg(unix)]
16use std::os::fd::AsFd;
17use std::path::PathBuf;
18
19#[cfg(unix)]
20use nix::{
21 errno::Errno,
22 sys::socket::{getsockopt, sockopt::Ipv6V6Only},
23};
24
25pub use self::{
26 compute::available_parallelism,
27 limits::*,
28 usage::{Usage, statm, thread_usage, usage},
29};
30use crate::{Result, at};
31
32/// Returns the current executable path without a trailing deletion marker.
33///
34/// The literal ` (deleted)` suffix is removed when the path is valid UTF-8.
35/// Other paths remain unchanged, and executable lookup errors are propagated.
36pub fn current_exe() -> Result<PathBuf> {
37 let exe = std::env::current_exe()?;
38 match exe.to_str() {
39 | None => Ok(exe),
40 | Some(str) => Ok(str
41 .strip_suffix(" (deleted)")
42 .map(PathBuf::from)
43 .unwrap_or(exe)),
44 }
45}
46
47/// Reports whether the current executable path carries a deletion marker.
48///
49/// A trailing ` (deleted)` suffix can indicate that the executable was removed
50/// or replaced. Lookup failures and non-UTF-8 paths return `false`.
51#[must_use]
52pub fn current_exe_deleted() -> bool {
53 std::env::current_exe().is_ok_and(|exe| {
54 exe.to_str()
55 .is_some_and(|exe| exe.ends_with(" (deleted)"))
56 })
57}
58
59/// Searches newline-delimited `KEY=VALUE` text for a key.
60///
61/// Lines without `=` are ignored, and the first exact key match is returned.
62/// The borrowed value contains everything after the first `=`, including any
63/// additional separators.
64#[inline]
65#[must_use]
66pub fn uevent_find<'a>(uevent: &'a str, key: &'a str) -> Option<&'a str> {
67 uevent
68 .lines()
69 .filter_map(|line| line.split_once('='))
70 .find(|&(key_, _)| key.eq(key_))
71 .map(at!(1))
72}
73
74/// Classifies the socket address families recognized by the server.
75///
76/// IPv4 and IPv6 addresses share the Internet variant, while Unix-domain
77/// addresses use the local variant. Other address families are rejected by
78/// [`get_socket_family`].
79#[cfg(unix)]
80#[derive(Clone, Copy, Debug)]
81pub enum SocketFamily {
82 /// An IPv4 or IPv6 Internet socket.
83 ///
84 /// Both Internet address families map to this variant.
85 Inet,
86
87 /// A Unix-domain socket.
88 ///
89 /// Every recognized local socket address maps to this variant.
90 Unix,
91}
92
93/// Determines the address family of an open socket file descriptor on Unix.
94///
95/// IPv4 and IPv6 descriptors return [`SocketFamily::Inet`], and Unix-domain
96/// descriptors return [`SocketFamily::Unix`]. Socket inspection failures,
97/// missing families, and unsupported families are returned as errors.
98#[cfg(unix)]
99pub fn get_socket_family(fd: i32) -> Result<SocketFamily> {
100 use nix::sys::socket::{AddressFamily, SockaddrLike, SockaddrStorage};
101
102 use crate::{Err, err};
103
104 let sockname: SockaddrStorage = nix::sys::socket::getsockname(fd)?;
105
106 let family = sockname
107 .family()
108 .ok_or_else(|| err!("Invalid socket"))?;
109
110 match family {
111 | AddressFamily::Inet | AddressFamily::Inet6 => Ok(SocketFamily::Inet),
112 | AddressFamily::Unix => Ok(SocketFamily::Unix),
113 | _ => Err!("Unknown socket family: {family:?}"),
114 }
115}
116
117/// Whether an IPv6 socket serves IPv6 traffic alone.
118///
119/// A dual-stack socket answers on the IPv4-mapped range too, so an unspecified
120/// address of one binds both families. The option does not exist on other
121/// families, which are reported as unrestricted.
122#[cfg(unix)]
123pub fn is_ipv6_only<F: AsFd>(socket: &F) -> Result<bool> {
124 getsockopt(socket, Ipv6V6Only).or_else(|e| match e {
125 | Errno::ENOPROTOOPT => Ok(false),
126 | _ => Err(e.into()),
127 })
128}