tuwunel_core/utils/sys/
storage.rs1use std::path::Path;
13#[cfg(unix)]
14use std::{
15 ffi::OsStr,
16 fs,
17 fs::{FileType, read_to_string},
18 path::PathBuf,
19};
20
21#[cfg(unix)]
22use itertools::Itertools;
23#[cfg(unix)]
24use libc::dev_t;
25
26use crate::Result;
27#[cfg(unix)]
28use crate::{
29 result::FlatOk,
30 utils::{result::LogDebugErr, string::SplitInfallible},
31};
32
33#[derive(Clone, Debug, Default)]
35pub struct MultiDevice {
36 pub level: Option<String>,
38
39 pub raid_disks: usize,
41
42 pub md: Vec<MultiQueue>,
44}
45
46#[derive(Clone, Debug, Default)]
48pub struct MultiQueue {
49 pub nr_requests: Option<usize>,
51
52 pub mq: Vec<Queue>,
54}
55
56#[derive(Clone, Debug, Default)]
58pub struct Queue {
59 pub id: usize,
61
62 pub nr_tags: Option<usize>,
64
65 pub cpu_list: Vec<usize>,
67}
68
69#[cfg(unix)]
71#[must_use]
72pub fn md_discover(path: &Path) -> MultiDevice {
73 let dev_id = dev_from_path(path)
74 .log_debug_err()
75 .unwrap_or_default();
76
77 let md_path = block_path(dev_id).join("md/");
78
79 let raid_disks_path = md_path.join("raid_disks");
80
81 let raid_disks: usize = read_to_string(&raid_disks_path)
82 .ok()
83 .as_deref()
84 .map(str::trim)
85 .map(str::parse)
86 .flat_ok()
87 .unwrap_or(0);
88
89 let single_fallback = raid_disks.eq(&0).then(|| block_path(dev_id));
90
91 MultiDevice {
92 raid_disks,
93
94 level: read_to_string(md_path.join("level"))
95 .ok()
96 .as_deref()
97 .map(str::trim)
98 .map(ToOwned::to_owned),
99
100 md: (0..raid_disks)
101 .map(|i| format!("rd{i}/block"))
102 .map(|path| md_path.join(&path))
103 .filter_map(|ref path| path.canonicalize().ok())
104 .map(|mut path| {
105 path.pop();
106 path
107 })
108 .chain(single_fallback)
109 .map(|path| mq_discover(&path))
110 .filter(|mq| !mq.mq.is_empty())
111 .collect(),
112 }
113}
114
115#[cfg(not(unix))]
120#[must_use]
121pub fn md_discover(_path: &Path) -> MultiDevice { MultiDevice::default() }
122
123#[cfg(unix)]
125#[must_use]
126fn mq_discover(path: &Path) -> MultiQueue {
127 let mq_path = path.join("mq/");
128
129 let nr_requests_path = path.join("queue/nr_requests");
130
131 MultiQueue {
132 nr_requests: read_to_string(&nr_requests_path)
133 .ok()
134 .as_deref()
135 .map(str::trim)
136 .map(str::parse)
137 .flat_ok(),
138
139 mq: fs::read_dir(&mq_path)
140 .into_iter()
141 .flat_map(IntoIterator::into_iter)
142 .filter_map(Result::ok)
143 .filter(|entry| {
144 entry
145 .file_type()
146 .as_ref()
147 .is_ok_and(FileType::is_dir)
148 })
149 .map(|dir| queue_discover(&dir.path()))
150 .sorted_by_key(|mq| mq.id)
151 .collect::<Vec<_>>(),
152 }
153}
154
155#[cfg(unix)]
157fn queue_discover(dir: &Path) -> Queue {
158 let queue_id = dir.file_name();
159
160 let nr_tags_path = dir.join("nr_tags");
161
162 let cpu_list_path = dir.join("cpu_list");
163
164 Queue {
165 id: queue_id
166 .and_then(OsStr::to_str)
167 .map(str::parse)
168 .flat_ok()
169 .expect("queue has some numerical identifier"),
170
171 nr_tags: read_to_string(&nr_tags_path)
172 .ok()
173 .as_deref()
174 .map(str::trim)
175 .map(str::parse)
176 .flat_ok(),
177
178 cpu_list: read_to_string(&cpu_list_path)
179 .iter()
180 .flat_map(|list| list.trim().split(','))
181 .map(str::trim)
182 .map(str::parse)
183 .filter_map(Result::ok)
184 .collect(),
185 }
186}
187
188#[cfg(unix)]
190pub fn name_from_path(path: &Path) -> Result<String> {
191 use std::io::{Error, ErrorKind::NotFound};
192
193 let (major, minor) = dev_from_path(path)?;
194 let path = block_path((major, minor)).join("uevent");
195 read_to_string(path)
196 .iter()
197 .map(String::as_str)
198 .flat_map(str::lines)
199 .map(|line| line.split_once_infallible("="))
200 .find_map(|(key, val)| (key == "DEVNAME").then_some(val))
201 .ok_or_else(|| Error::new(NotFound, "DEVNAME not found."))
202 .map_err(Into::into)
203 .map(Into::into)
204}
205
206#[cfg(not(unix))]
211pub fn name_from_path(_path: &Path) -> Result<String> {
212 use std::io::{Error, ErrorKind::Unsupported};
213
214 Err(Error::new(Unsupported, "Block device discovery requires sysfs.").into())
215}
216
217#[cfg(unix)]
219fn dev_from_path(path: &Path) -> Result<(dev_t, dev_t)> {
220 use std::os::unix::fs::MetadataExt;
221
222 let stat = fs::metadata(path)?;
223
224 #[cfg(target_os = "linux")]
227 let dev_id = stat.dev();
228
229 #[cfg(not(target_os = "linux"))]
230 let dev_id = stat.dev().try_into()?;
231
232 let (major, minor) = (libc::major(dev_id), libc::minor(dev_id));
233
234 #[cfg(target_os = "linux")]
235 let (major, minor) = (major.into(), minor.into());
236
237 #[cfg(target_os = "android")]
238 let (major, minor) = (major.try_into()?, minor.try_into()?);
239
240 #[cfg(not(any(
241 target_os = "linux",
242 target_os = "android",
243 target_vendor = "apple"
244 )))]
245 let (major, minor) = (major.try_into()?, minor.try_into()?);
246
247 Ok((major, minor))
248}
249
250#[cfg(unix)]
251fn block_path((major, minor): (dev_t, dev_t)) -> PathBuf {
252 format!("/sys/dev/block/{major}:{minor}/").into()
253}