container_device_interface/
spec_dirs.rs

1use std::{
2    collections::HashMap,
3    error::Error,
4    fmt, fs,
5    path::{Path, PathBuf},
6};
7
8use lazy_static::lazy_static;
9use path_clean::clean;
10
11use crate::{
12    cache::{Cache, CdiOption},
13    spec::{read_spec, Spec},
14    utils::is_cdi_spec,
15};
16
17// DEFAULT_STATIC_DIR is the default directory for static CDI Specs.
18const DEFAULT_STATIC_DIR: &str = "/etc/cdi";
19// DEFAULT_DYNAMIC_DIR is the default directory for generated CDI Specs
20const DEFAULT_DYNAMIC_DIR: &str = "/var/run/cdi";
21
22lazy_static! {
23    // DEFAULT_SPEC_DIRS is the default Spec directory configuration.
24    // While altering this variable changes the package defaults,
25    // the preferred way of overriding the default directories is
26    // to use a with_spec_dirs options. Otherwise the change is only
27    // effective if it takes place before creating the Registry or
28    // other Cache instances.
29    pub static ref DEFAULT_SPEC_DIRS: &'static [&'static str] = &[
30        DEFAULT_STATIC_DIR,
31        DEFAULT_DYNAMIC_DIR,
32    ];
33}
34
35#[derive(Debug)]
36pub struct SpecError {
37    message: String,
38}
39
40impl SpecError {
41    pub fn new(info: &str) -> Self {
42        Self {
43            message: info.to_owned(),
44        }
45    }
46}
47
48impl fmt::Display for SpecError {
49    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
50        write!(f, "spec error message {}", self.message)
51    }
52}
53
54impl Error for SpecError {}
55
56pub fn convert_errors(
57    spec_errors: &HashMap<String, Vec<Box<dyn Error>>>,
58) -> HashMap<String, Vec<Box<dyn Error + Send + Sync + 'static>>> {
59    spec_errors
60        .iter()
61        .map(|(key, value)| {
62            (
63                key.clone(),
64                value
65                    .iter()
66                    .map(|error| {
67                        Box::new(SpecError::new(&error.to_string()))
68                            as Box<dyn Error + Send + Sync + 'static>
69                    })
70                    .collect(),
71            )
72        })
73        .collect()
74}
75
76/// with_spec_dirs returns an option to override the CDI Spec directories.
77pub fn with_spec_dirs(dirs: &[&str]) -> CdiOption {
78    let cleaned_dirs: Vec<String> = dirs
79        .iter()
80        .map(|dir| {
81            clean(PathBuf::from(*dir))
82                .into_os_string()
83                .into_string()
84                .unwrap()
85        })
86        .collect();
87
88    Box::new(move |cache: &mut Cache| {
89        cache.spec_dirs.clone_from(&cleaned_dirs);
90    })
91}
92
93#[allow(dead_code)]
94fn traverse_dir<F>(dir_path: &Path, traverse_fn: &mut F) -> Result<(), Box<dyn Error>>
95where
96    F: FnMut(&Path) -> Result<(), Box<dyn Error>>,
97{
98    if let Ok(entries) = fs::read_dir(dir_path) {
99        for entry in entries.flatten() {
100            let path = entry.path();
101            if path.is_dir() {
102                traverse_dir(&path, traverse_fn)?;
103            } else {
104                traverse_fn(&path)?;
105            }
106        }
107    }
108    Ok(())
109}
110
111// scan_spec_dirs scans the given directories looking for CDI Spec files,
112// which are all files with a '.json' or '.yaml' suffix. For every Spec
113// file discovered, if it's a cdi spec, then loads a Spec from the file
114// with the priority (the index of the directory in the slice of directories given),
115// then collect the CDI Specs, and any error encountered while loading the Spec return Error.
116#[allow(dead_code)]
117pub(crate) fn scan_spec_dirs<P: AsRef<Path>>(dirs: &[P]) -> Result<Vec<Spec>, Box<dyn Error>> {
118    let mut scaned_specs = Vec::new();
119    for (priority, dir) in dirs.iter().enumerate() {
120        let dir_path = dir.as_ref();
121        if !dir_path.is_dir() {
122            continue;
123        }
124
125        let mut operation = |path: &Path| -> Result<(), Box<dyn Error>> {
126            if !path.is_dir() && is_cdi_spec(path) {
127                let spec = match read_spec(&path.to_path_buf(), priority as i32) {
128                    Ok(spec) => spec,
129                    Err(err) => {
130                        return Err(Box::new(SpecError::new(&err.to_string())));
131                    }
132                };
133                scaned_specs.push(spec);
134            }
135            Ok(())
136        };
137
138        if let Err(e) = traverse_dir(dir_path, &mut operation) {
139            return Err(Box::new(SpecError::new(&e.to_string())));
140        }
141    }
142
143    Ok(scaned_specs)
144}
145
146#[cfg(test)]
147mod tests {
148    //TODO
149}