1use std::{
2 borrow::Cow,
3 collections::{HashMap, HashSet},
4};
5
6use anyhow::{anyhow, bail, Context, Result};
7use camino::{Utf8Path, Utf8PathBuf};
8use nix::unistd;
9use users::{Groups, Users, UsersCache};
10
11use super::{
12 attributes::Mode, Attrs, Filesystem, SetAttrs, DEFAULT_DIRECTORY_MODE, DEFAULT_FILE_MODE,
13};
14
15pub struct MemoryFilesystem {
17 map: HashMap<Utf8PathBuf, Node>,
18 users: UsersCache,
19
20 uid: u32,
21 gid: u32,
22}
23
24#[derive(Debug)]
25enum Node {
26 File {
27 attrs: FSAttrs,
28 content: String,
29 },
30 Directory {
31 attrs: FSAttrs,
32 children: Vec<String>,
33 },
34 Symlink {
35 target: Utf8PathBuf,
36 },
37}
38
39#[derive(Debug)]
40struct FSAttrs {
41 uid: u32,
42 gid: u32,
43 mode: u16,
44}
45
46impl MemoryFilesystem {
47 const ROOT: u32 = 0;
48 const DEFAULT_OWNER: u32 = Self::ROOT;
49 const DEFAULT_GROUP: u32 = Self::ROOT;
50
51 pub fn new() -> Self {
53 let mut map = HashMap::new();
54 map.insert(
55 "/".into(),
56 Node::Directory {
57 attrs: FSAttrs {
58 uid: Self::DEFAULT_OWNER,
59 gid: Self::DEFAULT_GROUP,
60 mode: DEFAULT_DIRECTORY_MODE.into(),
61 },
62 children: vec![],
63 },
64 );
65 MemoryFilesystem {
66 map,
67 users: UsersCache::new(),
68 uid: unistd::getuid().as_raw(),
69 gid: unistd::getgid().as_raw(),
70 }
71 }
72
73 pub fn to_path_set(&self) -> HashSet<&Utf8Path> {
75 self.map.keys().map(|i| i.as_ref()).collect()
76 }
77}
78
79impl Default for MemoryFilesystem {
80 fn default() -> Self {
81 Self::new()
82 }
83}
84
85impl Filesystem for MemoryFilesystem {
86 fn create_directory(&mut self, path: impl AsRef<Utf8Path>, attrs: SetAttrs) -> Result<()> {
87 let path = path.as_ref();
88 let (parent, name) = self
89 .canonical_split(path)
90 .with_context(|| format!("Splitting {path}"))?;
91 let attrs = self.internal_attrs(attrs, DEFAULT_DIRECTORY_MODE)?;
92 let children = vec![];
93 self.insert_node(&parent, name, Node::Directory { attrs, children })
94 .with_context(|| format!("Creating directory: {path}"))
95 }
96
97 fn create_file(
98 &mut self,
99 path: impl AsRef<Utf8Path>,
100 attrs: SetAttrs,
101 content: String,
102 ) -> Result<()> {
103 let path = path.as_ref();
104 let (parent, name) = self.canonical_split(path)?;
105 let attrs = self.internal_attrs(attrs, DEFAULT_FILE_MODE)?;
106 self.insert_node(&parent, name, Node::File { attrs, content })
107 .with_context(|| format!("Creating file: {path}"))
108 }
109
110 fn create_symlink(
111 &mut self,
112 path: impl AsRef<Utf8Path>,
113 target: impl AsRef<Utf8Path>,
114 ) -> Result<()> {
115 let path = path.as_ref();
116 let (parent, name) = self.canonical_split(path)?;
117 self.insert_node(
118 &parent,
119 name,
120 Node::Symlink {
121 target: target.as_ref().to_owned(),
122 },
123 )
124 .with_context(|| format!("Creating symlink: {path}"))
125 }
126
127 fn exists(&self, path: impl AsRef<Utf8Path>) -> bool {
128 match self.canonicalize(path) {
129 Ok(path) => self.map.contains_key(&path),
130 _ => false,
131 }
132 }
133
134 fn is_directory(&self, path: impl AsRef<Utf8Path>) -> bool {
135 match self.canonicalize(path) {
136 Err(_) => false,
137 Ok(path) => matches!(self.map.get(&path), Some(Node::Directory { .. })),
138 }
139 }
140
141 fn is_file(&self, path: impl AsRef<Utf8Path>) -> bool {
142 match self.canonicalize(path) {
143 Err(_) => false,
144 Ok(path) => matches!(self.map.get(&path), Some(Node::File { .. })),
145 }
146 }
147
148 fn is_link(&self, path: impl AsRef<Utf8Path>) -> bool {
149 matches!(self.map.get(path.as_ref()), Some(Node::Symlink { .. }))
150 }
151
152 fn list_directory(&self, path: impl AsRef<Utf8Path>) -> Result<Vec<String>> {
153 let path = self.canonicalize(path)?;
154 Ok(match self.node_from_path(&path)? {
155 Node::Directory { children, .. } => children.clone(),
156 Node::File { .. } => bail!("Tried to list directory of a file: {}", path),
157 Node::Symlink { .. } => unreachable!("Non-canonical path: {}", path),
158 })
159 }
160
161 fn read_file(&self, path: impl AsRef<Utf8Path>) -> Result<String> {
162 let path = self.canonicalize(path)?;
163 Ok(match self.node_from_path(&path)? {
164 Node::File { content, .. } => content.clone(),
165 Node::Directory { .. } => bail!("Tried to read directory as a file: {}", path),
166 Node::Symlink { .. } => unreachable!("Non-canonical path: {}", path),
167 })
168 }
169
170 fn read_link(&self, path: impl AsRef<Utf8Path>) -> Result<Utf8PathBuf> {
171 Ok(match self.node_from_path(&path)? {
172 Node::Symlink { target } => target.clone(),
173 _ => bail!("Not a symlink: {}", path.as_ref()),
174 })
175 }
176
177 fn attributes(&self, path: impl AsRef<Utf8Path>) -> Result<Attrs> {
178 let path = self.canonicalize(path)?;
179 let node = self.node_from_path(&path)?;
180 let attrs = match node {
181 Node::Directory { attrs, .. } | Node::File { attrs, .. } => attrs,
182 Node::Symlink { .. } => panic!("Non-canonical path: {path}"),
183 };
184 let owner = Cow::Owned(
185 self.users
186 .get_user_by_uid(attrs.uid)
187 .ok_or_else(|| anyhow!("Failed to get user from UID: {}", attrs.uid))?
188 .name()
189 .to_string_lossy()
190 .into_owned(),
191 );
192 let group = Cow::Owned(
193 self.users
194 .get_group_by_gid(attrs.gid)
195 .ok_or_else(|| anyhow!("Failed to get group from GID: {}", attrs.gid))?
196 .name()
197 .to_string_lossy()
198 .into_owned(),
199 );
200 let mode = attrs.mode.into();
201 Ok(Attrs { owner, group, mode })
202 }
203
204 fn set_attributes(&mut self, path: impl AsRef<Utf8Path>, set_attrs: SetAttrs) -> Result<()> {
205 let use_default = set_attrs.mode.is_none();
206 let mut fs_attrs = self.internal_attrs(set_attrs, 0.into())?;
207 let path = self.canonicalize(path)?;
208 let node = self
209 .map
210 .get_mut(&path)
211 .ok_or_else(|| anyhow!("No such file or directory: {}", path))?;
212 match node {
213 Node::Directory { attrs, .. } => {
214 if use_default {
215 fs_attrs.mode = DEFAULT_DIRECTORY_MODE.into();
216 }
217 *attrs = fs_attrs;
218 Ok(())
219 }
220 Node::File { attrs, .. } => {
221 if use_default {
222 fs_attrs.mode = DEFAULT_FILE_MODE.into();
223 }
224 *attrs = fs_attrs;
225 Ok(())
226 }
227 Node::Symlink { .. } => Err(anyhow!("Non-canonical path: {}", path)),
228 }
229 }
230}
231
232impl MemoryFilesystem {
233 fn canonical_split<'s>(&self, path: &'s Utf8Path) -> Result<(Utf8PathBuf, &'s str)> {
234 match super::split(path) {
235 None => Err(anyhow!("Cannot create {}", path)),
236 Some((parent, name)) => Ok((self.canonicalize(parent)?, name)),
237 }
238 }
239
240 fn internal_attrs(&self, attrs: SetAttrs, default_mode: Mode) -> Result<FSAttrs> {
241 let uid = match attrs.owner {
242 Some(owner) => self
243 .users
244 .get_user_by_name(owner)
245 .ok_or_else(|| anyhow!("No such user: {}", owner))?
246 .uid(),
247 None => self.uid,
248 };
249 let gid = match attrs.group {
250 Some(group) => self
251 .users
252 .get_group_by_name(group)
253 .ok_or_else(|| anyhow!("No such group: {}", group))?
254 .gid(),
255 None => self.gid,
256 };
257 let mode = attrs.mode.unwrap_or(default_mode).into();
258 Ok(FSAttrs { uid, gid, mode })
259 }
260
261 fn insert_node(&mut self, parent: impl AsRef<Utf8Path>, name: &str, node: Node) -> Result<()> {
270 let parent = parent.as_ref();
272 let path = parent.join(name);
273 if self.map.contains_key(&path) {
274 bail!("File exists: {:?}", path);
275 }
276 let parent_node = self
277 .map
278 .get_mut(parent)
279 .ok_or_else(|| anyhow!("Parent directory not found: {}", parent))?;
280 match parent_node {
282 Node::Directory {
283 ref mut children, ..
284 } => children.push(name.into()),
285 _ => panic!("Parent not a directory: {parent}"),
286 }
287 self.map.insert(path, node);
289 Ok(())
290 }
291
292 fn node_from_path(&self, path: impl AsRef<Utf8Path>) -> Result<&Node> {
293 let path = path.as_ref();
294 self.map
295 .get(path)
296 .ok_or_else(|| anyhow!("No such file or directory: {}", path))
297 }
298}
299
300#[cfg(test)]
301mod tests {
302 use crate::{Filesystem, SetAttrs};
303
304 use super::MemoryFilesystem;
305
306 #[test]
307 fn exists() {
308 let mut fs = MemoryFilesystem::new();
309 assert!(fs.exists("/"));
310 assert!(!fs.exists("/entry"));
311 fs.create_directory("/entry", SetAttrs::default()).unwrap();
312 assert!(fs.exists("/entry"));
313 }
314
315 #[test]
316 fn symlink_make_sub_directory() {
317 let mut fs = MemoryFilesystem::new();
318 fs.create_directory("/primary", SetAttrs::default())
319 .unwrap();
320 fs.create_directory("/secondary", SetAttrs::default())
321 .unwrap();
322 fs.create_symlink("/primary/link", "/secondary/target")
323 .unwrap();
324 fs.create_directory("/secondary/target", SetAttrs::default())
325 .unwrap();
326 fs.create_directory("/primary/link/through", SetAttrs::default())
327 .unwrap();
328 assert!(fs.exists("/primary/link/through"));
329 }
330}