Assign inode values properly in AuthFS
Previously, inode_table provides inode -> entry mapping, and the inode
value is derived from the entry name, which is supposed to be the remote
FD value. That is, remote FD was equal to the filesystem entry name as
string and the actual inode value inside the filesystem.
This change makes inode an internal notion in the filesystem, which is
really the correct way. When a client provides the path -> entry mapping
(root_entries), AuthFS now breaks it into two mappings (see below) and
assigns the inode for its own internal purpose.
To look up an entry in to root directory, we use the path -> inode
table (root_entries) to find the inode, then look for the actual entry
with the inode -> entry table (inode_table).
This gives us the freedom to add more inodes without risking breaking
existing entries. It also makes it possible to create inodes not
backing by a remote FD. For example, later we could open file by path
like "framework/framework.jar" relative to a path FD of /system on the
host side. That is, we wouldn't necessarily need to open
"/system/framework" and create an inode, then use that inode to open
"framework.jar".
Bug: 203251769
Test: atest AuthFsHostTest
Change-Id: I49b222b91002da12a25fbf03b0c6285f0223f16b
diff --git a/authfs/src/fusefs.rs b/authfs/src/fusefs.rs
index 2370acb..64ccc41 100644
--- a/authfs/src/fusefs.rs
+++ b/authfs/src/fusefs.rs
@@ -16,7 +16,7 @@
use anyhow::Result;
use log::{debug, error, warn};
-use std::collections::{btree_map, BTreeMap};
+use std::collections::{btree_map, BTreeMap, HashMap};
use std::convert::TryFrom;
use std::ffi::{CStr, OsStr};
use std::fs::OpenOptions;
@@ -24,8 +24,8 @@
use std::mem::MaybeUninit;
use std::option::Option;
use std::os::unix::{ffi::OsStrExt, io::AsRawFd};
-use std::path::Path;
-use std::sync::{Arc, Mutex};
+use std::path::{Path, PathBuf};
+use std::sync::Mutex;
use std::time::Duration;
use fuse::filesystem::{
@@ -65,10 +65,15 @@
VerifiedNewDirectory { dir: RemoteDirEditor },
}
+// AuthFS needs to be `Sync` to be accepted by fuse::worker::start_message_loop as a `FileSystem`.
struct AuthFs {
- /// A table for looking up an `AuthFsEntry` given an `Inode`. This needs to be `Sync` to be
- /// used in `fuse::worker::start_message_loop`.
- inode_table: Arc<Mutex<BTreeMap<Inode, AuthFsEntry>>>,
+ /// 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>,
/// Maximum bytes in the write transaction to the FUSE device. This limits the maximum buffer
/// size in a read request (including FUSE protocol overhead) that the filesystem writes to.
@@ -76,9 +81,18 @@
}
impl AuthFs {
- pub fn new(root_entries: Arc<Mutex<BTreeMap<Inode, AuthFsEntry>>>, max_write: u32) -> AuthFs {
- // TODO(203251769): Make root_entries a path -> entry map, then assign inodes internally.
- AuthFs { inode_table: root_entries, max_write }
+ pub fn new(root_entries_by_path: HashMap<PathBuf, AuthFsEntry>, max_write: u32) -> AuthFs {
+ let mut next_inode = ROOT_INODE + 1;
+ let mut inode_table = BTreeMap::new();
+ let mut root_entries = HashMap::new();
+
+ root_entries_by_path.into_iter().for_each(|(path_buf, entry)| {
+ next_inode += 1;
+ root_entries.insert(path_buf, next_inode);
+ inode_table.insert(next_inode, entry);
+ });
+
+ AuthFs { inode_table: Mutex::new(inode_table), root_entries, max_write }
}
/// Handles the file associated with `inode` if found. This function returns whatever
@@ -254,17 +268,14 @@
}
fn lookup(&self, _ctx: Context, parent: Inode, name: &CStr) -> io::Result<Entry> {
- // TODO(victorhsieh): convert root directory (inode == 1) to a readonly directory. Right
- // now, it's the (global) inode pool, so all inodes can be accessed from root.
if parent == ROOT_INODE {
- // Only accept file name that looks like an integrer. Files in the pool are simply
- // exposed by their inode number. Also, there is currently no directory structure.
- let num = name.to_str().map_err(|_| io::Error::from_raw_os_error(libc::EINVAL))?;
+ 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 necessary here since the files are configured
- // to be static.
- let inode =
- num.parse::<Inode>().map_err(|_| io::Error::from_raw_os_error(libc::ENOENT))?;
+ // `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, .. } => {
@@ -575,7 +586,7 @@
/// Mount and start the FUSE instance. This requires CAP_SYS_ADMIN.
pub fn loop_forever(
- root_entries: BTreeMap<Inode, AuthFsEntry>,
+ root_entries: HashMap<PathBuf, AuthFsEntry>,
mountpoint: &Path,
extra_options: &Option<String>,
) -> Result<(), fuse::Error> {
@@ -606,7 +617,7 @@
dev_fuse,
max_write,
max_read,
- AuthFs::new(Arc::new(Mutex::new(root_entries)), max_write),
+ AuthFs::new(root_entries, max_write),
)
}
diff --git a/authfs/src/main.rs b/authfs/src/main.rs
index 432cfab..f6a2a56 100644
--- a/authfs/src/main.rs
+++ b/authfs/src/main.rs
@@ -29,7 +29,7 @@
use anyhow::{bail, Context, Result};
use log::error;
-use std::collections::BTreeMap;
+use std::collections::HashMap;
use std::convert::TryInto;
use std::path::PathBuf;
use structopt::StructOpt;
@@ -44,7 +44,7 @@
use auth::FakeAuthenticator;
use file::{RemoteDirEditor, RemoteFileEditor, RemoteFileReader, RemoteMerkleTreeReader};
use fsverity::{VerifiedFileEditor, VerifiedFileReader};
-use fusefs::{AuthFsEntry, Inode};
+use fusefs::AuthFsEntry;
#[derive(StructOpt)]
struct Args {
@@ -160,14 +160,14 @@
Ok(AuthFsEntry::VerifiedNewDirectory { dir })
}
-fn prepare_root_dir_entries(args: &Args) -> Result<BTreeMap<Inode, AuthFsEntry>> {
- let mut root_entries = BTreeMap::new();
+fn prepare_root_dir_entries(args: &Args) -> Result<HashMap<PathBuf, AuthFsEntry>> {
+ let mut root_entries = HashMap::new();
let service = file::get_rpc_binder_service(args.cid)?;
for config in &args.remote_ro_file {
root_entries.insert(
- config.remote_fd.try_into()?,
+ remote_fd_to_path_buf(config.remote_fd),
new_remote_verified_file_entry(
service.clone(),
config.remote_fd,
@@ -179,7 +179,7 @@
for remote_fd in &args.remote_ro_file_unverified {
let remote_fd = *remote_fd;
root_entries.insert(
- remote_fd.try_into()?,
+ remote_fd_to_path_buf(remote_fd),
new_remote_unverified_file_entry(
service.clone(),
remote_fd,
@@ -191,7 +191,7 @@
for remote_fd in &args.remote_new_rw_file {
let remote_fd = *remote_fd;
root_entries.insert(
- remote_fd.try_into()?,
+ remote_fd_to_path_buf(remote_fd),
new_remote_new_verified_file_entry(service.clone(), remote_fd)?,
);
}
@@ -199,7 +199,7 @@
for remote_fd in &args.remote_new_rw_dir {
let remote_fd = *remote_fd;
root_entries.insert(
- remote_fd.try_into()?,
+ remote_fd_to_path_buf(remote_fd),
new_remote_new_verified_dir_entry(service.clone(), remote_fd)?,
);
}
@@ -207,6 +207,10 @@
Ok(root_entries)
}
+fn remote_fd_to_path_buf(fd: i32) -> PathBuf {
+ PathBuf::from(fd.to_string())
+}
+
fn try_main() -> Result<()> {
let args = Args::from_args();