Support remote readonly directory

A remote readonly directory allows a user process to open/read files at
the configured mountpoint sub-directory, e.g.
/authfs/42/system/framework/framework.jar. Only allowlisted files are
visible.

There will be transparent integrity checks for all files under such a
directory, but it is not done in this change yet (tracked by
b/203251769).

See doc of `Args::remote_ro_dir` in main.rs for more details.

Bug: 203251769
Test: atest AuthFsHostTest

Change-Id: I716d6820a047761159c79947504579677c0fdeec
diff --git a/authfs/src/fusefs.rs b/authfs/src/fusefs.rs
index ca73174..b456f33 100644
--- a/authfs/src/fusefs.rs
+++ b/authfs/src/fusefs.rs
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-use anyhow::{bail, Result};
+use anyhow::{anyhow, bail, Result};
 use log::{debug, warn};
 use std::collections::{btree_map, BTreeMap};
 use std::convert::TryFrom;
@@ -24,7 +24,7 @@
 use std::mem::MaybeUninit;
 use std::option::Option;
 use std::os::unix::{ffi::OsStrExt, io::AsRawFd};
-use std::path::{Path, PathBuf};
+use std::path::{Component, Path, PathBuf};
 use std::sync::atomic::{AtomicU64, Ordering};
 use std::sync::Mutex;
 use std::time::Duration;
@@ -37,8 +37,8 @@
 
 use crate::common::{divide_roundup, ChunkedSizeIter, CHUNK_SIZE};
 use crate::file::{
-    InMemoryDir, RandomWrite, ReadByChunk, RemoteDirEditor, RemoteFileEditor, RemoteFileReader,
-    RemoteMerkleTreeReader,
+    validate_basename, InMemoryDir, RandomWrite, ReadByChunk, RemoteDirEditor, RemoteFileEditor,
+    RemoteFileReader, RemoteMerkleTreeReader,
 };
 use crate::fsverity::{VerifiedFileEditor, VerifiedFileReader};
 
@@ -97,34 +97,79 @@
         AuthFs { inode_table: Mutex::new(inode_table), next_inode: AtomicU64::new(ROOT_INODE + 1) }
     }
 
+    /// Add an `AuthFsEntry` as `basename` to the filesystem root.
     pub fn add_entry_at_root_dir(
         &mut self,
         basename: PathBuf,
         entry: AuthFsEntry,
     ) -> Result<Inode> {
-        if basename.is_absolute() {
-            bail!("Invalid entry name: {:?}", basename);
-        }
+        validate_basename(&basename)?;
+        self.add_entry_at_ro_dir_by_path(ROOT_INODE, &basename, entry)
+    }
 
-        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))?
-        {
+    /// Add an `AuthFsEntry` by path from the `ReadonlyDirectory` represented by `dir_inode`. The
+    /// path must be a related path. If some ancestor directories do not exist, they will be
+    /// created (also as `ReadonlyDirectory`) automatically.
+    pub fn add_entry_at_ro_dir_by_path(
+        &mut self,
+        dir_inode: Inode,
+        path: &Path,
+        entry: AuthFsEntry,
+    ) -> Result<Inode> {
+        // 1. Make sure the parent directories all exist. Derive the entry's parent inode.
+        let parent_path =
+            path.parent().ok_or_else(|| anyhow!("No parent directory: {:?}", path))?;
+        let parent_inode =
+            parent_path.components().try_fold(dir_inode, |current_dir_inode, path_component| {
+                match path_component {
+                    Component::RootDir => bail!("Absolute path is not supported"),
+                    Component::Normal(name) => {
+                        let inode_table = self.inode_table.get_mut().unwrap();
+                        // Locate the internal directory structure.
+                        let current_dir_entry =
+                            inode_table.get_mut(&current_dir_inode).ok_or_else(|| {
+                                anyhow!("Unknown directory inode {}", current_dir_inode)
+                            })?;
+                        let dir = match current_dir_entry {
+                            AuthFsEntry::ReadonlyDirectory { dir } => dir,
+                            _ => unreachable!("Not a ReadonlyDirectory"),
+                        };
+                        // Return directory inode. Create first if not exists.
+                        if let Some(existing_inode) = dir.lookup_inode(name.as_ref()) {
+                            Ok(existing_inode)
+                        } else {
+                            let new_inode = self.next_inode.fetch_add(1, Ordering::Relaxed);
+                            let new_dir_entry =
+                                AuthFsEntry::ReadonlyDirectory { dir: InMemoryDir::new() };
+
+                            // Actually update the tables.
+                            dir.add_entry(name.as_ref(), new_inode)?;
+                            if inode_table.insert(new_inode, new_dir_entry).is_some() {
+                                bail!("Unexpected to find a duplicated inode");
+                            }
+                            Ok(new_inode)
+                        }
+                    }
+                    _ => Err(anyhow!("Path is not canonical: {:?}", path)),
+                }
+            })?;
+
+        // 2. Insert the entry to the parent directory, as well as the inode table.
+        let inode_table = self.inode_table.get_mut().unwrap();
+        match inode_table.get_mut(&parent_inode).expect("previously returned inode") {
             AuthFsEntry::ReadonlyDirectory { dir } => {
+                let basename =
+                    path.file_name().ok_or_else(|| anyhow!("Bad file name: {:?}", path))?;
                 let new_inode = self.next_inode.fetch_add(1, Ordering::Relaxed);
 
-                dir.add_entry(&basename, new_inode)?;
+                // Actually update the tables.
+                dir.add_entry(basename.as_ref(), new_inode)?;
                 if inode_table.insert(new_inode, entry).is_some() {
-                    bail!(
-                        "Found duplicated inode {} when adding {}",
-                        new_inode,
-                        basename.display()
-                    );
+                    bail!("Unexpected to find a duplicated inode");
                 }
                 Ok(new_inode)
             }
-            _ => bail!("Not a ReadonlyDirectory"),
+            _ => unreachable!("Not a ReadonlyDirectory"),
         }
     }
 }
@@ -333,9 +378,8 @@
 
         // 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::ReadonlyDirectory { dir } => {
+                create_dir_stat(inode, dir.number_of_entries())
             }
             AuthFsEntry::UnverifiedReadonly { file_size, .. }
             | AuthFsEntry::VerifiedReadonly { file_size, .. } => {
@@ -613,6 +657,9 @@
                         let new_dir = dir.mkdir(basename, new_inode)?;
                         Ok(AuthFsEntry::VerifiedNewDirectory { dir: new_dir })
                     }
+                    AuthFsEntry::ReadonlyDirectory { .. } => {
+                        Err(io::Error::from_raw_os_error(libc::EACCES))
+                    }
                     _ => Err(io::Error::from_raw_os_error(libc::EBADF)),
                 }
             })?;