Merge "Introduce authfs_service"
diff --git a/authfs/aidl/com/android/virt/fs/AuthFsConfig.aidl b/authfs/aidl/com/android/virt/fs/AuthFsConfig.aidl
new file mode 100644
index 0000000..dfccee5
--- /dev/null
+++ b/authfs/aidl/com/android/virt/fs/AuthFsConfig.aidl
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.virt.fs;
+
+import com.android.virt.fs.InputFdAnnotation;
+import com.android.virt.fs.OutputFdAnnotation;
+
+/** @hide */
+parcelable AuthFsConfig {
+    /** Port of the filesystem backend. */
+    int port;
+
+    /** Annotation for the remote input file descriptors. */
+    InputFdAnnotation[] inputFdAnnotations;
+
+    /** Annotation for the remote output file descriptors. */
+    OutputFdAnnotation[] outputFdAnnotations;
+}
diff --git a/authfs/aidl/com/android/virt/fs/IAuthFs.aidl b/authfs/aidl/com/android/virt/fs/IAuthFs.aidl
new file mode 100644
index 0000000..464a9a0
--- /dev/null
+++ b/authfs/aidl/com/android/virt/fs/IAuthFs.aidl
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.virt.fs;
+
+import com.android.virt.fs.AuthFsConfig;
+
+/** @hide */
+interface IAuthFs {
+    /** Returns a file descriptor given the name of a remote file descriptor. */
+    ParcelFileDescriptor openFile(long remoteFdName, boolean writable);
+}
diff --git a/authfs/aidl/com/android/virt/fs/IAuthFsService.aidl b/authfs/aidl/com/android/virt/fs/IAuthFsService.aidl
new file mode 100644
index 0000000..b349db2
--- /dev/null
+++ b/authfs/aidl/com/android/virt/fs/IAuthFsService.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.virt.fs;
+
+import com.android.virt.fs.AuthFsConfig;
+import com.android.virt.fs.IAuthFs;
+
+/** @hide */
+interface IAuthFsService {
+    /**
+     * Creates an AuthFS mount given the config. Returns the binder object that represent the AuthFS
+     * instance. The AuthFS setup is deleted once the lifetime of the returned binder object ends.
+     */
+    IAuthFs mount(in AuthFsConfig config);
+}
diff --git a/authfs/aidl/com/android/virt/fs/InputFdAnnotation.aidl b/authfs/aidl/com/android/virt/fs/InputFdAnnotation.aidl
new file mode 100644
index 0000000..dafb137
--- /dev/null
+++ b/authfs/aidl/com/android/virt/fs/InputFdAnnotation.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.virt.fs;
+
+/** @hide */
+parcelable InputFdAnnotation {
+    /**
+     * File descriptor number to be passed to the program.  This is also the same file descriptor
+     * number used in the backend server.
+     */
+    int fd;
+
+    /** The actual file size in bytes of the backing file to be read. */
+    long fileSize;
+}
diff --git a/authfs/aidl/com/android/virt/fs/OutputFdAnnotation.aidl b/authfs/aidl/com/android/virt/fs/OutputFdAnnotation.aidl
new file mode 100644
index 0000000..4e4e621
--- /dev/null
+++ b/authfs/aidl/com/android/virt/fs/OutputFdAnnotation.aidl
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.virt.fs;
+
+/** @hide */
+parcelable OutputFdAnnotation {
+    /**
+     * File descriptor number to be passed to the program.  This is currently assumed to be same as
+     * the file descriptor number used in the backend server.
+     */
+    int fd;
+}
diff --git a/authfs/service/Android.bp b/authfs/service/Android.bp
new file mode 100644
index 0000000..3f3a0c3
--- /dev/null
+++ b/authfs/service/Android.bp
@@ -0,0 +1,22 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_binary {
+    name: "authfs_service",
+    srcs: [
+        "src/main.rs",
+    ],
+    edition: "2018",
+    rustlibs: [
+        "authfs_aidl_interface-rust",
+        "libandroid_logger",
+        "libanyhow",
+        "libbinder_rs",
+        "liblibc",
+        "liblog_rust",
+        "libnix",
+        "libshared_child",
+    ],
+    prefer_rlib: true,
+}
diff --git a/authfs/service/src/authfs.rs b/authfs/service/src/authfs.rs
new file mode 100644
index 0000000..5b41244
--- /dev/null
+++ b/authfs/service/src/authfs.rs
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+use anyhow::{bail, Context, Result};
+use log::{debug, error, warn};
+use nix::mount::{umount2, MntFlags};
+use nix::sys::statfs::{statfs, FsType};
+use shared_child::SharedChild;
+use std::ffi::{OsStr, OsString};
+use std::fs::{remove_dir, OpenOptions};
+use std::path::PathBuf;
+use std::process::Command;
+use std::thread::sleep;
+use std::time::{Duration, Instant};
+
+use crate::common::new_binder_exception;
+use authfs_aidl_interface::aidl::com::android::virt::fs::IAuthFs::{BnAuthFs, IAuthFs};
+use authfs_aidl_interface::aidl::com::android::virt::fs::{
+    AuthFsConfig::AuthFsConfig, InputFdAnnotation::InputFdAnnotation,
+    OutputFdAnnotation::OutputFdAnnotation,
+};
+use authfs_aidl_interface::binder::{
+    self, BinderFeatures, ExceptionCode, Interface, ParcelFileDescriptor, Strong,
+};
+
+const AUTHFS_BIN: &str = "/system/bin/authfs";
+const AUTHFS_SETUP_POLL_INTERVAL_MS: Duration = Duration::from_millis(50);
+const AUTHFS_SETUP_TIMEOUT_SEC: Duration = Duration::from_secs(10);
+const FUSE_SUPER_MAGIC: FsType = FsType(0x65735546);
+
+/// An `AuthFs` instance is supposed to be backed by an `authfs` process. When the lifetime of the
+/// instance is over, it should leave no trace on the system: the process should be terminated, the
+/// FUSE should be unmounted, and the mount directory should be deleted.
+pub struct AuthFs {
+    mountpoint: OsString,
+    process: SharedChild,
+}
+
+impl Interface for AuthFs {}
+
+impl IAuthFs for AuthFs {
+    fn openFile(
+        &self,
+        remote_fd_name: i64,
+        writable: bool,
+    ) -> binder::Result<ParcelFileDescriptor> {
+        let mut path = PathBuf::from(&self.mountpoint);
+        path.push(remote_fd_name.to_string());
+        let file = OpenOptions::new().read(true).write(writable).open(&path).map_err(|e| {
+            new_binder_exception(
+                ExceptionCode::SERVICE_SPECIFIC,
+                format!("failed to open {:?} on authfs: {}", &path, e),
+            )
+        })?;
+        Ok(ParcelFileDescriptor::new(file))
+    }
+}
+
+impl AuthFs {
+    /// Mount an authfs at `mountpoint` with specified FD annotations.
+    pub fn mount_and_wait(
+        mountpoint: OsString,
+        config: &AuthFsConfig,
+        debuggable: bool,
+    ) -> Result<Strong<dyn IAuthFs>> {
+        let child = run_authfs(
+            &mountpoint,
+            &config.inputFdAnnotations,
+            &config.outputFdAnnotations,
+            debuggable,
+        )?;
+        wait_until_authfs_ready(&mountpoint).map_err(|e| {
+            debug!("Wait for authfs: {:?}", child.wait());
+            e
+        })?;
+
+        let authfs = AuthFs { mountpoint, process: child };
+        Ok(BnAuthFs::new_binder(authfs, BinderFeatures::default()))
+    }
+}
+
+impl Drop for AuthFs {
+    /// On drop, try to erase all the traces for this authfs mount.
+    fn drop(&mut self) {
+        debug!("Dropping AuthFs instance at mountpoint {:?}", &self.mountpoint);
+        if let Err(e) = self.process.kill() {
+            error!("Failed to kill authfs: {}", e);
+        }
+        match self.process.wait() {
+            Ok(status) => debug!("authfs exit code: {}", status),
+            Err(e) => warn!("Failed to wait for authfs: {}", e),
+        }
+        // The client may still hold the file descriptors that refer to this filesystem. Use
+        // MNT_DETACH to detach the mountpoint, and automatically unmount when there is no more
+        // reference.
+        if let Err(e) = umount2(self.mountpoint.as_os_str(), MntFlags::MNT_DETACH) {
+            error!("Failed to umount authfs at {:?}: {}", &self.mountpoint, e)
+        }
+
+        if let Err(e) = remove_dir(&self.mountpoint) {
+            error!("Failed to clean up mount directory {:?}: {}", &self.mountpoint, e)
+        }
+    }
+}
+
+fn run_authfs(
+    mountpoint: &OsStr,
+    in_fds: &[InputFdAnnotation],
+    out_fds: &[OutputFdAnnotation],
+    debuggable: bool,
+) -> Result<SharedChild> {
+    let mut args = vec![mountpoint.to_owned(), OsString::from("--cid=2")];
+    for conf in in_fds {
+        // TODO(b/185178698): Many input files need to be signed and verified.
+        // or can we use debug cert for now, which is better than nothing?
+        args.push(OsString::from("--remote-ro-file-unverified"));
+        args.push(OsString::from(format!("{}:{}:{}", conf.fd, conf.fd, conf.fileSize)));
+    }
+    for conf in out_fds {
+        args.push(OsString::from("--remote-new-rw-file"));
+        args.push(OsString::from(format!("{}:{}", conf.fd, conf.fd)));
+    }
+    if debuggable {
+        args.push(OsString::from("--debug"));
+    }
+
+    let mut command = Command::new(AUTHFS_BIN);
+    command.args(&args);
+    SharedChild::spawn(&mut command).context("Spawn authfs")
+}
+
+fn wait_until_authfs_ready(mountpoint: &OsStr) -> Result<()> {
+    let start_time = Instant::now();
+    loop {
+        if is_fuse(mountpoint)? {
+            break;
+        }
+        if start_time.elapsed() > AUTHFS_SETUP_TIMEOUT_SEC {
+            bail!("Time out mounting authfs");
+        }
+        sleep(AUTHFS_SETUP_POLL_INTERVAL_MS);
+    }
+    Ok(())
+}
+
+fn is_fuse(path: &OsStr) -> Result<bool> {
+    Ok(statfs(path)?.filesystem_type() == FUSE_SUPER_MAGIC)
+}
diff --git a/authfs/service/src/common.rs b/authfs/service/src/common.rs
new file mode 100644
index 0000000..00efe9e
--- /dev/null
+++ b/authfs/service/src/common.rs
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+use std::ffi::CString;
+
+use authfs_aidl_interface::binder::{ExceptionCode, Status};
+
+/// Helper function to create a binder exception.
+pub fn new_binder_exception<T: AsRef<str>>(exception: ExceptionCode, message: T) -> Status {
+    Status::new_exception(exception, CString::new(message.as_ref()).as_deref().ok())
+}
diff --git a/authfs/service/src/main.rs b/authfs/service/src/main.rs
new file mode 100644
index 0000000..0ba19cb
--- /dev/null
+++ b/authfs/service/src/main.rs
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//! AuthFsService facilitates authfs mounting (which is a privileged operation) for the client. The
+//! client will provide an `AuthFsConfig` which includes the backend address (only port for now) and
+//! the filesystem configuration. It is up to the client to ensure the backend server is running. On
+//! a successful mount, the client receives an `IAuthFs`, and through the binder object, the client
+//! is able to retrieve "remote file descriptors".
+
+mod authfs;
+mod common;
+
+use anyhow::{bail, Context, Result};
+use log::*;
+use std::ffi::OsString;
+use std::fs::{create_dir, read_dir, remove_dir_all, remove_file};
+use std::sync::atomic::{AtomicUsize, Ordering};
+
+use crate::common::new_binder_exception;
+use authfs_aidl_interface::aidl::com::android::virt::fs::AuthFsConfig::AuthFsConfig;
+use authfs_aidl_interface::aidl::com::android::virt::fs::IAuthFs::IAuthFs;
+use authfs_aidl_interface::aidl::com::android::virt::fs::IAuthFsService::{
+    BnAuthFsService, IAuthFsService,
+};
+use authfs_aidl_interface::binder::{
+    self, add_service, BinderFeatures, ExceptionCode, Interface, ProcessState, Strong,
+};
+
+const SERVICE_NAME: &str = "authfs_service";
+// TODO: Replace with a valid directory setup in the VM.
+const SERVICE_ROOT: &str = "/data/local/tmp/authfs";
+
+/// Implementation of `IAuthFsService`.
+pub struct AuthFsService {
+    serial_number: AtomicUsize,
+    debuggable: bool,
+}
+
+impl Interface for AuthFsService {}
+
+impl IAuthFsService for AuthFsService {
+    fn mount(&self, config: &AuthFsConfig) -> binder::Result<Strong<dyn IAuthFs>> {
+        self.validate(config)?;
+
+        let mountpoint = self.get_next_mount_point();
+
+        // The directory is supposed to be deleted when `AuthFs` is dropped.
+        create_dir(&mountpoint).map_err(|e| {
+            new_binder_exception(
+                ExceptionCode::SERVICE_SPECIFIC,
+                format!("Cannot create mount directory {:?}: {}", &mountpoint, e),
+            )
+        })?;
+
+        authfs::AuthFs::mount_and_wait(mountpoint, config, self.debuggable).map_err(|e| {
+            new_binder_exception(
+                ExceptionCode::SERVICE_SPECIFIC,
+                format!("mount_and_wait failed: {:?}", e),
+            )
+        })
+    }
+}
+
+impl AuthFsService {
+    fn new_binder(debuggable: bool) -> Strong<dyn IAuthFsService> {
+        let service = AuthFsService { serial_number: AtomicUsize::new(1), debuggable };
+        BnAuthFsService::new_binder(service, BinderFeatures::default())
+    }
+
+    fn validate(&self, config: &AuthFsConfig) -> binder::Result<()> {
+        if config.port < 0 {
+            return Err(new_binder_exception(
+                ExceptionCode::ILLEGAL_ARGUMENT,
+                format!("Invalid port: {}", config.port),
+            ));
+        }
+        Ok(())
+    }
+
+    fn get_next_mount_point(&self) -> OsString {
+        let previous = self.serial_number.fetch_add(1, Ordering::Relaxed);
+        OsString::from(format!("{}/{}", SERVICE_ROOT, previous))
+    }
+}
+
+fn clean_up_working_directory() -> Result<()> {
+    for entry in read_dir(SERVICE_ROOT)? {
+        let entry = entry?;
+        let path = entry.path();
+        if path.is_dir() {
+            remove_dir_all(path)?;
+        } else if path.is_file() {
+            remove_file(path)?;
+        } else {
+            bail!("Unrecognized path type: {:?}", path);
+        }
+    }
+    Ok(())
+}
+
+fn main() -> Result<()> {
+    let debuggable = env!("TARGET_BUILD_VARIANT") != "user";
+    let log_level = if debuggable { log::Level::Trace } else { log::Level::Info };
+    android_logger::init_once(
+        android_logger::Config::default().with_tag("authfs_service").with_min_level(log_level),
+    );
+
+    clean_up_working_directory()?;
+
+    ProcessState::start_thread_pool();
+
+    let service = AuthFsService::new_binder(debuggable).as_binder();
+    add_service(SERVICE_NAME, service)
+        .with_context(|| format!("Failed to register service {}", SERVICE_NAME))?;
+    debug!("{} is running", SERVICE_NAME);
+
+    ProcessState::join_thread_pool();
+    bail!("Unexpected exit after join_thread_pool")
+}