Implement root directory as a ReadonlyDirectory
This makes the handling in `lookup` consistent without having to deal
with inode 1 as a special case.
AuthFs is now initialized outside of fusefs module, in order to allow
the caller to add named entries to the filesystem's root directory more
directly.
The `ReadonlyDirectory` can also be used later to support remote
readonly directory.
Bug: 203251769
Test: atest AuthFsHostTest
Change-Id: Ia27f7f3c2f39d48559c329f6a086408745fbc3d9
diff --git a/authfs/src/file.rs b/authfs/src/file.rs
index bbe5e6c..6353209 100644
--- a/authfs/src/file.rs
+++ b/authfs/src/file.rs
@@ -1,12 +1,13 @@
-mod remote_dir;
+mod dir;
mod remote_file;
-pub use remote_dir::RemoteDirEditor;
+pub use dir::{InMemoryDir, RemoteDirEditor};
pub use remote_file::{RemoteFileEditor, RemoteFileReader, RemoteMerkleTreeReader};
use binder::unstable_api::{new_spibinder, AIBinder};
use binder::FromIBinder;
use std::io;
+use std::path::{Path, MAIN_SEPARATOR};
use crate::common::CHUNK_SIZE;
use authfs_aidl_interface::aidl::com::android::virt::fs::IVirtFdService::IVirtFdService;
@@ -71,3 +72,12 @@
/// Resizes the file to the new size.
fn resize(&self, size: u64) -> io::Result<()>;
}
+
+/// Checks whether the path is a simple file name without any directory separator.
+pub fn validate_basename(path: &Path) -> io::Result<()> {
+ if matches!(path.to_str(), Some(path_str) if !path_str.contains(MAIN_SEPARATOR)) {
+ Ok(())
+ } else {
+ Err(io::Error::from_raw_os_error(libc::EINVAL))
+ }
+}
diff --git a/authfs/src/file/remote_dir.rs b/authfs/src/file/dir.rs
similarity index 75%
rename from authfs/src/file/remote_dir.rs
rename to authfs/src/file/dir.rs
index d311b93..2eaaddd 100644
--- a/authfs/src/file/remote_dir.rs
+++ b/authfs/src/file/dir.rs
@@ -14,12 +14,12 @@
* limitations under the License.
*/
-use std::collections::HashMap;
+use std::collections::{hash_map, HashMap};
use std::io;
use std::path::{Path, PathBuf};
use super::remote_file::RemoteFileEditor;
-use super::{VirtFdService, VirtFdServiceStatus};
+use super::{validate_basename, VirtFdService, VirtFdServiceStatus};
use crate::fsverity::VerifiedFileEditor;
use crate::fusefs::Inode;
@@ -104,7 +104,8 @@
fn validate_argument(&self, basename: &Path) -> io::Result<()> {
// Kernel should only give us a basename.
- debug_assert!(basename.parent().is_none());
+ debug_assert!(validate_basename(basename).is_ok());
+
if self.entries.contains_key(basename) {
Err(io::Error::from_raw_os_error(libc::EEXIST))
} else if self.entries.len() >= MAX_ENTRIES.into() {
@@ -115,6 +116,43 @@
}
}
+/// An in-memory directory representation of a directory structure.
+pub struct InMemoryDir(HashMap<PathBuf, Inode>);
+
+impl InMemoryDir {
+ /// Creates an empty instance of `InMemoryDir`.
+ pub fn new() -> Self {
+ // Hash map is empty since "." and ".." are excluded in entries.
+ InMemoryDir(HashMap::new())
+ }
+
+ /// Returns the number of entries in the directory (not including "." and "..").
+ pub fn number_of_entries(&self) -> u16 {
+ self.0.len() as u16 // limited to MAX_ENTRIES
+ }
+
+ /// Adds an entry (name and the inode number) to the directory. Fails if already exists. The
+ /// caller is responsible for ensure the inode uniqueness.
+ pub fn add_entry(&mut self, basename: &Path, inode: Inode) -> io::Result<()> {
+ validate_basename(basename)?;
+ if self.0.len() >= MAX_ENTRIES.into() {
+ return Err(io::Error::from_raw_os_error(libc::EMLINK));
+ }
+
+ if let hash_map::Entry::Vacant(entry) = self.0.entry(basename.to_path_buf()) {
+ entry.insert(inode);
+ Ok(())
+ } else {
+ Err(io::Error::from_raw_os_error(libc::EEXIST))
+ }
+ }
+
+ /// Looks up an entry inode by name. `None` if not found.
+ pub fn lookup_inode(&self, basename: &Path) -> Option<Inode> {
+ self.0.get(basename).copied()
+ }
+}
+
fn into_io_error(e: VirtFdServiceStatus) -> io::Error {
let maybe_errno = e.service_specific_error();
if maybe_errno > 0 {
diff --git a/authfs/src/fusefs.rs b/authfs/src/fusefs.rs
index 3d1e2c7..ca73174 100644
--- a/authfs/src/fusefs.rs
+++ b/authfs/src/fusefs.rs
@@ -14,9 +14,9 @@
* limitations under the License.
*/
-use anyhow::Result;
+use anyhow::{bail, Result};
use log::{debug, warn};
-use std::collections::{btree_map, BTreeMap, HashMap};
+use std::collections::{btree_map, BTreeMap};
use std::convert::TryFrom;
use std::ffi::{CStr, OsStr};
use std::fs::OpenOptions;
@@ -37,7 +37,7 @@
use crate::common::{divide_roundup, ChunkedSizeIter, CHUNK_SIZE};
use crate::file::{
- RandomWrite, ReadByChunk, RemoteDirEditor, RemoteFileEditor, RemoteFileReader,
+ InMemoryDir, RandomWrite, ReadByChunk, RemoteDirEditor, RemoteFileEditor, RemoteFileReader,
RemoteMerkleTreeReader,
};
use crate::fsverity::{VerifiedFileEditor, VerifiedFileReader};
@@ -58,6 +58,8 @@
/// `AuthFsEntry` defines the filesystem entry type supported by AuthFS.
pub enum AuthFsEntry {
+ /// A read-only directory (writable during initialization). Root directory is an example.
+ ReadonlyDirectory { dir: InMemoryDir },
/// A file type that is verified against fs-verity signature (thus read-only). The file is
/// served from a remote server.
VerifiedReadonly {
@@ -75,38 +77,60 @@
}
// AuthFS needs to be `Sync` to be accepted by fuse::worker::start_message_loop as a `FileSystem`.
-struct AuthFs {
+pub struct AuthFs {
/// Table for `Inode` to `AuthFsEntry` lookup. This needs to be `Sync` to be used in
/// `fuse::worker::start_message_loop`.
inode_table: Mutex<BTreeMap<Inode, AuthFsEntry>>,
- /// Root directory entry table for path to `Inode` lookup. The root directory content should
- /// remain constant throughout the filesystem's lifetime.
- root_entries: HashMap<PathBuf, Inode>,
-
/// The next available inode number.
next_inode: AtomicU64,
}
+// Implementation for preparing an `AuthFs` instance, before starting to serve.
+// TODO(victorhsieh): Consider implement a builder to separate the mutable initialization from the
+// immutable / interiorly mutable serving phase.
impl AuthFs {
- pub fn new(root_entries_by_path: HashMap<PathBuf, AuthFsEntry>) -> AuthFs {
- let mut next_inode = ROOT_INODE + 1;
+ pub fn new() -> AuthFs {
let mut inode_table = BTreeMap::new();
- let mut root_entries = HashMap::new();
+ inode_table.insert(ROOT_INODE, AuthFsEntry::ReadonlyDirectory { dir: InMemoryDir::new() });
- root_entries_by_path.into_iter().for_each(|(path_buf, entry)| {
- root_entries.insert(path_buf, next_inode);
- inode_table.insert(next_inode, entry);
- next_inode += 1;
- });
-
- AuthFs {
- inode_table: Mutex::new(inode_table),
- root_entries,
- next_inode: AtomicU64::new(next_inode),
- }
+ AuthFs { inode_table: Mutex::new(inode_table), next_inode: AtomicU64::new(ROOT_INODE + 1) }
}
+ pub fn add_entry_at_root_dir(
+ &mut self,
+ basename: PathBuf,
+ entry: AuthFsEntry,
+ ) -> Result<Inode> {
+ if basename.is_absolute() {
+ bail!("Invalid entry name: {:?}", basename);
+ }
+
+ let inode_table = &mut *self.inode_table.get_mut().unwrap();
+ match inode_table
+ .get_mut(&ROOT_INODE)
+ .ok_or_else(|| io::Error::from_raw_os_error(libc::ENOENT))?
+ {
+ AuthFsEntry::ReadonlyDirectory { dir } => {
+ let new_inode = self.next_inode.fetch_add(1, Ordering::Relaxed);
+
+ dir.add_entry(&basename, new_inode)?;
+ if inode_table.insert(new_inode, entry).is_some() {
+ bail!(
+ "Found duplicated inode {} when adding {}",
+ new_inode,
+ basename.display()
+ );
+ }
+ Ok(new_inode)
+ }
+ _ => bail!("Not a ReadonlyDirectory"),
+ }
+ }
+}
+
+// Implementation for serving requests.
+impl AuthFs {
/// Handles the file associated with `inode` if found. This function returns whatever
/// `handle_fn` returns.
fn handle_inode<F, R>(&self, inode: &Inode, handle_fn: F) -> io::Result<R>
@@ -265,8 +289,8 @@
Ok(total)
}
-// No need to support enumerating directory entries.
-struct EmptyDirectoryIterator {}
+// TODO(205715172): Support enumerating directory entries.
+pub struct EmptyDirectoryIterator {}
impl DirectoryIterator for EmptyDirectoryIterator {
fn next(&mut self) -> Option<DirEntry> {
@@ -290,58 +314,47 @@
}
fn lookup(&self, _ctx: Context, parent: Inode, name: &CStr) -> io::Result<Entry> {
- if parent == ROOT_INODE {
- let inode = *self
- .root_entries
- .get(cstr_to_path(name))
- .ok_or_else(|| io::Error::from_raw_os_error(libc::ENOENT))?;
- // Normally, `lookup` is required to increase a reference count for the inode (while
- // `forget` will decrease it). It is not yet necessary until we start to support
- // deletion (only for `VerifiedNewDirectory`).
- let st = self.handle_inode(&inode, |config| match config {
- AuthFsEntry::UnverifiedReadonly { file_size, .. }
- | AuthFsEntry::VerifiedReadonly { file_size, .. } => {
- create_stat(inode, *file_size, AccessMode::ReadOnly)
- }
- AuthFsEntry::VerifiedNew { editor } => {
- create_stat(inode, editor.size(), AccessMode::ReadWrite)
- }
- AuthFsEntry::VerifiedNewDirectory { dir } => {
- create_dir_stat(inode, dir.number_of_entries())
- }
- })?;
- Ok(Entry {
- inode,
- generation: 0,
- attr: st,
- entry_timeout: DEFAULT_METADATA_TIMEOUT,
- attr_timeout: DEFAULT_METADATA_TIMEOUT,
- })
- } else {
- let inode = self.handle_inode(&parent, |config| match config {
- AuthFsEntry::VerifiedNewDirectory { dir } => {
- let path: &Path = cstr_to_path(name);
- dir.find_inode(path).ok_or_else(|| io::Error::from_raw_os_error(libc::ENOENT))
- }
- _ => Err(io::Error::from_raw_os_error(libc::ENOTDIR)),
- })?;
- let st = self.handle_inode(&inode, |config| match config {
- AuthFsEntry::VerifiedNew { editor } => {
- create_stat(inode, editor.size(), AccessMode::ReadWrite)
- }
- AuthFsEntry::VerifiedNewDirectory { dir } => {
- create_dir_stat(inode, dir.number_of_entries())
- }
- _ => Err(io::Error::from_raw_os_error(libc::EBADF)),
- })?;
- Ok(Entry {
- inode,
- generation: 0,
- attr: st,
- entry_timeout: DEFAULT_METADATA_TIMEOUT,
- attr_timeout: DEFAULT_METADATA_TIMEOUT,
- })
- }
+ // Look up the entry's inode number in parent directory.
+ let inode = self.handle_inode(&parent, |parent_entry| match parent_entry {
+ AuthFsEntry::ReadonlyDirectory { dir } => {
+ let path = cstr_to_path(name);
+ dir.lookup_inode(path).ok_or_else(|| io::Error::from_raw_os_error(libc::ENOENT))
+ }
+ AuthFsEntry::VerifiedNewDirectory { dir } => {
+ let path = cstr_to_path(name);
+ dir.find_inode(path).ok_or_else(|| io::Error::from_raw_os_error(libc::ENOENT))
+ }
+ _ => Err(io::Error::from_raw_os_error(libc::ENOTDIR)),
+ })?;
+
+ // Normally, `lookup` is required to increase a reference count for the inode (while
+ // `forget` will decrease it). It is not yet necessary until we start to support
+ // deletion (only for `VerifiedNewDirectory`).
+
+ // Create the entry's stat if found.
+ let st = self.handle_inode(&inode, |entry| match entry {
+ AuthFsEntry::ReadonlyDirectory { .. } => {
+ unreachable!("FUSE shouldn't need to look up the root inode");
+ //create_dir_stat(inode, dir.number_of_entries())
+ }
+ AuthFsEntry::UnverifiedReadonly { file_size, .. }
+ | AuthFsEntry::VerifiedReadonly { file_size, .. } => {
+ create_stat(inode, *file_size, AccessMode::ReadOnly)
+ }
+ AuthFsEntry::VerifiedNew { editor } => {
+ create_stat(inode, editor.size(), AccessMode::ReadWrite)
+ }
+ AuthFsEntry::VerifiedNewDirectory { dir } => {
+ create_dir_stat(inode, dir.number_of_entries())
+ }
+ })?;
+ Ok(Entry {
+ inode,
+ generation: 0,
+ attr: st,
+ entry_timeout: DEFAULT_METADATA_TIMEOUT,
+ attr_timeout: DEFAULT_METADATA_TIMEOUT,
+ })
}
fn getattr(
@@ -353,17 +366,20 @@
self.handle_inode(&inode, |config| {
Ok((
match config {
+ AuthFsEntry::ReadonlyDirectory { dir } => {
+ create_dir_stat(inode, dir.number_of_entries())
+ }
AuthFsEntry::UnverifiedReadonly { file_size, .. }
| AuthFsEntry::VerifiedReadonly { file_size, .. } => {
- create_stat(inode, *file_size, AccessMode::ReadOnly)?
+ create_stat(inode, *file_size, AccessMode::ReadOnly)
}
AuthFsEntry::VerifiedNew { editor } => {
- create_stat(inode, editor.size(), AccessMode::ReadWrite)?
+ create_stat(inode, editor.size(), AccessMode::ReadWrite)
}
AuthFsEntry::VerifiedNewDirectory { dir } => {
- create_dir_stat(inode, dir.number_of_entries())?
+ create_dir_stat(inode, dir.number_of_entries())
}
- },
+ }?,
DEFAULT_METADATA_TIMEOUT,
))
})
@@ -386,7 +402,8 @@
// No need to check access modes since all the modes are allowed to the
// read-writable file.
}
- AuthFsEntry::VerifiedNewDirectory { .. } => {
+ AuthFsEntry::ReadonlyDirectory { .. }
+ | AuthFsEntry::VerifiedNewDirectory { .. } => {
// TODO(victorhsieh): implement when needed.
return Err(io::Error::from_raw_os_error(libc::ENOSYS));
}
@@ -612,7 +629,7 @@
/// Mount and start the FUSE instance. This requires CAP_SYS_ADMIN.
pub fn loop_forever(
- root_entries: HashMap<PathBuf, AuthFsEntry>,
+ authfs: AuthFs,
mountpoint: &Path,
extra_options: &Option<String>,
) -> Result<(), fuse::Error> {
@@ -637,12 +654,7 @@
fuse::mount(mountpoint, "authfs", libc::MS_NOSUID | libc::MS_NODEV, &mount_options)
.expect("Failed to mount fuse");
- fuse::worker::start_message_loop(
- dev_fuse,
- MAX_WRITE_BYTES,
- MAX_READ_BYTES,
- AuthFs::new(root_entries),
- )
+ fuse::worker::start_message_loop(dev_fuse, MAX_WRITE_BYTES, MAX_READ_BYTES, authfs)
}
fn cstr_to_path(cstr: &CStr) -> &Path {
diff --git a/authfs/src/main.rs b/authfs/src/main.rs
index f6a2a56..ae446e3 100644
--- a/authfs/src/main.rs
+++ b/authfs/src/main.rs
@@ -29,7 +29,6 @@
use anyhow::{bail, Context, Result};
use log::error;
-use std::collections::HashMap;
use std::convert::TryInto;
use std::path::PathBuf;
use structopt::StructOpt;
@@ -44,7 +43,7 @@
use auth::FakeAuthenticator;
use file::{RemoteDirEditor, RemoteFileEditor, RemoteFileReader, RemoteMerkleTreeReader};
use fsverity::{VerifiedFileEditor, VerifiedFileReader};
-use fusefs::AuthFsEntry;
+use fusefs::{AuthFs, AuthFsEntry};
#[derive(StructOpt)]
struct Args {
@@ -160,51 +159,49 @@
Ok(AuthFsEntry::VerifiedNewDirectory { dir })
}
-fn prepare_root_dir_entries(args: &Args) -> Result<HashMap<PathBuf, AuthFsEntry>> {
- let mut root_entries = HashMap::new();
-
+fn prepare_root_dir_entries(authfs: &mut AuthFs, args: &Args) -> Result<()> {
let service = file::get_rpc_binder_service(args.cid)?;
for config in &args.remote_ro_file {
- root_entries.insert(
+ authfs.add_entry_at_root_dir(
remote_fd_to_path_buf(config.remote_fd),
new_remote_verified_file_entry(
service.clone(),
config.remote_fd,
service.getFileSize(config.remote_fd)?.try_into()?,
)?,
- );
+ )?;
}
for remote_fd in &args.remote_ro_file_unverified {
let remote_fd = *remote_fd;
- root_entries.insert(
+ authfs.add_entry_at_root_dir(
remote_fd_to_path_buf(remote_fd),
new_remote_unverified_file_entry(
service.clone(),
remote_fd,
service.getFileSize(remote_fd)?.try_into()?,
)?,
- );
+ )?;
}
for remote_fd in &args.remote_new_rw_file {
let remote_fd = *remote_fd;
- root_entries.insert(
+ authfs.add_entry_at_root_dir(
remote_fd_to_path_buf(remote_fd),
new_remote_new_verified_file_entry(service.clone(), remote_fd)?,
- );
+ )?;
}
for remote_fd in &args.remote_new_rw_dir {
let remote_fd = *remote_fd;
- root_entries.insert(
+ authfs.add_entry_at_root_dir(
remote_fd_to_path_buf(remote_fd),
new_remote_new_verified_dir_entry(service.clone(), remote_fd)?,
- );
+ )?;
}
- Ok(root_entries)
+ Ok(())
}
fn remote_fd_to_path_buf(fd: i32) -> PathBuf {
@@ -219,8 +216,9 @@
android_logger::Config::default().with_tag("authfs").with_min_level(log_level),
);
- let root_entries = prepare_root_dir_entries(&args)?;
- fusefs::loop_forever(root_entries, &args.mount_point, &args.extra_options)?;
+ let mut authfs = AuthFs::new();
+ prepare_root_dir_entries(&mut authfs, &args)?;
+ fusefs::loop_forever(authfs, &args.mount_point, &args.extra_options)?;
bail!("Unexpected exit after the handler loop")
}