tuwunel_core/utils/sys/
storage.rs1use std::{
4 ffi::OsStr,
5 fs,
6 fs::{FileType, read_to_string},
7 iter::IntoIterator,
8 path::{Path, PathBuf},
9};
10
11use itertools::Itertools;
12use libc::dev_t;
13
14use crate::{
15 Result,
16 result::FlatOk,
17 utils::{result::LogDebugErr, string::SplitInfallible},
18};
19
20#[derive(Clone, Debug, Default)]
22pub struct MultiDevice {
23 pub level: Option<String>,
25
26 pub raid_disks: usize,
28
29 pub md: Vec<MultiQueue>,
31}
32
33#[derive(Clone, Debug, Default)]
35pub struct MultiQueue {
36 pub nr_requests: Option<usize>,
38
39 pub mq: Vec<Queue>,
41}
42
43#[derive(Clone, Debug, Default)]
45pub struct Queue {
46 pub id: usize,
48
49 pub nr_tags: Option<usize>,
51
52 pub cpu_list: Vec<usize>,
54}
55
56#[must_use]
58pub fn md_discover(path: &Path) -> MultiDevice {
59 let dev_id = dev_from_path(path)
60 .log_debug_err()
61 .unwrap_or_default();
62
63 let md_path = block_path(dev_id).join("md/");
64
65 let raid_disks_path = md_path.join("raid_disks");
66
67 let raid_disks: usize = read_to_string(&raid_disks_path)
68 .ok()
69 .as_deref()
70 .map(str::trim)
71 .map(str::parse)
72 .flat_ok()
73 .unwrap_or(0);
74
75 let single_fallback = raid_disks.eq(&0).then(|| block_path(dev_id));
76
77 MultiDevice {
78 raid_disks,
79
80 level: read_to_string(md_path.join("level"))
81 .ok()
82 .as_deref()
83 .map(str::trim)
84 .map(ToOwned::to_owned),
85
86 md: (0..raid_disks)
87 .map(|i| format!("rd{i}/block"))
88 .map(|path| md_path.join(&path))
89 .filter_map(|ref path| path.canonicalize().ok())
90 .map(|mut path| {
91 path.pop();
92 path
93 })
94 .chain(single_fallback)
95 .map(|path| mq_discover(&path))
96 .filter(|mq| !mq.mq.is_empty())
97 .collect(),
98 }
99}
100
101#[must_use]
103fn mq_discover(path: &Path) -> MultiQueue {
104 let mq_path = path.join("mq/");
105
106 let nr_requests_path = path.join("queue/nr_requests");
107
108 MultiQueue {
109 nr_requests: read_to_string(&nr_requests_path)
110 .ok()
111 .as_deref()
112 .map(str::trim)
113 .map(str::parse)
114 .flat_ok(),
115
116 mq: fs::read_dir(&mq_path)
117 .into_iter()
118 .flat_map(IntoIterator::into_iter)
119 .filter_map(Result::ok)
120 .filter(|entry| {
121 entry
122 .file_type()
123 .as_ref()
124 .is_ok_and(FileType::is_dir)
125 })
126 .map(|dir| queue_discover(&dir.path()))
127 .sorted_by_key(|mq| mq.id)
128 .collect::<Vec<_>>(),
129 }
130}
131
132fn queue_discover(dir: &Path) -> Queue {
134 let queue_id = dir.file_name();
135
136 let nr_tags_path = dir.join("nr_tags");
137
138 let cpu_list_path = dir.join("cpu_list");
139
140 Queue {
141 id: queue_id
142 .and_then(OsStr::to_str)
143 .map(str::parse)
144 .flat_ok()
145 .expect("queue has some numerical identifier"),
146
147 nr_tags: read_to_string(&nr_tags_path)
148 .ok()
149 .as_deref()
150 .map(str::trim)
151 .map(str::parse)
152 .flat_ok(),
153
154 cpu_list: read_to_string(&cpu_list_path)
155 .iter()
156 .flat_map(|list| list.trim().split(','))
157 .map(str::trim)
158 .map(str::parse)
159 .filter_map(Result::ok)
160 .collect(),
161 }
162}
163
164pub fn name_from_path(path: &Path) -> Result<String> {
166 use std::io::{Error, ErrorKind::NotFound};
167
168 let (major, minor) = dev_from_path(path)?;
169 let path = block_path((major, minor)).join("uevent");
170 read_to_string(path)
171 .iter()
172 .map(String::as_str)
173 .flat_map(str::lines)
174 .map(|line| line.split_once_infallible("="))
175 .find_map(|(key, val)| (key == "DEVNAME").then_some(val))
176 .ok_or_else(|| Error::new(NotFound, "DEVNAME not found."))
177 .map_err(Into::into)
178 .map(Into::into)
179}
180
181#[allow(
184 clippy::useless_conversion,
185 clippy::unnecessary_fallible_conversions
186)]
187fn dev_from_path(path: &Path) -> Result<(dev_t, dev_t)> {
188 #[cfg(target_family = "unix")]
189 use std::os::unix::fs::MetadataExt;
190
191 let stat = fs::metadata(path)?;
192 let dev_id = stat.dev().try_into()?;
193 let (major, minor) = (libc::major(dev_id), libc::minor(dev_id));
194
195 Ok((major.try_into()?, minor.try_into()?))
196}
197
198fn block_path((major, minor): (dev_t, dev_t)) -> PathBuf {
199 format!("/sys/dev/block/{major}:{minor}/").into()
200}