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)?;