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/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 {