authfs: support remote output directory
This change contains 3 major groups:
- authfs/{aidl, fd_server}: new AIDL API and the service implementation
- authfs/src: implement FUSE APIs for creating directory and file, by
interact with the new service API as a client
- authfs/tests, tests/: test coverage
A few notable changes that might help reviewing:
- Now that both AuthFs and FdService struct is no longer immutable (in
order to allow writable directory), their BTreeMap are now guarded by
Arc<Mutex<_>>.
* AuthFs::insert_new_inode and FdService::insert_new_fd are designed
specifically to allow querying then mutating the map, which isn't
trivial.
- File and directory modes from the user program / VFS are currently
ignored (just not to grow the change size).
- Some shuffling of test paths to make it easy to clean up in tearDown.
Bug: 203251769
Test: AuthFsHostTest
Change-Id: I50f3f1ba8a3ebd969cf0f25a8feab2ec8cb1a2dc
diff --git a/authfs/fd_server/src/aidl.rs b/authfs/fd_server/src/aidl.rs
index 48547e7..0c41eac 100644
--- a/authfs/fd_server/src/aidl.rs
+++ b/authfs/fd_server/src/aidl.rs
@@ -16,23 +16,27 @@
use anyhow::Result;
use log::error;
-use nix::errno::Errno;
+use nix::{
+ dir::Dir, errno::Errno, fcntl::openat, fcntl::OFlag, sys::stat::mkdirat, sys::stat::Mode,
+};
use std::cmp::min;
-use std::collections::BTreeMap;
+use std::collections::{btree_map, BTreeMap};
use std::convert::TryInto;
use std::fs::File;
use std::io;
use std::os::unix::fs::FileExt;
-use std::os::unix::io::AsRawFd;
+use std::os::unix::io::{AsRawFd, FromRawFd};
+use std::path::MAIN_SEPARATOR;
+use std::sync::{Arc, Mutex};
use crate::fsverity;
use authfs_aidl_interface::aidl::com::android::virt::fs::IVirtFdService::{
BnVirtFdService, IVirtFdService, MAX_REQUESTING_DATA,
};
use authfs_aidl_interface::binder::{
- BinderFeatures, Interface, Result as BinderResult, Status, StatusCode, Strong,
+ BinderFeatures, ExceptionCode, Interface, Result as BinderResult, Status, StatusCode, Strong,
};
-use binder_common::new_binder_service_specific_error;
+use binder_common::{new_binder_exception, new_binder_service_specific_error};
fn validate_and_cast_offset(offset: i64) -> Result<u64, Status> {
offset.try_into().map_err(|_| new_errno_error(Errno::EINVAL))
@@ -64,26 +68,53 @@
/// A readable/writable file to serve by this server. This backing file should just be a
/// regular file and does not have any specific property.
ReadWrite(File),
+
+ /// A writable directory to serve by this server.
+ OutputDir(Dir),
}
pub struct FdService {
- /// A pool of opened files, may be readonly or read-writable.
- fd_pool: BTreeMap<i32, FdConfig>,
+ /// A pool of opened files and directories, which can be looked up by the FD number.
+ fd_pool: Arc<Mutex<BTreeMap<i32, FdConfig>>>,
}
impl FdService {
pub fn new_binder(fd_pool: BTreeMap<i32, FdConfig>) -> Strong<dyn IVirtFdService> {
- BnVirtFdService::new_binder(FdService { fd_pool }, BinderFeatures::default())
+ BnVirtFdService::new_binder(
+ FdService { fd_pool: Arc::new(Mutex::new(fd_pool)) },
+ BinderFeatures::default(),
+ )
}
- /// Handles the requesting file `id` with `handler` if it is in the FD pool. This function
- /// returns whatever the handler returns.
- fn handle_fd<F, R>(&self, id: i32, handler: F) -> BinderResult<R>
+ /// Handles the requesting file `id` with `handle_fn` if it is in the FD pool. This function
+ /// returns whatever `handle_fn` returns.
+ fn handle_fd<F, R>(&self, id: i32, handle_fn: F) -> BinderResult<R>
where
F: FnOnce(&FdConfig) -> BinderResult<R>,
{
- let fd_config = self.fd_pool.get(&id).ok_or_else(|| new_errno_error(Errno::EBADF))?;
- handler(fd_config)
+ let fd_pool = self.fd_pool.lock().unwrap();
+ let fd_config = fd_pool.get(&id).ok_or_else(|| new_errno_error(Errno::EBADF))?;
+ handle_fn(fd_config)
+ }
+
+ /// Inserts a new FD and corresponding `FdConfig` created by `create_fn` to the FD pool, then
+ /// returns the new FD number.
+ fn insert_new_fd<F>(&self, fd: i32, create_fn: F) -> BinderResult<i32>
+ where
+ F: FnOnce(&mut FdConfig) -> BinderResult<(i32, FdConfig)>,
+ {
+ let mut fd_pool = self.fd_pool.lock().unwrap();
+ let mut fd_config = fd_pool.get_mut(&fd).ok_or_else(|| new_errno_error(Errno::EBADF))?;
+ let (new_fd, new_fd_config) = create_fn(&mut fd_config)?;
+ if let btree_map::Entry::Vacant(entry) = fd_pool.entry(new_fd) {
+ entry.insert(new_fd_config);
+ Ok(new_fd)
+ } else {
+ Err(new_binder_exception(
+ ExceptionCode::ILLEGAL_STATE,
+ format!("The newly created FD {} is already in the pool unexpectedly", new_fd),
+ ))
+ }
}
}
@@ -101,6 +132,7 @@
new_errno_error(Errno::EIO)
})
}
+ FdConfig::OutputDir(_) => Err(new_errno_error(Errno::EISDIR)),
})
}
@@ -133,6 +165,7 @@
// use.
Err(new_errno_error(Errno::ENOSYS))
}
+ FdConfig::OutputDir(_) => Err(new_errno_error(Errno::EISDIR)),
})
}
@@ -162,6 +195,7 @@
// There is no signature for a writable file.
Err(new_errno_error(Errno::ENOSYS))
}
+ FdConfig::OutputDir(_) => Err(new_errno_error(Errno::EISDIR)),
})
}
@@ -179,6 +213,7 @@
new_errno_error(Errno::EIO)
})? as i32)
}
+ FdConfig::OutputDir(_) => Err(new_errno_error(Errno::EISDIR)),
})
}
@@ -194,6 +229,7 @@
new_errno_error(Errno::EIO)
})
}
+ FdConfig::OutputDir(_) => Err(new_errno_error(Errno::EISDIR)),
})
}
@@ -218,6 +254,50 @@
// for a writable file.
Err(new_errno_error(Errno::ENOSYS))
}
+ FdConfig::OutputDir(_) => Err(new_errno_error(Errno::EISDIR)),
+ })
+ }
+
+ fn createFileInDirectory(&self, fd: i32, basename: &str) -> BinderResult<i32> {
+ if basename.contains(MAIN_SEPARATOR) {
+ return Err(new_errno_error(Errno::EINVAL));
+ }
+ self.insert_new_fd(fd, |config| match config {
+ FdConfig::OutputDir(dir) => {
+ let new_fd = openat(
+ dir.as_raw_fd(),
+ basename,
+ // TODO(205172873): handle the case when the file already exist, e.g. truncate
+ // or fail, and possibly allow the client to specify. For now, always truncate.
+ OFlag::O_CREAT | OFlag::O_RDWR | OFlag::O_TRUNC,
+ Mode::S_IRUSR | Mode::S_IWUSR,
+ )
+ .map_err(new_errno_error)?;
+ // SAFETY: new_fd is just created and not an error.
+ let new_file = unsafe { File::from_raw_fd(new_fd) };
+ Ok((new_fd, FdConfig::ReadWrite(new_file)))
+ }
+ _ => Err(new_errno_error(Errno::ENOTDIR)),
+ })
+ }
+
+ fn createDirectoryInDirectory(&self, dir_fd: i32, basename: &str) -> BinderResult<i32> {
+ if basename.contains(MAIN_SEPARATOR) {
+ return Err(new_errno_error(Errno::EINVAL));
+ }
+ self.insert_new_fd(dir_fd, |config| match config {
+ FdConfig::OutputDir(_) => {
+ mkdirat(dir_fd, basename, Mode::S_IRWXU).map_err(new_errno_error)?;
+ let new_dir = Dir::openat(
+ dir_fd,
+ basename,
+ OFlag::O_DIRECTORY | OFlag::O_RDONLY,
+ Mode::empty(),
+ )
+ .map_err(new_errno_error)?;
+ Ok((new_dir.as_raw_fd(), FdConfig::OutputDir(new_dir)))
+ }
+ _ => Err(new_errno_error(Errno::ENOTDIR)),
})
}
}
diff --git a/authfs/fd_server/src/main.rs b/authfs/fd_server/src/main.rs
index 3413ce6..bbcd49f 100644
--- a/authfs/fd_server/src/main.rs
+++ b/authfs/fd_server/src/main.rs
@@ -28,6 +28,7 @@
use anyhow::{bail, Result};
use binder_common::rpc_server::run_rpc_server;
use log::debug;
+use nix::dir::Dir;
use std::collections::BTreeMap;
use std::fs::File;
use std::os::unix::io::FromRawFd;
@@ -77,6 +78,12 @@
Ok((fd, FdConfig::ReadWrite(file)))
}
+fn parse_arg_rw_dirs(arg: &str) -> Result<(i32, FdConfig)> {
+ let fd = arg.parse::<i32>()?;
+
+ Ok((fd, FdConfig::OutputDir(Dir::from_fd(fd)?)))
+}
+
struct Args {
fd_pool: BTreeMap<i32, FdConfig>,
ready_fd: Option<File>,
@@ -93,6 +100,10 @@
.long("rw-fds")
.multiple(true)
.number_of_values(1))
+ .arg(clap::Arg::with_name("rw-dirs")
+ .long("rw-dirs")
+ .multiple(true)
+ .number_of_values(1))
.arg(clap::Arg::with_name("ready-fd")
.long("ready-fd")
.takes_value(true))
@@ -111,6 +122,12 @@
fd_pool.insert(fd, config);
}
}
+ if let Some(args) = matches.values_of("rw-dirs") {
+ for arg in args {
+ let (fd, config) = parse_arg_rw_dirs(arg)?;
+ fd_pool.insert(fd, config);
+ }
+ }
let ready_fd = if let Some(arg) = matches.value_of("ready-fd") {
let fd = arg.parse::<i32>()?;
Some(fd_to_file(fd)?)