1use std::{
2 collections::{HashMap, HashSet},
3 ffi::OsStr,
4 fs::read_to_string,
5 io::Error,
6 path::{Path, PathBuf},
7 process::Command,
8};
9
10pub static MOD_D: &str = "/lib/modules";
11pub static MOD_DEP_F: &str = "modules.dep";
12pub static MOD_INFO_EXE: &str = "/usr/sbin/modinfo";
13
14#[derive(Debug, Clone)]
16pub struct KernelInfo {
17 pub version: String,
18 path: PathBuf,
19 dep_path: PathBuf,
20 is_valid: bool,
21 _ext: String,
22
23 deplist: HashMap<String, Vec<String>>,
26
27 lookup_deplist: HashSet<String>,
30}
31
32impl KernelInfo {
33 pub fn new(rootpath: &str, kver: &str) -> Result<Self, Error> {
44 Ok(KernelInfo {
45 version: kver.to_owned(),
46 path: PathBuf::from(if ["", "/"].contains(&rootpath) {
47 MOD_D.to_string()
48 } else {
49 format!("{}/{}", rootpath, MOD_D)
50 }),
51 dep_path: PathBuf::from(""),
52 deplist: HashMap::default(),
53 lookup_deplist: HashSet::default(),
54 _ext: "".to_string(),
55 is_valid: false,
56 }
57 .init()?)
58 }
59
60 fn init(mut self) -> Result<Self, Error> {
62 if !self._ext.is_empty() {
63 return Ok(self);
64 }
65
66 self.path = self.path.join(&self.version);
67 self.dep_path = self.dep_path.join(self.path.as_os_str()).join(MOD_DEP_F);
68 self.load_deps()?;
69
70 Ok(self)
71 }
72
73 pub fn get_kernel_path(&self) -> PathBuf {
75 PathBuf::from(&self.path)
76 }
77
78 fn get_fext(&self, fname: Option<&OsStr>) -> String {
81 format!(
82 ".ko{}",
83 fname
84 .unwrap()
85 .to_owned()
86 .to_str()
87 .unwrap()
88 .rsplit_once(".ko")
89 .map_or("", |(_, l)| l)
90 )
91 }
92
93 fn load_deps(&mut self) -> Result<(), Error> {
96 if !self._ext.is_empty() {
97 return Ok(());
98 }
99
100 let modpath = self.get_kernel_path().join("kernel");
101 self.is_valid = Path::new(modpath.to_str().unwrap()).is_dir();
102 if self.is_valid {
103 for line in read_to_string(self.dep_path.as_os_str())?.lines() {
104 if let Some(sl) = line.split_once(':') {
105 let (modpath, moddeps) = (sl.0.trim(), sl.1.trim());
106 if self._ext.is_empty() {
107 self._ext = self.get_fext(PathBuf::from(modpath).file_name());
108 }
109
110 let mut deplist: Vec<String> = vec![];
111 let mut deplist_idx: Vec<String> = vec![];
112
113 if !moddeps.is_empty() {
114 deplist = moddeps.split(' ').map(|x| x.to_owned()).collect();
115 deplist_idx = deplist
116 .iter()
117 .map(|x| {
118 x.split('/')
119 .last()
120 .unwrap()
121 .split_once('.')
122 .unwrap()
123 .0
124 .to_string()
125 })
126 .collect();
127 }
128
129 self.deplist.insert(modpath.to_owned(), deplist);
130 self.lookup_deplist.extend(deplist_idx.into_iter());
131 }
132 }
133 }
134
135 Ok(())
136 }
137
138 pub fn is_valid(&self) -> bool {
141 self.is_valid
142 }
143
144 #[allow(dead_code)]
146 pub fn get_dep_path(&self) -> &str {
147 self.dep_path.to_str().unwrap()
148 }
149
150 fn expand_module_name<'a>(&'a self, name: &'a String) -> &String {
156 let mut m_name: String;
157 if !name.ends_with(self._ext.as_str()) {
158 m_name = format!("{}{}", name, self._ext); } else {
160 m_name = name.to_owned();
161 }
162
163 if !m_name.starts_with("kernel/") {
164 if !m_name.contains('/') {
166 m_name = format!("/{}", m_name); }
168
169 for fmodname in self.deplist.keys() {
170 let mm_name = m_name.replace('_', "-");
173 if fmodname.ends_with(&m_name) || fmodname.ends_with(&mm_name) {
174 return fmodname;
175 }
176 }
177 }
178
179 let out = Command::new(MOD_INFO_EXE).arg(name).output();
180 match out {
181 Ok(_) => match String::from_utf8(out.unwrap().stdout) {
182 Ok(data) => {
183 for line in data.lines().map(|el| el.replace(' ', "")) {
184 if line.starts_with("filename:/") && line.contains("/kernel/") {
185 let t_modname = format!(
186 "kernel/{}",
187 line.split("/kernel/").collect::<Vec<&str>>()[1]
188 );
189 for fmodname in self.deplist.keys() {
190 if *fmodname == t_modname {
191 return fmodname;
192 }
193 }
194 }
195 }
196 }
197 Err(_) => todo!(),
198 },
199 Err(_) => todo!(),
200 }
201
202 name
203 }
204
205 fn get_mod_dep(&self, name: &String, mods: &mut HashSet<String>) {
208 let mdeps = self.deplist.get(name).unwrap();
209 for mdep in mdeps {
210 mods.insert(mdep.to_owned());
211
212 let d_mdeps = self.deplist.get(mdep).unwrap();
214 if !d_mdeps.is_empty() {
215 for d_dep in d_mdeps {
216 mods.insert(d_dep.to_owned());
217 self.get_mod_dep(d_dep, mods);
218 }
219 }
220 }
221 }
222
223 pub fn get_deps_for(&self, names: &[String]) -> HashMap<String, Vec<String>> {
225 let mut mod_tree: HashMap<String, Vec<String>> = HashMap::new();
226 for kmodname in names {
227 let r_kmodname = self.expand_module_name(kmodname);
228 if !r_kmodname.contains('/') {
229 continue;
230 }
231
232 let mut mod_deps: HashSet<String> = HashSet::default();
233 let mut r_deps: Vec<String> = vec![];
234
235 self.get_mod_dep(r_kmodname, &mut mod_deps);
236
237 for v in mod_deps {
238 r_deps.push(v);
239 }
240 mod_tree.insert(r_kmodname.to_owned(), r_deps);
241 }
242
243 mod_tree
244 }
245
246 pub fn is_dep(&self, name: &str) -> bool {
248 self.lookup_deplist.contains(name)
249 }
250
251 pub fn get_deps_for_flatten(&self, names: &[String]) -> Vec<String> {
254 let mut buff: HashSet<String> = HashSet::default();
255 for (mname, mdeps) in &self.get_deps_for(names) {
256 buff.insert(mname.to_owned());
257 buff.extend(mdeps.to_owned());
258 }
259
260 buff.iter().map(|x| x.to_owned()).collect()
261 }
262
263 pub fn get_disk_modules(&self) -> Vec<String> {
265 let mut buff: HashSet<String> = HashSet::default();
266
267 for (modname, moddeps) in &self.deplist {
268 buff.insert(modname.to_owned());
269 buff.extend(moddeps.to_owned());
270 }
271
272 let mut mods: Vec<String> = buff.iter().map(|x| x.to_string()).collect();
273 mods.sort();
274
275 mods
276 }
277}