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/aidl/com/android/virt/fs/IVirtFdService.aidl b/authfs/aidl/com/android/virt/fs/IVirtFdService.aidl
index bf4ac61..64828fb 100644
--- a/authfs/aidl/com/android/virt/fs/IVirtFdService.aidl
+++ b/authfs/aidl/com/android/virt/fs/IVirtFdService.aidl
@@ -79,4 +79,23 @@
* @return file FD that represents the new created directory.
*/
int createDirectoryInDirectory(int dirFd, String basename);
+
+ /** Filesystem stats that AuthFS is interested in.*/
+ parcelable FsStat {
+ /** Block size of the filesystem */
+ long blockSize;
+ /** Fragment size of the filesystem */
+ long fragmentSize;
+ /** Number of blocks in the filesystem */
+ long blockNumbers;
+ /** Number of free blocks */
+ long blockAvailable;
+ /** Number of free inodes */
+ long inodesAvailable;
+ /** Maximum filename length */
+ long maxFilename;
+ }
+
+ /** Returns relevant filesystem stats. */
+ FsStat statfs();
}
diff --git a/authfs/fd_server/src/aidl.rs b/authfs/fd_server/src/aidl.rs
index fa1914a..92be504 100644
--- a/authfs/fd_server/src/aidl.rs
+++ b/authfs/fd_server/src/aidl.rs
@@ -18,6 +18,7 @@
use log::error;
use nix::{
dir::Dir, errno::Errno, fcntl::openat, fcntl::OFlag, sys::stat::mkdirat, sys::stat::Mode,
+ sys::statvfs::statvfs, sys::statvfs::Statvfs,
};
use std::cmp::min;
use std::collections::{btree_map, BTreeMap};
@@ -31,7 +32,7 @@
use crate::fsverity;
use authfs_aidl_interface::aidl::com::android::virt::fs::IVirtFdService::{
- BnVirtFdService, IVirtFdService, MAX_REQUESTING_DATA,
+ BnVirtFdService, FsStat::FsStat, IVirtFdService, MAX_REQUESTING_DATA,
};
use authfs_aidl_interface::binder::{
BinderFeatures, ExceptionCode, Interface, Result as BinderResult, Status, StatusCode, Strong,
@@ -331,6 +332,22 @@
_ => Err(new_errno_error(Errno::ENOTDIR)),
})
}
+
+ fn statfs(&self) -> BinderResult<FsStat> {
+ let st = statvfs("/data").map_err(new_errno_error)?;
+ try_into_fs_stat(st).map_err(|_e| new_errno_error(Errno::EINVAL))
+ }
+}
+
+fn try_into_fs_stat(st: Statvfs) -> Result<FsStat, std::num::TryFromIntError> {
+ Ok(FsStat {
+ blockSize: st.block_size().try_into()?,
+ fragmentSize: st.fragment_size().try_into()?,
+ blockNumbers: st.blocks().try_into()?,
+ blockAvailable: st.blocks_available().try_into()?,
+ inodesAvailable: st.files_available().try_into()?,
+ maxFilename: st.name_max().try_into()?,
+ })
}
fn read_into_buf(file: &File, max_size: usize, offset: u64) -> io::Result<Vec<u8>> {
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")
}
diff --git a/authfs/tests/java/src/com/android/fs/AuthFsHostTest.java b/authfs/tests/java/src/com/android/fs/AuthFsHostTest.java
index c27c5cd..2d7668a 100644
--- a/authfs/tests/java/src/com/android/fs/AuthFsHostTest.java
+++ b/authfs/tests/java/src/com/android/fs/AuthFsHostTest.java
@@ -439,6 +439,18 @@
assertFailedOnMicrodroid("test -f " + authfsInputDir + "/system/bin/sh");
}
+ @Test
+ public void testStatfs() throws Exception {
+ // Setup
+ runFdServerOnAndroid("--open-dir 3:" + TEST_OUTPUT_DIR, "--rw-dirs 3");
+ runAuthFsOnMicrodroid("--remote-new-rw-dir 3 --cid " + VMADDR_CID_HOST);
+
+ // Verify
+ // Magic matches. Has only 2 inodes (root and "/3").
+ assertEquals(
+ FUSE_SUPER_MAGIC_HEX + " 2", runOnMicrodroid("stat -f -c '%t %c' " + MOUNT_DIR));
+ }
+
private void expectBackingFileConsistency(
String authFsPath, String backendPath, String expectedHash)
throws DeviceNotAvailableException {