Skip to main content

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}