Skip to content

Commit

Permalink
Add a trivial root directory implementation (#24)
Browse files Browse the repository at this point in the history
This change adds the foundations of the in-memory representation of the
contents of a sandboxfs instance.  In particular, a Node trait that will
handle all node-related operations and the basics of a Dir
implementation.

The implementation of the root directory does nothing interesting at
this point: it just exposes the . and .. entries and returns ENOENT for
any lookups, which is sufficient to introduce the basic file system
concepts.

Note that this does not yet allow us to enable more tests in
travis-build.sh, so the new code introduced by this change was purely
tested locally by mounting the file system and ensuring an "ls -a" shows
the dot entries and that accessing any file returns ENOENT.
  • Loading branch information
jmmv committed Aug 21, 2018
1 parent d1c1eeb commit 625ac9a
Show file tree
Hide file tree
Showing 4 changed files with 254 additions and 1 deletion.
2 changes: 2 additions & 0 deletions Cargo.toml
Expand Up @@ -18,4 +18,6 @@ env_logger = "0.5"
failure = "0.1"
fuse = "0.3"
getopts = "0.2"
libc = "0.2"
log = "0.4"
time = "0.1"
81 changes: 80 additions & 1 deletion src/lib.rs
Expand Up @@ -12,25 +12,104 @@
// License for the specific language governing permissions and limitations
// under the License.

#[macro_use] extern crate failure;
extern crate fuse;
extern crate libc;
#[macro_use] extern crate log;
extern crate time;

use std::collections::HashMap;
use std::ffi::OsStr;
use std::io;
use std::path::Path;
use std::sync::{Arc, Mutex};
use time::Timespec;

mod nodes;

// TODO(jmmv): Make configurable via a flag and store inside SandboxFS.
pub const TTL: Timespec = Timespec { sec: 60, nsec: 0 };

/// FUSE file system implementation of sandboxfs.
struct SandboxFS {
/// Mapping of inode numbers to in-memory nodes that tracks all files known by sandboxfs.
nodes: Arc<Mutex<HashMap<u64, Arc<nodes::Node>>>>,
}

impl SandboxFS {
/// Creates a new `SandboxFS` instance.
fn new() -> SandboxFS {
SandboxFS {}
let root = {
let now = time::get_time();
let uid = unsafe { libc::getuid() } as u32;
let gid = unsafe { libc::getgid() } as u32;
nodes::Dir::new_root(now, uid, gid)
};

let mut nodes = HashMap::new();
nodes.insert(root.inode(), root);
SandboxFS {
nodes: Arc::from(Mutex::from(nodes)),
}
}

/// Gets a node given its `inode`.
///
/// We assume that the inode number is valid and that we have a known node for it; otherwise,
/// we crash. The rationale for this is that this function is always called on inode numbers
/// requested by the kernel, and we can trust that the kernel will only ever ask us for inode
/// numbers we have previously told it about.
fn find_node(&mut self, inode: u64) -> Arc<nodes::Node> {
let nodes = self.nodes.lock().unwrap();
match nodes.get(&inode) {
Some(node) => node.clone(),
None => panic!("Kernel requested unknown inode {}", inode),
}
}
}

impl fuse::Filesystem for SandboxFS {
fn getattr(&mut self, _req: &fuse::Request, inode: u64, reply: fuse::ReplyAttr) {
let node = self.find_node(inode);
match node.getattr() {
Ok(attr) => reply.attr(&TTL, &attr),
Err(e) => reply.error(e.errno()),
}
}

fn lookup(&mut self, _req: &fuse::Request, parent: u64, name: &OsStr, reply: fuse::ReplyEntry) {
let dir_node = self.find_node(parent);
match dir_node.lookup(name) {
Ok((node, attr)) => {
{
let mut nodes = self.nodes.lock().unwrap();
if !nodes.contains_key(&node.inode()) {
nodes.insert(node.inode(), node);
}
}
reply.entry(&TTL, &attr, 0);
},
Err(e) => reply.error(e.errno()),
}
}

fn readdir(&mut self, _req: &fuse::Request, inode: u64, _handle: u64, offset: i64,
mut reply: fuse::ReplyDirectory) {
if offset == 0 {
let node = self.find_node(inode);
match node.readdir(&mut reply) {
Ok(()) => reply.ok(),
Err(e) => reply.error(e.errno()),
}
} else {
assert!(offset > 0, "Do not know what to do with a negative offset");
// Our node.readdir() implementation reads the whole directory in one go. Therefore,
// if we get an offset different than zero, it's because the kernel has already
// completed the first read and is asking us for extra entries -- of which there will
// be none.
reply.ok();
}
}
}

/// Mounts a new sandboxfs instance on the given `mount_point`.
Expand Down
96 changes: 96 additions & 0 deletions src/nodes/dir.rs
@@ -0,0 +1,96 @@
// Copyright 2018 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy
// of the License at:
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.

extern crate fuse;
extern crate libc;
extern crate time;

use std::ffi::OsStr;
use std::sync::{Arc, Mutex};
use self::time::Timespec;
use super::KernelError;
use super::Node;
use super::NodeResult;

/// Representation of a directory node.
pub struct Dir {
inode: u64,
state: Mutex<MutableDir>,
}

/// Holds the mutable data of a directory node.
struct MutableDir {
parent: u64,
attr: fuse::FileAttr,
}

impl Dir {
/// Creates a new scaffold directory to represent the root directory.
///
/// `time` is the timestamp to be used for all node times.
///
/// `uid` and `gid` indicate the ownership details of the node. These should always match the
/// values of the currently-running process -- but not necessarily if we want to let users
/// customize these via flags at some point.
pub fn new_root(time: Timespec, uid: u32, gid: u32) -> Arc<Node> {
let inode = fuse::FUSE_ROOT_ID;

let attr = fuse::FileAttr {
ino: inode,
kind: fuse::FileType::Directory,
nlink: 2, // "." entry plus whichever initial named node points at this.
size: 2, // TODO(jmmv): Reevaluate what directory sizes should be.
blocks: 1, // TODO(jmmv): Reevaluate what directory blocks should be.
atime: time,
mtime: time,
ctime: time,
crtime: time,
perm: 0o555 as u16, // Scaffold directories cannot be mutated by the user.
uid: uid,
gid: gid,
rdev: 0,
flags: 0,
};

let state = MutableDir { parent: inode, attr };

Arc::new(Dir {
inode,
state: Mutex::from(state),
})
}
}

impl Node for Dir {
fn inode(&self) -> u64 {
self.inode
}

fn getattr(&self) -> NodeResult<fuse::FileAttr> {
let state = self.state.lock().unwrap();
Ok(state.attr.clone())
}

fn lookup(&self, _name: &OsStr) -> NodeResult<(Arc<Node>, fuse::FileAttr)> {
return Err(KernelError::from_errno(libc::ENOENT));
}

fn readdir(&self, reply: &mut fuse::ReplyDirectory) -> NodeResult<()> {
let state = self.state.lock().unwrap();

reply.add(self.inode, 0, fuse::FileType::Directory, ".");
reply.add(state.parent, 1, fuse::FileType::Directory, "..");
Ok(())
}
}
76 changes: 76 additions & 0 deletions src/nodes/mod.rs
@@ -0,0 +1,76 @@
// Copyright 2018 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy
// of the License at:
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.

use fuse;
use std::ffi::OsStr;
use std::sync::Arc;

mod dir;
pub use self::dir::Dir;

/// Type that represents an error understood by the kernel.
#[derive(Debug, Fail)]
#[fail(display = "errno={}", errno)]
pub struct KernelError {
errno: i32,
}

impl KernelError {
/// Constructs a new error given a raw errno code.
fn from_errno(errno: i32) -> KernelError {
KernelError { errno }
}

/// Obtains the errno code contained in this error, which can be fed back into the kernel.
pub fn errno(&self) -> i32 {
return self.errno;
}
}

/// Generic result type for of all node operations.
pub type NodeResult<T> = Result<T, KernelError>;

/// Abstract representation of a file system node.
///
/// Due to the way nodes and node operations are represented in the kernel, this trait exposes a
/// collection of methods that do not all make sense for all possible node types: some methods will
/// only make sense for directories and others will only make sense for regular files, for example.
/// These conflicting methods come with a default implementation that panics.
pub trait Node {
/// Returns the inode number of this node.
///
/// The inode number is immutable and, as such, this information can be queried without having
/// to lock the node.
fn inode(&self) -> u64;

/// Retrieves the node's metadata.
fn getattr(&self) -> NodeResult<fuse::FileAttr>;

/// Looks up a node with the given name within the current node and returns the found node and
/// its attributes at the time of the query.
///
/// The attributes are returned to avoid having to relock the node on the caller side in order
/// to supply those attributes to the kernel.
fn lookup(&self, _name: &OsStr) -> NodeResult<(Arc<Node>, fuse::FileAttr)> {
panic!("Not implemented");
}

/// Reads all directory entries into the given reply object.
///
/// It is the responsibility of the caller to invoke `reply.ok()` on the reply object. This is
/// for consistency with the handling of any errors returned by this function.
fn readdir(&self, _reply: &mut fuse::ReplyDirectory) -> NodeResult<()> {
panic!("Not implemented");
}
}

0 comments on commit 625ac9a

Please sign in to comment.