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