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)),
         })
     }
 }