air_sys_types/system.rs
1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
5//! Types de la famille `system` — informations machine, entropie.
6//!
7//! Cf. `docs/specs/layer-0/family-system.md`.
8
9use alloc::ffi::CString;
10use core::time::Duration;
11
12use bitflags::bitflags;
13
14/// Nombre de CPU logiques adressables par un [`CpuSet`] (`CPU_SETSIZE` glibc).
15pub const CPU_SETSIZE: usize = 1024;
16
17/// Nombre de mots `u64` du masque ([`CPU_SETSIZE`] bits).
18const CPUSET_WORDS: usize = CPU_SETSIZE / 64;
19
20/// Masque d'affinité CPU — miroir de `cpu_set_t` (`CPU_SETSIZE` bits, 128
21/// octets), affinité des workers io-wq (`IORING_REGISTER_IOWQ_AFF`) et, à
22/// terme, `sched_setaffinity(2)` (cf. `family-system`).
23///
24/// Construit vide ; les CPU sont ajoutés un à un. Les indices **hors borne**
25/// (`≥ CPU_SETSIZE`) sont **ignorés sans panique** (les accesseurs renvoient
26/// `false`) — zéro présomption (Principe 3), jamais d'indexation qui panique.
27#[repr(C)]
28#[derive(Clone, Copy, PartialEq, Eq)]
29pub struct CpuSet {
30 /// Bitmap des CPU (LSB du mot 0 = CPU 0), layout `cpu_set_t`.
31 bits: [u64; CPUSET_WORDS],
32}
33
34impl CpuSet {
35 /// Masque **vide** (aucun CPU sélectionné).
36 #[must_use]
37 pub const fn new() -> Self {
38 Self {
39 bits: [0; CPUSET_WORDS],
40 }
41 }
42
43 /// Position `(mot, décalage de bit)` d'un CPU, ou `None` si hors borne. Le
44 /// décalage reste `usize` : un `u64 << usize` est licite, pas de cast lossy.
45 const fn locate(cpu: usize) -> Option<(usize, usize)> {
46 if cpu >= CPU_SETSIZE {
47 return None;
48 }
49 // `cpu < CPU_SETSIZE` ⇒ `cpu / 64 < CPUSET_WORDS` et `cpu % 64 < 64`.
50 Some((cpu / 64, cpu % 64))
51 }
52
53 /// Ajoute `cpu` au masque. Retourne `false` (sans rien changer) si `cpu` est
54 /// hors borne (`≥ CPU_SETSIZE`).
55 pub fn set(&mut self, cpu: usize) -> bool {
56 match Self::locate(cpu) {
57 Some((word, bit)) => {
58 // `locate` garantit `word < CPUSET_WORDS` ⇒ `get_mut` est `Some`
59 // (l'`expect` est structurellement inatteignable).
60 *self
61 .bits
62 .get_mut(word)
63 .expect("word < CPUSET_WORDS (borné par locate)") |= 1u64 << bit;
64 true
65 }
66 None => false,
67 }
68 }
69
70 /// Retire `cpu` du masque. Retourne `false` si `cpu` est hors borne.
71 pub fn clear(&mut self, cpu: usize) -> bool {
72 match Self::locate(cpu) {
73 Some((word, bit)) => {
74 *self
75 .bits
76 .get_mut(word)
77 .expect("word < CPUSET_WORDS (borné par locate)") &= !(1u64 << bit);
78 true
79 }
80 None => false,
81 }
82 }
83
84 /// `true` si `cpu` est dans le masque (`false` si hors borne).
85 #[must_use]
86 pub fn contains(&self, cpu: usize) -> bool {
87 match Self::locate(cpu) {
88 Some((word, bit)) => self.bits.get(word).is_some_and(|w| w & (1u64 << bit) != 0),
89 None => false,
90 }
91 }
92
93 /// Nombre de CPU sélectionnés (popcount du masque).
94 #[must_use]
95 pub fn count(&self) -> u32 {
96 self.bits.iter().map(|w| w.count_ones()).sum()
97 }
98
99 /// `true` si aucun CPU n'est sélectionné.
100 #[must_use]
101 pub fn is_empty(&self) -> bool {
102 self.bits.iter().all(|&w| w == 0)
103 }
104
105 /// Vue octets du masque (layout `cpu_set_t`), pour le passage au kernel
106 /// (`IORING_REGISTER_IOWQ_AFF` lit `nr_args` octets de cumpask).
107 #[must_use]
108 pub fn as_bytes(&self) -> &[u8] {
109 // SAFETY: `bits` est un `[u64; N]` `#[repr(C)]` densément peuplé ; toute
110 // configuration de bits est un motif d'octets valide (pas de padding,
111 // pas d'invariant de validité sur `u8`). La tranche est liée à `&self`.
112 unsafe {
113 core::slice::from_raw_parts(
114 core::ptr::from_ref(self).cast::<u8>(),
115 core::mem::size_of::<Self>(),
116 )
117 }
118 }
119
120 /// Vue octets **mutable** du masque (layout `cpu_set_t`), pour **remplir** le masque
121 /// depuis une source d'octets (p. ex. le `cpu_set_t *` reçu par la face libc
122 /// `sched_setaffinity`). Symétrique d'[`as_bytes`](Self::as_bytes). Additif ADR-085.
123 #[must_use]
124 pub fn as_mut_bytes(&mut self) -> &mut [u8] {
125 // SAFETY: `bits` est un `[u64; N]` `#[repr(C)]` densément peuplé (aucun padding,
126 // aucun invariant de validité sur `u8`) ⇒ tout motif d'octets écrit est valide.
127 // La tranche est liée à `&mut self` (exclusivité garantie par le borrow checker).
128 unsafe {
129 core::slice::from_raw_parts_mut(
130 core::ptr::from_mut(self).cast::<u8>(),
131 core::mem::size_of::<Self>(),
132 )
133 }
134 }
135}
136
137impl Default for CpuSet {
138 fn default() -> Self {
139 Self::new()
140 }
141}
142
143impl core::fmt::Debug for CpuSet {
144 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
145 // N'affiche que les CPU sélectionnés (le masque brut, 1024 bits, serait
146 // illisible).
147 f.debug_struct("CpuSet")
148 .field("selected_count", &self.count())
149 .finish()
150 }
151}
152
153/// Informations machine retournées par `uname(2)`.
154///
155/// Chaque champ est une chaîne C-compatible convertie en [`CString`]
156/// propriétaire. Le contenu est défini par le kernel ; aucun des champs
157/// n'est garanti UTF-8.
158#[derive(Debug, Clone)]
159pub struct UtsName {
160 /// Nom du système d'exploitation (typiquement `"Linux"`).
161 pub sysname: CString,
162 /// Nom de nœud réseau du système (hostname).
163 pub nodename: CString,
164 /// Version du kernel (ex. `"5.15.0-91-generic"`).
165 pub release: CString,
166 /// Informations de build du kernel.
167 pub version: CString,
168 /// Architecture matérielle (ex. `"x86_64"`, `"aarch64"`).
169 pub machine: CString,
170 /// Nom de domaine NIS (Linux-spécifique). Vide si non défini.
171 pub domainname: CString,
172}
173
174/// Statistiques globales du système retournées par `sysinfo(2)`.
175///
176/// Les valeurs de RAM et de swap sont exprimées en **octets** — le wrapper
177/// applique la multiplication par `mem_unit` en interne avant de
178/// construire ce type. Les `load_average` (1, 5 et 15 minutes) sont
179/// normalisés en `f64`.
180#[derive(Debug, Clone)]
181pub struct SystemInfo {
182 /// Durée de fonctionnement du système depuis le boot.
183 pub uptime: Duration,
184 /// Charge moyenne sur 1, 5 et 15 minutes (valeurs en nombre de processus).
185 pub load_average: [f64; 3],
186 /// Mémoire RAM totale (octets).
187 pub total_ram: u64,
188 /// Mémoire RAM libre (octets).
189 pub free_ram: u64,
190 /// Mémoire partagée (octets).
191 pub shared_ram: u64,
192 /// Mémoire en cache tampon (octets).
193 pub buffer_ram: u64,
194 /// Espace swap total (octets).
195 pub total_swap: u64,
196 /// Espace swap libre (octets).
197 pub free_swap: u64,
198 /// Nombre courant de processus.
199 pub processes: u16,
200 /// Total de mémoire high (zones >896 Mo sur x86 32 bits ; 0 sur 64 bits).
201 pub total_high: u64,
202 /// Mémoire high libre.
203 pub free_high: u64,
204 /// Unité de granularité mémoire du kernel (toujours 1 après conversion).
205 pub mem_unit: u32,
206}
207
208bitflags! {
209 /// Drapeaux pour `getrandom(2)` (cf. `linux/random.h`).
210 ///
211 /// En l'absence de flag, `getrandom` utilise le pool `urandom` et
212 /// bloque jusqu'à ce qu'il soit initialisé après le boot. C'est le
213 /// comportement recommandé pour 99 % des usages cryptographiques.
214 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
215 pub struct GetrandomFlags: u32 {
216 /// Retourne [`crate::Errno::EAGAIN`] immédiatement si l'entropie
217 /// n'est pas encore disponible, au lieu de bloquer.
218 const NONBLOCK = 1;
219 /// Utilise le pool `random` (bloquant quand l'entropie est faible).
220 /// Cryptographiquement équivalent à `urandom` sur Linux moderne ;
221 /// à éviter sauf contrainte spécifique.
222 const RANDOM = 2;
223 /// Retourne immédiatement même si le pool n'est pas initialisé.
224 /// **Réservé aux usages non-cryptographiques.** La qualité de
225 /// l'entropie n'est pas garantie avant l'initialisation du pool.
226 const INSECURE = 4;
227 }
228}
229
230#[cfg(test)]
231mod cpuset_tests;