1 // Copyright 2018 Google Inc.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 // use this file except in compliance with the License.  You may obtain a copy
5 // of the License at:
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
12 // License for the specific language governing permissions and limitations
13 // under the License.
14 
15 //! Implementation of the sandboxfs FUSE file system.
16 
17 // Keep these in sync with the list of checks in main.rs.
18 #![warn(bad_style, missing_docs)]
19 #![warn(unused, unused_extern_crates, unused_import_braces, unused_qualifications)]
20 #![warn(unsafe_code)]
21 
22 // For portability reasons, we need to be able to cast integer values to system-level opaque
23 // types such as "mode_t".  Because we don't know the size of those integers on the platform we
24 // are building for, sometimes the casts do widen the values but other times they are no-ops.
25 #![allow(clippy::identity_conversion)]
26 
27 // We construct complex structures in multiple places, and allowing for redundant field names
28 // increases readability.
29 #![allow(clippy::redundant_field_names)]
30 
31 #[cfg(feature = "profiling")] extern crate cpuprofiler;
32 #[macro_use] extern crate failure;
33 extern crate fuse;
34 #[macro_use] extern crate log;
35 extern crate nix;
36 extern crate serde_derive;
37 extern crate signal_hook;
38 #[cfg(test)] extern crate tempfile;
39 #[cfg(test)] extern crate users;
40 extern crate threadpool;
41 extern crate time;
42 extern crate xattr;
43 
44 use failure::{Fallible, ResultExt};
45 use nix::errno::Errno;
46 use nix::{sys, unistd};
47 use std::collections::HashMap;
48 use std::ffi::OsStr;
49 use std::fmt;
50 use std::fs;
51 use std::os::unix::ffi::OsStrExt;
52 use std::path::{Component, Path, PathBuf};
53 use std::result::Result;
54 use std::sync::{Arc, Mutex};
55 use std::sync::atomic::{AtomicUsize, Ordering};
56 use std::thread;
57 use time::Timespec;
58 
59 mod concurrent;
60 mod errors;
61 mod nodes;
62 mod profiling;
63 mod reconfig;
64 #[cfg(test)] mod testutils;
65 
66 pub use errors::{flatten_causes, KernelError, MappingError};
67 pub use nodes::{ArcCache, NoCache, PathCache};
68 pub use profiling::ScopedProfiler;
69 pub use reconfig::{open_input, open_output};
70 
71 /// Mapping describes how an individual path within the sandbox is connected to an external path
72 /// in the underlying file system.
73 #[derive(Debug, Eq, PartialEq)]
74 pub struct Mapping {
75     path: PathBuf,
76     underlying_path: PathBuf,
77     writable: bool,
78 }
79 impl Mapping {
80     /// Creates a new mapping from the individual components.
81     ///
82     /// `path` is the inside the sandbox's mount point where the `underlying_path` is exposed.
83     /// Both must be absolute paths.  `path` must also not contain dot-dot components, though it
84     /// may contain dot components and repeated path separators.
from_parts(path: PathBuf, underlying_path: PathBuf, writable: bool) -> Result<Self, MappingError>85     pub fn from_parts(path: PathBuf, underlying_path: PathBuf, writable: bool)
86         -> Result<Self, MappingError> {
87         if !path.is_absolute() {
88             return Err(MappingError::PathNotAbsolute { path });
89         }
90         let is_normalized = {
91             let mut components = path.components();
92             assert_eq!(components.next(), Some(Component::RootDir), "Path expected to be absolute");
93             let is_not_normal: fn(&Component) -> bool = |c| match c {
94                 Component::CurDir => panic!("Dot components ought to have been skipped"),
95                 Component::Normal(_) => false,
96                 Component::ParentDir | Component::Prefix(_) => true,
97                 Component::RootDir => panic!("Root directory should have already been handled"),
98             };
99             components.find(is_not_normal).is_none()
100         };
101         if !is_normalized {
102             return Err(MappingError::PathNotNormalized{ path });
103         }
104 
105         if !underlying_path.is_absolute() {
106             return Err(MappingError::PathNotAbsolute { path: underlying_path });
107         }
108 
109         Ok(Mapping { path, underlying_path, writable })
110     }
111 
112     /// Returns true if this is a mapping for the root directory.
is_root(&self) -> bool113     fn is_root(&self) -> bool {
114         self.path.parent().is_none()
115     }
116 }
117 
118 impl fmt::Display for Mapping {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result119     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
120         let writability = if self.writable { "read/write" } else { "read-only" };
121         write!(f, "{} -> {} ({})", self.path.display(), self.underlying_path.display(), writability)
122     }
123 }
124 
125 /// Monotonically-increasing generator of identifiers.
126 pub struct IdGenerator {
127     last_id: AtomicUsize,
128 }
129 
130 impl IdGenerator {
131     /// Generation number to return to the kernel for any inode number.
132     ///
133     /// We don't reuse inode numbers throughout the lifetime of a sandboxfs instance and we do not
134     /// support FUSE daemon restarts without going through an unmount/mount sequence, so it is OK
135     /// to have a constant number here.
136     const GENERATION: u64 = 0;
137 
138     /// Constructs a new generator that starts at the given value.
new(start_value: u64) -> Self139     fn new(start_value: u64) -> Self {
140         IdGenerator { last_id: AtomicUsize::new(start_value as usize) }
141     }
142 
143     /// Obtains a new identifier.
next(&self) -> u64144     pub fn next(&self) -> u64 {
145         let id = self.last_id.fetch_add(1, Ordering::AcqRel);
146         // TODO(https://github.com/rust-lang/rust/issues/51577): Drop :: prefix.
147         if id >= ::std::u64::MAX as usize {
148             panic!("Ran out of identifiers");
149         }
150         id as u64
151     }
152 }
153 
154 /// FUSE file system implementation of sandboxfs.
155 struct SandboxFS {
156     /// Monotonically-increasing generator of identifiers for this file system instance.
157     ids: Arc<IdGenerator>,
158 
159     /// Mapping of inode numbers to in-memory nodes that tracks all files known by sandboxfs.
160     nodes: Arc<Mutex<HashMap<u64, nodes::ArcNode>>>,
161 
162     /// Mapping of handle numbers to file open handles.
163     handles: Arc<Mutex<HashMap<u64, nodes::ArcHandle>>>,
164 
165     /// Cache of sandboxfs nodes indexed by their underlying path.
166     cache: ArcCache,
167 
168     /// How long to tell the kernel to cache file metadata for.
169     ttl: Timespec,
170 
171     /// Whether support for xattrs is enabled or not.
172     xattrs: bool,
173 }
174 
175 /// A view of a `SandboxFS` instance to allow for concurrent reconfigurations.
176 ///
177 /// This structure exists because `fuse::mount` takes ownership of the file system so there is no
178 /// way for us to share that instance across threads.  Instead, we construct a reduced view of the
179 /// fields we need for reconfiguration and pass those across threads.
180 #[derive(Clone)]
181 struct ReconfigurableSandboxFS {
182     /// The root node of the file system, on which to apply all reconfiguration operations.
183     root: nodes::ArcNode,
184 
185     /// Monotonically-increasing generator of identifiers for this file system instance.
186     ids: Arc<IdGenerator>,
187 
188     /// Mapping of inode numbers to in-memory nodes that tracks all files known by sandboxfs.
189     nodes: Arc<Mutex<HashMap<u64, nodes::ArcNode>>>,
190 
191     /// Cache of sandboxfs nodes indexed by their underlying path.
192     cache: ArcCache,
193 }
194 
195 /// Splits an absolute path into components, stripping the first root component.
196 ///
197 /// If the input path was the root directory, the result is an empty set of components.  Otherwise,
198 /// the first returned component is always a `Component::Normal` component.
199 // TODO(jmmv): The callers of this function should be able to operate on the iterator alone,
200 // without having to materialize the components into a vector.  Doing so requires changing all
201 // directory traversal functions to also operate on a `Components` iterator (possibly a `Peekable`
202 // one) so it's not clear to me that that's going to be faster; need to measure.
split_abs_path(path: &Path) -> Vec<Component>203 fn split_abs_path(path: &Path) -> Vec<Component> {
204     let mut components = path.components();
205     let root = components.next().expect("Should have been called on an absolute path only");
206     debug_assert_eq!(Component::RootDir, root,
207         "Paths in mappings are always absolute but got {:?}", path);
208     components.collect::<Vec<_>>()
209 }
210 
211 /// Applies a mapping to the given root node.
212 ///
213 /// This code is shared by the application of `--mapping` flags and by the application of new
214 /// mappings as part of a reconfiguration operation.  We want both processes to behave identically.
apply_mapping(mapping: &Mapping, root: &dyn nodes::Node, ids: &IdGenerator, cache: &dyn nodes::Cache) -> Fallible<nodes::ArcNode>215 fn apply_mapping(mapping: &Mapping, root: &dyn nodes::Node, ids: &IdGenerator,
216     cache: &dyn nodes::Cache) -> Fallible<nodes::ArcNode> {
217     let components = split_abs_path(&mapping.path);
218 
219     // The input `root` node is an existing node that corresponds to the root.  If we don't find
220     // any path components in the given mapping, it means we are trying to remap that same node.
221     ensure!(!components.is_empty(), "Root can be mapped at most once");
222 
223     root.map(&components, &mapping.underlying_path, mapping.writable, &ids, cache)
224 }
225 
226 /// Creates the initial node hierarchy based on a collection of `mappings`.
create_root(mappings: &[Mapping], ids: &IdGenerator, cache: &dyn nodes::Cache) -> Fallible<nodes::ArcNode>227 fn create_root(mappings: &[Mapping], ids: &IdGenerator, cache: &dyn nodes::Cache)
228     -> Fallible<nodes::ArcNode> {
229     let now = time::get_time();
230 
231     let (root, rest) = if mappings.is_empty() {
232         (nodes::Dir::new_empty(ids.next(), None, now), mappings)
233     } else {
234         let first = &mappings[0];
235         if first.is_root() {
236             let fs_attr = fs::symlink_metadata(&first.underlying_path)
237                 .context(format!("Failed to map root: stat failed for {:?}",
238                                  &first.underlying_path))?;
239             ensure!(fs_attr.is_dir(), "Failed to map root: {:?} is not a directory",
240                     &first.underlying_path);
241             (nodes::Dir::new_mapped(ids.next(), &first.underlying_path, &fs_attr, first.writable),
242                 &mappings[1..])
243         } else {
244             (nodes::Dir::new_empty(ids.next(), None, now), mappings)
245         }
246     };
247 
248     for mapping in rest {
249         apply_mapping(mapping, root.as_ref(), ids, cache)
250             .context(format!("Cannot map '{}'", mapping))?;
251     }
252 
253     Ok(root)
254 }
255 
256 impl SandboxFS {
257     /// Creates a new `SandboxFS` instance.
create(mappings: &[Mapping], ttl: Timespec, cache: ArcCache, xattrs: bool) -> Fallible<SandboxFS>258     fn create(mappings: &[Mapping], ttl: Timespec, cache: ArcCache, xattrs: bool)
259         -> Fallible<SandboxFS> {
260         let ids = IdGenerator::new(fuse::FUSE_ROOT_ID);
261 
262         let mut nodes = HashMap::new();
263         let root = create_root(mappings, &ids, cache.as_ref())?;
264         assert_eq!(fuse::FUSE_ROOT_ID, root.inode());
265         nodes.insert(root.inode(), root);
266 
267         Ok(SandboxFS {
268             ids: Arc::from(ids),
269             nodes: Arc::from(Mutex::from(nodes)),
270             handles: Arc::from(Mutex::from(HashMap::new())),
271             cache: cache,
272             ttl: ttl,
273             xattrs: xattrs,
274         })
275     }
276 
277     /// Creates a reconfigurable view of this file system, to safely pass across threads.
reconfigurable(&mut self) -> ReconfigurableSandboxFS278     fn reconfigurable(&mut self) -> ReconfigurableSandboxFS {
279         ReconfigurableSandboxFS {
280             root: self.find_node(fuse::FUSE_ROOT_ID).expect("Root node must always exist"),
281             ids: self.ids.clone(),
282             nodes: self.nodes.clone(),
283             cache: self.cache.clone(),
284         }
285     }
286 
287     /// Gets a node given its `inode`.
find_node(&mut self, inode: u64) -> nodes::NodeResult<nodes::ArcNode>288     fn find_node(&mut self, inode: u64) -> nodes::NodeResult<nodes::ArcNode> {
289         let nodes = self.nodes.lock().unwrap();
290         match nodes.get(&inode) {
291             Some(node) => Ok(node.clone()),
292             None => Err(KernelError::from_errno(Errno::ENOENT)),
293         }
294     }
295 
296     /// Gets a node given its `inode` and ensures it is writable.
find_writable_node(&mut self, inode: u64) -> nodes::NodeResult<nodes::ArcNode>297     fn find_writable_node(&mut self, inode: u64) -> nodes::NodeResult<nodes::ArcNode> {
298         let node = self.find_node(inode)?;
299         if !node.writable() {
300             Err(KernelError::from_errno(Errno::EPERM))
301         } else {
302             Ok(node.clone())
303         }
304     }
305 
306     /// Gets a handle given its identifier.
307     ///
308     /// We assume that the identifier is valid and that we have a known handle for it; otherwise,
309     /// we crash.  The rationale for this is that this function is always called on handles
310     /// requested by the kernel, and we can trust that the kernel will only ever ask us for handles
311     /// numbers we have previously told it about.
find_handle(&mut self, fh: u64) -> nodes::ArcHandle312     fn find_handle(&mut self, fh: u64) -> nodes::ArcHandle {
313         let handles = self.handles.lock().unwrap();
314         handles.get(&fh).expect("Kernel requested unknown handle").clone()
315     }
316 
317     /// Tracks a new file handle and assigns an identifier to it.
insert_handle(&mut self, handle: nodes::ArcHandle) -> u64318     fn insert_handle(&mut self, handle: nodes::ArcHandle) -> u64 {
319         let fh = self.ids.next();
320         let mut handles = self.handles.lock().unwrap();
321         debug_assert!(!handles.contains_key(&fh));
322         handles.insert(fh, handle);
323         fh
324     }
325 
326     /// Tracks a node, which may already be known.
insert_node(&mut self, node: nodes::ArcNode)327     fn insert_node(&mut self, node: nodes::ArcNode) {
328         let mut nodes = self.nodes.lock().unwrap();
329         nodes.entry(node.inode()).or_insert(node);
330     }
331 
332     /// Same as `create` but leaves the handling of the `fuse::Reply` to the caller.
create2(&mut self, req: &fuse::Request, parent: u64, name: &OsStr, mode: u32, flags: u32) -> nodes::NodeResult<(fuse::FileAttr, u64)>333     fn create2(&mut self, req: &fuse::Request, parent: u64, name: &OsStr, mode: u32, flags: u32)
334         -> nodes::NodeResult<(fuse::FileAttr, u64)> {
335         let dir_node = self.find_writable_node(parent)?;
336         let (node, handle, attr) = dir_node.create(
337             name, nix_uid(req), nix_gid(req), mode, flags, &self.ids, self.cache.as_ref())?;
338         self.insert_node(node);
339         let fh = self.insert_handle(handle);
340         Ok((attr, fh))
341     }
342 
343     /// Same as `getattr` but leaves the handling of the `fuse::Reply` to the caller.
getattr2(&mut self, inode: u64) -> nodes::NodeResult<fuse::FileAttr>344     fn getattr2(&mut self, inode: u64) -> nodes::NodeResult<fuse::FileAttr> {
345         let node = self.find_node(inode)?;
346         node.getattr()
347     }
348 
349     /// Same as `lookup` but leaves the handling of the `fuse::Reply` to the caller.
lookup2(&mut self, parent: u64, name: &OsStr) -> nodes::NodeResult<fuse::FileAttr>350     fn lookup2(&mut self, parent: u64, name: &OsStr) -> nodes::NodeResult<fuse::FileAttr> {
351         let dir_node = self.find_node(parent)?;
352         let (node, attr) = dir_node.lookup(name, &self.ids, self.cache.as_ref())?;
353         let mut nodes = self.nodes.lock().unwrap();
354         if !nodes.contains_key(&node.inode()) {
355             nodes.insert(node.inode(), node);
356         }
357         Ok(attr)
358     }
359 
360     /// Same as `mkdir` but leaves the handling of the `fuse::Reply` to the caller.
mkdir2(&mut self, req: &fuse::Request, parent: u64, name: &OsStr, mode: u32) -> nodes::NodeResult<fuse::FileAttr>361     fn mkdir2(&mut self, req: &fuse::Request, parent: u64, name: &OsStr, mode: u32)
362         -> nodes::NodeResult<fuse::FileAttr> {
363         let dir_node = self.find_writable_node(parent)?;
364         let (node, attr) = dir_node.mkdir(
365             name, nix_uid(req), nix_gid(req), mode, &self.ids, self.cache.as_ref())?;
366         self.insert_node(node);
367         Ok(attr)
368     }
369 
370     /// Same as `mknod` but leaves the handling of the `fuse::Reply` to the caller.
mknod2(&mut self, req: &fuse::Request, parent: u64, name: &OsStr, mode: u32, rdev: u32) -> nodes::NodeResult<fuse::FileAttr>371     fn mknod2(&mut self, req: &fuse::Request, parent: u64, name: &OsStr, mode: u32, rdev: u32)
372         -> nodes::NodeResult<fuse::FileAttr> {
373         let dir_node = self.find_writable_node(parent)?;
374 
375         let (node, attr) = dir_node.mknod(
376             name, nix_uid(req), nix_gid(req), mode, rdev, &self.ids, self.cache.as_ref())?;
377         self.insert_node(node);
378         Ok(attr)
379     }
380 
381     /// Same as `open` and `opendir` but leaves the handling of the `fuse::Reply` to the caller.
open2(&mut self, inode: u64, flags: u32) -> nodes::NodeResult<u64>382     fn open2(&mut self, inode: u64, flags: u32) -> nodes::NodeResult<u64> {
383         let node = self.find_node(inode)?;
384         let handle = node.open(flags)?;
385         Ok(self.insert_handle(handle))
386     }
387 
388     /// Same as `readlink` but leaves the handling of the `fuse::Reply` to the caller.
readlink2(&mut self, inode: u64) -> nodes::NodeResult<PathBuf>389     fn readlink2(&mut self, inode: u64) -> nodes::NodeResult<PathBuf> {
390         let node = self.find_node(inode)?;
391         node.readlink()
392     }
393 
394     /// Same as `release` and `releasedir` but leaves the handling of the `fuse::Reply` to the
395     /// caller.
release2(&mut self, fh: u64)396     fn release2(&mut self, fh: u64) {
397         let mut handles = self.handles.lock().unwrap();
398         handles.remove(&fh).expect("Kernel tried to release an unknown handle");
399     }
400 
401     /// Same as `rename` but leaves the handling of the `fuse::Reply` to the caller.
rename2(&mut self, parent: u64, name: &OsStr, new_parent: u64, new_name: &OsStr) -> nodes::NodeResult<()>402     fn rename2(&mut self, parent: u64, name: &OsStr, new_parent: u64, new_name: &OsStr)
403         -> nodes::NodeResult<()> {
404         let dir_node = self.find_writable_node(parent)?;
405         if parent == new_parent {
406             dir_node.rename(name, new_name, self.cache.as_ref())
407         } else {
408             let new_dir_node = self.find_writable_node(new_parent)?;
409             dir_node.rename_and_move_source(name, new_dir_node, new_name, self.cache.as_ref())
410         }
411     }
412 
413     /// Same as `rmdir` but leaves the handling of the `fuse::Reply` to the caller.
rmdir2(&mut self, parent: u64, name: &OsStr) -> nodes::NodeResult<()>414     fn rmdir2(&mut self, parent: u64, name: &OsStr) -> nodes::NodeResult<()> {
415         let dir_node = self.find_writable_node(parent)?;
416         dir_node.rmdir(name, self.cache.as_ref())
417     }
418 
419     /// Same as `setattr` but leaves the handling of the `fuse::Reply` to the caller.
420     #[allow(clippy::too_many_arguments)]
setattr2(&mut self, inode: u64, mode: Option<u32>, uid: Option<u32>, gid: Option<u32>, size: Option<u64>, atime: Option<Timespec>, mtime: Option<Timespec>) -> nodes::NodeResult<fuse::FileAttr>421     fn setattr2(&mut self, inode: u64, mode: Option<u32>, uid: Option<u32>,
422         gid: Option<u32>, size: Option<u64>, atime: Option<Timespec>, mtime: Option<Timespec>)
423         -> nodes::NodeResult<fuse::FileAttr> {
424         let node = self.find_writable_node(inode)?;
425         let values = nodes::AttrDelta {
426             mode: mode.map(|m| sys::stat::Mode::from_bits_truncate(m as sys::stat::mode_t)),
427             uid: uid.map(unistd::Uid::from_raw),
428             gid: gid.map(unistd::Gid::from_raw),
429             atime: atime.map(nodes::conv::timespec_to_timeval),
430             mtime: mtime.map(nodes::conv::timespec_to_timeval),
431             size: size,
432         };
433         node.setattr(&values)
434     }
435 
436     /// Same as `symlink` but leaves the handling of the `fuse::Reply` to the caller.
symlink2(&mut self, req: &fuse::Request, parent: u64, name: &OsStr, link: &Path) -> nodes::NodeResult<fuse::FileAttr>437     fn symlink2(&mut self, req: &fuse::Request, parent: u64, name: &OsStr, link: &Path)
438         -> nodes::NodeResult<fuse::FileAttr> {
439         let dir_node = self.find_writable_node(parent)?;
440         let (node, attr) = dir_node.symlink(
441             name, link, nix_uid(req), nix_gid(req), &self.ids, self.cache.as_ref())?;
442         self.insert_node(node);
443         Ok(attr)
444     }
445 
446     /// Same as `unlink` but leaves the handling of the `fuse::Reply` to the caller.
unlink2(&mut self, parent: u64, name: &OsStr) -> nodes::NodeResult<()>447     fn unlink2(&mut self, parent: u64, name: &OsStr) -> nodes::NodeResult<()> {
448         let dir_node = self.find_writable_node(parent)?;
449         dir_node.unlink(name, self.cache.as_ref())
450     }
451 
452     /// Same as `setxattr` but leaves the handling of the `fuse::Reply` to the caller.
setxattr2(&mut self, inode: u64, name: &OsStr, value: &[u8]) -> nodes::NodeResult<()>453     fn setxattr2(&mut self, inode: u64, name: &OsStr, value: &[u8]) -> nodes::NodeResult<()> {
454         let node = self.find_writable_node(inode)?;
455         node.setxattr(name, value)
456     }
457 
458     /// Same as `getxattr` but leaves the handling of the `fuse::Reply` to the caller.
getxattr2(&mut self, inode: u64, name: &OsStr) -> nodes::NodeResult<Vec<u8>>459     fn getxattr2(&mut self, inode: u64, name: &OsStr) -> nodes::NodeResult<Vec<u8>> {
460         let node = self.find_node(inode)?;
461         match node.getxattr(name) {
462             Ok(None) => {
463                 #[cfg(target_os = "linux")]
464                 let code = Errno::ENODATA;
465 
466                 #[cfg(any(target_os = "dragonfly", target_os = "freebsd", target_os = "macos"))]
467                 let code = Errno::ENOATTR;
468 
469                 #[cfg(not(any(target_os = "dragonfly", target_os = "freebsd", target_os = "linux", target_os = "macos")))]
470                 compile_error!("Don't know what error to return on a missing getxattr");
471 
472                 Err(KernelError::from_errno(code))
473             },
474             Ok(Some(value)) => Ok(value),
475             Err(e) => Err(e),
476         }
477     }
478 
479     /// Same as `listxattr` but leaves the handling of the `fuse::Reply` to the caller.
listxattr2(&mut self, inode: u64) -> nodes::NodeResult<Option<xattr::XAttrs>>480     fn listxattr2(&mut self, inode: u64) -> nodes::NodeResult<Option<xattr::XAttrs>> {
481         let node = self.find_node(inode)?;
482         node.listxattr()
483     }
484 
485     /// Same as `removexattr` but leaves the handling of the `fuse::Reply` to the caller.
removexattr2(&mut self, inode: u64, name: &OsStr) -> nodes::NodeResult<()>486     fn removexattr2(&mut self, inode: u64, name: &OsStr) -> nodes::NodeResult<()> {
487         let node = self.find_writable_node(inode)?;
488         node.removexattr(name)
489     }
490 }
491 
492 /// Creates a file `path` with the given `uid`/`gid` pair.
493 ///
494 /// The file is created via the `create` lambda, which can create any type of file it wishes.  The
495 /// `delete` lambda should match this creation and allow the deletion of the file, and this is used
496 /// as a cleanup function when the ownership cannot be successfully changed.
create_as<T, E: From<Errno> + fmt::Display, P: AsRef<Path>>( path: &P, uid: unistd::Uid, gid: unistd::Gid, create: impl Fn(&P) -> Result<T, E>, delete: impl Fn(&P) -> Result<(), E>) -> Result<T, E>497 fn create_as<T, E: From<Errno> + fmt::Display, P: AsRef<Path>>(
498     path: &P, uid: unistd::Uid, gid: unistd::Gid,
499     create: impl Fn(&P) -> Result<T, E>,
500     delete: impl Fn(&P) -> Result<(), E>)
501     -> Result<T, E> {
502 
503     let result = create(path)?;
504 
505     unistd::fchownat(
506         None, path.as_ref(), Some(uid), Some(gid), unistd::FchownatFlags::NoFollowSymlink)
507         .map_err(|e| {
508             let chown_errno = match e {
509                 nix::Error::Sys(chown_errno) => chown_errno,
510                 unknown_chown_error => {
511                     warn!("fchownat({}) failed with unexpected non-errno error: {:?}",
512                           path.as_ref().display(), unknown_chown_error);
513                     Errno::EIO
514                 },
515             };
516 
517             if let Err(e) = delete(path) {
518                 warn!("Cannot delete created file {} after failing to change ownership: {}",
519                       path.as_ref().display(), e);
520             }
521 
522             chown_errno
523         })?;
524 
525     Ok(result)
526 }
527 
528 /// Returns a `unistd::Uid` representation of the UID in a `fuse::Request`.
nix_uid(req: &fuse::Request) -> unistd::Uid529 fn nix_uid(req: &fuse::Request) -> unistd::Uid {
530     unistd::Uid::from_raw(req.uid() as u32)
531 }
532 
533 /// Returns a `unistd::Gid` representation of the GID in a `fuse::Request`.
nix_gid(req: &fuse::Request) -> unistd::Gid534 fn nix_gid(req: &fuse::Request) -> unistd::Gid {
535     unistd::Gid::from_raw(req.gid() as u32)
536 }
537 
538 /// Converts a collection of extended attribute names into a raw vector of null-terminated strings.
539 ///
540 // TODO(jmmv): This conversion is unnecessary.  `Xattrs` has the raw representation of the extended
541 // attributes, which we could forward to the kernel directly.
xattrs_to_u8(xattrs: xattr::XAttrs) -> Vec<u8>542 fn xattrs_to_u8(xattrs: xattr::XAttrs) -> Vec<u8> {
543     let mut length = 0;
544     for xa in xattrs.clone().into_iter() {
545         length += xa.len() + 1;
546     }
547     let mut data = Vec::with_capacity(length);
548     for xa in xattrs.into_iter() {
549         for b in xa.as_bytes() {
550             data.push(*b);
551         }
552         data.push(0);
553     }
554     data
555 }
556 
557 /// Responds to a successful xattr get or list request.
558 ///
559 /// If `size` is zero, the kernel wants to know the length of `value`.  Otherwise, we are being
560 /// asked for the actual value, which should not be longer than `size`.
reply_xattr(size: u32, value: &[u8], reply: fuse::ReplyXattr)561 fn reply_xattr(size: u32, value: &[u8], reply: fuse::ReplyXattr) {
562     if size == 0 {
563         if value.len() > std::u32::MAX as usize {
564             warn!("xattr data too long ({} bytes); cannot reply", value.len());
565             reply.error(Errno::EIO as i32);
566         } else {
567             reply.size(value.len() as u32);
568         }
569     } else if (size as usize) < value.len() {
570         reply.error(Errno::ERANGE as i32);
571     } else {
572         reply.data(value);
573     }
574 }
575 
576 impl fuse::Filesystem for SandboxFS {
create(&mut self, req: &fuse::Request, parent: u64, name: &OsStr, mode: u32, flags: u32, reply: fuse::ReplyCreate)577     fn create(&mut self, req: &fuse::Request, parent: u64, name: &OsStr, mode: u32, flags: u32,
578         reply: fuse::ReplyCreate) {
579         match self.create2(req, parent, name, mode, flags) {
580             Ok((attr, fh)) => reply.created(&self.ttl, &attr, IdGenerator::GENERATION, fh, 0),
581             Err(e) => reply.error(e.errno_as_i32()),
582         }
583     }
584 
getattr(&mut self, _req: &fuse::Request, inode: u64, reply: fuse::ReplyAttr)585     fn getattr(&mut self, _req: &fuse::Request, inode: u64, reply: fuse::ReplyAttr) {
586         match self.getattr2(inode) {
587             Ok(attr) => reply.attr(&self.ttl, &attr),
588             Err(e) => reply.error(e.errno_as_i32()),
589         }
590     }
591 
link(&mut self, _req: &fuse::Request, _inode: u64, _newparent: u64, _newname: &OsStr, reply: fuse::ReplyEntry)592     fn link(&mut self, _req: &fuse::Request, _inode: u64, _newparent: u64, _newname: &OsStr,
593         reply: fuse::ReplyEntry) {
594         // We don't support hardlinks at this point.
595         reply.error(Errno::EPERM as i32);
596     }
597 
lookup(&mut self, _req: &fuse::Request, parent: u64, name: &OsStr, reply: fuse::ReplyEntry)598     fn lookup(&mut self, _req: &fuse::Request, parent: u64, name: &OsStr, reply: fuse::ReplyEntry) {
599         match self.lookup2(parent, name) {
600             Ok(attr) => reply.entry(&self.ttl, &attr, IdGenerator::GENERATION),
601             Err(e) => reply.error(e.errno_as_i32()),
602         }
603     }
604 
mkdir(&mut self, req: &fuse::Request, parent: u64, name: &OsStr, mode: u32, reply: fuse::ReplyEntry)605     fn mkdir(&mut self, req: &fuse::Request, parent: u64, name: &OsStr, mode: u32,
606         reply: fuse::ReplyEntry) {
607         match self.mkdir2(req, parent, name, mode) {
608             Ok(attr) => reply.entry(&self.ttl, &attr, IdGenerator::GENERATION),
609             Err(e) => reply.error(e.errno_as_i32()),
610         }
611     }
612 
mknod(&mut self, req: &fuse::Request, parent: u64, name: &OsStr, mode: u32, rdev: u32, reply: fuse::ReplyEntry)613     fn mknod(&mut self, req: &fuse::Request, parent: u64, name: &OsStr, mode: u32, rdev: u32,
614         reply: fuse::ReplyEntry) {
615         match self.mknod2(req, parent, name, mode, rdev) {
616             Ok(attr) => reply.entry(&self.ttl, &attr, IdGenerator::GENERATION),
617             Err(e) => reply.error(e.errno_as_i32()),
618         }
619     }
620 
open(&mut self, _req: &fuse::Request, inode: u64, flags: u32, reply: fuse::ReplyOpen)621     fn open(&mut self, _req: &fuse::Request, inode: u64, flags: u32, reply: fuse::ReplyOpen) {
622         match self.open2(inode, flags) {
623             Ok(fh) => reply.opened(fh, 0),
624             Err(e) => reply.error(e.errno_as_i32()),
625         }
626     }
627 
opendir(&mut self, _req: &fuse::Request, inode: u64, flags: u32, reply: fuse::ReplyOpen)628     fn opendir(&mut self, _req: &fuse::Request, inode: u64, flags: u32, reply: fuse::ReplyOpen) {
629         match self.open2(inode, flags) {
630             Ok(fh) => reply.opened(fh, 0),
631             Err(e) => reply.error(e.errno_as_i32()),
632         }
633     }
634 
read(&mut self, _req: &fuse::Request, _inode: u64, fh: u64, offset: i64, size: u32, reply: fuse::ReplyData)635     fn read(&mut self, _req: &fuse::Request, _inode: u64, fh: u64, offset: i64, size: u32,
636         reply: fuse::ReplyData) {
637         let handle = self.find_handle(fh);
638 
639         match handle.read(offset, size) {
640             Ok(data) => reply.data(&data),
641             Err(e) => reply.error(e.errno_as_i32()),
642         }
643     }
644 
readdir(&mut self, _req: &fuse::Request, _inode: u64, handle: u64, offset: i64, mut reply: fuse::ReplyDirectory)645     fn readdir(&mut self, _req: &fuse::Request, _inode: u64, handle: u64, offset: i64,
646                mut reply: fuse::ReplyDirectory) {
647         let handle = self.find_handle(handle);
648         match handle.readdir(&self.ids, self.cache.as_ref(), offset, &mut reply) {
649             Ok(()) => reply.ok(),
650             Err(e) => reply.error(e.errno_as_i32()),
651         }
652     }
653 
readlink(&mut self, _req: &fuse::Request, inode: u64, reply: fuse::ReplyData)654     fn readlink(&mut self, _req: &fuse::Request, inode: u64, reply: fuse::ReplyData) {
655         match self.readlink2(inode) {
656             Ok(target) => reply.data(target.as_os_str().as_bytes()),
657             Err(e) => reply.error(e.errno_as_i32()),
658         }
659     }
660 
release(&mut self, _req: &fuse::Request, _inode: u64, fh: u64, _flags: u32, _lock_owner: u64, _flush: bool, reply: fuse::ReplyEmpty)661     fn release(&mut self, _req: &fuse::Request, _inode: u64, fh: u64, _flags: u32, _lock_owner: u64,
662         _flush: bool, reply: fuse::ReplyEmpty) {
663         self.release2(fh);
664         reply.ok();
665     }
666 
releasedir(&mut self, _req: &fuse::Request, _inode: u64, fh: u64, _flags: u32, reply: fuse::ReplyEmpty)667     fn releasedir(&mut self, _req: &fuse::Request, _inode: u64, fh: u64, _flags: u32,
668         reply: fuse::ReplyEmpty) {
669         self.release2(fh);
670         reply.ok();
671     }
672 
rename(&mut self, _req: &fuse::Request, parent: u64, name: &OsStr, new_parent: u64, new_name: &OsStr, reply: fuse::ReplyEmpty)673     fn rename(&mut self, _req: &fuse::Request, parent: u64, name: &OsStr, new_parent: u64,
674         new_name: &OsStr, reply: fuse::ReplyEmpty) {
675         match self.rename2(parent, name, new_parent, new_name) {
676             Ok(()) => reply.ok(),
677             Err(e) => reply.error(e.errno_as_i32()),
678         }
679     }
680 
rmdir(&mut self, _req: &fuse::Request, parent: u64, name: &OsStr, reply: fuse::ReplyEmpty)681     fn rmdir(&mut self, _req: &fuse::Request, parent: u64, name: &OsStr, reply: fuse::ReplyEmpty) {
682         match self.rmdir2(parent, name) {
683             Ok(()) => reply.ok(),
684             Err(e) => reply.error(e.errno_as_i32()),
685         }
686     }
687 
setattr(&mut self, _req: &fuse::Request, inode: u64, mode: Option<u32>, uid: Option<u32>, gid: Option<u32>, size: Option<u64>, atime: Option<Timespec>, mtime: Option<Timespec>, _fh: Option<u64>, _crtime: Option<Timespec>, _chgtime: Option<Timespec>, _bkuptime: Option<Timespec>, _flags: Option<u32>, reply: fuse::ReplyAttr)688     fn setattr(&mut self, _req: &fuse::Request, inode: u64, mode: Option<u32>, uid: Option<u32>,
689         gid: Option<u32>, size: Option<u64>, atime: Option<Timespec>, mtime: Option<Timespec>,
690         _fh: Option<u64>, _crtime: Option<Timespec>, _chgtime: Option<Timespec>,
691         _bkuptime: Option<Timespec>, _flags: Option<u32>, reply: fuse::ReplyAttr) {
692         match self.setattr2(inode, mode, uid, gid, size, atime, mtime) {
693             Ok(attr) => reply.attr(&self.ttl, &attr),
694             Err(e) => reply.error(e.errno_as_i32()),
695         }
696     }
697 
symlink(&mut self, req: &fuse::Request, parent: u64, name: &OsStr, link: &Path, reply: fuse::ReplyEntry)698     fn symlink(&mut self, req: &fuse::Request, parent: u64, name: &OsStr, link: &Path,
699         reply: fuse::ReplyEntry) {
700         match self.symlink2(req, parent, name, link) {
701             Ok(attr) => reply.entry(&self.ttl, &attr, IdGenerator::GENERATION),
702             Err(e) => reply.error(e.errno_as_i32()),
703         }
704     }
705 
unlink(&mut self, _req: &fuse::Request, parent: u64, name: &OsStr, reply: fuse::ReplyEmpty)706     fn unlink(&mut self, _req: &fuse::Request, parent: u64, name: &OsStr, reply: fuse::ReplyEmpty) {
707         match self.unlink2(parent, name) {
708             Ok(()) => reply.ok(),
709             Err(e) => reply.error(e.errno_as_i32()),
710         }
711     }
712 
write(&mut self, _req: &fuse::Request, _inode: u64, fh: u64, offset: i64, data: &[u8], _flags: u32, reply: fuse::ReplyWrite)713     fn write(&mut self, _req: &fuse::Request, _inode: u64, fh: u64, offset: i64, data: &[u8],
714         _flags: u32, reply: fuse::ReplyWrite) {
715         let handle = self.find_handle(fh);
716 
717         match handle.write(offset, data) {
718             Ok(size) => reply.written(size),
719             Err(e) => reply.error(e.errno_as_i32()),
720         }
721     }
722 
setxattr(&mut self, _req: &fuse::Request<'_>, inode: u64, name: &OsStr, value: &[u8], _flags: u32, _position: u32, reply: fuse::ReplyEmpty)723     fn setxattr(&mut self, _req: &fuse::Request<'_>, inode: u64, name: &OsStr, value: &[u8],
724         _flags: u32, _position: u32, reply: fuse::ReplyEmpty) {
725         if !self.xattrs {
726             reply.error(Errno::ENOSYS as i32);
727             return;
728         }
729 
730         match self.setxattr2(inode, name, value) {
731             Ok(()) => reply.ok(),
732             Err(e) => reply.error(e.errno_as_i32()),
733         }
734     }
735 
getxattr(&mut self, _req: &fuse::Request<'_>, inode: u64, name: &OsStr, size: u32, reply: fuse::ReplyXattr)736     fn getxattr(&mut self, _req: &fuse::Request<'_>, inode: u64, name: &OsStr, size: u32,
737         reply: fuse::ReplyXattr) {
738         if !self.xattrs {
739             reply.error(Errno::ENOSYS as i32);
740             return;
741         }
742 
743         match self.getxattr2(inode, name) {
744             Ok(value) => reply_xattr(size, value.as_slice(), reply),
745             Err(e) => reply.error(e.errno_as_i32()),
746         }
747     }
748 
listxattr(&mut self, _req: &fuse::Request<'_>, inode: u64, size: u32, reply: fuse::ReplyXattr)749     fn listxattr(&mut self, _req: &fuse::Request<'_>, inode: u64, size: u32,
750         reply: fuse::ReplyXattr) {
751         if !self.xattrs {
752             reply.error(Errno::ENOSYS as i32);
753             return;
754         }
755 
756         match self.listxattr2(inode) {
757             Ok(Some(xattrs)) => reply_xattr(size, xattrs_to_u8(xattrs).as_slice(), reply),
758             Ok(None) => {
759                 if size == 0 {
760                     reply.size(0);
761                 } else {
762                     reply.data(&[]);
763                 }
764             },
765             Err(e) => reply.error(e.errno_as_i32()),
766         }
767     }
768 
removexattr(&mut self, _req: &fuse::Request<'_>, inode: u64, name: &OsStr, reply: fuse::ReplyEmpty)769     fn removexattr(&mut self, _req: &fuse::Request<'_>, inode: u64, name: &OsStr,
770         reply: fuse::ReplyEmpty) {
771         if !self.xattrs {
772             reply.error(Errno::ENOSYS as i32);
773             return;
774         }
775 
776         match self.removexattr2(inode, name) {
777             Ok(()) => reply.ok(),
778             Err(e) => reply.error(e.errno_as_i32()),
779         }
780     }
781 }
782 
783 impl reconfig::ReconfigurableFS for ReconfigurableSandboxFS {
create_sandbox(&self, id: &str, mut mappings: &[Mapping]) -> Fallible<()>784     fn create_sandbox(&self, id: &str, mut mappings: &[Mapping]) -> Fallible<()> {
785         // Special-case the first mapping if it is for the "root" directory.  We know that this
786         // mapping, if present, must come first (as otherwise it will fail when applied later on
787         // anyway).  But if it is first, we must treat it as if we were mapping the "root" itself.
788         let root_node = match mappings.get(0) {
789             Some(mapping) => {
790                 if mapping.path.as_path() == Path::new(&"/") {
791                     let path = reconfig::make_path(id, mapping.path.clone())?;
792                     mappings = &mappings[1..];
793                     let m = Mapping::from_parts(
794                         path, mapping.underlying_path.clone(), mapping.writable)?;
795                     apply_mapping(&m, self.root.as_ref(), self.ids.as_ref(), self.cache.as_ref())
796                         .context(format!("Cannot map '{}'", mapping))?
797                 } else {
798                     self.root.find_subdir(OsStr::new(id), self.ids.as_ref())?
799                 }
800             },
801             None => self.root.find_subdir(OsStr::new(id), self.ids.as_ref())?,
802         };
803 
804         // TODO(jmmv): Even though we don't hold the root lock any longer, this *still* is very
805         // inefficient because keep locking/unlocking the top directory for every mapping.  Should
806         // pass the list of mappings down to the `map` operation... but that'd only fix this issue
807         // for the top-level directory; what about all intermediate directories for all mappings?
808         for mapping in mappings {
809             apply_mapping(
810                 mapping, root_node.clone().as_ref(), self.ids.as_ref(), self.cache.as_ref())
811                 .context(format!("Cannot map '{}'", mapping))?;
812         }
813         Ok(())
814     }
815 
destroy_sandbox(&self, id: &str) -> Fallible<()>816     fn destroy_sandbox(&self, id: &str) -> Fallible<()> {
817         let mut inodes = vec!();
818         let result = self.root.unmap_subdir(OsStr::new(id), &mut inodes);
819 
820         let mut nodes = self.nodes.lock().unwrap();
821         for inode in inodes {
822             nodes.remove(&inode);
823         }
824 
825         result
826     }
827 }
828 
829 /// Mounts a new sandboxfs instance on the given `mount_point` and maps all `mappings` within it.
830 #[allow(clippy::too_many_arguments)]
mount(mount_point: &Path, options: &[&str], mappings: &[Mapping], ttl: Timespec, cache: ArcCache, xattrs: bool, input: fs::File, output: fs::File, threads: usize) -> Fallible<()>831 pub fn mount(mount_point: &Path, options: &[&str], mappings: &[Mapping], ttl: Timespec,
832     cache: ArcCache, xattrs: bool, input: fs::File, output: fs::File, threads: usize)
833     -> Fallible<()> {
834     let mut os_options = options.iter().map(AsRef::as_ref).collect::<Vec<&OsStr>>();
835 
836     // Delegate permissions checks to the kernel for efficiency and to avoid having to implement
837     // them on our own.
838     os_options.push(OsStr::new("-o"));
839     os_options.push(OsStr::new("default_permissions"));
840 
841     let mut fs = SandboxFS::create(mappings, ttl, cache, xattrs)?;
842     let reconfigurable_fs = fs.reconfigurable();
843     info!("Mounting file system onto {:?}", mount_point);
844 
845     let (signals, mut session) = {
846         let installer = concurrent::SignalsInstaller::prepare();
847         let session = fuse::Session::new(fs, &mount_point, &os_options)?;
848         let signals = installer.install(PathBuf::from(mount_point))?;
849         (signals, session)
850     };
851 
852     let config_handler = {
853         let mut input = concurrent::ShareableFile::from(input);
854         let reader = input.reader()?;
855         let handler = thread::spawn(move || {
856             match reconfig::run_loop(reader, output, threads, &reconfigurable_fs) {
857                 Ok(()) => info!(
858                     "Reached end of reconfiguration input; file system mappings are now frozen"),
859                 Err(e) => warn!("Reconfigurations stopped due to internal error: {}", e),
860             }
861         });
862 
863         session.run()?;
864         handler
865     };
866     // The input must be closed to let the reconfiguration thread to exit, which then lets the join
867     // operation below complete, hence the scope above.
868     if let Some(signo) = signals.caught() {
869         info!("Caught signal {}", signo);
870         return Err(format_err!("Caught signal {}", signo));
871     }
872 
873     match config_handler.join() {
874         Ok(_) => Ok(()),
875         Err(_) => Err(format_err!("Reconfiguration thread panicked")),
876     }
877 }
878 
879 #[cfg(test)]
880 mod tests {
881     use super::*;
882     use std::os::unix::fs::MetadataExt;
883     use tempfile::tempdir;
884 
885     #[test]
test_mapping_new_ok()886     fn test_mapping_new_ok() {
887         let mapping = Mapping::from_parts(
888             PathBuf::from("/foo/.///bar"),  // Must be absolute and normalized.
889             PathBuf::from("/bar/./baz/../abc"),  // Must be absolute but needn't be normalized.
890             false).unwrap();
891         assert_eq!(PathBuf::from("/foo/bar"), mapping.path);
892         assert_eq!(PathBuf::from("/bar/baz/../abc"), mapping.underlying_path);
893         assert!(!mapping.writable);
894     }
895 
896     #[test]
test_mapping_new_path_is_not_absolute()897     fn test_mapping_new_path_is_not_absolute() {
898         let err = Mapping::from_parts(
899             PathBuf::from("foo"), PathBuf::from("/bar"), false).unwrap_err();
900         assert_eq!(MappingError::PathNotAbsolute { path: PathBuf::from("foo") }, err);
901     }
902 
903     #[test]
test_mapping_new_path_is_not_normalized()904     fn test_mapping_new_path_is_not_normalized() {
905         let trailing_dotdot = PathBuf::from("/foo/..");
906         assert_eq!(
907             MappingError::PathNotNormalized { path: trailing_dotdot.clone() },
908             Mapping::from_parts(trailing_dotdot, PathBuf::from("/bar"), false).unwrap_err());
909 
910         let intermediate_dotdot = PathBuf::from("/foo/../bar/baz");
911         assert_eq!(
912             MappingError::PathNotNormalized { path: intermediate_dotdot.clone() },
913             Mapping::from_parts(intermediate_dotdot, PathBuf::from("/bar"), true).unwrap_err());
914     }
915 
916     #[test]
test_mapping_new_underlying_path_is_not_absolute()917     fn test_mapping_new_underlying_path_is_not_absolute() {
918         let err = Mapping::from_parts(
919             PathBuf::from("/foo"), PathBuf::from("bar"), false).unwrap_err();
920         assert_eq!(MappingError::PathNotAbsolute { path: PathBuf::from("bar") }, err);
921     }
922 
923     #[test]
test_mapping_is_root()924     fn test_mapping_is_root() {
925         let irrelevant = PathBuf::from("/some/place");
926         assert!(Mapping::from_parts(
927             PathBuf::from("/"), irrelevant.clone(), false).unwrap().is_root());
928         assert!(Mapping::from_parts(
929             PathBuf::from("///"), irrelevant.clone(), false).unwrap().is_root());
930         assert!(Mapping::from_parts(
931             PathBuf::from("/./"), irrelevant.clone(), false).unwrap().is_root());
932         assert!(!Mapping::from_parts(
933             PathBuf::from("/a"), irrelevant.clone(), false).unwrap().is_root());
934         assert!(!Mapping::from_parts(
935             PathBuf::from("/a/b"), irrelevant, false).unwrap().is_root());
936     }
937 
938     #[test]
id_generator_ok()939     fn id_generator_ok() {
940         let ids = IdGenerator::new(10);
941         assert_eq!(10, ids.next());
942         assert_eq!(11, ids.next());
943         assert_eq!(12, ids.next());
944     }
945 
946     #[test]
947     #[should_panic(expected = "Ran out of identifiers")]
id_generator_exhaustion()948     fn id_generator_exhaustion() {
949         let ids = IdGenerator::new(std::u64::MAX);
950         ids.next();  // OK, still at limit.
951         ids.next();  // Should panic.
952     }
953 
954     #[test]
test_split_abs_path()955     fn test_split_abs_path() {
956         let empty: [Component; 0] = [];
957         assert_eq!(
958             &empty,
959             split_abs_path(&Path::new("/")).as_slice());
960         assert_eq!(
961             &[Component::Normal(OsStr::new("foo"))],
962             split_abs_path(&Path::new("/foo")).as_slice());
963         assert_eq!(
964             &[Component::Normal(OsStr::new("foo")), Component::Normal(OsStr::new("bar"))],
965             split_abs_path(&Path::new("/foo/bar")).as_slice());
966     }
967 
do_create_as_ok_test(uid: unistd::Uid, gid: unistd::Gid)968     fn do_create_as_ok_test(uid: unistd::Uid, gid: unistd::Gid) {
969         let root = tempdir().unwrap();
970         let file = root.path().join("dir");
971         create_as(&file, uid, gid, |p| fs::create_dir(&p), |p| fs::remove_dir(&p)).unwrap();
972         let fs_attr = fs::symlink_metadata(&file).unwrap();
973         assert_eq!((uid.as_raw(), gid.as_raw()), (fs_attr.uid(), fs_attr.gid()));
974     }
975 
976     #[test]
create_as_self()977     fn create_as_self() {
978         do_create_as_ok_test(unistd::Uid::current(), unistd::Gid::current());
979     }
980 
981     #[test]
create_as_other()982     fn create_as_other() {
983         if !unistd::Uid::current().is_root() {
984             info!("Test requires root privileges; skipping");
985             return;
986         }
987 
988         let config = testutils::Config::get();
989         match config.unprivileged_user {
990             Some(user) => {
991                 let uid = unistd::Uid::from_raw(user.uid());
992                 let gid = unistd::Gid::from_raw(user.primary_group_id());
993                 do_create_as_ok_test(uid, gid);
994             },
995             None => {
996                 panic!("UNPRIVILEGED_USER must be set when running as root for this test to run");
997             }
998         }
999     }
1000 
1001     #[test]
create_as_create_error_wins_over_delete_error()1002     fn create_as_create_error_wins_over_delete_error() {
1003         let path = PathBuf::from("irrelevant");
1004         let err = create_as(
1005             &path, unistd::Uid::current(), unistd::Gid::current(),
1006             |_| Err::<(), nix::Error>(nix::Error::from_errno(Errno::EPERM)),
1007             |_| Err::<(), nix::Error>(nix::Error::from_errno(Errno::ENOENT))).unwrap_err();
1008         assert_eq!(nix::Error::from_errno(Errno::EPERM), err);
1009     }
1010 
1011     #[test]
create_as_file_deleted_if_chown_fails()1012     fn create_as_file_deleted_if_chown_fails() {
1013         if unistd::Uid::current().is_root() {
1014             info!("Test requires non-root privileges; skipping");
1015             return;
1016         }
1017 
1018         let other_uid = unistd::Uid::from_raw(unistd::Uid::current().as_raw() + 1);
1019         let gid = unistd::Gid::current();
1020 
1021         let root = tempdir().unwrap();
1022         let file = root.path().join("dir");
1023         create_as(&file, other_uid, gid, |p| fs::create_dir(&p), |p| fs::remove_dir(&p))
1024             .unwrap_err();
1025         fs::symlink_metadata(&file).unwrap_err();
1026     }
1027 }
1028