Send logd logs from VM to host

When the debug level of a VM is not "none", logd logs from the VM is
sent to the host. This is done by running the VM with another virtual
console and running logcat as a daemon process whose output is set to
the new virtual console device. The launch of the daemon process is
controlled by microdroid. It starts the process only when the debug
level is set to above "none".

For now, the virtual console device is backed by the same file
descriptor as the kernel console logs. A follow-up change will introduce
a new dedicated file descriptor.

Bug: 200914564
Test: start microdroid using the `vm` tool. logcat logs are shown in
stdout.

Change-Id: I1748d30c5c997cda73f7b9f082ca84b0b3d25f1e
diff --git a/microdroid/bootconfig.x86_64 b/microdroid/bootconfig.x86_64
index 20d64f7..2977ee3 100644
--- a/microdroid/bootconfig.x86_64
+++ b/microdroid/bootconfig.x86_64
@@ -1 +1 @@
-androidboot.boot_devices = pci0000:00/0000:00:02.0,pci0000:00/0000:00:03.0,pci0000:00/0000:00:04.0
+androidboot.boot_devices = pci0000:00/0000:00:03.0,pci0000:00/0000:00:04.0,pci0000:00/0000:00:05.0
diff --git a/microdroid/init.rc b/microdroid/init.rc
index 74da023..ad551cc 100644
--- a/microdroid/init.rc
+++ b/microdroid/init.rc
@@ -195,6 +195,11 @@
     seclabel u:r:shell:s0
     setenv HOSTNAME console
 
+service seriallogging /system/bin/logcat -b all -v threadtime -f /dev/hvc1 *:V
+    disabled
+    user logd
+    group root logd
+
 on fs
     write /dev/event-log-tags "# content owned by logd
 "
diff --git a/microdroid/ueventd.rc b/microdroid/ueventd.rc
index 271e134..85f2f9d 100644
--- a/microdroid/ueventd.rc
+++ b/microdroid/ueventd.rc
@@ -24,3 +24,6 @@
 # these should not be world writable
 /dev/rtc0                 0640   system     system
 /dev/tty0                 0660   root       system
+
+# Virtual console for logcat
+/dev/hvc1                 0660   logd       logd
diff --git a/microdroid_manager/src/main.rs b/microdroid_manager/src/main.rs
index ac62e58..a2fab07 100644
--- a/microdroid_manager/src/main.rs
+++ b/microdroid_manager/src/main.rs
@@ -49,6 +49,7 @@
 const VMADDR_CID_HOST: u32 = 2;
 
 const APEX_CONFIG_DONE_PROP: &str = "apex_config.done";
+const LOGD_ENABLED_PROP: &str = "ro.boot.logd.enabled";
 
 fn get_vms_rpc_binder() -> Result<Strong<dyn IVirtualMachineService>> {
     // SAFETY: AIBinder returned by RpcClient has correct reference count, and the ownership can be
@@ -223,6 +224,12 @@
     info!("notifying payload started");
     service.notifyPayloadStarted()?;
 
+    // Start logging if enabled
+    // TODO(b/200914564) set filterspec if debug_level is app_only
+    if system_properties::read(LOGD_ENABLED_PROP)? == "1" {
+        system_properties::write("ctl.start", "seriallogging")?;
+    }
+
     let exit_status = command.spawn()?.wait()?;
     if let Some(code) = exit_status.code() {
         info!("notifying payload finished");
diff --git a/virtualizationservice/src/crosvm.rs b/virtualizationservice/src/crosvm.rs
index 8a5a7dd..dfb1cbb 100644
--- a/virtualizationservice/src/crosvm.rs
+++ b/virtualizationservice/src/crosvm.rs
@@ -243,28 +243,31 @@
         command.arg("--mem").arg(memory_mib.to_string());
     }
 
+    // Keep track of what file descriptors should be mapped to the crosvm process.
+    let mut preserved_fds = config.indirect_files.iter().map(|file| file.as_raw_fd()).collect();
+
     // Setup the serial devices.
     // 1. uart device: used as the output device by bootloaders and as early console by linux
     // 2. virtio-console device: used as the console device
+    // 3. virtio-console device: used as the logcat output
     //
     // When log_fd is not specified, the devices are attached to sink, which means what's written
     // there is discarded.
-    //
+    let path = config.log_fd.as_ref().map(|fd| add_preserved_fd(&mut preserved_fds, fd));
+    let backend = path.as_ref().map_or("sink", |_| "file");
+    let path_arg = path.as_ref().map_or(String::new(), |path| format!(",path={}", path));
+
     // 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.
-    let backend = if let Some(log_fd) = config.log_fd {
-        command.stdout(log_fd);
-        "stdout"
-    } else {
-        "sink"
-    };
-    command.arg(format!("--serial=type={},hardware=serial", backend));
-    command.arg(format!("--serial=type={},hardware=virtio-console", backend));
-
-    // Keep track of what file descriptors should be mapped to the crosvm process.
-    let mut preserved_fds = config.indirect_files.iter().map(|file| file.as_raw_fd()).collect();
+    // /dev/ttyS0
+    command.arg(format!("--serial=type={}{},hardware=serial", backend, &path_arg));
+    // /dev/hvc0
+    command.arg(format!("--serial=type={}{},hardware=virtio-console,num=1", backend, &path_arg));
+    // /dev/hvc1
+    // TODO(b/200914564) use a different fd for logcat log
+    command.arg(format!("--serial=type={}{},hardware=virtio-console,num=2", backend, &path_arg));
 
     if let Some(bootloader) = &config.bootloader {
         command.arg("--bios").arg(add_preserved_fd(&mut preserved_fds, bootloader));