Merge "Fix potential uninitialized variables bug"
diff --git a/authfs/src/file/remote_file.rs b/authfs/src/file/remote_file.rs
index 903c143..039285f 100644
--- a/authfs/src/file/remote_file.rs
+++ b/authfs/src/file/remote_file.rs
@@ -17,6 +17,7 @@
 use std::cmp::min;
 use std::convert::TryFrom;
 use std::io;
+use std::path::Path;
 
 use super::{ChunkBuffer, RandomWrite, ReadByChunk, VirtFdService};
 use crate::common::CHUNK_SIZE;
@@ -48,6 +49,29 @@
     pub fn new(service: VirtFdService, file_fd: i32) -> Self {
         RemoteFileReader { service, file_fd }
     }
+
+    pub fn new_by_path(
+        service: VirtFdService,
+        dir_fd: i32,
+        related_path: &Path,
+    ) -> io::Result<Self> {
+        let file_fd =
+            service.openFileInDirectory(dir_fd, related_path.to_str().unwrap()).map_err(|e| {
+                io::Error::new(
+                    io::ErrorKind::Other,
+                    format!(
+                        "Failed to create a remote file reader by path {}: {}",
+                        related_path.display(),
+                        e.get_description()
+                    ),
+                )
+            })?;
+        Ok(RemoteFileReader { service, file_fd })
+    }
+
+    pub fn get_remote_fd(&self) -> i32 {
+        self.file_fd
+    }
 }
 
 impl ReadByChunk for RemoteFileReader {
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)),
                 }
             })?;
diff --git a/authfs/src/main.rs b/authfs/src/main.rs
index ae446e3..f08d9ea 100644
--- a/authfs/src/main.rs
+++ b/authfs/src/main.rs
@@ -30,7 +30,7 @@
 use anyhow::{bail, Context, Result};
 use log::error;
 use std::convert::TryInto;
-use std::path::PathBuf;
+use std::path::{Path, PathBuf};
 use structopt::StructOpt;
 
 mod auth;
@@ -41,7 +41,9 @@
 mod fusefs;
 
 use auth::FakeAuthenticator;
-use file::{RemoteDirEditor, RemoteFileEditor, RemoteFileReader, RemoteMerkleTreeReader};
+use file::{
+    InMemoryDir, RemoteDirEditor, RemoteFileEditor, RemoteFileReader, RemoteMerkleTreeReader,
+};
 use fsverity::{VerifiedFileEditor, VerifiedFileReader};
 use fusefs::{AuthFs, AuthFsEntry};
 
@@ -80,6 +82,23 @@
     #[structopt(long)]
     remote_new_rw_file: Vec<i32>,
 
+    /// A read-only directory that represents a remote directory. The directory view is constructed
+    /// and finalized during the filesystem initialization based on the provided mapping file
+    /// (which is a serialized protobuf of android.security.fsverity.FSVerityDigests, which
+    /// essentially provides <file path, fs-verity digest> mappings of exported files). The mapping
+    /// file is supposed to come from a trusted location in order to provide a trusted view as well
+    /// as verified access of included files with their fs-verity digest. Not all files on the
+    /// remote host may be included in the mapping file, so the directory view may be partial. The
+    /// directory structure won't change throughout the filesystem lifetime.
+    ///
+    /// For example, `--remote-ro-dir 5:/path/to/mapping:/prefix/` tells the filesystem to
+    /// construct a directory structure defined in the mapping file at $MOUNTPOINT/5, which may
+    /// include a file like /5/system/framework/framework.jar. "/prefix/" tells the filesystem to
+    /// strip the path (e.g. "/system/") from the mount point to match the expected location of the
+    /// remote FD (e.g. a directory FD of "/system" in the remote).
+    #[structopt(long, parse(try_from_str = parse_remote_new_ro_dir_option))]
+    remote_ro_dir: Vec<OptionRemoteRoDir>,
+
     /// A new directory that is assumed empty in the backing filesystem. New files created in this
     /// directory are integrity-protected in the same way as --remote-new-verified-file. Can be
     /// multiple.
@@ -103,6 +122,20 @@
     _certificate_path: PathBuf,
 }
 
+struct OptionRemoteRoDir {
+    /// ID to refer to the remote dir.
+    remote_dir_fd: i32,
+
+    /// A mapping file that describes the expecting file/directory structure and integrity metadata
+    /// in the remote directory. The file contains serialized protobuf of
+    /// android.security.fsverity.FSVerityDigests.
+    /// TODO(203251769): Really use the file when it's generated.
+    #[allow(dead_code)]
+    mapping_file_path: PathBuf,
+
+    prefix: PathBuf,
+}
+
 fn parse_remote_ro_file_option(option: &str) -> Result<OptionRemoteRoFile> {
     let strs: Vec<&str> = option.split(':').collect();
     if strs.len() != 2 {
@@ -114,6 +147,18 @@
     })
 }
 
+fn parse_remote_new_ro_dir_option(option: &str) -> Result<OptionRemoteRoDir> {
+    let strs: Vec<&str> = option.split(':').collect();
+    if strs.len() != 3 {
+        bail!("Invalid option: {}", option);
+    }
+    Ok(OptionRemoteRoDir {
+        remote_dir_fd: strs[0].parse::<i32>().unwrap(),
+        mapping_file_path: PathBuf::from(strs[1]),
+        prefix: PathBuf::from(strs[2]),
+    })
+}
+
 fn new_remote_verified_file_entry(
     service: file::VirtFdService,
     remote_fd: i32,
@@ -201,6 +246,39 @@
         )?;
     }
 
+    for config in &args.remote_ro_dir {
+        let dir_root_inode = authfs.add_entry_at_root_dir(
+            remote_fd_to_path_buf(config.remote_dir_fd),
+            AuthFsEntry::ReadonlyDirectory { dir: InMemoryDir::new() },
+        )?;
+
+        // TODO(203251769): Read actual path from config.mapping_file_path when it's generated.
+        let paths = vec![
+            Path::new("/system/framework/framework.jar"),
+            Path::new("/system/framework/services.jar"),
+        ];
+
+        for path in &paths {
+            let file_entry = {
+                // TODO(205883847): Not all files will be used. Open the remote file lazily.
+                let related_path = path.strip_prefix(&config.prefix)?;
+                let remote_file = RemoteFileReader::new_by_path(
+                    service.clone(),
+                    config.remote_dir_fd,
+                    related_path,
+                )?;
+                let file_size = service.getFileSize(remote_file.get_remote_fd())?.try_into()?;
+                // TODO(203251769): Switch to VerifiedReadonly
+                AuthFsEntry::UnverifiedReadonly { reader: remote_file, file_size }
+            };
+            authfs.add_entry_at_ro_dir_by_path(
+                dir_root_inode,
+                path.strip_prefix("/")?,
+                file_entry,
+            )?;
+        }
+    }
+
     Ok(())
 }
 
diff --git a/authfs/tests/java/src/com/android/fs/AuthFsHostTest.java b/authfs/tests/java/src/com/android/fs/AuthFsHostTest.java
index 8a13ef3..70d48c2 100644
--- a/authfs/tests/java/src/com/android/fs/AuthFsHostTest.java
+++ b/authfs/tests/java/src/com/android/fs/AuthFsHostTest.java
@@ -407,6 +407,38 @@
         assertFailedOnMicrodroid("mkdir " + authfsOutputDir + "/some_dir/dir");
     }
 
+    @Test
+    public void testInputDirectory_CanReadFile() throws Exception {
+        // Setup
+        String authfsInputDir = MOUNT_DIR + "/3";
+        runFdServerOnAndroid("--open-dir 3:/system", "--ro-dirs 3");
+        // TODO(203251769): Replace /dev/null with real manifest file when it's generated. We
+        // currently hard-coded the files for the test manually, and ignore the integrity check.
+        runAuthFsOnMicrodroid("--remote-ro-dir 3:/dev/null:/system --cid " + VMADDR_CID_HOST);
+
+        // Action
+        String actualHash =
+                computeFileHashOnMicrodroid(authfsInputDir + "/system/framework/framework.jar");
+
+        // Verify
+        String expectedHash = computeFileHashOnAndroid("/system/framework/framework.jar");
+        assertEquals("Expect consistent hash through /authfs/3: ", expectedHash, actualHash);
+    }
+
+    @Test
+    public void testInputDirectory_OnlyAllowlistedFilesExist() throws Exception {
+        // Setup
+        String authfsInputDir = MOUNT_DIR + "/3";
+        runFdServerOnAndroid("--open-dir 3:/system", "--ro-dirs 3");
+        // TODO(203251769): Replace /dev/null with real manifest file when it's generated. We
+        // currently hard-coded the files for the test manually, and ignore the integrity check.
+        runAuthFsOnMicrodroid("--remote-ro-dir 3:/dev/null:/system --cid " + VMADDR_CID_HOST);
+
+        // Verify
+        runOnMicrodroid("test -f " + authfsInputDir + "/system/framework/services.jar");
+        assertFailedOnMicrodroid("test -f " + authfsInputDir + "/system/bin/sh");
+    }
+
     private void expectBackingFileConsistency(
             String authFsPath, String backendPath, String expectedHash)
             throws DeviceNotAvailableException {
diff --git a/tests/benchmark/Android.bp b/tests/benchmark/Android.bp
index 1826524..cf9d16e 100644
--- a/tests/benchmark/Android.bp
+++ b/tests/benchmark/Android.bp
@@ -1,3 +1,7 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 cc_binary {
     name: "fs_benchmark",
     static_executable: true,