bpf_sys/
headers.rs

1// Copyright 2019 Authors of Red Sift
2//
3// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
4// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
5// http://opensource.org/licenses/MIT>, at your option. This file may not be
6// copied, modified, or distributed except according to those terms.
7
8use 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
157/// List all available kernel header paths under /lib/modules
158pub 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
170/// Get user defined custom path of the Linux kernel header directory
171///
172/// It returns `KERNEL_SOURCE` environment variable if it is set
173pub fn get_custom_header_path() -> Option<PathBuf> {
174    Some(PathBuf::from(env::var(ENV_SOURCE_PATH).ok()?))
175}
176
177/// Set user defined custom path of the Linux kernel header directory
178pub fn set_custom_header_path(path: impl AsRef<Path>) {
179    env::set_var(ENV_SOURCE_PATH, path.as_ref().as_os_str())
180}
181
182/// Get user defined custom version of the Linux kernel header
183///
184/// It returns `KERNEL_VERSION` environment variable if it is set
185pub fn get_custom_header_version() -> Option<String> {
186    env::var(ENV_SOURCE_VERSION).ok()
187}