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 extern crate fuse;
new(stream: &'a mut St) -> Self16 extern crate time;
17
18 use {create_as, IdGenerator};
19 use failure::{Fallible, ResultExt};
20 use nix::{errno, fcntl, sys, unistd};
21 use nix::dir as rawdir;
is_terminated(&self) -> bool22 use nodes::{
23 ArcHandle, ArcNode, AttrDelta, Cache, Handle, KernelError, Node, NodeResult, conv, setattr};
24 use std::collections::HashMap;
25 use std::ffi::{OsStr, OsString};
26 use std::os::unix::ffi::OsStrExt;
27 use std::os::unix::fs::{self as unix_fs, DirBuilderExt, OpenOptionsExt};
28 use std::fs;
29 use std::io;
poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output>30 use std::path::{Component, Path, PathBuf};
31 use std::sync::{Arc, Mutex};
32
33 /// Takes the components of a path and returns the first normal component and the rest.
34 ///
35 /// This assumes that the input path is normalized and that the very first component is a normal
36 /// component as defined by `Component::Normal`.
37 fn split_components<'a>(components: &'a [Component<'a>]) -> (&'a OsStr, &'a [Component<'a>]) {
38 debug_assert!(!components.is_empty());
39 let name = match components[0] {
40 Component::Normal(name) => name,
41 _ => panic!("Input list of components is not normalized"),
42 };
43 (name, &components[1..])
44 }
45
46 /// Contents of a single `fuse::ReplyDirectory` reply; used for pagination.
47 struct ReplyEntry {
48 inode: u64,
49 fs_type: fuse::FileType,
50 name: OsString,
51 }
52
53 /// Handle for an open directory.
54 struct OpenDir {
55 // These are copies of the fields that also exist in the Dir corresponding to this OpenDir.
56 // Ideally we could just hold an immutable reference to the Dir instance... but this is hard
57 // to do because, when opendir() gets called on an abstract Node, we do not get access to the
58 // ArcNode that corresponds to it (to clone it). Given that these values are immutable on
59 // the node, holding a copy here is fine.
60 inode: u64,
61 writable: bool,
62 state: Arc<Mutex<MutableDir>>,
63
64 /// Handle for the open directory file descriptor. This is `None` if the directory does not
65 /// have an underlying path.
66 handle: Mutex<Option<rawdir::Dir>>,
67
68 /// Contents of this directory. This is populated on the first `readdir` request that has an
69 /// offset of zero and reused for all further calls until the contents are consumed.
70 ///
71 /// Doing this means that mutations to the directory won't be visible to `readdir` while a
72 /// "stream" of partial `readdir` calls is in progress. This is probably a good thing.
73 reply_contents: Mutex<Vec<ReplyEntry>>,
74 }
75
76 impl OpenDir {
77 /// Reads all directory entries in one go.
78 ///
79 /// `_ids` and `_cache` are the file system-wide bookkeeping objects needed to instantiate new
80 /// nodes, used when readdir discovers an underlying node that was not yet known.
81 fn readdirall(&self, ids: &IdGenerator, cache: &dyn Cache) -> NodeResult<Vec<ReplyEntry>> {
82 let mut reply = vec!();
83
84 let mut state = self.state.lock().unwrap();
85
86 reply.push(ReplyEntry {
87 inode: self.inode,
88 fs_type: fuse::FileType::Directory,
89 name: OsString::from(".")
90 });
91 reply.push(ReplyEntry {
92 inode: state.parent,
93 fs_type: fuse::FileType::Directory,
94 name: OsString::from("..")
95 });
96
97 // First, return the entries that correspond to explicit mappings performed by the user at
98 // either mount time or during a reconfiguration. Those should clobber any on-disk
99 // contents that we discover later when we issue the readdir on the underlying directory,
100 // if any.
101 for (name, dirent) in &state.children {
102 if dirent.explicit_mapping {
103 reply.push(ReplyEntry {
104 inode: dirent.node.inode(),
105 fs_type: dirent.node.file_type_cached(),
106 name: name.clone()
107 });
108 }
109 }
110
111 let mut handle = self.handle.lock().unwrap();
112
113 if handle.is_none() {
114 debug_assert!(state.underlying_path.is_none());
115 return Ok(reply);
116 }
117 debug_assert!(state.underlying_path.is_some());
118 let handle = handle.as_mut().unwrap();
119 for entry in handle.iter() {
120 let entry = entry?;
121 let name = entry.file_name();
122
123 let name = OsStr::from_bytes(name.to_bytes()).to_os_string();
124
125 if name == "." || name == ".." {
126 continue;
127 }
128
129 if let Some(dirent) = state.children.get(&name) {
130 // Found a previously-known on-disk entry. Must return it "as is" (even if its
131 // type might have changed) because, if we called into `cache.get_or_create` below,
132 // we might recreate the node unintentionally. Note that mappings were handled
133 // earlier, so only handle the non-mapping case here.
134 if !dirent.explicit_mapping {
135 reply.push(ReplyEntry {
136 inode: dirent.node.inode(),
137 fs_type: dirent.node.file_type_cached(),
138 name: name.clone(),
139 });
140 }
141 continue;
142 }
143
144 let path = state.underlying_path.as_ref().unwrap().join(&name);
145
146 // TODO(jmmv): In theory we shouldn't need to issue a stat for every entry during a
147 // readdir. However, it's much easier to handle things this way because we currently
148 // require a file's metadata in order to instantiate a node. Note that the Go variant
149 // of this code does the same and an attempt to "fix" this resulted in more complex
150 // code and no visible performance gains. That said, it'd be worth to investigate this
151 // again.
152 let fs_attr = fs::symlink_metadata(&path)?;
153
154 let fs_type = conv::filetype_fs_to_fuse(&path, fs_attr.file_type());
155 let child = cache.get_or_create(ids, &path, &fs_attr, self.writable);
156
157 reply.push(ReplyEntry { inode: child.inode(), fs_type: fs_type, name: name.clone() });
158
159 // Do the insertion into state.children after calling reply.add() to be able to move
160 // the name into the key without having to copy it again.
161 let dirent = Dirent {
162 node: child.clone(),
163 explicit_mapping: false,
164 };
165 // TODO(jmmv): We should remove stale entries at some point (possibly here), but the Go
166 // variant does not do this so any implications of this are not tested. The reason this
167 // hasn't caused trouble yet is because: on readdir, we don't use any contents from
168 // state.children that correspond to unmapped entries, and any stale entries visited
169 // during lookup will result in an ENOENT.
170 state.children.insert(name, dirent);
171 }
172 // No need to worry about rewinding handle.iter() for future reads on the same OpenDir:
173 // the rawdir::Dir implementation does this for us.
174 Ok(reply)
175 }
176 }
177
178 impl Handle for OpenDir {
179 fn readdir(&self, ids: &IdGenerator, cache: &dyn Cache, offset: i64,
180 reply: &mut fuse::ReplyDirectory) -> NodeResult<()> {
181 let mut offset: usize = offset as usize;
182
183 let mut contents = self.reply_contents.lock().unwrap();
184 if offset == 0 {
185 *contents = self.readdirall(ids, cache)?;
186 } else {
187 // When the kernel asks us to return extra entries from a partially-read directory, it
188 // does so by giving us the offset of the last entry we returned -- not the first one
189 // that we ought to return. Therefore, advance the offset by one to avoid duplicate
190 // return values and to avoid entering an infinite loop.
191 offset += 1;
192 }
193
194 while offset < contents.len() {
195 let entry = &contents[offset];
196 if reply.add(entry.inode, offset as i64, entry.fs_type, &entry.name) {
197 break; // Reply buffer is full.
198 }
199 offset += 1;
200 }
201 Ok(())
202 }
203 }
204
205 /// Representation of a directory entry.
206 #[derive(Clone)]
207 pub struct Dirent {
208 node: ArcNode,
209 explicit_mapping: bool,
210 }
211
212 /// Representation of a directory node.
213 pub struct Dir {
214 inode: u64,
215 writable: bool,
216 state: Arc<Mutex<MutableDir>>,
217 }
218
219 /// Holds the mutable data of a directory node.
220 pub struct MutableDir {
221 parent: u64,
222 underlying_path: Option<PathBuf>,
223 attr: fuse::FileAttr,
224 children: HashMap<OsString, Dirent>,
225 }
226
227 impl Dir {
228 /// Creates a new scaffold directory to represent an in-memory directory.
229 ///
230 /// The directory's timestamps are set to `now` and the ownership is set to the current user.
231 pub fn new_empty(inode: u64, parent: Option<&dyn Node>, now: time::Timespec) -> ArcNode {
232 let attr = fuse::FileAttr {
233 ino: inode,
234 kind: fuse::FileType::Directory,
235 nlink: 2, // "." entry plus whichever initial named node points at this.
236 size: 2, // TODO(jmmv): Reevaluate what directory sizes should be.
237 blocks: 1, // TODO(jmmv): Reevaluate what directory blocks should be.
238 atime: now,
239 mtime: now,
240 ctime: now,
241 crtime: now,
242 perm: 0o555 as u16, // Scaffold directories cannot be mutated by the user.
243 uid: unistd::getuid().as_raw(),
244 gid: unistd::getgid().as_raw(),
245 rdev: 0,
246 flags: 0,
247 };
248
249 let state = MutableDir {
250 parent: parent.map_or(inode, Node::inode),
251 underlying_path: None,
252 attr: attr,
253 children: HashMap::new(),
254 };
255
256 Arc::new(Dir {
257 inode: inode,
258 writable: false,
259 state: Arc::from(Mutex::from(state)),
260 })
261 }
262
263 /// Creates a new directory whose contents are backed by another directory.
264 ///
265 /// `inode` is the node number to assign to the created in-memory directory and has no relation
266 /// to the underlying directory. `underlying_path` indicates the path to the directory outside
267 /// of the sandbox that backs this one. `fs_attr` contains the stat data for the given path.
268 ///
269 /// `fs_attr` is an input parameter because, by the time we decide to instantiate a directory
270 /// node (e.g. as we discover directory entries during readdir or lookup), we have already
271 /// issued a stat on the underlying file system and we cannot re-do it for efficiency reasons.
272 pub fn new_mapped(inode: u64, underlying_path: &Path, fs_attr: &fs::Metadata, writable: bool)
273 -> ArcNode {
274 if !fs_attr.is_dir() {
275 panic!("Can only construct based on dirs");
276 }
277
278 // For directories, assume a fixed link count of 2 that does not change throughout the
279 // lifetime of the directory (except for its own removal).
280 //
281 // An alternative would be to inherit the link count from the underlying file system and
282 // trust that it is accurate in the (typical) absence of hard links for directories.
283 // I tried that on 2020-02-10 on macOS Catalina with APFS and discovered that this
284 // assumption does not work because this system seems to count *all* directory entries as
285 // links (not just subdirectories).
286 let nlink = 2;
287
288 let attr = conv::attr_fs_to_fuse(underlying_path, inode, nlink, &fs_attr);
289
290 let state = MutableDir {
291 parent: inode,
292 underlying_path: Some(PathBuf::from(underlying_path)),
293 attr: attr,
294 children: HashMap::new(),
295 };
296
297 Arc::new(Dir { inode, writable, state: Arc::from(Mutex::from(state)) })
298 }
299
300 /// Creates a new scaffold directory as a child of the current one.
301 ///
302 /// Errors are all logged, not reported. The rationale is that a scaffold directory for an
303 /// intermediate path component of a mapping has to always be created, as it takes preference
304 /// over any other on-disk contents.
305 ///
306 /// This is purely a helper function for `map`. As a result, the caller is responsible for
307 /// inserting the new directory into the children of the current directory.
308 fn new_scaffold_child(&self, underlying_path: Option<&PathBuf>, name: &OsStr, ids: &IdGenerator,
309 now: time::Timespec) -> ArcNode {
310 if let Some(path) = underlying_path {
311 let child_path = path.join(name);
312 match fs::symlink_metadata(&child_path) {
313 Ok(fs_attr) => {
314 if fs_attr.is_dir() {
315 return Dir::new_mapped(ids.next(), &child_path, &fs_attr, self.writable);
316 }
317
318 info!("Mapping clobbers non-directory {} with an immutable directory",
319 child_path.display());
320 },
321 Err(e) => {
322 if e.kind() != io::ErrorKind::NotFound {
323 warn!("Mapping clobbers {} due to an error: {}", child_path.display(), e);
324 }
325 },
326 }
327 }
328 Dir::new_empty(ids.next(), Some(self), now)
329 }
330
331 /// Same as `getattr` but with the node already locked.
332 fn getattr_locked(inode: u64, state: &mut MutableDir) -> NodeResult<fuse::FileAttr> {
333 if let Some(path) = &state.underlying_path {
334 let fs_attr = fs::symlink_metadata(path)?;
335 if !fs_attr.is_dir() {
336 warn!("Path {} backing a directory node is no longer a directory; got {:?}",
337 path.display(), fs_attr.file_type());
338 return Err(KernelError::from_errno(errno::Errno::EIO));
339 }
340 state.attr = conv::attr_fs_to_fuse(path, inode, state.attr.nlink, &fs_attr);
341 }
342
343 Ok(state.attr)
344 }
345
346 /// Gets the underlying path of the entry `name` in this directory.
347 ///
348 /// This also ensures that the entry is writable, which is determined by the directory itself
349 /// being mapped to an underlying path and the entry not being an explicit mapping.
350 fn get_writable_path(state: &mut MutableDir, name: &OsStr) -> NodeResult<PathBuf> {
351 if state.underlying_path.is_none() {
352 return Err(KernelError::from_errno(errno::Errno::EPERM));
353 }
354 let path = state.underlying_path.as_ref().unwrap().join(name);
355
356 if let Some(node) = state.children.get(name) {
357 if node.explicit_mapping {
358 return Err(KernelError::from_errno(errno::Errno::EPERM));
359 }
360 };
361
362 Ok(path)
363 }
364
365 // Same as `lookup` but with the node already locked.
366 fn lookup_locked(writable: bool, state: &mut MutableDir, name: &OsStr, ids: &IdGenerator,
367 cache: &dyn Cache) -> NodeResult<(ArcNode, fuse::FileAttr)> {
368 if let Some(dirent) = state.children.get(name) {
369 let refreshed_attr = dirent.node.getattr()?;
370 return Ok((dirent.node.clone(), refreshed_attr))
371 }
372
373 let (child, attr) = {
374 let path = match &state.underlying_path {
375 Some(underlying_path) => underlying_path.join(name),
376 None => return Err(KernelError::from_errno(errno::Errno::ENOENT)),
377 };
378 let fs_attr = fs::symlink_metadata(&path)?;
379 let node = cache.get_or_create(ids, &path, &fs_attr, writable);
380 let attr = conv::attr_fs_to_fuse(
381 path.as_path(), node.inode(), node.getattr()?.nlink, &fs_attr);
382 (node, attr)
383 };
384 let dirent = Dirent {
385 node: child.clone(),
386 explicit_mapping: false,
387 };
388 state.children.insert(name.to_os_string(), dirent);
389 Ok((child, attr))
390 }
391
392 /// Obtains the node and attributes of an underlying file immediately after its creation.
393 ///
394 /// `writable` and `state` are the properties of the node, passed in as arguments because we
395 /// have to hold the node locked already.
396 ///
397 /// `path` and `name` are the path to the underlying file and the basename to lookup in the
398 /// directory, respectively. It is expected that the basename of `path` matches `name`.
399 ///
400 /// `exp_type` is the type of the node we expect to find for the just-created file. If the
401 /// node doesn't match this type, it means we encountered a race on the underlying file system
402 /// and we fail the lookup. (This is an artifact of how we currently implement this function
403 /// as this condition should just be impossible.)
404 fn post_create_lookup(writable: bool, state: &mut MutableDir, path: &Path, name: &OsStr,
405 exp_type: fuse::FileType, ids: &IdGenerator, cache: &dyn Cache)
406 -> NodeResult<(ArcNode, fuse::FileAttr)> {
407 debug_assert_eq!(path.file_name().unwrap(), name);
408
409 // TODO(https://github.com/bazelbuild/sandboxfs/issues/43): We abuse lookup here to handle
410 // the node creation and the child insertion into the directory, but we shouldn't do this
411 // because lookup performs an extra stat that we should not be issuing. But to resolve this
412 // we need to be able to synthesize the returned attr, which means we need to track ctimes
413 // internally.
414 match Dir::lookup_locked(writable, state, name, ids, cache) {
415 Ok((node, attr)) => {
416 if node.file_type_cached() != exp_type {
417 warn!("Newly-created file {} was replaced or deleted before create finished",
418 path.display());
419 return Err(KernelError::from_errno(errno::Errno::EIO));
420 }
421 Ok((node, attr))
422 },
423 Err(e) => {
424 if let Err(e) = fs::remove_file(&path) {
425 warn!("Failed to clean up newly-created {}: {}", path.display(), e);
426 }
427 Err(e)
428 }
429 }
430 }
431
432 /// Common implementation for the `rmdir` and `unlink` operations.
433 ///
434 /// The behavior of these operations differs only in the syscall we invoke to delete the
435 /// underlying entry, which is passed in as the `remove` parameter.
436 fn remove_any<R>(&self, name: &OsStr, remove: R, cache: &dyn Cache) -> NodeResult<()>
437 where R: Fn(&PathBuf) -> io::Result<()> {
438 let mut state = self.state.lock().unwrap();
439 let path = Dir::get_writable_path(&mut state, name)?;
440
441 remove(&path)?;
442
443 // Removing the underlying path from the cache is not racy within the same directory: we
444 // hold the directory node locked while we perform the operations below to remove the child,
445 // which means that no other lookup on the node can complete.
446 //
447 // However... this is racy if the same underlying path is mapped in more than one location
448 // because different lookups on different nodes could still race against the cache state.
449 // We don't bother for now though: the Rust FUSE library serializes all requests so this
450 // situation cannot arise.
451 let entry = state.children.remove(name)
452 .expect("Presence guaranteed by get_writable_path call above");
453 entry.node.delete(cache);
454 Ok(())
455 }
456 }
457
458 impl Node for Dir {
459 fn inode(&self) -> u64 {
460 self.inode
461 }
462
463 fn writable(&self) -> bool {
464 self.writable
465 }
466
467 fn file_type_cached(&self) -> fuse::FileType {
468 fuse::FileType::Directory
469 }
470
471 fn delete(&self, cache: &dyn Cache) {
472 let mut state = self.state.lock().unwrap();
473 assert!(
474 state.underlying_path.is_some(),
475 "Delete already called or trying to delete an explicit mapping");
476 cache.delete(state.underlying_path.as_ref().unwrap(), state.attr.kind);
477 state.underlying_path = None;
478 // Make the hard link count for the directory be zero. This is pretty much arbitrary as the
479 // semantics for hard link counts on directories are not well defined, and thus different
480 // OSes and file systems behave inconsistently. For example, Linux's FUSE forces this to
481 // zero, and macOS's APFS keeps this at 2.
482 debug_assert!(state.attr.nlink >= 2);
483 state.attr.nlink -= 2;
484 }
485
486 fn set_underlying_path(&self, path: &Path, cache: &dyn Cache) {
487 let mut state = self.state.lock().unwrap();
488 debug_assert!(state.underlying_path.is_some(),
489 "Renames should not have been allowed in scaffold or deleted nodes");
490 cache.rename(
491 state.underlying_path.as_ref().unwrap(), path.to_owned(), state.attr.kind);
492 state.underlying_path = Some(PathBuf::from(path));
493
494 // This is racy: if other file operations are going on inside this subtree, they will fail
495 // with ENOENT until we have updated their underlying paths after the move. However, as we
496 // are currently single-threaded (because the Rust FUSE bindings don't support multiple
497 // threads), we are fine.
498 for (name, dirent) in &state.children {
499 dirent.node.set_underlying_path(&path.join(name), cache);
500 }
501 }
502
503 fn find_subdir(&self, name: &OsStr, ids: &IdGenerator) -> Fallible<ArcNode> {
504 let mut state = self.state.lock().unwrap();
505
506 match state.children.get(name) {
507 Some(dirent) => {
508 ensure!(dirent.explicit_mapping, "Not a mapping");
509 Ok(dirent.node.clone())
510 },
511 None => {
512 let child = self.new_scaffold_child(None, name, ids, time::get_time());
513 let dirent = Dirent { node: child.clone(), explicit_mapping: true };
514 state.children.insert(name.to_os_string(), dirent);
515 Ok(child)
516 },
517 }
518 }
519
520 fn map(&self, components: &[Component], underlying_path: &Path, writable: bool,
521 ids: &IdGenerator, cache: &dyn Cache) -> Fallible<ArcNode> {
522 debug_assert!(
523 !components.is_empty(),
524 "Must not be reached because we don't have the containing ArcNode to return it");
525 let (name, remainder) = split_components(components);
526
527 let mut state = self.state.lock().unwrap();
528
529 if let Some(dirent) = state.children.get(name) {
530 // TODO(jmmv): We should probably mark this dirent as an explicit mapping if it already
531 // wasn't, but the Go variant of this code doesn't do this -- so investigate later.
532 ensure!(dirent.node.file_type_cached() == fuse::FileType::Directory
533 && !remainder.is_empty(), "Already mapped");
534 return dirent.node.map(remainder, underlying_path, writable, ids, cache);
535 }
536
537 let child = if remainder.is_empty() {
538 let fs_attr = fs::symlink_metadata(underlying_path)
539 .context(format!("Stat failed for {:?}", underlying_path))?;
540 cache.get_or_create(ids, underlying_path, &fs_attr, writable)
541 } else {
542 self.new_scaffold_child(state.underlying_path.as_ref(), name, ids, time::get_time())
543 };
544
545 let dirent = Dirent { node: child.clone(), explicit_mapping: true };
546 state.children.insert(name.to_os_string(), dirent);
547
548 if remainder.is_empty() {
549 Ok(child)
550 } else {
551 ensure!(child.file_type_cached() == fuse::FileType::Directory, "Already mapped");
552 child.map(remainder, underlying_path, writable, ids, cache)
553 }
554 }
555
556 fn unmap(&self, inodes: &mut Vec<u64>) -> Fallible<()> {
557 let mut state = self.state.lock().unwrap();
558 for dirent in state.children.values() {
559 dirent.node.unmap(inodes)?;
560 }
561 state.children.clear();
562
563 inodes.push(self.inode);
564 Ok(())
565 }
566
567 fn unmap_subdir(&self, name: &OsStr, inodes: &mut Vec<u64>) -> Fallible<()> {
568 let mut state = self.state.lock().unwrap();
569 match state.children.remove_entry(name) {
570 Some((name, dirent)) => {
571 if dirent.explicit_mapping {
572 dirent.node.unmap(inodes)
573 } else {
574 let err = format_err!("{:?} is not a mapping", &name);
575 state.children.insert(name, dirent);
576 Err(err)
577 }
578 },
579 None => Err(format_err!("Unknown entry")),
580 }
581 }
582
583 #[allow(clippy::type_complexity)]
584 fn create(&self, name: &OsStr, uid: unistd::Uid, gid: unistd::Gid, mode: u32, flags: u32,
585 ids: &IdGenerator, cache: &dyn Cache) -> NodeResult<(ArcNode, ArcHandle, fuse::FileAttr)> {
586 let mut state = self.state.lock().unwrap();
587 let path = Dir::get_writable_path(&mut state, name)?;
588
589 let mut options = conv::flags_to_openoptions(flags, self.writable)?;
590 options.create(true);
591 options.mode(mode);
592
593 let file = create_as(&path, uid, gid, |p| options.open(&p), |p| fs::remove_file(&p))?;
594 let (node, attr) = Dir::post_create_lookup(self.writable, &mut state, &path, name,
595 fuse::FileType::RegularFile, ids, cache)?;
596 Ok((node.clone(), node.handle_from(file), attr))
597 }
598
599 fn getattr(&self) -> NodeResult<fuse::FileAttr> {
600 let mut state = self.state.lock().unwrap();
601 Dir::getattr_locked(self.inode, &mut state)
602 }
603
604 fn getxattr(&self, name: &OsStr) -> NodeResult<Option<Vec<u8>>> {
605 let state = self.state.lock().unwrap();
606 match &state.underlying_path {
607 Some(path) => Ok(xattr::get(path, name)?),
608 None => Ok(None),
609 }
610 }
611
612 fn listxattr(&self) -> NodeResult<Option<xattr::XAttrs>> {
613 let state = self.state.lock().unwrap();
614 match &state.underlying_path {
615 Some(path) => Ok(Some(xattr::list(path)?)),
616 None => Ok(None),
617 }
618 }
619
620 fn lookup(&self, name: &OsStr, ids: &IdGenerator, cache: &dyn Cache)
621 -> NodeResult<(ArcNode, fuse::FileAttr)> {
622 let mut state = self.state.lock().unwrap();
623 Dir::lookup_locked(self.writable, &mut state, name, ids, cache)
624 }
625
626 fn mkdir(&self, name: &OsStr, uid: unistd::Uid, gid: unistd::Gid, mode: u32, ids: &IdGenerator,
627 cache: &dyn Cache) -> NodeResult<(ArcNode, fuse::FileAttr)> {
628 let mut state = self.state.lock().unwrap();
629 let path = Dir::get_writable_path(&mut state, name)?;
630
631 create_as(
632 &path, uid, gid,
633 |p| fs::DirBuilder::new().mode(mode).create(&p),
634 |p| fs::remove_dir(&p))?;
635 Dir::post_create_lookup(self.writable, &mut state, &path, name,
636 fuse::FileType::Directory, ids, cache)
637 }
638
639 fn mknod(&self, name: &OsStr, uid: unistd::Uid, gid: unistd::Gid, mode: u32, rdev: u32,
640 ids: &IdGenerator, cache: &dyn Cache) -> NodeResult<(ArcNode, fuse::FileAttr)> {
641 let mut state = self.state.lock().unwrap();
642 let path = Dir::get_writable_path(&mut state, name)?;
643
644 if mode > u32::from(std::u16::MAX) {
645 warn!("mknod got too-big mode {} (exceeds {})", mode, std::u16::MAX);
646 }
647 let mode = mode as sys::stat::mode_t;
648
649 // We have to break apart the incoming mode into a separate file type flag and a permissions
650 // set... only to have mknod() combine them later once again. Doesn't make a lot of sense
651 // but that the API we get from nix, hence ensure we are doing the right thing.
652 let (sflag, perm) = {
653 let sflag = sys::stat::SFlag::from_bits_truncate(mode);
654 let perm = sys::stat::Mode::from_bits_truncate(mode);
655
656 let truncated_mode = sflag.bits() | perm.bits();
657 if truncated_mode != mode {
658 warn!("mknod cannot only handle {} from mode {}", truncated_mode, mode);
659 }
660
661 (sflag, perm)
662 };
663
664 let exp_filetype = match sflag {
665 sys::stat::SFlag::S_IFBLK => fuse::FileType::BlockDevice,
666 sys::stat::SFlag::S_IFCHR => fuse::FileType::CharDevice,
667 sys::stat::SFlag::S_IFIFO => fuse::FileType::NamedPipe,
668 sys::stat::SFlag::S_IFREG => fuse::FileType::RegularFile,
669 _ => {
670 warn!("mknod received request to create {} with type {:?}, which is not supported",
671 path.display(), sflag);
672 return Err(KernelError::from_errno(errno::Errno::EIO));
673 },
674 };
675
676 #[allow(clippy::cast_lossless)]
677 create_as(
678 &path, uid, gid,
679 |p| sys::stat::mknod(p, sflag, perm, rdev as sys::stat::dev_t),
680 |p| unistd::unlink(p))?;
681 Dir::post_create_lookup(self.writable, &mut state, &path, name, exp_filetype, ids, cache)
682 }
683
684 fn open(&self, flags: u32) -> NodeResult<ArcHandle> {
685 let flags = flags as i32;
686 let oflag = fcntl::OFlag::from_bits_truncate(flags);
687
688 let handle = {
689 let state = self.state.lock().unwrap();
690
691 match state.underlying_path.as_ref() {
692 Some(path) => Some(rawdir::Dir::open(path, oflag, sys::stat::Mode::S_IRUSR)?),
693 None => None,
694 }
695 };
696
697 Ok(Arc::from(OpenDir {
698 inode: self.inode,
699 writable: self.writable,
700 state: self.state.clone(),
701 handle: Mutex::from(handle),
702 reply_contents: Mutex::from(vec!()),
703 }))
704 }
705
706 fn removexattr(&self, name: &OsStr) -> NodeResult<()> {
707 let state = self.state.lock().unwrap();
708 match &state.underlying_path {
709 Some(path) => Ok(xattr::remove(path, name)?),
710 None => Err(KernelError::from_errno(errno::Errno::EACCES)),
711 }
712 }
713
714 fn rename(&self, old_name: &OsStr, new_name: &OsStr, cache: &dyn Cache) -> NodeResult<()> {
715 let mut state = self.state.lock().unwrap();
716
717 let old_path = Dir::get_writable_path(&mut state, old_name)?;
718 let new_path = Dir::get_writable_path(&mut state, new_name)?;
719
720 fs::rename(&old_path, &new_path)?;
721
722 let dirent = state.children.remove(old_name)
723 .expect("get_writable_path call above ensured the child exists");
724 dirent.node.set_underlying_path(&new_path, cache);
725 state.children.insert(new_name.to_owned(), dirent);
726 Ok(())
727 }
728
729 fn rename_and_move_source(&self, old_name: &OsStr, new_dir: ArcNode, new_name: &OsStr,
730 cache: &dyn Cache) -> NodeResult<()> {
731 debug_assert!(self.inode != new_dir.as_ref().inode(),
732 "Same-directory renames have to be done via `rename`");
733
734 let mut state = self.state.lock().unwrap();
735
736 let old_path = Dir::get_writable_path(&mut state, old_name)?;
737
738 let (old_name, dirent) = state.children.remove_entry(old_name)
739 .expect("get_writable_path call above ensured the child exists");
740 let result = new_dir.rename_and_move_target(&dirent, &old_path, new_name, cache);
741 if result.is_err() {
742 // "Roll back" any changes we did to the current directory because the rename could not
743 // be completed on the target.
744 state.children.insert(old_name, dirent);
745 }
746 result
747 }
748
749 fn rename_and_move_target(&self, dirent: &Dirent, old_path: &Path, new_name: &OsStr,
750 cache: &dyn Cache) -> NodeResult<()> {
751 // We are locking the target node while the source node is already locked, so this can
752 // deadlock. The previous Go implementation of this code ordered the locks based on inode
753 // numbers so we should do the same thing here, but then this two-phase move implementation
754 // does not work.
755 //
756 // The benefit of this implementation, however, is that the rename-and-move operation is
757 // oblivious to the type of the target node type. We just delegate part of the move to the
758 // target, which can choose to operate as necessary.
759 //
760 // TODO(jmmv): Redo this once FUSE operations can be called concurrently, which at the
761 // moment are serialized because the FUSE library we use does not support concurrency. We
762 // have an integration test to catch this race, which will ensure this doesn't go unnoticed.
763 let mut state = self.state.lock().unwrap();
764
765 let new_path = Dir::get_writable_path(&mut state, new_name)?;
766
767 fs::rename(&old_path, &new_path)?;
768
769 dirent.node.set_underlying_path(&new_path, cache);
770 state.children.insert(new_name.to_owned(), dirent.clone());
771 Ok(())
772 }
773
774 fn rmdir(&self, name: &OsStr, cache: &dyn Cache) -> NodeResult<()> {
775 // TODO(jmmv): Figure out how to remove the redundant closure.
776 #[allow(clippy::redundant_closure)]
777 self.remove_any(name, |p| fs::remove_dir(p), cache)
778 }
779
780 fn setattr(&self, delta: &AttrDelta) -> NodeResult<fuse::FileAttr> {
781 let mut state = self.state.lock().unwrap();
782 state.attr = setattr(state.underlying_path.as_ref(), &state.attr, delta)?;
783 Ok(state.attr)
784 }
785
786 fn setxattr(&self, name: &OsStr, value: &[u8]) -> NodeResult<()> {
787 let state = self.state.lock().unwrap();
788 match &state.underlying_path {
789 Some(path) => Ok(xattr::set(path, name, value)?),
790 None => Err(KernelError::from_errno(errno::Errno::EACCES)),
791 }
792 }
793
794 fn symlink(&self, name: &OsStr, link: &Path, uid: unistd::Uid, gid: unistd::Gid,
795 ids: &IdGenerator, cache: &dyn Cache) -> NodeResult<(ArcNode, fuse::FileAttr)> {
796 let mut state = self.state.lock().unwrap();
797 let path = Dir::get_writable_path(&mut state, name)?;
798
799 create_as(&path, uid, gid, |p| unix_fs::symlink(link, &p), |p| fs::remove_file(&p))?;
800 Dir::post_create_lookup(self.writable, &mut state, &path, name,
801 fuse::FileType::Symlink, ids, cache)
802 }
803
804 fn unlink(&self, name: &OsStr, cache: &dyn Cache) -> NodeResult<()> {
805 // TODO(jmmv): Figure out how to remove the redundant closure.
806 #[allow(clippy::redundant_closure)]
807 self.remove_any(name, |p| fs::remove_file(p), cache)
808 }
809 }
810