authfs: Remote file editor over binder

The remote editor allows to read/write from/to a remote file. This
change:
 - Adds new binder API `writeFile`.
 - Update fd_server to serve a read/writable fd. Read operation is done
   through the existing read API when applicable.
 - Adds `RemoteFileEditor` as a binder client for both read and write.

Bug: 171279640
Test: adb shell exec 9<>/data/local/tmp/output fd_server --rw-fds 9
Test: with changes in fusefs.rs, saw file changed correctly (md5sum)

Change-Id: I78ef198ee8a3a0f2d99717dc0c00fccde757f3de
diff --git a/authfs/aidl/com/android/virt/fs/IVirtFdService.aidl b/authfs/aidl/com/android/virt/fs/IVirtFdService.aidl
index 628ee3c..189f43a 100644
--- a/authfs/aidl/com/android/virt/fs/IVirtFdService.aidl
+++ b/authfs/aidl/com/android/virt/fs/IVirtFdService.aidl
@@ -45,4 +45,10 @@
 
     /** Returns the fs-verity signature of the given file ID. */
     byte[] readFsveritySignature(int id);
+
+    /**
+     * Writes the buffer to the given file ID from the file's offset. Returns the number of bytes
+     * written.
+     */
+    int writeFile(int id, in byte[] buf, long offset);
 }
diff --git a/authfs/fd_server/Android.bp b/authfs/fd_server/Android.bp
index 6f010ce..9c810a8 100644
--- a/authfs/fd_server/Android.bp
+++ b/authfs/fd_server/Android.bp
@@ -7,6 +7,7 @@
     srcs: ["src/main.rs"],
     rustlibs: [
         "authfs_aidl_interface-rust",
+        "libandroid_logger",
         "libanyhow",
         "libbinder_rs",
         "libclap",
diff --git a/authfs/fd_server/src/main.rs b/authfs/fd_server/src/main.rs
index cbd7712..9395afc 100644
--- a/authfs/fd_server/src/main.rs
+++ b/authfs/fd_server/src/main.rs
@@ -42,7 +42,8 @@
     BnVirtFdService, IVirtFdService, ERROR_IO, ERROR_UNKNOWN_FD, MAX_REQUESTING_DATA,
 };
 use authfs_aidl_interface::binder::{
-    add_service, ExceptionCode, Interface, ProcessState, Result as BinderResult, Status, Strong,
+    add_service, ExceptionCode, Interface, ProcessState, Result as BinderResult, Status,
+    StatusCode, Strong,
 };
 
 const SERVICE_NAME: &str = "authfs_fd_server";
@@ -70,38 +71,41 @@
     }
 }
 
-/// Configuration of a read-only file to serve by this server. The file is supposed to be verifiable
-/// with the associated fs-verity metadata.
-struct ReadonlyFdConfig {
-    /// The file to read from. fs-verity metadata can be retrieved from this file's FD.
-    file: File,
+/// Configuration of a file descriptor to be served/exposed/shared.
+enum FdConfig {
+    /// A read-only file to serve by this server. The file is supposed to be verifiable with the
+    /// associated fs-verity metadata.
+    Readonly {
+        /// The file to read from. fs-verity metadata can be retrieved from this file's FD.
+        file: File,
 
-    /// Alternative Merkle tree stored in another file.
-    alt_merkle_file: Option<File>,
+        /// Alternative Merkle tree stored in another file.
+        alt_merkle_tree: Option<File>,
 
-    /// Alternative signature stored in another file.
-    alt_signature_file: Option<File>,
+        /// Alternative signature stored in another file.
+        alt_signature: Option<File>,
+    },
+
+    /// 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),
 }
 
 struct FdService {
-    /// A pool of read-only files
-    fd_pool: BTreeMap<i32, ReadonlyFdConfig>,
+    /// A pool of opened files, may be readonly or read-writable.
+    fd_pool: BTreeMap<i32, FdConfig>,
 }
 
 impl FdService {
-    pub fn new_binder(fd_pool: BTreeMap<i32, ReadonlyFdConfig>) -> Strong<dyn IVirtFdService> {
+    pub fn new_binder(fd_pool: BTreeMap<i32, FdConfig>) -> Strong<dyn IVirtFdService> {
         let result = BnVirtFdService::new_binder(FdService { fd_pool });
         result.as_binder().set_requesting_sid(false);
         result
     }
 
-    fn get_file_config(&self, id: i32) -> BinderResult<&ReadonlyFdConfig> {
+    fn get_file_config(&self, id: i32) -> BinderResult<&FdConfig> {
         self.fd_pool.get(&id).ok_or_else(|| Status::from(ERROR_UNKNOWN_FD))
     }
-
-    fn get_file(&self, id: i32) -> BinderResult<&File> {
-        Ok(&self.get_file_config(id)?.file)
-    }
 }
 
 impl Interface for FdService {}
@@ -111,38 +115,88 @@
         let size: usize = validate_and_cast_size(size)?;
         let offset: u64 = validate_and_cast_offset(offset)?;
 
-        read_into_buf(self.get_file(id)?, size, offset).map_err(|e| {
-            error!("readFile: read error: {}", e);
-            Status::from(ERROR_IO)
-        })
+        match self.get_file_config(id)? {
+            FdConfig::Readonly { file, .. } | FdConfig::ReadWrite(file) => {
+                read_into_buf(&file, size, offset).map_err(|e| {
+                    error!("readFile: read error: {}", e);
+                    Status::from(ERROR_IO)
+                })
+            }
+        }
     }
 
     fn readFsverityMerkleTree(&self, id: i32, offset: i64, size: i32) -> BinderResult<Vec<u8>> {
         let size: usize = validate_and_cast_size(size)?;
         let offset: u64 = validate_and_cast_offset(offset)?;
 
-        if let Some(file) = &self.get_file_config(id)?.alt_merkle_file {
-            read_into_buf(&file, size, offset).map_err(|e| {
-                error!("readFsverityMerkleTree: read error: {}", e);
-                Status::from(ERROR_IO)
-            })
-        } else {
-            // TODO(victorhsieh) retrieve from the fd when the new ioctl is ready
-            Err(new_binder_exception(ExceptionCode::UNSUPPORTED_OPERATION, "Not implemented yet"))
+        match &self.get_file_config(id)? {
+            FdConfig::Readonly { alt_merkle_tree, .. } => {
+                if let Some(file) = &alt_merkle_tree {
+                    read_into_buf(&file, size, offset).map_err(|e| {
+                        error!("readFsverityMerkleTree: read error: {}", e);
+                        Status::from(ERROR_IO)
+                    })
+                } else {
+                    // TODO(victorhsieh) retrieve from the fd when the new ioctl is ready
+                    Err(new_binder_exception(
+                        ExceptionCode::UNSUPPORTED_OPERATION,
+                        "Not implemented yet",
+                    ))
+                }
+            }
+            FdConfig::ReadWrite(_file) => {
+                // For a writable file, Merkle tree is not expected to be served since Auth FS
+                // doesn't trust it anyway. Auth FS may keep the Merkle tree privately for its own
+                // use.
+                Err(new_binder_exception(ExceptionCode::UNSUPPORTED_OPERATION, "Unsupported"))
+            }
         }
     }
 
     fn readFsveritySignature(&self, id: i32) -> BinderResult<Vec<u8>> {
-        if let Some(file) = &self.get_file_config(id)?.alt_signature_file {
-            // Supposedly big enough buffer size to store signature.
-            let size = MAX_REQUESTING_DATA as usize;
-            read_into_buf(&file, size, 0).map_err(|e| {
-                error!("readFsveritySignature: read error: {}", e);
-                Status::from(ERROR_IO)
-            })
-        } else {
-            // TODO(victorhsieh) retrieve from the fd when the new ioctl is ready
-            Err(new_binder_exception(ExceptionCode::UNSUPPORTED_OPERATION, "Not implemented yet"))
+        match &self.get_file_config(id)? {
+            FdConfig::Readonly { alt_signature, .. } => {
+                if let Some(file) = &alt_signature {
+                    // Supposedly big enough buffer size to store signature.
+                    let size = MAX_REQUESTING_DATA as usize;
+                    read_into_buf(&file, size, 0).map_err(|e| {
+                        error!("readFsveritySignature: read error: {}", e);
+                        Status::from(ERROR_IO)
+                    })
+                } else {
+                    // TODO(victorhsieh) retrieve from the fd when the new ioctl is ready
+                    Err(new_binder_exception(
+                        ExceptionCode::UNSUPPORTED_OPERATION,
+                        "Not implemented yet",
+                    ))
+                }
+            }
+            FdConfig::ReadWrite(_file) => {
+                // There is no signature for a writable file.
+                Err(new_binder_exception(ExceptionCode::UNSUPPORTED_OPERATION, "Unsupported"))
+            }
+        }
+    }
+
+    fn writeFile(&self, id: i32, buf: &[u8], offset: i64) -> BinderResult<i32> {
+        match &self.get_file_config(id)? {
+            FdConfig::Readonly { .. } => Err(StatusCode::INVALID_OPERATION.into()),
+            FdConfig::ReadWrite(file) => {
+                let offset: u64 = offset.try_into().map_err(|_| {
+                    new_binder_exception(ExceptionCode::ILLEGAL_ARGUMENT, "Invalid offset")
+                })?;
+                // Check buffer size just to make `as i32` safe below.
+                if buf.len() > i32::MAX as usize {
+                    return Err(new_binder_exception(
+                        ExceptionCode::ILLEGAL_ARGUMENT,
+                        "Buffer size is too big",
+                    ));
+                }
+                Ok(file.write_at(buf, offset).map_err(|e| {
+                    error!("writeFile: write error: {}", e);
+                    Status::from(ERROR_IO)
+                })? as i32)
+            }
         }
     }
 }
@@ -169,29 +223,42 @@
     Ok(unsafe { File::from_raw_fd(fd) })
 }
 
-fn parse_arg_ro_fds(arg: &str) -> Result<(i32, ReadonlyFdConfig)> {
+fn parse_arg_ro_fds(arg: &str) -> Result<(i32, FdConfig)> {
     let result: Result<Vec<i32>, _> = arg.split(':').map(|x| x.parse::<i32>()).collect();
     let fds = result?;
     if fds.len() > 3 {
         bail!("Too many options: {}", arg);
     }
-
     Ok((
         fds[0],
-        ReadonlyFdConfig {
+        FdConfig::Readonly {
             file: fd_to_file(fds[0])?,
-            alt_merkle_file: fds.get(1).map(|fd| fd_to_file(*fd)).transpose()?,
-            alt_signature_file: fds.get(2).map(|fd| fd_to_file(*fd)).transpose()?,
+            // Alternative Merkle tree, if provided
+            alt_merkle_tree: fds.get(1).map(|fd| fd_to_file(*fd)).transpose()?,
+            // Alternative signature, if provided
+            alt_signature: fds.get(2).map(|fd| fd_to_file(*fd)).transpose()?,
         },
     ))
 }
 
-fn parse_args() -> Result<BTreeMap<i32, ReadonlyFdConfig>> {
+fn parse_arg_rw_fds(arg: &str) -> Result<(i32, FdConfig)> {
+    let fd = arg.parse::<i32>()?;
+    let file = fd_to_file(fd)?;
+    if file.metadata()?.len() > 0 {
+        bail!("File is expected to be empty");
+    }
+    Ok((fd, FdConfig::ReadWrite(file)))
+}
+
+fn parse_args() -> Result<BTreeMap<i32, FdConfig>> {
     #[rustfmt::skip]
     let matches = clap::App::new("fd_server")
         .arg(clap::Arg::with_name("ro-fds")
              .long("ro-fds")
-             .required(true)
+             .multiple(true)
+             .number_of_values(1))
+        .arg(clap::Arg::with_name("rw-fds")
+             .long("rw-fds")
              .multiple(true)
              .number_of_values(1))
         .get_matches();
@@ -203,10 +270,20 @@
             fd_pool.insert(fd, config);
         }
     }
+    if let Some(args) = matches.values_of("rw-fds") {
+        for arg in args {
+            let (fd, config) = parse_arg_rw_fds(arg)?;
+            fd_pool.insert(fd, config);
+        }
+    }
     Ok(fd_pool)
 }
 
 fn main() -> Result<()> {
+    android_logger::init_once(
+        android_logger::Config::default().with_tag("fd_server").with_min_level(log::Level::Debug),
+    );
+
     let fd_pool = parse_args()?;
 
     ProcessState::start_thread_pool();
diff --git a/authfs/src/remote_file.rs b/authfs/src/remote_file.rs
index 01e803c..ed7381c 100644
--- a/authfs/src/remote_file.rs
+++ b/authfs/src/remote_file.rs
@@ -21,6 +21,7 @@
 
 use crate::common::CHUNK_SIZE;
 use crate::reader::ReadOnlyDataByChunk;
+use crate::writer::RandomWrite;
 
 use authfs_aidl_interface::aidl::com::android::virt::fs::IVirtFdService;
 use authfs_aidl_interface::binder::Strong;
@@ -36,6 +37,23 @@
     }
 }
 
+fn remote_read_chunk(
+    service: &Arc<Mutex<VirtFdService>>,
+    remote_fd: i32,
+    chunk_index: u64,
+    mut buf: &mut [u8],
+) -> io::Result<usize> {
+    let offset = i64::try_from(chunk_index * CHUNK_SIZE)
+        .map_err(|_| io::Error::from_raw_os_error(libc::EOVERFLOW))?;
+
+    let chunk = service
+        .lock()
+        .unwrap()
+        .readFile(remote_fd, offset, buf.len() as i32)
+        .map_err(|e| io::Error::new(io::ErrorKind::Other, e.get_description()))?;
+    buf.write(&chunk)
+}
+
 pub struct RemoteChunkedFileReader {
     // This needs to have Sync trait to be used in fuse::worker::start_message_loop.
     service: Arc<Mutex<VirtFdService>>,
@@ -49,17 +67,8 @@
 }
 
 impl ReadOnlyDataByChunk for RemoteChunkedFileReader {
-    fn read_chunk(&self, chunk_index: u64, mut buf: &mut [u8]) -> io::Result<usize> {
-        let offset = i64::try_from(chunk_index * CHUNK_SIZE)
-            .map_err(|_| io::Error::from_raw_os_error(libc::EOVERFLOW))?;
-
-        let service = Arc::clone(&self.service);
-        let chunk = service
-            .lock()
-            .unwrap()
-            .readFile(self.file_fd, offset, buf.len() as i32)
-            .map_err(|e| io::Error::new(io::ErrorKind::Other, e.get_description()))?;
-        buf.write(&chunk)
+    fn read_chunk(&self, chunk_index: u64, buf: &mut [u8]) -> io::Result<usize> {
+        remote_read_chunk(&self.service, self.file_fd, chunk_index, buf)
     }
 }
 
@@ -81,8 +90,8 @@
         let offset = i64::try_from(chunk_index * CHUNK_SIZE)
             .map_err(|_| io::Error::from_raw_os_error(libc::EOVERFLOW))?;
 
-        let service = Arc::clone(&self.service);
-        let chunk = service
+        let chunk = self
+            .service
             .lock()
             .unwrap()
             .readFsverityMerkleTree(self.file_fd, offset, buf.len() as i32)
@@ -90,3 +99,36 @@
         buf.write(&chunk)
     }
 }
+
+pub struct RemoteFileEditor {
+    // This needs to have Sync trait to be used in fuse::worker::start_message_loop.
+    service: Arc<Mutex<VirtFdService>>,
+    file_fd: i32,
+}
+
+impl RemoteFileEditor {
+    #[allow(dead_code)]
+    pub fn new(service: Arc<Mutex<VirtFdService>>, file_fd: i32) -> Self {
+        RemoteFileEditor { service, file_fd }
+    }
+}
+
+impl RandomWrite for RemoteFileEditor {
+    fn write_at(&self, buf: &[u8], offset: u64) -> io::Result<usize> {
+        let offset =
+            i64::try_from(offset).map_err(|_| io::Error::from_raw_os_error(libc::EOVERFLOW))?;
+        let size = self
+            .service
+            .lock()
+            .unwrap()
+            .writeFile(self.file_fd, &buf, offset)
+            .map_err(|e| io::Error::new(io::ErrorKind::Other, e.get_description()))?;
+        Ok(size as usize) // within range because size is supposed to <= buf.len(), which is a usize
+    }
+}
+
+impl ReadOnlyDataByChunk for RemoteFileEditor {
+    fn read_chunk(&self, chunk_index: u64, buf: &mut [u8]) -> io::Result<usize> {
+        remote_read_chunk(&self.service, self.file_fd, chunk_index, buf)
+    }
+}