fd_server: support open file by path at a dir fd

When fd_server allows the client/authfs to (only) read a directory, authfs
will be able to serve the shared directory remotely (and implement its own
authentication).

Bug: 203251769
Test: atest AuthFsHostTest

Change-Id: If7209ec496b305ba8f469a382c2f775dcd0d1711
diff --git a/authfs/aidl/com/android/virt/fs/IVirtFdService.aidl b/authfs/aidl/com/android/virt/fs/IVirtFdService.aidl
index 58ccfc3..bf4ac61 100644
--- a/authfs/aidl/com/android/virt/fs/IVirtFdService.aidl
+++ b/authfs/aidl/com/android/virt/fs/IVirtFdService.aidl
@@ -57,12 +57,20 @@
     long getFileSize(int fd);
 
     /**
+     * Open a file given the remote directory FD.
+     *
+     * @param pathname The file path to open. Must be a related path.
+     * @return file A remote FD that represents the opened file.
+     */
+    int openFileInDirectory(int dirFd, String pathname);
+
+    /**
      * Create a file given the remote directory FD.
      *
      * @param basename The file name to create. Must not contain directory separator.
      * @return file A remote FD that represents the new created file.
      */
-    int createFileInDirectory(int fd, String basename);
+    int createFileInDirectory(int dirFd, String basename);
 
     /**
      * Create a directory inside the given remote directory FD.
@@ -70,5 +78,5 @@
      * @param basename The directory name to create. Must not contain directory separator.
      * @return file FD that represents the new created directory.
      */
-    int createDirectoryInDirectory(int id, String basename);
+    int createDirectoryInDirectory(int dirFd, String basename);
 }
diff --git a/authfs/fd_server/src/aidl.rs b/authfs/fd_server/src/aidl.rs
index 0c41eac..fa1914a 100644
--- a/authfs/fd_server/src/aidl.rs
+++ b/authfs/fd_server/src/aidl.rs
@@ -25,8 +25,8 @@
 use std::fs::File;
 use std::io;
 use std::os::unix::fs::FileExt;
-use std::os::unix::io::{AsRawFd, FromRawFd};
-use std::path::MAIN_SEPARATOR;
+use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
+use std::path::{Component, Path, PathBuf, MAIN_SEPARATOR};
 use std::sync::{Arc, Mutex};
 
 use crate::fsverity;
@@ -59,9 +59,11 @@
         file: File,
 
         /// Alternative Merkle tree stored in another file.
+        /// TODO(205987437): Replace with .fsv_meta file.
         alt_merkle_tree: Option<File>,
 
         /// Alternative signature stored in another file.
+        /// TODO(205987437): Replace with .fsv_meta file.
         alt_signature: Option<File>,
     },
 
@@ -69,6 +71,9 @@
     /// regular file and does not have any specific property.
     ReadWrite(File),
 
+    /// A read-only directory to serve by this server.
+    InputDir(Dir),
+
     /// A writable directory to serve by this server.
     OutputDir(Dir),
 }
@@ -132,7 +137,7 @@
                     new_errno_error(Errno::EIO)
                 })
             }
-            FdConfig::OutputDir(_) => Err(new_errno_error(Errno::EISDIR)),
+            FdConfig::InputDir(_) | FdConfig::OutputDir(_) => Err(new_errno_error(Errno::EISDIR)),
         })
     }
 
@@ -165,7 +170,7 @@
                 // use.
                 Err(new_errno_error(Errno::ENOSYS))
             }
-            FdConfig::OutputDir(_) => Err(new_errno_error(Errno::EISDIR)),
+            FdConfig::InputDir(_) | FdConfig::OutputDir(_) => Err(new_errno_error(Errno::EISDIR)),
         })
     }
 
@@ -195,7 +200,7 @@
                 // There is no signature for a writable file.
                 Err(new_errno_error(Errno::ENOSYS))
             }
-            FdConfig::OutputDir(_) => Err(new_errno_error(Errno::EISDIR)),
+            FdConfig::InputDir(_) | FdConfig::OutputDir(_) => Err(new_errno_error(Errno::EISDIR)),
         })
     }
 
@@ -213,7 +218,7 @@
                     new_errno_error(Errno::EIO)
                 })? as i32)
             }
-            FdConfig::OutputDir(_) => Err(new_errno_error(Errno::EISDIR)),
+            FdConfig::InputDir(_) | FdConfig::OutputDir(_) => Err(new_errno_error(Errno::EISDIR)),
         })
     }
 
@@ -229,7 +234,7 @@
                     new_errno_error(Errno::EIO)
                 })
             }
-            FdConfig::OutputDir(_) => Err(new_errno_error(Errno::EISDIR)),
+            FdConfig::InputDir(_) | FdConfig::OutputDir(_) => Err(new_errno_error(Errno::EISDIR)),
         })
     }
 
@@ -254,7 +259,31 @@
                 // for a writable file.
                 Err(new_errno_error(Errno::ENOSYS))
             }
-            FdConfig::OutputDir(_) => Err(new_errno_error(Errno::EISDIR)),
+            FdConfig::InputDir(_) | FdConfig::OutputDir(_) => Err(new_errno_error(Errno::EISDIR)),
+        })
+    }
+
+    fn openFileInDirectory(&self, fd: i32, file_path: &str) -> BinderResult<i32> {
+        let path_buf = PathBuf::from(file_path);
+        // Checks if the path is a simple, related path.
+        if path_buf.components().any(|c| !matches!(c, Component::Normal(_))) {
+            return Err(new_errno_error(Errno::EINVAL));
+        }
+
+        self.insert_new_fd(fd, |config| match config {
+            FdConfig::InputDir(dir) => {
+                let file = open_readonly_at(dir.as_raw_fd(), &path_buf).map_err(new_errno_error)?;
+
+                // TODO(205987437): Provide the corresponding ".fsv_meta" file when it's created.
+                Ok((
+                    file.as_raw_fd(),
+                    FdConfig::Readonly { file, alt_merkle_tree: None, alt_signature: None },
+                ))
+            }
+            FdConfig::OutputDir(_) => {
+                Err(new_errno_error(Errno::ENOSYS)) // TODO: Implement when needed
+            }
+            _ => Err(new_errno_error(Errno::ENOTDIR)),
         })
     }
 
@@ -263,6 +292,7 @@
             return Err(new_errno_error(Errno::EINVAL));
         }
         self.insert_new_fd(fd, |config| match config {
+            FdConfig::InputDir(_) => Err(new_errno_error(Errno::EACCES)),
             FdConfig::OutputDir(dir) => {
                 let new_fd = openat(
                     dir.as_raw_fd(),
@@ -286,6 +316,7 @@
             return Err(new_errno_error(Errno::EINVAL));
         }
         self.insert_new_fd(dir_fd, |config| match config {
+            FdConfig::InputDir(_) => Err(new_errno_error(Errno::EACCES)),
             FdConfig::OutputDir(_) => {
                 mkdirat(dir_fd, basename, Mode::S_IRWXU).map_err(new_errno_error)?;
                 let new_dir = Dir::openat(
@@ -313,3 +344,10 @@
 fn new_errno_error(errno: Errno) -> Status {
     new_binder_service_specific_error(errno as i32, errno.desc())
 }
+
+fn open_readonly_at(dir_fd: RawFd, path: &Path) -> nix::Result<File> {
+    let new_fd = openat(dir_fd, path, OFlag::O_RDONLY, Mode::empty())?;
+    // SAFETY: new_fd is just created successfully and not owned.
+    let new_file = unsafe { File::from_raw_fd(new_fd) };
+    Ok(new_file)
+}
diff --git a/authfs/fd_server/src/main.rs b/authfs/fd_server/src/main.rs
index bbcd49f..f5a3cba 100644
--- a/authfs/fd_server/src/main.rs
+++ b/authfs/fd_server/src/main.rs
@@ -78,9 +78,13 @@
     Ok((fd, FdConfig::ReadWrite(file)))
 }
 
+fn parse_arg_ro_dirs(arg: &str) -> Result<(i32, FdConfig)> {
+    let fd = arg.parse::<i32>()?;
+    Ok((fd, FdConfig::InputDir(Dir::from_fd(fd)?)))
+}
+
 fn parse_arg_rw_dirs(arg: &str) -> Result<(i32, FdConfig)> {
     let fd = arg.parse::<i32>()?;
-
     Ok((fd, FdConfig::OutputDir(Dir::from_fd(fd)?)))
 }
 
@@ -100,6 +104,10 @@
              .long("rw-fds")
              .multiple(true)
              .number_of_values(1))
+        .arg(clap::Arg::with_name("ro-dirs")
+             .long("ro-dirs")
+             .multiple(true)
+             .number_of_values(1))
         .arg(clap::Arg::with_name("rw-dirs")
              .long("rw-dirs")
              .multiple(true)
@@ -122,6 +130,12 @@
             fd_pool.insert(fd, config);
         }
     }
+    if let Some(args) = matches.values_of("ro-dirs") {
+        for arg in args {
+            let (fd, config) = parse_arg_ro_dirs(arg)?;
+            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)?;