Merge "microdroid_manager: waits for a config file"
diff --git a/microdroid/microdroid_payload.json b/microdroid/microdroid_payload.json
index 287aabd..ec4ff17 100644
--- a/microdroid/microdroid_payload.json
+++ b/microdroid/microdroid_payload.json
@@ -6,5 +6,6 @@
   "apk": {
     "name": "com.android.microdroid.test",
     "path": "/apex/com.android.virt/app/MicrodroidTestApp/MicrodroidTestApp.apk"
-  }
+  },
+  "payload_config_path": "/mnt/apk/assets/vm_config.json"
 }
diff --git a/microdroid_manager/Android.bp b/microdroid_manager/Android.bp
index 2c79196..38d500c 100644
--- a/microdroid_manager/Android.bp
+++ b/microdroid_manager/Android.bp
@@ -2,8 +2,8 @@
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
-rust_binary {
-    name: "microdroid_manager",
+rust_defaults {
+    name: "microdroid_manager_defaults",
     crate_name: "microdroid_manager",
     srcs: ["src/main.rs"],
     edition: "2018",
@@ -19,3 +19,22 @@
     ],
     init_rc: ["microdroid_manager.rc"],
 }
+
+rust_binary {
+    name: "microdroid_manager",
+    defaults: ["microdroid_manager_defaults"],
+}
+
+rust_test {
+    name: "microdroid_manager_test",
+    defaults: ["microdroid_manager_defaults"],
+    test_suites: ["device-tests"],
+    rustlibs: [
+        "libtempfile",
+    ],
+    multilib: {
+        lib32: {
+            enabled: false,
+        },
+    },
+}
diff --git a/microdroid_manager/src/ioutil.rs b/microdroid_manager/src/ioutil.rs
new file mode 100644
index 0000000..e8732ad
--- /dev/null
+++ b/microdroid_manager/src/ioutil.rs
@@ -0,0 +1,74 @@
+// Copyright 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.
+
+//! IO utilities
+
+use std::fs::File;
+use std::io;
+use std::path::Path;
+use std::thread;
+use std::time::{Duration, Instant};
+
+const SLEEP_DURATION: Duration = Duration::from_millis(5);
+
+/// waits for a file with a timeout and returns it
+pub fn wait_for_file<P: AsRef<Path>>(path: P, timeout: Duration) -> io::Result<File> {
+    let begin = Instant::now();
+    loop {
+        match File::open(&path) {
+            Ok(file) => return Ok(file),
+            Err(error) => {
+                if error.kind() != io::ErrorKind::NotFound {
+                    return Err(error);
+                }
+                if begin.elapsed() > timeout {
+                    return Err(io::Error::from(io::ErrorKind::NotFound));
+                }
+                thread::sleep(SLEEP_DURATION);
+            }
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use std::io::{Read, Write};
+
+    #[test]
+    fn test_wait_for_file() -> io::Result<()> {
+        let test_dir = tempfile::TempDir::new().unwrap();
+        let test_file = test_dir.path().join("test.txt");
+        thread::spawn(move || -> io::Result<()> {
+            thread::sleep(Duration::from_secs(1));
+            File::create(test_file)?.write_all(b"test")
+        });
+
+        let test_file = test_dir.path().join("test.txt");
+        let mut file = wait_for_file(&test_file, Duration::from_secs(5))?;
+        let mut buffer = String::new();
+        file.read_to_string(&mut buffer)?;
+        assert_eq!("test", buffer);
+        Ok(())
+    }
+
+    #[test]
+    fn test_wait_for_file_fails() {
+        let test_dir = tempfile::TempDir::new().unwrap();
+        let test_file = test_dir.path().join("test.txt");
+        let file = wait_for_file(&test_file, Duration::from_secs(1));
+        assert!(file.is_err());
+        assert_eq!(io::ErrorKind::NotFound, file.unwrap_err().kind());
+    }
+}
diff --git a/microdroid_manager/src/main.rs b/microdroid_manager/src/main.rs
index 4f87a40..fbbe8f3 100644
--- a/microdroid_manager/src/main.rs
+++ b/microdroid_manager/src/main.rs
@@ -14,6 +14,7 @@
 
 //! Microdroid Manager
 
+mod ioutil;
 mod payload_config;
 mod signature;
 
@@ -21,7 +22,8 @@
 use log::{info, Level};
 use payload_config::{Task, VmPayloadConfig};
 use std::io;
-use std::process::Command;
+use std::path::Path;
+use std::process::{Command, Stdio};
 
 const LOG_TAG: &str = "MicrodroidManager";
 
@@ -32,7 +34,7 @@
 
     let signature = signature::load()?;
     if !signature.payload_config_path.is_empty() {
-        let config = VmPayloadConfig::load_from(&signature.payload_config_path)?;
+        let config = VmPayloadConfig::load_from(Path::new(&signature.payload_config_path))?;
         if let Some(main_task) = &config.task {
             exec(main_task)?;
         }
@@ -45,7 +47,8 @@
 /// TODO(jooyung): fork a child process
 fn exec(task: &Task) -> io::Result<()> {
     info!("executing main task {} {:?}...", task.command, task.args);
-    let exit_status = Command::new(&task.command).args(&task.args).status()?;
+    let exit_status =
+        Command::new(&task.command).args(&task.args).stdout(Stdio::inherit()).status()?;
     info!("exit with {}", &exit_status);
     Ok(())
 }
diff --git a/microdroid_manager/src/payload_config.rs b/microdroid_manager/src/payload_config.rs
index 1dd6d92..bac841a 100644
--- a/microdroid_manager/src/payload_config.rs
+++ b/microdroid_manager/src/payload_config.rs
@@ -16,8 +16,13 @@
 
 use log::info;
 use serde::{Deserialize, Serialize};
-use std::fs::File;
 use std::io;
+use std::path::Path;
+use std::time::Duration;
+
+use crate::ioutil;
+
+const WAIT_TIMEOUT: Duration = Duration::from_secs(10);
 
 #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
 pub struct VmPayloadConfig {
@@ -33,9 +38,9 @@
 }
 
 impl VmPayloadConfig {
-    pub fn load_from(path: &str) -> io::Result<VmPayloadConfig> {
-        info!("loading config from {}...", path);
-        let file = File::open(path)?;
+    pub fn load_from(path: &Path) -> io::Result<VmPayloadConfig> {
+        info!("loading config from {:?}...", path);
+        let file = ioutil::wait_for_file(path, WAIT_TIMEOUT)?;
         Ok(serde_json::from_reader(file)?)
     }
 }
diff --git a/tests/testapk/assets/vm_config.json b/tests/testapk/assets/vm_config.json
new file mode 100644
index 0000000..e619c3b
--- /dev/null
+++ b/tests/testapk/assets/vm_config.json
@@ -0,0 +1,6 @@
+{
+  "task": {
+    "command": "echo",
+    "args": ["hello", "microdroid"]
+  }
+}
\ No newline at end of file