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
This commit is contained in:
parent
72c774c9bd
commit
f7fc3d3665
|
@ -79,4 +79,23 @@ interface IVirtFdService {
|
|||
* @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();
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ use anyhow::Result;
|
|||
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 std::sync::{Arc, Mutex};
|
|||
|
||||
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 @@ impl IVirtFdService for FdService {
|
|||
_ => 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>> {
|
||||
|
|
|
@ -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()?,
|
||||
})
|
||||
}
|
|
@ -21,7 +21,7 @@ use std::convert::TryFrom;
|
|||
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 @@ use crate::file::{
|
|||
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 @@ pub enum AuthFsEntry {
|
|||
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 @@ pub struct AuthFs {
|
|||
|
||||
/// 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 @@ impl FileSystem for AuthFs {
|
|||
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.
|
||||
|
|
|
@ -37,6 +37,7 @@ mod auth;
|
|||
mod common;
|
||||
mod crypto;
|
||||
mod file;
|
||||
mod fsstat;
|
||||
mod fsverity;
|
||||
mod fusefs;
|
||||
|
||||
|
@ -44,6 +45,7 @@ use auth::FakeAuthenticator;
|
|||
use file::{
|
||||
InMemoryDir, RemoteDirEditor, RemoteFileEditor, RemoteFileReader, RemoteMerkleTreeReader,
|
||||
};
|
||||
use fsstat::RemoteFsStatsReader;
|
||||
use fsverity::{VerifiedFileEditor, VerifiedFileReader};
|
||||
use fusefs::{AuthFs, AuthFsEntry};
|
||||
|
||||
|
@ -204,9 +206,11 @@ fn new_remote_new_verified_dir_entry(
|
|||
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 @@ fn try_main() -> Result<()> {
|
|||
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")
|
||||
}
|
||||
|
|
|
@ -439,6 +439,18 @@ public final class AuthFsHostTest extends VirtualizationTestCaseBase {
|
|||
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 {
|
||||
|
|
Loading…
Reference in New Issue