Merge "Enable adb and adb root with debug level or debug policy"
diff --git a/microdroid/Android.bp b/microdroid/Android.bp
index bc4db6c..a2a4138 100644
--- a/microdroid/Android.bp
+++ b/microdroid/Android.bp
@@ -51,6 +51,7 @@
     deps: [
         "init_second_stage",
         "microdroid_build_prop",
+        "microdroid_init_debug_policy",
         "microdroid_init_rc",
         "microdroid_ueventd_rc",
         "microdroid_launcher",
diff --git a/microdroid/init.rc b/microdroid/init.rc
index 70c22d4..5187a12 100644
--- a/microdroid/init.rc
+++ b/microdroid/init.rc
@@ -21,13 +21,9 @@
     write /linkerconfig/ld.config.txt \#
     chmod 644 /linkerconfig/ld.config.txt
 
-# If VM is debuggable, send logs to outside ot the VM via the serial console.
-# If non-debuggable, logs are internally consumed at /dev/null
-on early-init && property:ro.boot.microdroid.debuggable=1
-    setprop ro.log.file_logger.path /dev/hvc2
-
-on early-init && property:ro.boot.microdroid.debuggable=0
-    setprop ro.log.file_logger.path /dev/null
+    # Applies debug policy to decide whether to enable adb, adb root, and logcat.
+    # We don't directly exec the binary to specify stdio_to_kmsg.
+    exec_start init_debug_policy
 
 on init
     mkdir /mnt/apk 0755 system system
@@ -47,8 +43,6 @@
     # payloads are not designed to run with bootstrap bionic
     setprop apex_config.done true
 
-    setprop ro.debuggable ${ro.boot.microdroid.debuggable:-0}
-
 on property:microdroid_manager.init_done=1
     # Stop ueventd to save memory
     stop ueventd
@@ -57,7 +51,7 @@
     # Mount tracefs (with GID=AID_READTRACEFS)
     mount tracefs tracefs /sys/kernel/tracing gid=3012
 
-on init && property:ro.boot.adb.enabled=1
+on property:init_debug_policy.adbd.enabled=1
     start adbd
 
 # Mount filesystems and start core system services.
@@ -179,3 +173,8 @@
     group shell log readproc
     seclabel u:r:shell:s0
     setenv HOSTNAME console
+
+service init_debug_policy /system/bin/init_debug_policy
+    oneshot
+    disabled
+    stdio_to_kmsg
diff --git a/microdroid/init_debug_policy/Android.bp b/microdroid/init_debug_policy/Android.bp
new file mode 100644
index 0000000..b56ef79
--- /dev/null
+++ b/microdroid/init_debug_policy/Android.bp
@@ -0,0 +1,11 @@
+rust_binary {
+    name: "microdroid_init_debug_policy",
+    srcs: ["src/init_debug_policy.rs"],
+    stem: "init_debug_policy",
+    rustlibs: [
+        "librustutils",
+    ],
+    installable: false, // match with microdroid_init_rc.
+    bootstrap: true,
+    prefer_rlib: true,
+}
diff --git a/microdroid/init_debug_policy/src/init_debug_policy.rs b/microdroid/init_debug_policy/src/init_debug_policy.rs
new file mode 100644
index 0000000..6c80926
--- /dev/null
+++ b/microdroid/init_debug_policy/src/init_debug_policy.rs
@@ -0,0 +1,57 @@
+// Copyright 2023, 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.
+
+//! Applies debug policies when booting microdroid
+
+use rustutils::system_properties;
+use rustutils::system_properties::PropertyWatcherError;
+use std::fs::File;
+use std::io::Read;
+
+/// Get debug policy value in bool. It's true iff the value is explicitly set to <1>.
+fn get_debug_policy_bool(path: &'static str) -> Option<bool> {
+    let mut file = File::open(path).ok()?;
+    let mut log: [u8; 4] = Default::default();
+    file.read_exact(&mut log).ok()?;
+    // DT spec uses big endian although Android is always little endian.
+    Some(u32::from_be_bytes(log) == 1)
+}
+
+fn main() -> Result<(), PropertyWatcherError> {
+    // If VM is debuggable or debug policy says so, send logs to outside ot the VM via the serial console.
+    // Otherwise logs are internally consumed at /dev/null
+    let log_path = if system_properties::read_bool("ro.boot.microdroid.debuggable", false)?
+        || get_debug_policy_bool("/sys/firmware/devicetree/base/avf/guest/common/log")
+            .unwrap_or_default()
+    {
+        "/dev/hvc2"
+    } else {
+        "/dev/null"
+    };
+    system_properties::write("ro.log.file_logger.path", log_path)?;
+
+    let (adbd_enabled, debuggable) = if system_properties::read_bool("ro.boot.adb.enabled", false)?
+        || get_debug_policy_bool("/sys/firmware/devicetree/base/avf/guest/microdroid/adb")
+            .unwrap_or_default()
+    {
+        // debuggable is required for adb root and bypassing adb authorization.
+        ("1", "1")
+    } else {
+        ("0", "0")
+    };
+    system_properties::write("init_debug_policy.adbd.enabled", adbd_enabled)?;
+    system_properties::write("ro.debuggable", debuggable)?;
+
+    Ok(())
+}
diff --git a/tests/hostside/Android.bp b/tests/hostside/Android.bp
index d217c00..78500af 100644
--- a/tests/hostside/Android.bp
+++ b/tests/hostside/Android.bp
@@ -36,6 +36,20 @@
     out: ["avf_debug_policy_without_console_output.dtbo"],
 }
 
+genrule {
+    name: "test_avf_debug_policy_with_adb",
+    defaults: ["test_avf_debug_policy_overlay"],
+    srcs: ["assets/avf_debug_policy_with_adb.dts"],
+    out: ["avf_debug_policy_with_adb.dtbo"],
+}
+
+genrule {
+    name: "test_avf_debug_policy_without_adb",
+    defaults: ["test_avf_debug_policy_overlay"],
+    srcs: ["assets/avf_debug_policy_without_adb.dts"],
+    out: ["avf_debug_policy_without_adb.dtbo"],
+}
+
 java_test_host {
     name: "MicrodroidHostTestCases",
     srcs: ["java/**/*.java"],
@@ -64,6 +78,8 @@
         ":test_avf_debug_policy_without_ramdump",
         ":test_avf_debug_policy_with_console_output",
         ":test_avf_debug_policy_without_console_output",
+        ":test_avf_debug_policy_with_adb",
+        ":test_avf_debug_policy_without_adb",
         "assets/bcc.dat",
     ],
     data_native_bins: [
diff --git a/tests/hostside/assets/avf_debug_policy_with_adb.dts b/tests/hostside/assets/avf_debug_policy_with_adb.dts
new file mode 100644
index 0000000..9ad15dd
--- /dev/null
+++ b/tests/hostside/assets/avf_debug_policy_with_adb.dts
@@ -0,0 +1,18 @@
+/dts-v1/;
+/plugin/;
+
+/ {
+    fragment@avf {
+        target-path = "/";
+
+        __overlay__ {
+            avf {
+                guest {
+                    microdroid {
+                        adb = <1>;
+                    };
+                };
+            };
+        };
+    };
+};
\ No newline at end of file
diff --git a/tests/hostside/assets/avf_debug_policy_with_ramdump.dts b/tests/hostside/assets/avf_debug_policy_with_ramdump.dts
index f1a5196..26db7be 100644
--- a/tests/hostside/assets/avf_debug_policy_with_ramdump.dts
+++ b/tests/hostside/assets/avf_debug_policy_with_ramdump.dts
@@ -11,6 +11,9 @@
                     common {
                         ramdump = <1>;
                     };
+                    microdroid {
+                        adb = <1>; // adb is required to check VM's bootargs.
+                    };
                 };
             };
         };
diff --git a/tests/hostside/assets/avf_debug_policy_without_adb.dts b/tests/hostside/assets/avf_debug_policy_without_adb.dts
new file mode 100644
index 0000000..992e0ff
--- /dev/null
+++ b/tests/hostside/assets/avf_debug_policy_without_adb.dts
@@ -0,0 +1,18 @@
+/dts-v1/;
+/plugin/;
+
+/ {
+    fragment@avf {
+        target-path = "/";
+
+        __overlay__ {
+            avf {
+                guest {
+                    microdroid {
+                        adb = <0>;
+                    };
+                };
+            };
+        };
+    };
+};
\ No newline at end of file
diff --git a/tests/hostside/assets/avf_debug_policy_without_ramdump.dts b/tests/hostside/assets/avf_debug_policy_without_ramdump.dts
index 5b15d51..194e314 100644
--- a/tests/hostside/assets/avf_debug_policy_without_ramdump.dts
+++ b/tests/hostside/assets/avf_debug_policy_without_ramdump.dts
@@ -11,6 +11,9 @@
                     common {
                         ramdump = <0>;
                     };
+                    microdroid {
+                        adb = <1>; // adb is required to check VM's bootargs.
+                    };
                 };
             };
         };
diff --git a/tests/hostside/java/com/android/microdroid/test/PvmfwDebugPolicyHostTests.java b/tests/hostside/java/com/android/microdroid/test/PvmfwDebugPolicyHostTests.java
index 755613a..10f7003 100644
--- a/tests/hostside/java/com/android/microdroid/test/PvmfwDebugPolicyHostTests.java
+++ b/tests/hostside/java/com/android/microdroid/test/PvmfwDebugPolicyHostTests.java
@@ -34,6 +34,7 @@
 import com.android.tradefed.device.TestDevice;
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.util.CommandStatus;
 import com.android.tradefed.util.CommandResult;
 import com.android.tradefed.util.FileUtil;
 
@@ -55,9 +56,11 @@
     @NonNull private static final String PACKAGE_FILE_NAME = "MicrodroidTestApp.apk";
     @NonNull private static final String PACKAGE_NAME = "com.android.microdroid.test";
     @NonNull private static final String MICRODROID_DEBUG_FULL = "full";
+    @NonNull private static final String MICRODROID_DEBUG_NONE = "none";
     @NonNull private static final String MICRODROID_CONFIG_PATH = "assets/vm_config_apex.json";
     @NonNull private static final String MICRODROID_LOG_PATH = TEST_ROOT + "log.txt";
     private static final int BOOT_COMPLETE_TIMEOUT_MS = 30000; // 30 seconds
+    private static final int BOOT_FAILURE_WAIT_TIME_MS = 10000; // 10 seconds
     private static final int CONSOLE_OUTPUT_WAIT_MS = 5000; // 5 seconds
 
     @NonNull private static final String CUSTOM_PVMFW_FILE_PREFIX = "pvmfw";
@@ -146,7 +149,7 @@
     public void testRamdump() throws Exception {
         Pvmfw pvmfw = createPvmfw("avf_debug_policy_with_ramdump.dtbo");
         pvmfw.serialize(mCustomPvmfwBinFileOnHost);
-        mMicrodroidDevice = launchProtectedVmAndWaitForBootCompleted();
+        mMicrodroidDevice = launchProtectedVmAndWaitForBootCompleted(MICRODROID_DEBUG_FULL);
 
         assertThat(readMicrodroidFileAsString(MICRODROID_CMDLINE_PATH)).contains("crashkernel=");
         assertThat(readMicrodroidFileAsString(MICRODROID_DT_BOOTARGS_PATH))
@@ -159,7 +162,7 @@
     public void testNoRamdump() throws Exception {
         Pvmfw pvmfw = createPvmfw("avf_debug_policy_without_ramdump.dtbo");
         pvmfw.serialize(mCustomPvmfwBinFileOnHost);
-        mMicrodroidDevice = launchProtectedVmAndWaitForBootCompleted();
+        mMicrodroidDevice = launchProtectedVmAndWaitForBootCompleted(MICRODROID_DEBUG_FULL);
 
         assertThat(readMicrodroidFileAsString(MICRODROID_CMDLINE_PATH))
                 .doesNotContain("crashkernel=");
@@ -193,6 +196,33 @@
                 .isFalse();
     }
 
+    @Test
+    public void testNoAdb_boots() throws Exception {
+        Pvmfw pvmfw = createPvmfw("avf_debug_policy_without_adb.dtbo");
+        pvmfw.serialize(mCustomPvmfwBinFileOnHost);
+
+        // VM would boot, but cannot verify directly because of no adbd in the VM.
+        CommandResult result = tryLaunchProtectedNonDebuggableVm();
+        assertThat(result.getStatus()).isEqualTo(CommandStatus.TIMED_OUT);
+        assertWithMessage("Microdroid should have booted")
+                .that(result.getStderr())
+                .contains("payload is ready");
+    }
+
+    @Test
+    public void testNoAdb_noConnection() throws Exception {
+        Pvmfw pvmfw = createPvmfw("avf_debug_policy_without_adb.dtbo");
+        pvmfw.serialize(mCustomPvmfwBinFileOnHost);
+
+        try {
+            launchProtectedVmAndWaitForBootCompleted(
+                    MICRODROID_DEBUG_NONE, BOOT_FAILURE_WAIT_TIME_MS);
+            assertWithMessage("adb shouldn't be available").fail();
+        } catch (Exception e) {
+            // expected exception. passthrough.
+        }
+    }
+
     @NonNull
     private String readMicrodroidFileAsString(@NonNull String path)
             throws DeviceNotAvailableException {
@@ -220,14 +250,20 @@
         return result.getStdout().contains("Run /init as init process");
     }
 
-    private ITestDevice launchProtectedVmAndWaitForBootCompleted()
+    private ITestDevice launchProtectedVmAndWaitForBootCompleted(String debugLevel)
             throws DeviceNotAvailableException {
+        return launchProtectedVmAndWaitForBootCompleted(debugLevel, BOOT_COMPLETE_TIMEOUT_MS);
+    }
+
+    private ITestDevice launchProtectedVmAndWaitForBootCompleted(
+            String debugLevel, long adbTimeoutMs) throws DeviceNotAvailableException {
         mMicrodroidDevice =
                 MicrodroidBuilder.fromDevicePath(
                                 getPathForPackage(PACKAGE_NAME), MICRODROID_CONFIG_PATH)
-                        .debugLevel(MICRODROID_DEBUG_FULL)
+                        .debugLevel(debugLevel)
                         .protectedVm(/* protectedVm= */ true)
                         .addBootFile(mCustomPvmfwBinFileOnHost, PVMFW_FILE_NAME)
+                        .setAdbConnectTimeoutMs(adbTimeoutMs)
                         .build(mAndroidDevice);
         assertThat(mMicrodroidDevice.waitForBootComplete(BOOT_COMPLETE_TIMEOUT_MS)).isTrue();
         assertThat(mMicrodroidDevice.enableAdbRoot()).isTrue();
diff --git a/virtualizationmanager/src/debug_config.rs b/virtualizationmanager/src/debug_config.rs
index eda854a..666c98d 100644
--- a/virtualizationmanager/src/debug_config.rs
+++ b/virtualizationmanager/src/debug_config.rs
@@ -34,6 +34,13 @@
 pub fn should_prepare_console_output(debug_level: DebugLevel) -> bool {
     debug_level != DebugLevel::NONE
         || get_debug_policy_bool("/proc/device-tree/avf/guest/common/log").unwrap_or_default()
+        || get_debug_policy_bool("/proc/device-tree/avf/guest/microdroid/adb").unwrap_or_default()
+}
+
+/// Get whether debug apexes (MICRODROID_REQUIRED_APEXES_DEBUG) are required.
+pub fn should_include_debug_apexes(debug_level: DebugLevel) -> bool {
+    debug_level != DebugLevel::NONE
+        || get_debug_policy_bool("/proc/device-tree/avf/guest/microdroid/adb").unwrap_or_default()
 }
 
 /// Decision to support ramdump
diff --git a/virtualizationmanager/src/payload.rs b/virtualizationmanager/src/payload.rs
index 02e8f8e..99aea01 100644
--- a/virtualizationmanager/src/payload.rs
+++ b/virtualizationmanager/src/payload.rs
@@ -14,6 +14,7 @@
 
 //! Payload disk image
 
+use crate::debug_config::should_include_debug_apexes;
 use android_system_virtualizationservice::aidl::android::system::virtualizationservice::{
     DiskImage::DiskImage,
     Partition::Partition,
@@ -382,7 +383,7 @@
     debug_level: DebugLevel,
 ) -> Vec<&'a ApexInfo> {
     let mut additional_apexes: Vec<&str> = MICRODROID_REQUIRED_APEXES.to_vec();
-    if debug_level != DebugLevel::NONE {
+    if should_include_debug_apexes(debug_level) {
         additional_apexes.extend(MICRODROID_REQUIRED_APEXES_DEBUG.to_vec());
     }