authfs: Support write config/operation in fuse
Bug: 171279640
Test: atest
Test: tools/device-test.sh
Change-Id: Ic611f72d51a5522d9ec6e6fdc82c115b5782c4ac
diff --git a/authfs/src/file.rs b/authfs/src/file.rs
index 1d52631..89fbd9d 100644
--- a/authfs/src/file.rs
+++ b/authfs/src/file.rs
@@ -2,7 +2,7 @@
mod remote_file;
pub use local_file::LocalFileReader;
-pub use remote_file::{RemoteFileReader, RemoteMerkleTreeReader};
+pub use remote_file::{RemoteFileEditor, RemoteFileReader, RemoteMerkleTreeReader};
use std::io;
diff --git a/authfs/src/file/remote_file.rs b/authfs/src/file/remote_file.rs
index b87891b..dbf6bd9 100644
--- a/authfs/src/file/remote_file.rs
+++ b/authfs/src/file/remote_file.rs
@@ -97,7 +97,6 @@
}
impl RemoteFileEditor {
- #[allow(dead_code)]
pub fn new(service: Arc<Mutex<VirtFdService>>, file_fd: i32) -> Self {
RemoteFileEditor { service, file_fd }
}
diff --git a/authfs/src/fsverity/builder.rs b/authfs/src/fsverity/builder.rs
index 86c4969..94b9718 100644
--- a/authfs/src/fsverity/builder.rs
+++ b/authfs/src/fsverity/builder.rs
@@ -53,6 +53,11 @@
Self { leaves: Vec::new(), file_size: 0 }
}
+ /// Gets size of the file represented by `MerkleLeaves`.
+ pub fn file_size(&self) -> u64 {
+ self.file_size
+ }
+
/// Updates the hash of the `index`-th leaf, and increase the size to `size_at_least` if the
/// current size is smaller.
pub fn update_hash(&mut self, index: usize, hash: &Sha256Hash, size_at_least: u64) {
diff --git a/authfs/src/fsverity/editor.rs b/authfs/src/fsverity/editor.rs
index fc4e101..543e9ac 100644
--- a/authfs/src/fsverity/editor.rs
+++ b/authfs/src/fsverity/editor.rs
@@ -75,7 +75,6 @@
merkle_tree: Arc<RwLock<MerkleLeaves>>,
}
-#[allow(dead_code)]
impl<F: ReadOnlyDataByChunk + RandomWrite> VerifiedFileEditor<F> {
/// Wraps a supposedly new file for integrity protection.
pub fn new(file: F) -> Self {
@@ -83,6 +82,7 @@
}
/// Calculates the fs-verity digest of the current file.
+ #[allow(dead_code)]
pub fn calculate_fsverity_digest(&self) -> io::Result<Sha256Hash> {
let merkle_tree = self.merkle_tree.read().unwrap();
merkle_tree.calculate_fsverity_digest().map_err(|e| io::Error::new(io::ErrorKind::Other, e))
@@ -142,6 +142,10 @@
)
}
}
+
+ pub fn size(&self) -> u64 {
+ self.merkle_tree.read().unwrap().file_size()
+ }
}
impl<F: ReadOnlyDataByChunk + RandomWrite> RandomWrite for VerifiedFileEditor<F> {
diff --git a/authfs/src/fusefs.rs b/authfs/src/fusefs.rs
index bdf7cd8..13ec87d 100644
--- a/authfs/src/fusefs.rs
+++ b/authfs/src/fusefs.rs
@@ -26,12 +26,18 @@
use std::path::Path;
use std::time::Duration;
-use fuse::filesystem::{Context, DirEntry, DirectoryIterator, Entry, FileSystem, ZeroCopyWriter};
+use fuse::filesystem::{
+ Context, DirEntry, DirectoryIterator, Entry, FileSystem, FsOptions, ZeroCopyReader,
+ ZeroCopyWriter,
+};
use fuse::mount::MountOption;
use crate::common::{divide_roundup, ChunkedSizeIter, CHUNK_SIZE};
-use crate::file::{LocalFileReader, ReadOnlyDataByChunk, RemoteFileReader, RemoteMerkleTreeReader};
-use crate::fsverity::VerifiedFileReader;
+use crate::file::{
+ LocalFileReader, RandomWrite, ReadOnlyDataByChunk, RemoteFileEditor, RemoteFileReader,
+ RemoteMerkleTreeReader,
+};
+use crate::fsverity::{VerifiedFileEditor, VerifiedFileReader};
const DEFAULT_METADATA_TIMEOUT: std::time::Duration = Duration::from_secs(5);
@@ -43,6 +49,7 @@
LocalUnverifiedReadonlyFile(LocalFileReader, u64),
RemoteVerifiedReadonlyFile(VerifiedFileReader<RemoteFileReader, RemoteMerkleTreeReader>, u64),
RemoteUnverifiedReadonlyFile(RemoteFileReader, u64),
+ RemoteVerifiedNewFile(VerifiedFileEditor<RemoteFileEditor>),
}
struct AuthFs {
@@ -84,11 +91,20 @@
}
}
-fn create_stat(ino: libc::ino_t, file_size: u64) -> io::Result<libc::stat64> {
+enum FileMode {
+ ReadOnly,
+ ReadWrite,
+}
+
+fn create_stat(ino: libc::ino_t, file_size: u64, file_mode: FileMode) -> io::Result<libc::stat64> {
let mut st = unsafe { MaybeUninit::<libc::stat64>::zeroed().assume_init() };
st.st_ino = ino;
- st.st_mode = libc::S_IFREG | libc::S_IRUSR | libc::S_IRGRP | libc::S_IROTH;
+ st.st_mode = match file_mode {
+ // Until needed, let's just grant the owner access.
+ FileMode::ReadOnly => libc::S_IFREG | libc::S_IRUSR,
+ FileMode::ReadWrite => libc::S_IFREG | libc::S_IRUSR | libc::S_IWUSR,
+ };
st.st_dev = 0;
st.st_nlink = 1;
st.st_uid = 0;
@@ -160,6 +176,12 @@
self.max_write
}
+ fn init(&self, _capable: FsOptions) -> io::Result<FsOptions> {
+ // Enable writeback cache for better performance especially since our bandwidth to the
+ // backend service is limited.
+ Ok(FsOptions::WRITEBACK_CACHE)
+ }
+
fn lookup(&self, _ctx: Context, _parent: Inode, name: &CStr) -> io::Result<Entry> {
// Only accept file name that looks like an integrer. Files in the pool are simply exposed
// by their inode number. Also, there is currently no directory structure.
@@ -173,7 +195,10 @@
| FileConfig::LocalUnverifiedReadonlyFile(_, file_size)
| FileConfig::RemoteUnverifiedReadonlyFile(_, file_size)
| FileConfig::RemoteVerifiedReadonlyFile(_, file_size) => {
- create_stat(inode, *file_size)?
+ create_stat(inode, *file_size, FileMode::ReadOnly)?
+ }
+ FileConfig::RemoteVerifiedNewFile(file) => {
+ create_stat(inode, file.size(), FileMode::ReadWrite)?
}
};
Ok(Entry {
@@ -197,7 +222,10 @@
| FileConfig::LocalUnverifiedReadonlyFile(_, file_size)
| FileConfig::RemoteUnverifiedReadonlyFile(_, file_size)
| FileConfig::RemoteVerifiedReadonlyFile(_, file_size) => {
- create_stat(inode, *file_size)?
+ create_stat(inode, *file_size, FileMode::ReadOnly)?
+ }
+ FileConfig::RemoteVerifiedNewFile(file) => {
+ create_stat(inode, file.size(), FileMode::ReadWrite)?
}
},
DEFAULT_METADATA_TIMEOUT,
@@ -229,6 +257,11 @@
// direct I/O here to avoid double cache.
Ok((None, fuse::sys::OpenOptions::DIRECT_IO))
}
+ FileConfig::RemoteVerifiedNewFile(_) => {
+ // No need to check access modes since all the modes are allowed to the
+ // read-writable file.
+ Ok((None, fuse::sys::OpenOptions::KEEP_CACHE))
+ }
}
}
@@ -256,6 +289,33 @@
FileConfig::RemoteUnverifiedReadonlyFile(file, file_size) => {
read_chunks(w, file, *file_size, offset, size)
}
+ FileConfig::RemoteVerifiedNewFile(file) => {
+ // Note that with FsOptions::WRITEBACK_CACHE, it's possible for the kernel to
+ // request a read even if the file is open with O_WRONLY.
+ read_chunks(w, file, file.size(), offset, size)
+ }
+ }
+ }
+
+ fn write<R: io::Read + ZeroCopyReader>(
+ &self,
+ _ctx: Context,
+ inode: Self::Inode,
+ _handle: Self::Handle,
+ mut r: R,
+ size: u32,
+ offset: u64,
+ _lock_owner: Option<u64>,
+ _delayed_write: bool,
+ _flags: u32,
+ ) -> io::Result<usize> {
+ match self.get_file_config(&inode)? {
+ FileConfig::RemoteVerifiedNewFile(file) => {
+ let mut buf = vec![0; size as usize];
+ r.read_exact(&mut buf)?;
+ file.write_at(&buf, offset)
+ }
+ _ => Err(io::Error::from_raw_os_error(libc::EBADF)),
}
}
}
diff --git a/authfs/src/main.rs b/authfs/src/main.rs
index 39482e3..a4b0d40 100644
--- a/authfs/src/main.rs
+++ b/authfs/src/main.rs
@@ -43,8 +43,8 @@
mod fusefs;
use auth::FakeAuthenticator;
-use file::{LocalFileReader, RemoteFileReader, RemoteMerkleTreeReader};
-use fsverity::VerifiedFileReader;
+use file::{LocalFileReader, RemoteFileEditor, RemoteFileReader, RemoteMerkleTreeReader};
+use fsverity::{VerifiedFileEditor, VerifiedFileReader};
use fusefs::{FileConfig, Inode};
#[derive(StructOpt)]
@@ -68,6 +68,13 @@
#[structopt(long, parse(try_from_str = parse_remote_ro_file_unverified_option))]
remote_ro_file_unverified: Vec<OptionRemoteRoFileUnverified>,
+ /// A new read-writable remote file with integrity check. Can be multiple.
+ ///
+ /// For example, `--remote-new-verified-file 12:34` tells the filesystem to associate entry 12
+ /// with a remote file 34.
+ #[structopt(long, parse(try_from_str = parse_remote_new_rw_file_option))]
+ remote_new_rw_file: Vec<OptionRemoteRwFile>,
+
/// Debug only. A read-only local file with integrity check. Can be multiple.
#[structopt(long, parse(try_from_str = parse_local_file_ro_option))]
local_ro_file: Vec<OptionLocalFileRo>,
@@ -102,6 +109,13 @@
file_size: u64,
}
+struct OptionRemoteRwFile {
+ ino: Inode,
+
+ /// ID to refer to the remote file.
+ remote_id: i32,
+}
+
struct OptionLocalFileRo {
ino: Inode,
@@ -151,6 +165,17 @@
})
}
+fn parse_remote_new_rw_file_option(option: &str) -> Result<OptionRemoteRwFile> {
+ let strs: Vec<&str> = option.split(':').collect();
+ if strs.len() != 2 {
+ bail!("Invalid option: {}", option);
+ }
+ Ok(OptionRemoteRwFile {
+ ino: strs[0].parse::<Inode>().unwrap(),
+ remote_id: strs[1].parse::<i32>().unwrap(),
+ })
+}
+
fn parse_local_file_ro_option(option: &str) -> Result<OptionLocalFileRo> {
let strs: Vec<&str> = option.split(':').collect();
if strs.len() != 5 {
@@ -223,6 +248,12 @@
Ok(FileConfig::LocalUnverifiedReadonlyFile(file_reader, file_size))
}
+fn new_config_remote_new_verified_file(remote_id: i32) -> Result<FileConfig> {
+ let remote_file =
+ RemoteFileEditor::new(Arc::new(Mutex::new(file::get_local_binder())), remote_id);
+ Ok(FileConfig::RemoteVerifiedNewFile(VerifiedFileEditor::new(remote_file)))
+}
+
fn prepare_file_pool(args: &Args) -> Result<BTreeMap<Inode, FileConfig>> {
let mut file_pool = BTreeMap::new();
@@ -240,6 +271,10 @@
);
}
+ for config in &args.remote_new_rw_file {
+ file_pool.insert(config.ino, new_config_remote_new_verified_file(config.remote_id)?);
+ }
+
for config in &args.local_ro_file {
file_pool.insert(
config.ino,
diff --git a/authfs/tools/device-test.sh b/authfs/tools/device-test.sh
index e85ad17..82aa6bc 100755
--- a/authfs/tools/device-test.sh
+++ b/authfs/tools/device-test.sh
@@ -6,20 +6,28 @@
#
# Setup:
# $ adb push testdata/input.4m* /data/local/tmp
+# $ adb push tools/device-test.sh /data/local/tmp/
#
# Shell 1:
-# $ adb shell 'cd /data/local/tmp && exec 9</system/bin/sh 8<input.4m 7<input.4m.merkle_dump 6<input.4m.fsv_sig 5<input.4m 4<input.4m.merkle_dump.bad 3<input.4m.fsv_sig fd_server --ro-fds 9 --ro-fds 8:7:6 --ro-fds 5:4:3'
+# $ adb shell /data/local/tmp/device-test.sh --run-fd-server
#
# Shell 2:
-# $ adb push tools/device-test.sh /data/local/tmp/ && adb shell /data/local/tmp/device-test.sh
+# $ adb shell /data/local/tmp/device-test.sh
+
+cd /data/local/tmp
+cat /dev/null > output
+
+if [[ $1 == "--run-fd-server" ]]; then
+ exec 9</system/bin/sh 8<input.4m 7<input.4m.merkle_dump 6<input.4m \
+ 5<input.4m.merkle_dump.bad 4<input.4m.fsv_sig 3<>output \
+ fd_server --ro-fds 9 --ro-fds 8:7:4 --ro-fds 6:5:4 --rw-fds 3
+fi
# Run with -u to enter new namespace.
if [[ $1 == "-u" ]]; then
exec unshare -mUr $0
fi
-cd /data/local/tmp
-
MOUNTPOINT=/data/local/tmp/authfs
trap "umount ${MOUNTPOINT}" EXIT;
mkdir -p ${MOUNTPOINT}
@@ -39,7 +47,8 @@
--local-ro-file-unverified 5:/system/bin/sh \
--remote-ro-file-unverified 6:9:${size} \
--remote-ro-file 7:8:${size2}:/dev/null \
- --remote-ro-file 8:5:${size2}:/dev/null \
+ --remote-ro-file 8:6:${size2}:/dev/null \
+ --remote-new-rw-file 9:3 \
&
sleep 0.1
@@ -55,6 +64,9 @@
echo
md5sum ${MOUNTPOINT}/7 input.4m
echo
+cat input.4m > ${MOUNTPOINT}/9
+md5sum ${MOUNTPOINT}/9 output
+echo
echo Checking error cases...
cat /data/local/tmp/authfs/8 2>&1 |grep -q ": I/O error" || echo "Failed to catch the problem"
echo "Done!"