safe_path/
scoped_dir_builder.rs1use std::io::{Error, ErrorKind, Result};
7use std::path::Path;
8
9use crate::{scoped_join, scoped_resolve, PinnedPathBuf};
10
11const DIRECTORY_MODE_DEFAULT: u32 = 0o777;
12const DIRECTORY_MODE_MASK: u32 = 0o777;
13
14#[derive(Debug)]
23pub struct ScopedDirBuilder {
24 root: PinnedPathBuf,
25 mode: u32,
26 recursive: bool,
27}
28
29impl ScopedDirBuilder {
30 pub fn new<P: AsRef<Path>>(root: P) -> Result<Self> {
32 let root = root.as_ref().canonicalize()?;
33 let root = PinnedPathBuf::from_path(root)?;
34 if !root.metadata()?.is_dir() {
35 return Err(Error::new(
36 ErrorKind::Other,
37 format!("Invalid root path: {}", root.display()),
38 ));
39 }
40
41 Ok(ScopedDirBuilder {
42 root,
43 mode: DIRECTORY_MODE_DEFAULT,
44 recursive: false,
45 })
46 }
47
48 pub fn recursive(&mut self, recursive: bool) -> &mut Self {
52 self.recursive = recursive;
53 self
54 }
55
56 pub fn mode(&mut self, mode: u32) -> &mut Self {
58 self.mode = mode & DIRECTORY_MODE_MASK;
59 self
60 }
61
62 pub fn create_with_unscoped_path<P: AsRef<Path>>(&self, path: P) -> Result<PinnedPathBuf> {
68 if !path.as_ref().is_absolute() {
69 return Err(Error::new(
70 ErrorKind::Other,
71 format!(
72 "Expected absolute directory path: {}",
73 path.as_ref().display()
74 ),
75 ));
76 }
77 let scoped_path = scoped_join("/", path)?;
79 let stripped_path = scoped_path.strip_prefix(self.root.target()).map_err(|_| {
80 Error::new(
81 ErrorKind::Other,
82 format!(
83 "Path {} is not under {}",
84 scoped_path.display(),
85 self.root.target().display()
86 ),
87 )
88 })?;
89
90 self.do_mkdir(&stripped_path)
91 }
92
93 pub fn create<P: AsRef<Path>>(&self, path: P) -> Result<PinnedPathBuf> {
97 let path = scoped_resolve(&self.root, path)?;
98 self.do_mkdir(&path)
99 }
100
101 fn do_mkdir(&self, path: &Path) -> Result<PinnedPathBuf> {
102 assert!(path.is_relative());
103 if path.file_name().is_none() {
104 if !self.recursive {
105 return Err(Error::new(
106 ErrorKind::AlreadyExists,
107 "directory already exists",
108 ));
109 } else {
110 return self.root.try_clone();
111 }
112 }
113
114 let levels = path.iter().count() - 1;
116 let mut dir = self.root.try_clone()?;
117 for (idx, comp) in path.iter().enumerate() {
118 match dir.open_child(comp) {
119 Ok(v) => {
120 if !v.metadata()?.is_dir() {
121 return Err(Error::new(
122 ErrorKind::Other,
123 format!("Path {} is not a directory", v.display()),
124 ));
125 } else if !self.recursive && idx == levels {
126 return Err(Error::new(
127 ErrorKind::AlreadyExists,
128 "directory already exists",
129 ));
130 }
131 dir = v;
132 }
133 Err(_e) => {
134 if !self.recursive && idx != levels {
135 return Err(Error::new(
136 ErrorKind::NotFound,
137 format!("parent directory does not exist"),
138 ));
139 }
140 dir = dir.mkdir(comp, self.mode)?;
141 }
142 }
143 }
144
145 Ok(dir)
146 }
147}
148
149#[cfg(test)]
150mod tests {
151 use super::*;
152 use std::fs;
153 use std::fs::DirBuilder;
154 use std::os::unix::fs::{symlink, MetadataExt};
155 use tempfile::tempdir;
156
157 #[test]
158 fn test_scoped_dir_builder() {
159 let rootfs_dir = tempdir().expect("failed to create tmpdir");
161 DirBuilder::new()
162 .create(rootfs_dir.path().join("b"))
163 .unwrap();
164 symlink(rootfs_dir.path().join("b"), rootfs_dir.path().join("a")).unwrap();
165 let rootfs_path = &rootfs_dir.path().join("a");
166
167 ScopedDirBuilder::new(rootfs_path.join("__does_not_exist__")).unwrap_err();
169 ScopedDirBuilder::new("__does_not_exist__").unwrap_err();
170
171 fs::write(rootfs_path.join("txt"), "test").unwrap();
173 ScopedDirBuilder::new(rootfs_path.join("txt")).unwrap_err();
174
175 let mut builder = ScopedDirBuilder::new(&rootfs_path).unwrap();
176
177 builder
179 .create_with_unscoped_path(rootfs_path.join("txt"))
180 .unwrap_err();
181 builder.create("/txt/a").unwrap_err();
183 builder.create_with_unscoped_path("/txt/a").unwrap_err();
185 builder
187 .create_with_unscoped_path(rootfs_path.join("."))
188 .unwrap_err();
189 builder
191 .create_with_unscoped_path(rootfs_path.join("a/b"))
192 .unwrap_err();
193 builder.create("a/b/c").unwrap_err();
194
195 let path = builder.create("a").unwrap();
196 assert!(rootfs_path.join("a").is_dir());
197 assert_eq!(path.target(), rootfs_path.join("a").canonicalize().unwrap());
198
199 builder
201 .create_with_unscoped_path(rootfs_path.join("a"))
202 .unwrap_err();
203
204 builder.recursive(true);
206 let path = builder
207 .create_with_unscoped_path(rootfs_path.join("a"))
208 .unwrap();
209 assert_eq!(path.target(), rootfs_path.join("a").canonicalize().unwrap());
210 let path = builder.create(".").unwrap();
211 assert_eq!(path.target(), rootfs_path.canonicalize().unwrap());
212
213 let umask = unsafe { libc::umask(0022) };
214 unsafe { libc::umask(umask) };
215
216 builder.mode(0o740);
217 let path = builder.create("a/b/c/d").unwrap();
218 assert_eq!(
219 path.target(),
220 rootfs_path.join("a/b/c/d").canonicalize().unwrap()
221 );
222 assert!(rootfs_path.join("a/b/c/d").is_dir());
223 assert_eq!(
224 rootfs_path.join("a").metadata().unwrap().mode() & 0o777,
225 DIRECTORY_MODE_DEFAULT & !umask,
226 );
227 assert_eq!(
228 rootfs_path.join("a/b").metadata().unwrap().mode() & 0o777,
229 0o740 & !umask
230 );
231 assert_eq!(
232 rootfs_path.join("a/b/c").metadata().unwrap().mode() & 0o777,
233 0o740 & !umask
234 );
235 assert_eq!(
236 rootfs_path.join("a/b/c/d").metadata().unwrap().mode() & 0o777,
237 0o740 & !umask
238 );
239
240 builder.create("txt/e/f").unwrap_err();
242 fs::write(rootfs_path.join("a/b/txt"), "test").unwrap();
243 builder.create("a/b/txt/h/i").unwrap_err();
244 }
245
246 #[test]
247 fn test_create_root() {
248 let mut builder = ScopedDirBuilder::new("/").unwrap();
249 builder.recursive(true);
250 builder.create("/").unwrap();
251 builder.create(".").unwrap();
252 builder.create("..").unwrap();
253 builder.create("../../.").unwrap();
254 builder.create("").unwrap();
255 builder.create_with_unscoped_path("/").unwrap();
256 builder.create_with_unscoped_path("/..").unwrap();
257 builder.create_with_unscoped_path("/../.").unwrap();
258 }
259
260 #[test]
261 fn test_create_with_absolute_path() {
262 let rootfs_dir = tempdir().expect("failed to create tmpdir");
264 DirBuilder::new()
265 .create(rootfs_dir.path().join("b"))
266 .unwrap();
267 symlink(rootfs_dir.path().join("b"), rootfs_dir.path().join("a")).unwrap();
268 let rootfs_path = &rootfs_dir.path().join("a");
269
270 let mut builder = ScopedDirBuilder::new(&rootfs_path).unwrap();
271 builder.create_with_unscoped_path("/").unwrap_err();
272 builder
273 .create_with_unscoped_path(rootfs_path.join("../__xxxx___xxx__"))
274 .unwrap_err();
275 builder
276 .create_with_unscoped_path(rootfs_path.join("c/d"))
277 .unwrap_err();
278
279 builder.create_with_unscoped_path(&rootfs_path).unwrap_err();
281 builder
282 .create_with_unscoped_path(rootfs_path.join("."))
283 .unwrap_err();
284
285 builder.recursive(true);
286 builder.create_with_unscoped_path(&rootfs_path).unwrap();
287 builder
288 .create_with_unscoped_path(rootfs_path.join("."))
289 .unwrap();
290 builder
291 .create_with_unscoped_path(rootfs_path.join("c/d"))
292 .unwrap();
293 }
294}