Implement statfs for authfs
Fixes: 206465653
Test: atest AuthFsHostTest
Test: Manually run `stat -f /path/to/authfs`, result looks correct
Test: odrefresh finished without a hack to avoid calling statvfs
Change-Id: I4ddf9ab4f5d6dfd8369a045fcbff778be8a127da
diff --git a/authfs/src/fsstat.rs b/authfs/src/fsstat.rs
new file mode 100644
index 0000000..81eaca1
--- /dev/null
+++ b/authfs/src/fsstat.rs
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2021 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 crate::file::VirtFdService;
+use authfs_aidl_interface::aidl::com::android::virt::fs::IVirtFdService::FsStat::FsStat;
+
+/// Relevant/interesting stats of a remote filesystem.
+pub struct RemoteFsStats {
+ /// Block size of the filesystem
+ pub block_size: u64,
+ /// Fragment size of the filesystem
+ pub fragment_size: u64,
+ /// Number of blocks in the filesystem
+ pub block_numbers: u64,
+ /// Number of free blocks
+ pub block_available: u64,
+ /// Number of free inodes
+ pub inodes_available: u64,
+ /// Maximum filename length
+ pub max_filename: u64,
+}
+
+pub struct RemoteFsStatsReader {
+ service: VirtFdService,
+}
+
+impl RemoteFsStatsReader {
+ pub fn new(service: VirtFdService) -> Self {
+ Self { service }
+ }
+
+ pub fn statfs(&self) -> io::Result<RemoteFsStats> {
+ let st = self.service.statfs().map_err(|e| {
+ error!("Failed to call statfs on fd_server: {:?}", e);
+ io::Error::from_raw_os_error(libc::EIO)
+ })?;
+ try_into_remote_fs_stats(st).map_err(|_| {
+ error!("Received invalid stats from fd_server");
+ io::Error::from_raw_os_error(libc::EIO)
+ })
+ }
+}
+
+fn try_into_remote_fs_stats(st: FsStat) -> Result<RemoteFsStats, std::num::TryFromIntError> {
+ Ok(RemoteFsStats {
+ block_size: st.blockSize.try_into()?,
+ fragment_size: st.fragmentSize.try_into()?,
+ block_numbers: st.blockNumbers.try_into()?,
+ block_available: st.blockAvailable.try_into()?,
+ inodes_available: st.inodesAvailable.try_into()?,
+ max_filename: st.maxFilename.try_into()?,
+ })
+}
diff --git a/authfs/src/fusefs.rs b/authfs/src/fusefs.rs
index b456f33..89bac45 100644
--- a/authfs/src/fusefs.rs
+++ b/authfs/src/fusefs.rs
@@ -21,7 +21,7 @@
use std::ffi::{CStr, OsStr};
use std::fs::OpenOptions;
use std::io;
-use std::mem::MaybeUninit;
+use std::mem::{zeroed, MaybeUninit};
use std::option::Option;
use std::os::unix::{ffi::OsStrExt, io::AsRawFd};
use std::path::{Component, Path, PathBuf};
@@ -40,6 +40,7 @@
validate_basename, InMemoryDir, RandomWrite, ReadByChunk, RemoteDirEditor, RemoteFileEditor,
RemoteFileReader, RemoteMerkleTreeReader,
};
+use crate::fsstat::RemoteFsStatsReader;
use crate::fsverity::{VerifiedFileEditor, VerifiedFileReader};
pub type Inode = u64;
@@ -66,7 +67,7 @@
reader: VerifiedFileReader<RemoteFileReader, RemoteMerkleTreeReader>,
file_size: u64,
},
- /// A file type that is a read-only passthrough from a file on a remote serrver.
+ /// 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
/// integrity is guaranteed with private Merkle tree.
@@ -84,17 +85,25 @@
/// The next available inode number.
next_inode: AtomicU64,
+
+ /// A reader to access the remote filesystem stats, which is supposed to be of "the" output
+ /// directory. We assume all output are stored in the same partition.
+ remote_fs_stats_reader: RemoteFsStatsReader,
}
// Implementation for preparing an `AuthFs` instance, before starting to serve.
// TODO(victorhsieh): Consider implement a builder to separate the mutable initialization from the
// immutable / interiorly mutable serving phase.
impl AuthFs {
- pub fn new() -> AuthFs {
+ pub fn new(remote_fs_stats_reader: RemoteFsStatsReader) -> AuthFs {
let mut inode_table = BTreeMap::new();
inode_table.insert(ROOT_INODE, AuthFsEntry::ReadonlyDirectory { dir: InMemoryDir::new() });
- AuthFs { inode_table: Mutex::new(inode_table), next_inode: AtomicU64::new(ROOT_INODE + 1) }
+ AuthFs {
+ inode_table: Mutex::new(inode_table),
+ next_inode: AtomicU64::new(ROOT_INODE + 1),
+ remote_fs_stats_reader,
+ }
}
/// Add an `AuthFsEntry` as `basename` to the filesystem root.
@@ -672,6 +681,32 @@
attr_timeout: DEFAULT_METADATA_TIMEOUT,
})
}
+
+ fn statfs(&self, _ctx: Context, _inode: Self::Inode) -> io::Result<libc::statvfs64> {
+ let remote_stat = self.remote_fs_stats_reader.statfs()?;
+
+ // Safe because we are zero-initializing a struct with only POD fields. Not all fields
+ // matter to FUSE. See also:
+ // https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/fs/fuse/inode.c?h=v5.15#n460
+ let mut st: libc::statvfs64 = unsafe { zeroed() };
+
+ // Use the remote stat as a template, since it'd matter the most to consider the writable
+ // files/directories that are written to the remote.
+ st.f_bsize = remote_stat.block_size;
+ st.f_frsize = remote_stat.fragment_size;
+ st.f_blocks = remote_stat.block_numbers;
+ st.f_bavail = remote_stat.block_available;
+ st.f_favail = remote_stat.inodes_available;
+ st.f_namemax = remote_stat.max_filename;
+ // Assuming we are not privileged to use all free spaces on the remote server, set the free
+ // blocks/fragment to the same available amount.
+ st.f_bfree = st.f_bavail;
+ st.f_ffree = st.f_favail;
+ // Number of inodes on the filesystem
+ st.f_files = self.inode_table.lock().unwrap().len() as u64;
+
+ Ok(st)
+ }
}
/// Mount and start the FUSE instance. This requires CAP_SYS_ADMIN.
diff --git a/authfs/src/main.rs b/authfs/src/main.rs
index 0bc71ce..00a4614 100644
--- a/authfs/src/main.rs
+++ b/authfs/src/main.rs
@@ -37,6 +37,7 @@
mod common;
mod crypto;
mod file;
+mod fsstat;
mod fsverity;
mod fusefs;
@@ -44,6 +45,7 @@
use file::{
InMemoryDir, RemoteDirEditor, RemoteFileEditor, RemoteFileReader, RemoteMerkleTreeReader,
};
+use fsstat::RemoteFsStatsReader;
use fsverity::{VerifiedFileEditor, VerifiedFileReader};
use fusefs::{AuthFs, AuthFsEntry};
@@ -204,9 +206,11 @@
Ok(AuthFsEntry::VerifiedNewDirectory { dir })
}
-fn prepare_root_dir_entries(authfs: &mut AuthFs, args: &Args) -> Result<()> {
- let service = file::get_rpc_binder_service(args.cid)?;
-
+fn prepare_root_dir_entries(
+ service: file::VirtFdService,
+ authfs: &mut AuthFs,
+ args: &Args,
+) -> Result<()> {
for config in &args.remote_ro_file {
authfs.add_entry_at_root_dir(
remote_fd_to_path_buf(config.remote_fd),
@@ -303,8 +307,10 @@
android_logger::Config::default().with_tag("authfs").with_min_level(log_level),
);
- let mut authfs = AuthFs::new();
- prepare_root_dir_entries(&mut authfs, &args)?;
+ let service = file::get_rpc_binder_service(args.cid)?;
+ let mut authfs = AuthFs::new(RemoteFsStatsReader::new(service.clone()));
+ prepare_root_dir_entries(service, &mut authfs, &args)?;
+
fusefs::loop_forever(authfs, &args.mount_point, &args.extra_options)?;
bail!("Unexpected exit after the handler loop")
}