Support console input to VM

createVm accepts a new ParcelFileDescriptor which when set is used as
the console input in the VM. The `vm` tool sets this to stdin unless
explicitly specified via `--console-in`. This will be useful for
bringing up custom OSes.

Note that Java APIs for this is not added in this CL.

Bug: 263360203
Test: Start a vm using the `vm` tool. Type something. Then do `cat
/dev/console` in the VM. It shows the typed characters.
Change-Id: I5d0dfcc4852387ec50897a8dcae2935dd4a2dd9a
diff --git a/compos/common/compos_client.rs b/compos/common/compos_client.rs
index b03addf..9648f20 100644
--- a/compos/common/compos_client.rs
+++ b/compos/common/compos_client.rs
@@ -136,8 +136,15 @@
         // Let logs go to logcat.
         let (console_fd, log_fd) = (None, None);
         let callback = Box::new(Callback {});
-        let instance = VmInstance::create(service, &config, console_fd, log_fd, Some(callback))
-            .context("Failed to create VM")?;
+        let instance = VmInstance::create(
+            service,
+            &config,
+            console_fd,
+            /*console_in_fd */ None,
+            log_fd,
+            Some(callback),
+        )
+        .context("Failed to create VM")?;
 
         instance.start()?;
 
diff --git a/demo_native/main.cpp b/demo_native/main.cpp
index fa87549..bc42036 100644
--- a/demo_native/main.cpp
+++ b/demo_native/main.cpp
@@ -223,10 +223,11 @@
     std::shared_ptr<IVirtualMachine> vm;
 
     VirtualMachineConfig config = std::move(app_config);
-    ScopedFileDescriptor console_fd(fcntl(fileno(stdout), F_DUPFD_CLOEXEC));
+    ScopedFileDescriptor console_out_fd(fcntl(fileno(stdout), F_DUPFD_CLOEXEC));
+    ScopedFileDescriptor console_in_fd(fcntl(fileno(stdin), F_DUPFD_CLOEXEC));
     ScopedFileDescriptor log_fd(fcntl(fileno(stdout), F_DUPFD_CLOEXEC));
 
-    ScopedAStatus ret = service.createVm(config, console_fd, log_fd, &vm);
+    ScopedAStatus ret = service.createVm(config, console_out_fd, console_in_fd, log_fd, &vm);
     if (!ret.isOk()) {
         return Error() << "Failed to create VM";
     }
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachine.java b/javalib/src/android/system/virtualmachine/VirtualMachine.java
index f96effa..19f57fb 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachine.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachine.java
@@ -804,7 +804,8 @@
                         android.system.virtualizationservice.VirtualMachineConfig.appConfig(
                                 appConfig);
 
-                mVirtualMachine = service.createVm(vmConfigParcel, mConsoleWriter, mLogWriter);
+                // TODO(b/263360203): use consoleInFd also in Java (via private APIs)
+                mVirtualMachine = service.createVm(vmConfigParcel, mConsoleWriter, /* consoleInFd */ null, mLogWriter);
                 mVirtualMachine.registerCallback(new CallbackTranslator(service));
                 mContext.registerComponentCallbacks(mMemoryManagementCallbacks);
                 mVirtualMachine.start();
diff --git a/rialto/tests/test.rs b/rialto/tests/test.rs
index 7048b44..98c935d 100644
--- a/rialto/tests/test.rs
+++ b/rialto/tests/test.rs
@@ -114,8 +114,15 @@
         taskProfiles: vec![],
         gdbPort: 0, // No gdb
     });
-    let vm = VmInstance::create(service.as_ref(), &config, Some(console), Some(log), None)
-        .context("Failed to create VM")?;
+    let vm = VmInstance::create(
+        service.as_ref(),
+        &config,
+        Some(console),
+        /* consoleIn */ None,
+        Some(log),
+        None,
+    )
+    .context("Failed to create VM")?;
 
     vm.start().context("Failed to start VM")?;
 
diff --git a/virtualizationmanager/src/aidl.rs b/virtualizationmanager/src/aidl.rs
index 86c8596..06274c8 100644
--- a/virtualizationmanager/src/aidl.rs
+++ b/virtualizationmanager/src/aidl.rs
@@ -172,11 +172,18 @@
     fn createVm(
         &self,
         config: &VirtualMachineConfig,
-        console_fd: Option<&ParcelFileDescriptor>,
+        console_out_fd: Option<&ParcelFileDescriptor>,
+        console_in_fd: Option<&ParcelFileDescriptor>,
         log_fd: Option<&ParcelFileDescriptor>,
     ) -> binder::Result<Strong<dyn IVirtualMachine>> {
         let mut is_protected = false;
-        let ret = self.create_vm_internal(config, console_fd, log_fd, &mut is_protected);
+        let ret = self.create_vm_internal(
+            config,
+            console_out_fd,
+            console_in_fd,
+            log_fd,
+            &mut is_protected,
+        );
         write_vm_creation_stats(config, is_protected, &ret);
         ret
     }
@@ -292,7 +299,8 @@
     fn create_vm_internal(
         &self,
         config: &VirtualMachineConfig,
-        console_fd: Option<&ParcelFileDescriptor>,
+        console_out_fd: Option<&ParcelFileDescriptor>,
+        console_in_fd: Option<&ParcelFileDescriptor>,
         log_fd: Option<&ParcelFileDescriptor>,
         is_protected: &mut bool,
     ) -> binder::Result<Strong<dyn IVirtualMachine>> {
@@ -341,8 +349,9 @@
         };
 
         let state = &mut *self.state.lock().unwrap();
-        let console_fd =
-            clone_or_prepare_logger_fd(&debug_config, console_fd, format!("Console({})", cid))?;
+        let console_out_fd =
+            clone_or_prepare_logger_fd(&debug_config, console_out_fd, format!("Console({})", cid))?;
+        let console_in_fd = console_in_fd.map(clone_file).transpose()?;
         let log_fd = clone_or_prepare_logger_fd(&debug_config, log_fd, format!("Log({})", cid))?;
 
         // Counter to generate unique IDs for temporary image files.
@@ -446,7 +455,8 @@
             cpus,
             host_cpu_topology,
             task_profiles: config.taskProfiles.clone(),
-            console_fd,
+            console_out_fd,
+            console_in_fd,
             log_fd,
             ramdump,
             indirect_files,
diff --git a/virtualizationmanager/src/crosvm.rs b/virtualizationmanager/src/crosvm.rs
index 856ff1e..13367c3 100644
--- a/virtualizationmanager/src/crosvm.rs
+++ b/virtualizationmanager/src/crosvm.rs
@@ -107,7 +107,8 @@
     pub cpus: Option<NonZeroU32>,
     pub host_cpu_topology: bool,
     pub task_profiles: Vec<String>,
-    pub console_fd: Option<File>,
+    pub console_out_fd: Option<File>,
+    pub console_in_fd: Option<File>,
     pub log_fd: Option<File>,
     pub ramdump: Option<File>,
     pub indirect_files: Vec<File>,
@@ -776,21 +777,29 @@
     //
     // When [console|log]_fd is not specified, the devices are attached to sink, which means what's
     // written there is discarded.
-    let console_arg = format_serial_arg(&mut preserved_fds, &config.console_fd);
-    let log_arg = format_serial_arg(&mut preserved_fds, &config.log_fd);
+    let console_out_arg = format_serial_out_arg(&mut preserved_fds, &config.console_out_fd);
+    let console_in_arg = config
+        .console_in_fd
+        .as_ref()
+        .map(|fd| format!(",input={}", add_preserved_fd(&mut preserved_fds, fd)))
+        .unwrap_or_default();
+    let log_arg = format_serial_out_arg(&mut preserved_fds, &config.log_fd);
     let failure_serial_path = add_preserved_fd(&mut preserved_fds, &failure_pipe_write);
-    let ramdump_arg = format_serial_arg(&mut preserved_fds, &config.ramdump);
+    let ramdump_arg = format_serial_out_arg(&mut preserved_fds, &config.ramdump);
 
     // Warning: Adding more serial devices requires you to shift the PCI device ID of the boot
     // disks in bootconfig.x86_64. This is because x86 crosvm puts serial devices and the block
     // devices in the same PCI bus and serial devices comes before the block devices. Arm crosvm
     // doesn't have the issue.
     // /dev/ttyS0
-    command.arg(format!("--serial={},hardware=serial,num=1", &console_arg));
+    command.arg(format!("--serial={}{},hardware=serial,num=1", &console_out_arg, &console_in_arg));
     // /dev/ttyS1
     command.arg(format!("--serial=type=file,path={},hardware=serial,num=2", &failure_serial_path));
     // /dev/hvc0
-    command.arg(format!("--serial={},hardware=virtio-console,num=1", &console_arg));
+    command.arg(format!(
+        "--serial={}{},hardware=virtio-console,num=1",
+        &console_out_arg, &console_in_arg
+    ));
     // /dev/hvc1
     command.arg(format!("--serial={},hardware=virtio-console,num=2", &ramdump_arg));
     // /dev/hvc2
@@ -890,7 +899,7 @@
 
 /// Adds the file descriptor for `file` (if any) to `preserved_fds`, and returns the appropriate
 /// string for a crosvm `--serial` flag. If `file` is none, creates a dummy sink device.
-fn format_serial_arg(preserved_fds: &mut Vec<RawFd>, file: &Option<File>) -> String {
+fn format_serial_out_arg(preserved_fds: &mut Vec<RawFd>, file: &Option<File>) -> String {
     if let Some(file) = file {
         format!("type=file,path={}", add_preserved_fd(preserved_fds, file))
     } else {
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualizationService.aidl b/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualizationService.aidl
index d72d5ac..99bc076 100644
--- a/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualizationService.aidl
+++ b/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualizationService.aidl
@@ -23,12 +23,14 @@
 interface IVirtualizationService {
     /**
      * Create the VM with the given config file, and return a handle to it ready to start it. If
-     * `consoleFd` is provided then console output from the VM will be sent to it. If `osLogFd` is
+     * `consoleOutFd` is provided then console output from the VM will be sent to it. If
+     * `consoleInFd` is provided then console input to the VM will be read from it. If `osLogFd` is
      * provided then the OS-level logs will be sent to it. `osLogFd` is supported only when the OS
      * running in the VM has the logging system. In case of Microdroid, the logging system is logd.
      */
     IVirtualMachine createVm(in VirtualMachineConfig config,
-            in @nullable ParcelFileDescriptor consoleFd,
+            in @nullable ParcelFileDescriptor consoleOutFd,
+            in @nullable ParcelFileDescriptor consoleInFd,
             in @nullable ParcelFileDescriptor osLogFd);
 
     /**
diff --git a/virtualizationservice/src/rkpvm.rs b/virtualizationservice/src/rkpvm.rs
index a4649f6..ebfb667 100644
--- a/virtualizationservice/src/rkpvm.rs
+++ b/virtualizationservice/src/rkpvm.rs
@@ -79,7 +79,7 @@
         taskProfiles: vec![],
         gdbPort: 0, // No gdb
     });
-    let vm = VmInstance::create(service.as_ref(), &config, None, None, None)
+    let vm = VmInstance::create(service.as_ref(), &config, None, None, None, None)
         .context("Failed to create service VM")?;
 
     info!("service_vm: Starting Service VM...");
diff --git a/vm/src/main.rs b/vm/src/main.rs
index bc3f4da..0800f57 100644
--- a/vm/src/main.rs
+++ b/vm/src/main.rs
@@ -74,6 +74,10 @@
         #[clap(long)]
         console: Option<PathBuf>,
 
+        /// Path to file for VM console input.
+        #[clap(long)]
+        console_in: Option<PathBuf>,
+
         /// Path to file for VM log output.
         #[clap(long)]
         log: Option<PathBuf>,
@@ -138,6 +142,10 @@
         #[clap(long)]
         console: Option<PathBuf>,
 
+        /// Path to file for VM console input.
+        #[clap(long)]
+        console_in: Option<PathBuf>,
+
         /// Path to file for VM log output.
         #[clap(long)]
         log: Option<PathBuf>,
@@ -193,6 +201,10 @@
         #[clap(long)]
         console: Option<PathBuf>,
 
+        /// Path to file for VM console input.
+        #[clap(long)]
+        console_in: Option<PathBuf>,
+
         /// Path to file for VM log output.
         #[clap(long)]
         log: Option<PathBuf>,
@@ -277,6 +289,7 @@
             config_path,
             payload_binary_name,
             console,
+            console_in,
             log,
             debug,
             protected,
@@ -297,6 +310,7 @@
             config_path,
             payload_binary_name,
             console.as_deref(),
+            console_in.as_deref(),
             log.as_deref(),
             debug,
             protected,
@@ -313,6 +327,7 @@
             storage,
             storage_size,
             console,
+            console_in,
             log,
             debug,
             protected,
@@ -328,6 +343,7 @@
             storage.as_deref(),
             storage_size,
             console.as_deref(),
+            console_in.as_deref(),
             log.as_deref(),
             debug,
             protected,
@@ -337,12 +353,13 @@
             gdb,
             kernel.as_deref(),
         ),
-        Opt::Run { name, config, cpu_topology, task_profiles, console, log, gdb } => {
+        Opt::Run { name, config, cpu_topology, task_profiles, console, console_in, log, gdb } => {
             command_run(
                 name,
                 get_service()?.as_ref(),
                 &config,
                 console.as_deref(),
+                console_in.as_deref(),
                 log.as_deref(),
                 /* mem */ None,
                 cpu_topology,
diff --git a/vm/src/run.rs b/vm/src/run.rs
index 54c1de4..27fd903 100644
--- a/vm/src/run.rs
+++ b/vm/src/run.rs
@@ -51,7 +51,8 @@
     storage_size: Option<u64>,
     config_path: Option<String>,
     payload_binary_name: Option<String>,
-    console_path: Option<&Path>,
+    console_out_path: Option<&Path>,
+    console_in_path: Option<&Path>,
     log_path: Option<&Path>,
     debug_level: DebugLevel,
     protected: bool,
@@ -152,7 +153,7 @@
         gdbPort: gdb.map(u16::from).unwrap_or(0) as i32, // 0 means no gdb
         customKernelImage: kernel,
     });
-    run(service, &config, &payload_config_str, console_path, log_path)
+    run(service, &config, &payload_config_str, console_out_path, console_in_path, log_path)
 }
 
 fn find_empty_payload_apk_path() -> Result<PathBuf, Error> {
@@ -185,7 +186,8 @@
     work_dir: Option<PathBuf>,
     storage: Option<&Path>,
     storage_size: Option<u64>,
-    console_path: Option<&Path>,
+    console_out_path: Option<&Path>,
+    console_in_path: Option<&Path>,
     log_path: Option<&Path>,
     debug_level: DebugLevel,
     protected: bool,
@@ -216,7 +218,8 @@
         storage_size,
         /* config_path= */ None,
         Some(payload_binary_name.to_owned()),
-        console_path,
+        console_out_path,
+        console_in_path,
         log_path,
         debug_level,
         protected,
@@ -235,7 +238,8 @@
     name: Option<String>,
     service: &dyn IVirtualizationService,
     config_path: &Path,
-    console_path: Option<&Path>,
+    console_out_path: Option<&Path>,
+    console_in_path: Option<&Path>,
     log_path: Option<&Path>,
     mem: Option<u32>,
     cpu_topology: CpuTopology,
@@ -262,7 +266,8 @@
         service,
         &VirtualMachineConfig::RawConfig(config),
         &format!("{:?}", config_path),
-        console_path,
+        console_out_path,
+        console_in_path,
         log_path,
     )
 }
@@ -283,28 +288,35 @@
     service: &dyn IVirtualizationService,
     config: &VirtualMachineConfig,
     payload_config: &str,
-    console_path: Option<&Path>,
+    console_out_path: Option<&Path>,
+    console_in_path: Option<&Path>,
     log_path: Option<&Path>,
 ) -> Result<(), Error> {
-    let console = if let Some(console_path) = console_path {
-        Some(
-            File::create(console_path)
-                .with_context(|| format!("Failed to open console file {:?}", console_path))?,
-        )
+    let console_out = if let Some(console_out_path) = console_out_path {
+        Some(File::create(console_out_path).with_context(|| {
+            format!("Failed to open console output file {:?}", console_out_path)
+        })?)
     } else {
-        Some(duplicate_stdout()?)
+        Some(duplicate_fd(io::stdout())?)
     };
+    let console_in =
+        if let Some(console_in_path) = console_in_path {
+            Some(File::create(console_in_path).with_context(|| {
+                format!("Failed to open console input file {:?}", console_in_path)
+            })?)
+        } else {
+            Some(duplicate_fd(io::stdin())?)
+        };
     let log = if let Some(log_path) = log_path {
         Some(
             File::create(log_path)
                 .with_context(|| format!("Failed to open log file {:?}", log_path))?,
         )
     } else {
-        Some(duplicate_stdout()?)
+        Some(duplicate_fd(io::stdout())?)
     };
-
     let callback = Box::new(Callback {});
-    let vm = VmInstance::create(service, config, console, log, Some(callback))
+    let vm = VmInstance::create(service, config, console_out, console_in, log, Some(callback))
         .context("Failed to create VM")?;
     vm.start().context("Failed to start VM")?;
 
@@ -349,12 +361,12 @@
     }
 }
 
-/// Safely duplicate the standard output file descriptor.
-fn duplicate_stdout() -> io::Result<File> {
-    let stdout_fd = io::stdout().as_raw_fd();
+/// Safely duplicate the file descriptor.
+fn duplicate_fd<T: AsRawFd>(file: T) -> io::Result<File> {
+    let fd = file.as_raw_fd();
     // Safe because this just duplicates a file descriptor which we know to be valid, and we check
     // for an error.
-    let dup_fd = unsafe { libc::dup(stdout_fd) };
+    let dup_fd = unsafe { libc::dup(fd) };
     if dup_fd < 0 {
         Err(io::Error::last_os_error())
     } else {
diff --git a/vmbase/example/tests/test.rs b/vmbase/example/tests/test.rs
index 085a620..3594523 100644
--- a/vmbase/example/tests/test.rs
+++ b/vmbase/example/tests/test.rs
@@ -106,8 +106,15 @@
     });
     let (handle, console) = android_log_fd()?;
     let (mut log_reader, log_writer) = pipe()?;
-    let vm = VmInstance::create(service.as_ref(), &config, Some(console), Some(log_writer), None)
-        .context("Failed to create VM")?;
+    let vm = VmInstance::create(
+        service.as_ref(),
+        &config,
+        Some(console),
+        /* consoleIn */ None,
+        Some(log_writer),
+        None,
+    )
+    .context("Failed to create VM")?;
     vm.start().context("Failed to start VM")?;
     info!("Started example VM.");
 
diff --git a/vmclient/src/lib.rs b/vmclient/src/lib.rs
index 8f25b99..cfd015a 100644
--- a/vmclient/src/lib.rs
+++ b/vmclient/src/lib.rs
@@ -175,14 +175,17 @@
     pub fn create(
         service: &dyn IVirtualizationService,
         config: &VirtualMachineConfig,
-        console: Option<File>,
+        console_out: Option<File>,
+        console_in: Option<File>,
         log: Option<File>,
         callback: Option<Box<dyn VmCallback + Send + Sync>>,
     ) -> BinderResult<Self> {
-        let console = console.map(ParcelFileDescriptor::new);
+        let console_out = console_out.map(ParcelFileDescriptor::new);
+        let console_in = console_in.map(ParcelFileDescriptor::new);
         let log = log.map(ParcelFileDescriptor::new);
 
-        let vm = service.createVm(config, console.as_ref(), log.as_ref())?;
+        let vm =
+            service.createVm(config, console_out.as_ref(), console_in.as_ref(), log.as_ref())?;
 
         let cid = vm.getCid()?;