authfs: instantiate read-only files lazily

The earlier implementation instantiates all remote files (defined in the
build manifest) at start. The instantiation includes 1) asking fd_server
to create the actual FDs, and 2) fetch the file's Merkle tree. The
instantiation of all files happens during AuthFS start, even if many
files aren't/won't be used.

This change makes the instnaitation lazy, i.e. 1) and 2) will only
happen when the file is first used.

Bug: 205883847
Test: atest AuthFsHostTest ComposHostTestCases
Change-Id: I2e4ee48b5442b56937e212505526b2f26eaadd91
diff --git a/authfs/src/fsverity/verifier.rs b/authfs/src/fsverity/verifier.rs
index 61b8e13..aaf4bf7 100644
--- a/authfs/src/fsverity/verifier.rs
+++ b/authfs/src/fsverity/verifier.rs
@@ -112,8 +112,8 @@
 }
 
 pub struct VerifiedFileReader<F: ReadByChunk, M: ReadByChunk> {
+    pub file_size: u64,
     chunked_file: F,
-    file_size: u64,
     merkle_tree: M,
     root_hash: HashBuffer,
 }
diff --git a/authfs/src/fusefs.rs b/authfs/src/fusefs.rs
index 84129b6..9b866f5 100644
--- a/authfs/src/fusefs.rs
+++ b/authfs/src/fusefs.rs
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+mod file;
 mod mount;
 
 use anyhow::{anyhow, bail, Result};
@@ -37,12 +38,13 @@
 
 use crate::common::{divide_roundup, ChunkedSizeIter, CHUNK_SIZE};
 use crate::file::{
-    validate_basename, Attr, EagerChunkReader, InMemoryDir, RandomWrite, ReadByChunk,
-    RemoteDirEditor, RemoteFileEditor, RemoteFileReader,
+    validate_basename, Attr, InMemoryDir, RandomWrite, ReadByChunk, RemoteDirEditor,
+    RemoteFileEditor, RemoteFileReader,
 };
 use crate::fsstat::RemoteFsStatsReader;
-use crate::fsverity::{VerifiedFileEditor, VerifiedFileReader};
+use crate::fsverity::VerifiedFileEditor;
 
+pub use self::file::LazyVerifiedReadonlyFile;
 pub use self::mount::mount_and_enter_message_loop;
 use self::mount::MAX_WRITE_BYTES;
 
@@ -61,10 +63,7 @@
     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 {
-        reader: VerifiedFileReader<RemoteFileReader, EagerChunkReader>,
-        file_size: u64,
-    },
+    VerifiedReadonly { reader: LazyVerifiedReadonlyFile },
     /// A file type that is a read-only passthrough from a file on a remote server.
     UnverifiedReadonly { reader: RemoteFileReader, file_size: u64 },
     /// A file type that is initially empty, and the content is stored on a remote server. File
@@ -537,10 +536,12 @@
                     AuthFsEntry::ReadonlyDirectory { dir } => {
                         create_dir_stat(inode, dir.number_of_entries(), AccessMode::ReadOnly)
                     }
-                    AuthFsEntry::UnverifiedReadonly { file_size, .. }
-                    | AuthFsEntry::VerifiedReadonly { file_size, .. } => {
+                    AuthFsEntry::UnverifiedReadonly { file_size, .. } => {
                         create_stat(inode, *file_size, AccessMode::ReadOnly)
                     }
+                    AuthFsEntry::VerifiedReadonly { reader } => {
+                        create_stat(inode, reader.file_size()?, AccessMode::ReadOnly)
+                    }
                     AuthFsEntry::VerifiedNew { editor, attr, .. } => {
                         create_stat(inode, editor.size(), AccessMode::Variable(attr.mode()))
                     }
@@ -608,10 +609,12 @@
                     AuthFsEntry::ReadonlyDirectory { dir } => {
                         create_dir_stat(inode, dir.number_of_entries(), AccessMode::ReadOnly)
                     }
-                    AuthFsEntry::UnverifiedReadonly { file_size, .. }
-                    | AuthFsEntry::VerifiedReadonly { file_size, .. } => {
+                    AuthFsEntry::UnverifiedReadonly { file_size, .. } => {
                         create_stat(inode, *file_size, AccessMode::ReadOnly)
                     }
+                    AuthFsEntry::VerifiedReadonly { reader } => {
+                        create_stat(inode, reader.file_size()?, AccessMode::ReadOnly)
+                    }
                     AuthFsEntry::VerifiedNew { editor, attr, .. } => {
                         create_stat(inode, editor.size(), AccessMode::Variable(attr.mode()))
                     }
@@ -708,8 +711,8 @@
     ) -> io::Result<usize> {
         self.handle_inode(&inode, |config| {
             match config {
-                AuthFsEntry::VerifiedReadonly { reader, file_size } => {
-                    read_chunks(w, reader, *file_size, offset, size)
+                AuthFsEntry::VerifiedReadonly { reader } => {
+                    read_chunks(w, reader, reader.file_size()?, offset, size)
                 }
                 AuthFsEntry::UnverifiedReadonly { reader, file_size } => {
                     read_chunks(w, reader, *file_size, offset, size)
diff --git a/authfs/src/fusefs/file.rs b/authfs/src/fusefs/file.rs
new file mode 100644
index 0000000..8c02281
--- /dev/null
+++ b/authfs/src/fusefs/file.rs
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+use log::error;
+use std::convert::TryInto;
+use std::io;
+use std::path::PathBuf;
+use std::sync::Mutex;
+
+use crate::file::{
+    ChunkBuffer, EagerChunkReader, ReadByChunk, RemoteFileReader, RemoteMerkleTreeReader,
+    VirtFdService,
+};
+use crate::fsverity::{merkle_tree_size, VerifiedFileReader};
+
+enum FileInfo {
+    ByPathUnderDirFd(i32, PathBuf),
+    ByFd(i32),
+}
+
+type Reader = VerifiedFileReader<RemoteFileReader, EagerChunkReader>;
+
+/// A lazily created read-only file that is verified against the given fs-verity digest.
+///
+/// The main purpose of this struct is to wrap and construct `VerifiedFileReader` lazily.
+pub struct LazyVerifiedReadonlyFile {
+    expected_digest: Vec<u8>,
+
+    service: VirtFdService,
+    file_info: FileInfo,
+
+    /// A lazily instantiated reader.
+    reader: Mutex<Option<Reader>>,
+}
+
+impl LazyVerifiedReadonlyFile {
+    /// Prepare the file by a remote path, related to a remote directory FD.
+    pub fn prepare_by_path(
+        service: VirtFdService,
+        remote_dir_fd: i32,
+        remote_path: PathBuf,
+        expected_digest: Vec<u8>,
+    ) -> Self {
+        LazyVerifiedReadonlyFile {
+            service,
+            file_info: FileInfo::ByPathUnderDirFd(remote_dir_fd, remote_path),
+            expected_digest,
+            reader: Mutex::new(None),
+        }
+    }
+
+    /// Prepare the file by a remote file FD.
+    pub fn prepare_by_fd(service: VirtFdService, remote_fd: i32, expected_digest: Vec<u8>) -> Self {
+        LazyVerifiedReadonlyFile {
+            service,
+            file_info: FileInfo::ByFd(remote_fd),
+            expected_digest,
+            reader: Mutex::new(None),
+        }
+    }
+
+    fn ensure_init_then<F, T>(&self, callback: F) -> io::Result<T>
+    where
+        F: FnOnce(&Reader) -> io::Result<T>,
+    {
+        let mut reader = self.reader.lock().unwrap();
+        if reader.is_none() {
+            let remote_file = match &self.file_info {
+                FileInfo::ByPathUnderDirFd(dir_fd, related_path) => {
+                    RemoteFileReader::new_by_path(self.service.clone(), *dir_fd, related_path)?
+                }
+                FileInfo::ByFd(file_fd) => RemoteFileReader::new(self.service.clone(), *file_fd),
+            };
+            let remote_fd = remote_file.get_remote_fd();
+            let file_size = self
+                .service
+                .getFileSize(remote_fd)
+                .map_err(|e| {
+                    error!("Failed to get file size of remote fd {}: {}", remote_fd, e);
+                    io::Error::from_raw_os_error(libc::EIO)
+                })?
+                .try_into()
+                .map_err(|e| {
+                    error!("Failed convert file size: {}", e);
+                    io::Error::from_raw_os_error(libc::EIO)
+                })?;
+            let instance = VerifiedFileReader::new(
+                remote_file,
+                file_size,
+                &self.expected_digest,
+                EagerChunkReader::new(
+                    RemoteMerkleTreeReader::new(self.service.clone(), remote_fd),
+                    merkle_tree_size(file_size),
+                )?,
+            )
+            .map_err(|e| {
+                error!("Failed instantiate a verified file reader: {}", e);
+                io::Error::from_raw_os_error(libc::EIO)
+            })?;
+            *reader = Some(instance);
+        }
+        callback(reader.as_ref().unwrap())
+    }
+
+    pub fn file_size(&self) -> io::Result<u64> {
+        self.ensure_init_then(|reader| Ok(reader.file_size))
+    }
+}
+
+impl ReadByChunk for LazyVerifiedReadonlyFile {
+    fn read_chunk(&self, chunk_index: u64, buf: &mut ChunkBuffer) -> io::Result<usize> {
+        self.ensure_init_then(|reader| reader.read_chunk(chunk_index, buf))
+    }
+}
diff --git a/authfs/src/main.rs b/authfs/src/main.rs
index f664ca2..bdca5b4 100644
--- a/authfs/src/main.rs
+++ b/authfs/src/main.rs
@@ -47,14 +47,11 @@
 mod fsverity;
 mod fusefs;
 
-use file::{
-    Attr, EagerChunkReader, InMemoryDir, RemoteDirEditor, RemoteFileEditor, RemoteFileReader,
-    RemoteMerkleTreeReader,
-};
+use file::{Attr, InMemoryDir, RemoteDirEditor, RemoteFileEditor, RemoteFileReader};
 use fsstat::RemoteFsStatsReader;
-use fsverity::{merkle_tree_size, VerifiedFileEditor, VerifiedFileReader};
+use fsverity::VerifiedFileEditor;
 use fsverity_digests_proto::fsverity_digests::FSVerityDigests;
-use fusefs::{AuthFs, AuthFsEntry};
+use fusefs::{AuthFs, AuthFsEntry, LazyVerifiedReadonlyFile};
 
 #[derive(StructOpt)]
 struct Args {
@@ -186,19 +183,13 @@
     service: file::VirtFdService,
     remote_fd: i32,
     expected_digest: &str,
-    file_size: u64,
 ) -> Result<AuthFsEntry> {
     Ok(AuthFsEntry::VerifiedReadonly {
-        reader: VerifiedFileReader::new(
-            RemoteFileReader::new(service.clone(), remote_fd),
-            file_size,
-            &from_hex_string(expected_digest)?,
-            EagerChunkReader::new(
-                RemoteMerkleTreeReader::new(service.clone(), remote_fd),
-                merkle_tree_size(file_size),
-            )?,
-        )?,
-        file_size,
+        reader: LazyVerifiedReadonlyFile::prepare_by_fd(
+            service,
+            remote_fd,
+            from_hex_string(expected_digest)?,
+        ),
     })
 }
 
@@ -239,12 +230,7 @@
     for config in &args.remote_ro_file {
         authfs.add_entry_at_root_dir(
             remote_fd_to_path_buf(config.remote_fd),
-            new_remote_verified_file_entry(
-                service.clone(),
-                config.remote_fd,
-                &config.digest,
-                service.getFileSize(config.remote_fd)?.try_into()?,
-            )?,
+            new_remote_verified_file_entry(service.clone(), config.remote_fd, &config.digest)?,
         )?;
     }
 
@@ -294,25 +280,13 @@
                 let remote_path_str = path_str.strip_prefix(&config.prefix).ok_or_else(|| {
                     anyhow!("Expect path {} to match prefix {}", path_str, config.prefix)
                 })?;
-                // TODO(205883847): Not all files will be used. Open the remote file lazily.
-                let remote_file = RemoteFileReader::new_by_path(
-                    service.clone(),
-                    config.remote_dir_fd,
-                    Path::new(remote_path_str),
-                )?;
-                let remote_fd = remote_file.get_remote_fd();
-                let file_size = service.getFileSize(remote_fd)?.try_into()?;
                 AuthFsEntry::VerifiedReadonly {
-                    reader: VerifiedFileReader::new(
-                        remote_file,
-                        file_size,
-                        &digest.digest,
-                        EagerChunkReader::new(
-                            RemoteMerkleTreeReader::new(service.clone(), remote_fd),
-                            merkle_tree_size(file_size),
-                        )?,
-                    )?,
-                    file_size,
+                    reader: LazyVerifiedReadonlyFile::prepare_by_path(
+                        service.clone(),
+                        config.remote_dir_fd,
+                        PathBuf::from(remote_path_str),
+                        digest.digest.clone(),
+                    ),
                 }
             };
             authfs.add_entry_at_ro_dir_by_path(dir_root_inode, Path::new(path_str), file_entry)?;