Merge "Implement statfs for authfs"
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 {