nydus_builder/
attributes.rs

1// Copyright 2024 Nydus Developers. All rights reserved.
2//
3// SPDX-License-Identifier: Apache-2.0
4
5use std::collections::HashMap;
6use std::path::{Path, PathBuf};
7use std::{fs, path};
8
9use anyhow::Result;
10use gix_attributes::parse;
11use gix_attributes::parse::Kind;
12
13const KEY_TYPE: &str = "type";
14const KEY_CRCS: &str = "crcs";
15const VAL_EXTERNAL: &str = "external";
16
17pub struct Parser {}
18
19#[derive(Clone, Debug, Eq, PartialEq, Default)]
20pub struct Item {
21    pub pattern: PathBuf,
22    pub attributes: HashMap<String, String>,
23}
24
25#[derive(Clone, Debug, Eq, PartialEq, Default)]
26pub struct Attributes {
27    pub items: HashMap<PathBuf, HashMap<String, String>>,
28    pub crcs: HashMap<PathBuf, Vec<u32>>,
29}
30
31impl Attributes {
32    /// Parse nydus attributes from a file.
33    pub fn from<P: AsRef<Path>>(path: P) -> Result<Attributes> {
34        let content = fs::read(path)?;
35        let _items = parse(&content);
36
37        let mut items = HashMap::new();
38        let mut crcs = HashMap::new();
39        for _item in _items {
40            let _item = _item?;
41            if let Kind::Pattern(pattern) = _item.0 {
42                let mut path = PathBuf::from(pattern.text.to_string());
43                if !path.is_absolute() {
44                    path = path::Path::new("/").join(path);
45                }
46                let mut current_path = path.clone();
47                let mut attributes = HashMap::new();
48                let mut _type = String::new();
49                let mut _crcs = vec![];
50                for line in _item.1 {
51                    let line = line?;
52                    let name = line.name.as_str();
53                    let state = line.state.as_bstr().unwrap_or_default();
54                    if name == KEY_TYPE {
55                        _type = state.to_string();
56                    }
57                    if name == KEY_CRCS {
58                        _crcs = state
59                            .to_string()
60                            .split(',')
61                            .map(|s| {
62                                let trimmed = s.trim();
63                                let hex_str = if let Some(stripped) = trimmed.strip_prefix("0x") {
64                                    stripped
65                                } else {
66                                    trimmed
67                                };
68                                u32::from_str_radix(hex_str, 16).map_err(|e| anyhow::anyhow!(e))
69                            })
70                            .collect::<Result<Vec<u32>, _>>()?;
71                    }
72                    attributes.insert(name.to_string(), state.to_string());
73                }
74                crcs.insert(path.clone(), _crcs);
75                items.insert(path, attributes);
76
77                // process parent directory
78                while let Some(parent) = current_path.parent() {
79                    if parent == Path::new("/") {
80                        break;
81                    }
82                    let mut attributes = HashMap::new();
83                    if !items.contains_key(parent) {
84                        attributes.insert(KEY_TYPE.to_string(), VAL_EXTERNAL.to_string());
85                        items.insert(parent.to_path_buf(), attributes);
86                    }
87                    current_path = parent.to_path_buf();
88                }
89            }
90        }
91
92        Ok(Attributes { items, crcs })
93    }
94
95    fn check_external(&self, attributes: &HashMap<String, String>) -> bool {
96        attributes.get(KEY_TYPE) == Some(&VAL_EXTERNAL.to_string())
97    }
98
99    pub fn is_external<P: AsRef<Path>>(&self, path: P) -> bool {
100        if let Some(attributes) = self.items.get(path.as_ref()) {
101            return self.check_external(attributes);
102        }
103        false
104    }
105
106    pub fn is_prefix_external<P: AsRef<Path>>(&self, target: P) -> bool {
107        self.items
108            .iter()
109            .any(|item| item.0.starts_with(&target) && self.check_external(item.1))
110    }
111
112    pub fn get_value<P: AsRef<Path>, K: AsRef<str>>(&self, path: P, key: K) -> Option<String> {
113        if let Some(attributes) = self.items.get(path.as_ref()) {
114            return attributes.get(key.as_ref()).map(|s| s.to_string());
115        }
116        None
117    }
118
119    pub fn get_values<P: AsRef<Path>>(&self, path: P) -> Option<&HashMap<String, String>> {
120        self.items.get(path.as_ref())
121    }
122
123    pub fn get_crcs<P: AsRef<Path>>(&self, path: P) -> Option<&Vec<u32>> {
124        self.crcs.get(path.as_ref())
125    }
126}
127
128#[cfg(test)]
129mod tests {
130    use std::{collections::HashMap, fs, path::PathBuf};
131
132    use super::{Attributes, Item};
133    use vmm_sys_util::tempfile::TempFile;
134
135    #[test]
136    fn test_attribute_parse() {
137        let file = TempFile::new().unwrap();
138        fs::write(
139            file.as_path(),
140            "/foo type=external crcs=0x1234,0x5678
141            /bar type=external crcs=0x1234,0x5678
142            /models/foo/bar type=external",
143        )
144        .unwrap();
145
146        let attributes = Attributes::from(file.as_path()).unwrap();
147        let _attributes_base: HashMap<String, String> =
148            [("type".to_string(), "external".to_string())]
149                .iter()
150                .cloned()
151                .collect();
152        let _attributes: HashMap<String, String> = [
153            ("type".to_string(), "external".to_string()),
154            ("crcs".to_string(), "0x1234,0x5678".to_string()),
155        ]
156        .iter()
157        .cloned()
158        .collect();
159
160        let items_map: HashMap<PathBuf, HashMap<String, String>> = vec![
161            Item {
162                pattern: PathBuf::from("/foo"),
163                attributes: _attributes.clone(),
164            },
165            Item {
166                pattern: PathBuf::from("/bar"),
167                attributes: _attributes.clone(),
168            },
169            Item {
170                pattern: PathBuf::from("/models"),
171                attributes: _attributes_base.clone(),
172            },
173            Item {
174                pattern: PathBuf::from("/models/foo"),
175                attributes: _attributes_base.clone(),
176            },
177            Item {
178                pattern: PathBuf::from("/models/foo/bar"),
179                attributes: _attributes_base.clone(),
180            },
181        ]
182        .into_iter()
183        .map(|item| (item.pattern, item.attributes))
184        .collect();
185
186        assert_eq!(attributes.items, items_map);
187        assert_eq!(attributes.get_crcs("/foo"), Some(&vec![0x1234, 0x5678]))
188    }
189}