1use crate::uname;
9
10use glob;
11use std::{
12 env,
13 error::Error,
14 fmt::{self, Display},
15 path::{Path, PathBuf},
16 process::Command,
17 str::FromStr,
18};
19
20const KCONFIG: &'static str = "include/linux/kconfig.h";
21const VERSION_H: &'static str = "include/generated/uapi/linux/version.h";
22const LIB_MODULES: &'static str = "/lib/modules";
23pub const ENV_SOURCE_PATH: &'static str = "KERNEL_SOURCE";
24pub const ENV_SOURCE_VERSION: &'static str = "KERNEL_VERSION";
25
26#[derive(Debug)]
27pub enum HeadersError {
28 NotFound,
29}
30impl Display for HeadersError {
31 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
32 write!(f, "No headers found")
33 }
34}
35impl Error for HeadersError {}
36
37struct KernelHeaders {
38 source: PathBuf,
39 build: PathBuf,
40}
41
42pub struct KernelVersion {
43 pub version: u8,
44 pub patchlevel: u8,
45 pub sublevel: u8,
46}
47
48pub fn prefix_kernel_headers(headers: &[&str]) -> Option<Vec<String>> {
49 let KernelHeaders { source, build } = kernel_headers_path().ok()?;
50 let mut ret: Vec<String> = Vec::new();
51 for header in headers {
52 if header.contains("generated") {
53 let path = build.join(header);
54 ret.push(path.to_string_lossy().into());
55 if header.ends_with("generated") {
56 ret.push(path.parent().unwrap().to_string_lossy().into());
57 }
58 } else {
59 ret.push(source.join(header).to_string_lossy().into());
60 }
61 }
62 Some(ret)
63}
64
65pub fn running_kernel_version() -> Option<String> {
66 get_custom_header_version().or_else(|| {
67 uname::uname()
68 .ok()
69 .map(|u| uname::to_str(&u.release).to_string())
70 })
71}
72
73pub fn build_kernel_version() -> Result<KernelVersion, Box<dyn Error>> {
74 let KernelHeaders { source: _, build } = kernel_headers_path()?;
75 let make_db = Command::new("make")
76 .arg("-qp")
77 .arg("-f")
78 .arg(build.join("Makefile"))
79 .output()?;
80 let reader = String::from_utf8(make_db.stdout)?;
81
82 let mut version = None::<u8>;
83 let mut patchlevel = None::<u8>;
84 let mut sublevel = None::<u8>;
85
86 for line in reader.lines() {
87 let mut var = line.split(" = ");
88 match var.next().map(|s| s.trim()) {
89 Some("VERSION") => version = var.next().map(u8::from_str).transpose()?,
90 Some("PATCHLEVEL") => patchlevel = var.next().map(u8::from_str).transpose()?,
91 Some("SUBLEVEL") => sublevel = var.next().map(u8::from_str).transpose()?,
92 _ => continue,
93 }
94
95 if version.is_some() && patchlevel.is_some() && sublevel.is_some() {
96 break;
97 }
98 }
99
100 Ok(KernelVersion {
101 version: version.unwrap(),
102 patchlevel: patchlevel.unwrap(),
103 sublevel: sublevel.unwrap(),
104 })
105}
106
107fn kernel_headers_path() -> Result<KernelHeaders, HeadersError> {
108 let source_path = get_custom_header_path();
109 let split_source_path = source_path.clone().and_then(split_kernel_headers);
110
111 if split_source_path.is_some() {
112 return Ok(split_source_path.unwrap());
113 }
114
115 source_path
116 .and_then(|s| {
117 let path = PathBuf::from(s);
118
119 if path.join(KCONFIG).is_file() {
120 Some(KernelHeaders {
121 source: path.clone(),
122 build: path,
123 })
124 } else {
125 None
126 }
127 })
128 .or_else(lib_modules_kernel_headers)
129 .ok_or(HeadersError::NotFound)
130}
131
132fn lib_modules_kernel_headers() -> Option<KernelHeaders> {
133 match running_kernel_version() {
134 Some(version) => split_kernel_headers(Path::new(LIB_MODULES).join(version)),
135 None => None,
136 }
137}
138
139fn split_kernel_headers(path: PathBuf) -> Option<KernelHeaders> {
140 let mut build = path.join("build");
141 let source = path.join("source");
142 let source = match (
143 source.join(KCONFIG).is_file(),
144 build.join(KCONFIG).is_file(),
145 ) {
146 (true, _) => source,
147 (false, true) => build.clone(),
148 _ => return None,
149 };
150 if !build.join(VERSION_H).is_file() {
151 build = source.clone()
152 };
153
154 Some(KernelHeaders { source, build })
155}
156
157pub fn available_kernel_header_paths() -> Vec<PathBuf> {
159 glob::glob(&format!("{}/*", LIB_MODULES))
160 .expect("error on glob")
161 .into_iter()
162 .filter_map(|res| {
163 res.as_ref().map_or(None, |ref path| {
164 split_kernel_headers(path.to_path_buf()).map(|_| path.to_path_buf())
165 })
166 })
167 .collect()
168}
169
170pub fn get_custom_header_path() -> Option<PathBuf> {
174 Some(PathBuf::from(env::var(ENV_SOURCE_PATH).ok()?))
175}
176
177pub fn set_custom_header_path(path: impl AsRef<Path>) {
179 env::set_var(ENV_SOURCE_PATH, path.as_ref().as_os_str())
180}
181
182pub fn get_custom_header_version() -> Option<String> {
186 env::var(ENV_SOURCE_VERSION).ok()
187}