Merge "XTS: Ensure Updatable VM is supported" into main
diff --git a/OWNERS b/OWNERS
index 717a4db..81217f3 100644
--- a/OWNERS
+++ b/OWNERS
@@ -30,8 +30,7 @@
victorhsieh@google.com
# Ferrochrome
-per-file android/FerrochromeApp/**=jiyong@google.com,jeongik@google.com
-per-file android/LinuxInstaller/**=jiyong@google.com,jeongik@google.com
per-file android/TerminalApp/**=jiyong@google.com,jeongik@google.com
per-file android/VmLauncherApp/**=jiyong@google.com,jeongik@google.com
per-file libs/vm_launcher_lib/**=jiyong@google.com,jeongik@google.com
+per-file build/debian/**=jiyong@google.com,jeongik@google.com
diff --git a/android/FerrochromeApp/Android.bp b/android/FerrochromeApp/Android.bp
deleted file mode 100644
index 3e4ad14..0000000
--- a/android/FerrochromeApp/Android.bp
+++ /dev/null
@@ -1,42 +0,0 @@
-package {
- default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-java_defaults {
- name: "VmPayloadInstaller",
- init_rc: [":custom_vm_setup.rc"],
- required: ["custom_vm_setup"],
- // TODO(b/348113995): move this app to product partition
- system_ext_specific: true,
- platform_apis: true,
- privileged: true,
-}
-
-android_app {
- name: "FerrochromeApp",
- srcs: ["java/**/*.java"],
- resource_dirs: ["res"],
- defaults: ["VmPayloadInstaller"],
- required: [
- "privapp-permissions-ferrochrome.xml",
- ],
-}
-
-prebuilt_etc {
- name: "privapp-permissions-ferrochrome.xml",
- src: "privapp-permissions-ferrochrome.xml",
- sub_dir: "permissions",
- system_ext_specific: true,
-}
-
-filegroup {
- name: "custom_vm_setup.rc",
- srcs: ["custom_vm_setup.rc"],
-}
-
-sh_binary {
- name: "custom_vm_setup",
- src: "custom_vm_setup.sh",
- system_ext_specific: true,
- host_supported: false,
-}
diff --git a/android/FerrochromeApp/AndroidManifest.xml b/android/FerrochromeApp/AndroidManifest.xml
deleted file mode 100644
index f6d3f6a..0000000
--- a/android/FerrochromeApp/AndroidManifest.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.virtualization.ferrochrome" >
-
- <uses-permission android:name="android.permission.INTERNET" />
- <uses-permission android:name="android.permission.KILL_BACKGROUND_PROCESSES" />
- <uses-permission android:name="android.permission.KILL_ALL_BACKGROUND_PROCESSES" />
- <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
- <uses-permission android:name="com.android.virtualization.vmlauncher.permission.USE_VM_LAUNCHER" />
-
- <queries>
- <intent>
- <action android:name="android.virtualization.VM_LAUNCHER" />
- </intent>
- <intent>
- <action android:name="android.virtualization.FERROCHROME_DOWNLOADER" />
- </intent>
- </queries>
- <application
- android:label="Ferrochrome">
- <activity android:name=".FerrochromeActivity"
- android:configChanges="orientation|screenSize|keyboard|keyboardHidden|navigation|uiMode"
- android:screenOrientation="landscape"
- android:resizeableActivity="false"
- android:exported="true">
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- </activity>
- <activity android:name=".OpenUrlActivity"
- android:theme="@android:style/Theme.NoDisplay"
- android:launchMode="singleTask"
- android:exported="true">
- <intent-filter>
- <action android:name="android.intent.action.SEND" />
- <category android:name="android.intent.category.DEFAULT" />
- <data android:mimeType="text/*" />
- </intent-filter>
- </activity>
- </application>
-
-</manifest>
diff --git a/android/FerrochromeApp/java/com/android/virtualization/ferrochrome/FerrochromeActivity.java b/android/FerrochromeApp/java/com/android/virtualization/ferrochrome/FerrochromeActivity.java
deleted file mode 100644
index dba0078..0000000
--- a/android/FerrochromeApp/java/com/android/virtualization/ferrochrome/FerrochromeActivity.java
+++ /dev/null
@@ -1,307 +0,0 @@
-/*
- * Copyright (C) 2024 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.virtualization.ferrochrome;
-
-import android.annotation.WorkerThread;
-import android.app.Activity;
-import android.app.ActivityManager;
-import android.content.Intent;
-import android.content.pm.ActivityInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.os.Bundle;
-import android.os.Environment;
-import android.os.SystemProperties;
-import android.text.TextUtils;
-import android.util.Log;
-import android.view.WindowManager;
-import android.widget.TextView;
-
-import java.io.BufferedReader;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.StandardCopyOption;
-import java.util.List;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-
-public class FerrochromeActivity extends Activity {
- private static final String TAG = FerrochromeActivity.class.getName();
- private static final String ACTION_VM_LAUNCHER = "android.virtualization.VM_LAUNCHER";
- private static final String ACTION_FERROCHROME_DOWNLOAD =
- "android.virtualization.FERROCHROME_DOWNLOADER";
- private static final String EXTRA_FERROCHROME_DEST_DIR = "dest_dir";
- private static final String EXTRA_FERROCHROME_UPDATE_NEEDED = "update_needed";
-
- private static final Path DEST_DIR =
- Path.of(Environment.getExternalStorageDirectory().getPath(), "ferrochrome");
- private static final String ASSET_DIR = "ferrochrome";
- private static final Path VERSION_FILE = Path.of(DEST_DIR.toString(), "version");
-
- private static final int REQUEST_CODE_VMLAUNCHER = 1;
- private static final int REQUEST_CODE_FERROCHROME_DOWNLOADER = 2;
-
- private ResolvedActivity mVmLauncher;
-
- ExecutorService executorService = Executors.newSingleThreadExecutor();
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- if (!isTaskRoot()) {
- // In case we launched this activity multiple times, only start one instance of this
- // activity by only starting this as the root activity in task.
- finish();
- Log.w(TAG, "Not starting because not task root");
- return;
- }
- setContentView(R.layout.activity_ferrochrome);
- getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
-
- // Find VM Launcher
- mVmLauncher = ResolvedActivity.resolve(getPackageManager(), ACTION_VM_LAUNCHER);
- if (mVmLauncher == null) {
- updateStatus("Failed to resolve VM Launcher");
- return;
- }
-
- // Clean up the existing vm launcher process if there is
- ActivityManager am = getSystemService(ActivityManager.class);
- am.killBackgroundProcesses(mVmLauncher.activityInfo.packageName);
-
- executorService.execute(
- () -> {
- if (hasLocalAssets()) {
- if (updateImageIfNeeded()) {
- updateStatus("Starting Ferrochrome...");
- runOnUiThread(
- () ->
- startActivityForResult(
- mVmLauncher.intent, REQUEST_CODE_VMLAUNCHER));
- }
- } else {
- tryLaunchDownloader();
- }
- });
- }
-
- @Override
- protected void onActivityResult(int requestCode, int resultCode, Intent data) {
- if (requestCode == REQUEST_CODE_VMLAUNCHER) {
- finishAndRemoveTask();
- } else if (requestCode == REQUEST_CODE_FERROCHROME_DOWNLOADER) {
- String destDir = data.getStringExtra(EXTRA_FERROCHROME_DEST_DIR);
- boolean updateNeeded =
- data.getBooleanExtra(EXTRA_FERROCHROME_UPDATE_NEEDED, /* default= */ true);
-
- if (resultCode != RESULT_OK || TextUtils.isEmpty(destDir)) {
- Log.w(
- TAG,
- "Ferrochrome downloader returned error, code="
- + resultCode
- + ", dest="
- + destDir);
- updateStatus("User didn't accepted ferrochrome download..");
- return;
- }
-
- Log.w(TAG, "Ferrochrome downloader returned OK");
-
- if (!updateNeeded) {
- updateStatus("Starting Ferrochrome...");
- startActivityForResult(mVmLauncher.intent, REQUEST_CODE_VMLAUNCHER);
- }
-
- executorService.execute(
- () -> {
- if (!extractImages(destDir)) {
- updateStatus("Images from downloader looks bad..");
- return;
- }
- updateStatus("Starting Ferrochrome...");
- runOnUiThread(
- () ->
- startActivityForResult(
- mVmLauncher.intent, REQUEST_CODE_VMLAUNCHER));
- });
- }
- }
-
- @WorkerThread
- private boolean hasLocalAssets() {
- try {
- String[] files = getAssets().list(ASSET_DIR);
- return files != null && files.length > 0;
- } catch (IOException e) {
- return false;
- }
- }
-
- @WorkerThread
- private boolean updateImageIfNeeded() {
- if (!isUpdateNeeded()) {
- Log.d(TAG, "No update needed.");
- return true;
- }
-
- try {
- if (Files.notExists(DEST_DIR)) {
- Files.createDirectory(DEST_DIR);
- }
-
- updateStatus("Copying images...");
- String[] files = getAssets().list("ferrochrome");
- for (String file : files) {
- updateStatus(file);
- Path dst = Path.of(DEST_DIR.toString(), file);
- updateFile(getAssets().open("ferrochrome/" + file), dst);
- }
- } catch (IOException e) {
- Log.e(TAG, "Error while updating image: " + e);
- updateStatus("Failed.");
- return false;
- }
- updateStatus("Done.");
-
- return extractImages(DEST_DIR.toAbsolutePath().toString());
- }
-
- @WorkerThread
- private void tryLaunchDownloader() {
- // TODO(jaewan): Add safeguard to check whether ferrochrome downloader is valid.
- Log.w(TAG, "No built-in assets found. Try again with ferrochrome downloader");
-
- ResolvedActivity downloader =
- ResolvedActivity.resolve(getPackageManager(), ACTION_FERROCHROME_DOWNLOAD);
- if (downloader == null) {
- Log.d(TAG, "Ferrochrome downloader doesn't exist");
- updateStatus("ChromeOS image not found. Please go/try-ferrochrome");
- return;
- }
- String pkgName = downloader.activityInfo.packageName;
- Log.d(TAG, "Resolved Ferrochrome Downloader, pkgName=" + pkgName);
- updateStatus("Launching Ferrochrome downloader for update");
-
- // onActivityResult() will handle downloader result.
- startActivityForResult(downloader.intent, REQUEST_CODE_FERROCHROME_DOWNLOADER);
- }
-
- @WorkerThread
- private boolean extractImages(String destDir) {
- updateStatus("Extracting images...");
-
- if (TextUtils.isEmpty(destDir)) {
- throw new RuntimeException("Internal error: destDir shouldn't be null");
- }
-
- SystemProperties.set("debug.custom_vm_setup.path", destDir);
- SystemProperties.set("debug.custom_vm_setup.done", "false");
- SystemProperties.set("debug.custom_vm_setup.start", "true");
- while (!SystemProperties.getBoolean("debug.custom_vm_setup.done", false)) {
- try {
- Thread.sleep(1000);
- } catch (Exception e) {
- Log.e(TAG, "Error while extracting image: " + e);
- updateStatus("Failed.");
- return false;
- }
- }
-
- updateStatus("Done.");
- return true;
- }
-
- @WorkerThread
- private boolean isUpdateNeeded() {
- Path[] pathsToCheck = {DEST_DIR, VERSION_FILE};
- for (Path p : pathsToCheck) {
- if (Files.notExists(p)) {
- Log.d(TAG, p.toString() + " does not exist.");
- return true;
- }
- }
-
- try {
- String installedVer = readLine(new FileInputStream(VERSION_FILE.toFile()));
- String updatedVer = readLine(getAssets().open("ferrochrome/version"));
- if (installedVer.equals(updatedVer)) {
- return false;
- }
- Log.d(TAG, "Version mismatch. Installed: " + installedVer + " Updated: " + updatedVer);
- } catch (IOException e) {
- Log.e(TAG, "Error while checking version: " + e);
- }
- return true;
- }
-
- private static String readLine(InputStream input) throws IOException {
- try (BufferedReader reader = new BufferedReader(new InputStreamReader(input))) {
- return reader.readLine();
- } catch (IOException e) {
- throw e;
- }
- }
-
- private static void updateFile(InputStream input, Path path) throws IOException {
- try {
- Files.copy(input, path, StandardCopyOption.REPLACE_EXISTING);
- } finally {
- input.close();
- }
- }
-
- private void updateStatus(String line) {
- runOnUiThread(
- () -> {
- TextView statusView = findViewById(R.id.status_txt_view);
- statusView.append(line + "\n");
- });
- }
-
- private static final class ResolvedActivity {
- public final ActivityInfo activityInfo;
- public final Intent intent;
-
- private ResolvedActivity(ActivityInfo activityInfo, Intent intent) {
- this.activityInfo = activityInfo;
- this.intent = intent;
- }
-
- /* synthetic access */
- static ResolvedActivity resolve(PackageManager pm, String action) {
- Intent intent = new Intent(action).setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
- List<ResolveInfo> resolveInfos =
- pm.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
- if (resolveInfos == null || resolveInfos.size() != 1) {
- Log.w(
- TAG,
- "Failed to resolve activity, action="
- + action
- + ", resolved="
- + resolveInfos);
- return null;
- }
- ActivityInfo activityInfo = resolveInfos.get(0).activityInfo;
- intent.setClassName(activityInfo.packageName, activityInfo.name);
- return new ResolvedActivity(activityInfo, intent);
- }
- }
-}
diff --git a/android/FerrochromeApp/java/com/android/virtualization/ferrochrome/OpenUrlActivity.java b/android/FerrochromeApp/java/com/android/virtualization/ferrochrome/OpenUrlActivity.java
deleted file mode 100644
index 433e89c..0000000
--- a/android/FerrochromeApp/java/com/android/virtualization/ferrochrome/OpenUrlActivity.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2024 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.virtualization.ferrochrome;
-
-import android.app.Activity;
-import android.content.Intent;
-import android.net.Uri;
-import android.os.Bundle;
-import android.util.Log;
-
-public class OpenUrlActivity extends Activity {
- private static final String TAG = OpenUrlActivity.class.getSimpleName();
-
- private static final String ACTION_VM_OPEN_URL = "android.virtualization.VM_OPEN_URL";
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- finish();
-
- if (!Intent.ACTION_SEND.equals(getIntent().getAction())) {
- return;
- }
- String text = getIntent().getStringExtra(Intent.EXTRA_TEXT);
- if (text == null) {
- return;
- }
- Uri uri = Uri.parse(text);
- if (uri == null) {
- return;
- }
- String scheme = uri.getScheme();
- if (!("http".equals(scheme) || "https".equals(scheme) || "mailto".equals(scheme))) {
- Log.e(TAG, "Unsupported URL scheme: " + scheme);
- return;
- }
- Log.i(TAG, "Sending " + scheme + " URL to VM");
- startActivity(
- new Intent(ACTION_VM_OPEN_URL)
- .setFlags(
- Intent.FLAG_ACTIVITY_SINGLE_TOP
- | Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP)
- .putExtra(Intent.EXTRA_TEXT, text));
- }
-}
diff --git a/android/FerrochromeApp/privapp-permissions-ferrochrome.xml b/android/FerrochromeApp/privapp-permissions-ferrochrome.xml
deleted file mode 100644
index 987db9c..0000000
--- a/android/FerrochromeApp/privapp-permissions-ferrochrome.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<permissions>
- <privapp-permissions package="com.android.virtualization.ferrochrome">
- <permission name="android.permission.KILL_ALL_BACKGROUND_PROCESSES"/>
- </privapp-permissions>
-</permissions>
\ No newline at end of file
diff --git a/android/FerrochromeApp/repack.sh b/android/FerrochromeApp/repack.sh
deleted file mode 100755
index b2a96dd..0000000
--- a/android/FerrochromeApp/repack.sh
+++ /dev/null
@@ -1,53 +0,0 @@
-#!/bin/bash
-# Repacks chromiumos_*.bin into the assets of FerrochromeApp
-
-usage() {
- echo "Usage: $0 CHROME_OS_DISK_IMAGE"
- exit 1
-}
-
-if [ "$#" -ne 1 ]; then
- usage
-fi
-
-disk=$1
-
-loop=$(sudo losetup --show -f -P ${disk})
-kern=$(sudo fdisk -x ${loop} | grep KERN-A | awk "{print\$1}")
-root=$(sudo fdisk -x ${loop} | grep ROOT-A | awk "{print\$1}")
-efi=$(sudo fdisk -x ${loop} | grep EFI-SYSTEM | awk "{print\$1}")
-state=$(sudo fdisk -x ${loop} | grep STATE | awk "{print\$1}")
-root_guid=$(sudo fdisk -x ${loop} | grep ROOT-A | awk "{print\$6}")
-
-tempdir=$(mktemp -d)
-pushd ${tempdir} > /dev/null
-echo Extracting partition images...
-sudo cp --sparse=always ${kern} kernel.img
-sudo cp --sparse=always ${root} root.img
-sudo cp --sparse=always ${efi} efi.img
-sudo cp --sparse=always ${state} state.img
-sudo chmod 777 *.img
-
-echo Archiving. This can take long...
-tar czvS -f images.tar.gz *.img
-
-echo Splitting...
-split -b 100M -d images.tar.gz images.tar.gz.part
-
-popd > /dev/null
-asset_dir=$(dirname $0)/assets/ferrochrome
-echo Updating ${asset_dir}...
-vm_config_template=$(dirname $0)/vm_config.json.template
-mkdir -p ${asset_dir}
-rm ${asset_dir}/images.tar.gz.part*
-mv ${tempdir}/images.tar.gz.part* ${asset_dir}
-sed -E s/GUID/${root_guid}/ ${vm_config_template} > ${asset_dir}/vm_config.json
-
-echo Calculating hash...
-hash=$(cat ${tempdir}/images.tar.gz ${asset_dir}/vm_config.json | sha1sum | cut -d' ' -f 1)
-echo ${hash} > ${asset_dir}/version
-
-echo Cleanup...
-sudo losetup -d ${loop}
-rm -rf ${tempdir}
-echo Done.
diff --git a/android/FerrochromeApp/vm_config.json.template b/android/FerrochromeApp/vm_config.json.template
deleted file mode 100644
index 380f016..0000000
--- a/android/FerrochromeApp/vm_config.json.template
+++ /dev/null
@@ -1,56 +0,0 @@
-{
- "name": "cros",
- "disks": [
- {
- "writable": true,
- "partitions": [
- {
- "label": "STATE",
- "path": "/data/local/tmp/state.img",
- "writable": true
- },
- {
- "label": "KERN-A",
- "path": "/data/local/tmp/kernel.img"
- },
- {
- "label": "ROOT-A",
- "path": "/data/local/tmp/root.img",
- "guid": "GUID"
- },
- {
- "label": "EFI-SYSTEM",
- "path": "/data/local/tmp/efi.img"
- }
- ]
- }
- ],
- "protected": false,
- "cpu_topology": "match_host",
- "platform_version": "~1.0",
- "memory_mib": 8096,
- "debuggable": true,
- "console_out": true,
- "connect_console": true,
- "console_input_device": "hvc0",
- "network": true,
- "input": {
- "touchscreen": true,
- "keyboard": true,
- "mouse": true,
- "trackpad": true,
- "switches": true
- },
- "audio": {
- "speaker": true,
- "microphone": true
- },
- "gpu": {
- "backend": "virglrenderer",
- "context_types": ["virgl2"]
- },
- "display": {
- "scale": "0.77",
- "refresh_rate": "30"
- }
-}
diff --git a/android/LinuxInstaller/Android.bp b/android/LinuxInstaller/Android.bp
deleted file mode 100644
index f70452d..0000000
--- a/android/LinuxInstaller/Android.bp
+++ /dev/null
@@ -1,41 +0,0 @@
-package {
- default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-android_app {
- name: "LinuxInstallerApp",
- srcs: ["java/**/*.java"],
- resource_dirs: ["res"],
- asset_dirs: ["assets"],
- manifest: "AndroidManifest.xml",
- defaults: ["VmPayloadInstaller"],
- overrides: ["LinuxInstallerAppStub"],
- required: [
- "privapp-permissions-linuxinstaller.xml",
- ],
- certificate: ":com.android.virtualization.linuxinstaller_certificate",
-}
-
-android_app {
- name: "LinuxInstallerAppStub",
- srcs: ["java/**/*.java"],
- resource_dirs: ["res"],
- manifest: "AndroidManifest_stub.xml",
- defaults: ["VmPayloadInstaller"],
- required: [
- "privapp-permissions-linuxinstaller.xml",
- ],
- certificate: ":com.android.virtualization.linuxinstaller_certificate",
-}
-
-prebuilt_etc {
- name: "privapp-permissions-linuxinstaller.xml",
- src: "privapp-permissions-linuxinstaller.xml",
- sub_dir: "permissions",
- system_ext_specific: true,
-}
-
-android_app_certificate {
- name: "com.android.virtualization.linuxinstaller_certificate",
- certificate: "com_android_virtualization_linuxinstaller",
-}
diff --git a/android/LinuxInstaller/AndroidManifest.xml b/android/LinuxInstaller/AndroidManifest.xml
deleted file mode 100644
index e5653f6..0000000
--- a/android/LinuxInstaller/AndroidManifest.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.virtualization.linuxinstaller"
- android:versionCode="2100000000" >
- <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
- <uses-permission android:name="android.permission.CHANGE_COMPONENT_ENABLED_STATE" />
- <queries>
- <intent>
- <action android:name="android.virtualization.VM_TERMINAL" />
- </intent>
- </queries>
- <application
- android:label="LinuxInstaller">
- <activity android:name=".MainActivity"
- android:exported="true">
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- </activity>
- </application>
-
-</manifest>
diff --git a/android/LinuxInstaller/AndroidManifest_stub.xml b/android/LinuxInstaller/AndroidManifest_stub.xml
deleted file mode 100644
index 49365ea..0000000
--- a/android/LinuxInstaller/AndroidManifest_stub.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.virtualization.linuxinstaller" >
- <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
- <uses-permission android:name="android.permission.CHANGE_COMPONENT_ENABLED_STATE" />
- <queries>
- <intent>
- <action android:name="android.virtualization.VM_TERMINAL" />
- </intent>
- </queries>
- <application
- android:label="LinuxInstaller">
- <activity android:name=".MainActivity"
- android:exported="true">
- </activity>
- </application>
-
-</manifest>
diff --git a/android/LinuxInstaller/com_android_virtualization_linuxinstaller.pk8 b/android/LinuxInstaller/com_android_virtualization_linuxinstaller.pk8
deleted file mode 100644
index 3f74303..0000000
--- a/android/LinuxInstaller/com_android_virtualization_linuxinstaller.pk8
+++ /dev/null
Binary files differ
diff --git a/android/LinuxInstaller/com_android_virtualization_linuxinstaller.x509.pem b/android/LinuxInstaller/com_android_virtualization_linuxinstaller.x509.pem
deleted file mode 100644
index 3ca64b7..0000000
--- a/android/LinuxInstaller/com_android_virtualization_linuxinstaller.x509.pem
+++ /dev/null
@@ -1,24 +0,0 @@
------BEGIN CERTIFICATE-----
-MIIEETCCAvmgAwIBAgIUfBxyELS+ri3QErq8DXHu+47xx4EwDQYJKoZIhvcNAQEL
-BQAwgZYxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQH
-DA1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKDAtHb29nbGUgSW5jLjEQMA4GA1UECwwH
-QW5kcm9pZDEyMDAGA1UEAwwpY29tX2FuZHJvaWRfdmlydHVhbGl6YXRpb25fbGlu
-dXhpbnN0YWxsZXIwIBcNMjQwODMwMTIyNjU2WhgPMjA1MjAxMTYxMjI2NTZaMIGW
-MQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91
-bnRhaW4gVmlldzEUMBIGA1UECgwLR29vZ2xlIEluYy4xEDAOBgNVBAsMB0FuZHJv
-aWQxMjAwBgNVBAMMKWNvbV9hbmRyb2lkX3ZpcnR1YWxpemF0aW9uX2xpbnV4aW5z
-dGFsbGVyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA8V/rH9ju6Wce
-1BdWuxfWaLmZJHGShXeDO6MB86Wrm10m26j9PFzd8/8FRKsZaujZphwNZsqBsdlt
-pWeNKts9T9luZn19Ci4E8A2EtgSxmfI8Fjwj/OJHHO0hG5+JcwIlUnmFQPcGtu/r
-EL3i7SfcF2ok+IC6aKYohnSbo+YkjyCSwb39i6POe6v6cPIZJtmOnecThS+fYCYR
-2yoMSSr3Bf8ayySrG0pJp7xZ1I5NixK6hUFZhQRLusyiv/KYTpAElMd+n1YJEYbf
-pW30DYAu+31S0hx8JXncFmI0uG3Zxx+LgNQwY8OPV6NPFfVwMPluZR6ep0tZ6q7e
-KIV2w5uC7QIDAQABo1MwUTAdBgNVHQ4EFgQU6FBYv7mW+9DR9q0c9uS4NNdX4Acw
-HwYDVR0jBBgwFoAU6FBYv7mW+9DR9q0c9uS4NNdX4AcwDwYDVR0TAQH/BAUwAwEB
-/zANBgkqhkiG9w0BAQsFAAOCAQEAj3bvUpwKjvpCggXzjMNkn7fAaQ0s1BubnkFe
-ge4zwz4tObP3OGRcxt5V9R5EZ7UY6bPcybA/rfg9FCzjcUQOBjmuepcQpbNHFW2I
-lasFa42UHkHSUFzeg2n9UC5iO3B+sclOr4EPaEE4HbG4B2vj++BYMW3C7PDyHc7R
-fq5ZsEEWcYUa8qZCO46I8AbMZ8iv1HpR4mZeQMkSxhD3uVHDQW+VqDTpzne/YBkJ
-yNfjpgFVZ/Y1E6BvvjzWZpBfj668fo7P3DekWHbvPPr/DiZ7OA6PCmAH1FBsi2c+
-xPgb9clDc2Zjb2Cd9lAoZdeB14zDOh6ZCF1c/i+qYt5tA9t+GA==
------END CERTIFICATE-----
diff --git a/android/LinuxInstaller/linux_image_builder/commands b/android/LinuxInstaller/linux_image_builder/commands
deleted file mode 100644
index 4d27475..0000000
--- a/android/LinuxInstaller/linux_image_builder/commands
+++ /dev/null
@@ -1,11 +0,0 @@
-upload init.sh:/root
-upload vsock.py:/usr/local/bin
-upload /tmp/ttyd:/usr/local/bin
-upload ttyd.service:/etc/systemd/system
-upload vsockip.service:/etc/systemd/system
-chmod 0777:/root/init.sh
-firstboot-command "/root/init.sh"
-chmod 0644:/etc/systemd/system/vsockip.service
-chmod 0644:/etc/systemd/system/ttyd.service
-chmod 0777:/usr/local/bin/vsock.py
-chmod 0777:/usr/local/bin/ttyd
diff --git a/android/LinuxInstaller/linux_image_builder/init.sh b/android/LinuxInstaller/linux_image_builder/init.sh
deleted file mode 100644
index bec5ac5..0000000
--- a/android/LinuxInstaller/linux_image_builder/init.sh
+++ /dev/null
@@ -1,4 +0,0 @@
-#!/bin/bash
-systemctl daemon-reload
-systemctl start ttyd && sudo systemctl enable ttyd
-systemctl start vsockip && sudo systemctl enable vsockip
diff --git a/android/LinuxInstaller/linux_image_builder/setup.sh b/android/LinuxInstaller/linux_image_builder/setup.sh
deleted file mode 100755
index 2883e61..0000000
--- a/android/LinuxInstaller/linux_image_builder/setup.sh
+++ /dev/null
@@ -1,29 +0,0 @@
-#!/bin/bash
-
-pushd $(dirname $0) > /dev/null
-tempdir=$(mktemp -d)
-echo Get Debian image and dependencies...
-wget https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-nocloud-arm64.raw -O ${tempdir}/debian.img
-wget https://github.com/tsl0922/ttyd/releases/download/1.7.7/ttyd.aarch64 -O ${tempdir}/ttyd
-
-echo Customize the image...
-virt-customize --commands-from-file <(sed "s|/tmp|$tempdir|g" commands) -a ${tempdir}/debian.img
-
-asset_dir=../assets/linux
-mkdir -p ${asset_dir}
-
-echo Copy files...
-
-pushd ${tempdir} > /dev/null
-tar czvS -f images.tar.gz debian.img
-popd > /dev/null
-mv ${tempdir}/images.tar.gz ${asset_dir}/images.tar.gz
-cp vm_config.json ${asset_dir}
-
-echo Calculating hash...
-hash=$(cat ${asset_dir}/images.tar.gz ${asset_dir}/vm_config.json | sha1sum | cut -d' ' -f 1)
-echo ${hash} > ${asset_dir}/hash
-
-popd > /dev/null
-echo Cleaning up...
-rm -rf ${tempdir}
\ No newline at end of file
diff --git a/android/LinuxInstaller/linux_image_builder/setup_x86_64.sh b/android/LinuxInstaller/linux_image_builder/setup_x86_64.sh
deleted file mode 100755
index c543b2a..0000000
--- a/android/LinuxInstaller/linux_image_builder/setup_x86_64.sh
+++ /dev/null
@@ -1,29 +0,0 @@
-#!/bin/bash
-
-pushd $(dirname $0) > /dev/null
-tempdir=$(mktemp -d)
-echo Get Debian image and dependencies...
-wget https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-nocloud-amd64.raw -O ${tempdir}/debian.img
-wget https://github.com/tsl0922/ttyd/releases/download/1.7.7/ttyd.x86_64 -O ${tempdir}/ttyd
-
-echo Customize the image...
-virt-customize --commands-from-file <(sed "s|/tmp|$tempdir|g" commands) -a ${tempdir}/debian.img
-
-asset_dir=../assets/linux
-mkdir -p ${asset_dir}
-
-echo Copy files...
-
-pushd ${tempdir} > /dev/null
-tar czvS -f images.tar.gz debian.img
-popd > /dev/null
-mv ${tempdir}/images.tar.gz ${asset_dir}/images.tar.gz
-cp vm_config.json ${asset_dir}
-
-echo Calculating hash...
-hash=$(cat ${asset_dir}/images.tar.gz ${asset_dir}/vm_config.json | sha1sum | cut -d' ' -f 1)
-echo ${hash} > ${asset_dir}/hash
-
-popd > /dev/null
-echo Cleaning up...
-rm -rf ${tempdir}
\ No newline at end of file
diff --git a/android/LinuxInstaller/linux_image_builder/ttyd.service b/android/LinuxInstaller/linux_image_builder/ttyd.service
deleted file mode 100644
index f71557d..0000000
--- a/android/LinuxInstaller/linux_image_builder/ttyd.service
+++ /dev/null
@@ -1,12 +0,0 @@
-[Unit]
-Description=TTYD
-After=syslog.target
-After=network.target
-[Service]
-ExecStart=/usr/local/bin/ttyd -W screen -aAxR -S main login
-Type=simple
-Restart=always
-User=root
-Group=root
-[Install]
-WantedBy=multi-user.target
diff --git a/android/LinuxInstaller/linux_image_builder/vsock.py b/android/LinuxInstaller/linux_image_builder/vsock.py
deleted file mode 100644
index 292d953..0000000
--- a/android/LinuxInstaller/linux_image_builder/vsock.py
+++ /dev/null
@@ -1,57 +0,0 @@
-#!/usr/bin/env python3
-
-import socket
-
-# Constants for vsock (from linux/vm_sockets.h)
-AF_VSOCK = 40
-SOCK_STREAM = 1
-VMADDR_CID_ANY = -1
-
-def get_local_ip():
- """Retrieves the first IPv4 address found on the system.
-
- Returns:
- str: The local IPv4 address, or '127.0.0.1' if no IPv4 address is found.
- """
-
- s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
- try:
- s.connect(('8.8.8.8', 80))
- ip = s.getsockname()[0]
- except Exception:
- ip = '127.0.0.1'
- finally:
- s.close()
- return ip
-
-def main():
- PORT = 1024
-
- # Create a vsock socket
- server_socket = socket.socket(AF_VSOCK, SOCK_STREAM)
-
- # Bind the socket to the server address
- server_address = (VMADDR_CID_ANY, PORT)
- server_socket.bind(server_address)
-
- # Listen for incoming connections
- server_socket.listen(1)
- print(f"VSOCK server listening on port {PORT}...")
-
- while True:
- # Accept a connection
- connection, client_address = server_socket.accept()
- print(f"Connection from: {client_address}")
-
- try:
- # Get the local IP address
- local_ip = get_local_ip()
-
- # Send the IP address to the client
- connection.sendall(local_ip.encode())
- finally:
- # Close the connection
- connection.close()
-
-if __name__ == "__main__":
- main()
diff --git a/android/LinuxInstaller/linux_image_builder/vsockip.service b/android/LinuxInstaller/linux_image_builder/vsockip.service
deleted file mode 100644
index a29020b..0000000
--- a/android/LinuxInstaller/linux_image_builder/vsockip.service
+++ /dev/null
@@ -1,12 +0,0 @@
-[Unit]
-Description=vsock ip service
-After=syslog.target
-After=network.target
-[Service]
-ExecStart=/usr/bin/python3 /usr/local/bin/vsock.py
-Type=simple
-Restart=always
-User=root
-Group=root
-[Install]
-WantedBy=multi-user.target
diff --git a/android/LinuxInstaller/privapp-permissions-linuxinstaller.xml b/android/LinuxInstaller/privapp-permissions-linuxinstaller.xml
deleted file mode 100644
index e46ec97..0000000
--- a/android/LinuxInstaller/privapp-permissions-linuxinstaller.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<permissions>
- <privapp-permissions package="com.android.virtualization.linuxinstaller">
- <permission name="android.permission.CHANGE_COMPONENT_ENABLED_STATE"/>
- </privapp-permissions>
-</permissions>
\ No newline at end of file
diff --git a/android/LinuxInstaller/res/layout/activity_main.xml b/android/LinuxInstaller/res/layout/activity_main.xml
deleted file mode 100644
index 3967167..0000000
--- a/android/LinuxInstaller/res/layout/activity_main.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<RelativeLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:fitsSystemWindows="true"
- android:paddingLeft="16dp"
- android:paddingRight="16dp">
- <TextView
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:textSize="28sp"
- android:id="@+id/status_txt_view"/>
-
-</RelativeLayout>
diff --git a/android/LinuxInstaller/.gitignore b/android/TerminalApp/.gitignore
similarity index 100%
rename from android/LinuxInstaller/.gitignore
rename to android/TerminalApp/.gitignore
diff --git a/android/TerminalApp/Android.bp b/android/TerminalApp/Android.bp
index 1a7c581..e5e8b0a 100644
--- a/android/TerminalApp/Android.bp
+++ b/android/TerminalApp/Android.bp
@@ -4,10 +4,17 @@
android_app {
name: "VmTerminalApp",
- srcs: ["java/**/*.java"],
+ srcs: [
+ "java/**/*.java",
+ "java/**/*.kt",
+ ],
resource_dirs: ["res"],
+ asset_dirs: ["assets"],
static_libs: [
"vm_launcher_lib",
+ "androidx-constraintlayout_constraintlayout",
+ "com.google.android.material_material",
+ "androidx.window_window",
],
platform_apis: true,
privileged: true,
@@ -19,3 +26,15 @@
"com.android.virt",
],
}
+
+filegroup {
+ name: "linux_vm_setup.rc",
+ srcs: ["linux_vm_setup.rc"],
+}
+
+sh_binary {
+ name: "linux_vm_setup",
+ src: "linux_vm_setup.sh",
+ init_rc: [":linux_vm_setup.rc"],
+ host_supported: false,
+}
diff --git a/android/TerminalApp/AndroidManifest.xml b/android/TerminalApp/AndroidManifest.xml
index e338c49..105e454 100644
--- a/android/TerminalApp/AndroidManifest.xml
+++ b/android/TerminalApp/AndroidManifest.xml
@@ -1,7 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.virtualization.terminal" >
+ xmlns:tools="http://schemas.android.com/tools"
+ package="com.android.virtualization.terminal">
+ <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MANAGE_VIRTUAL_MACHINE" />
<uses-permission android:name="android.permission.USE_CUSTOM_VIRTUAL_MACHINE" />
<uses-permission android:name="android.permission.INTERNET" />
@@ -9,32 +11,52 @@
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE"/>
<uses-feature android:name="android.software.virtualization_framework" android:required="true" />
+
<application
- android:label="@string/app_name"
+ android:label="@string/app_name"
android:icon="@mipmap/ic_launcher"
- android:usesCleartextTraffic="true">
+ android:theme="@style/Theme.Material3.DayNight.NoActionBar"
+ android:usesCleartextTraffic="true"
+ android:enabled="false">
<activity android:name=".MainActivity"
android:configChanges="orientation|screenSize|keyboard|keyboardHidden|navigation|uiMode|screenLayout|smallestScreenSize"
android:exported="true">
<intent-filter>
+ <action android:name="android.intent.action.MAIN" />
<action android:name="android.virtualization.VM_TERMINAL" />
+ <category android:name="android.intent.category.LAUNCHER" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
- <activity-alias
- android:name=".MainActivityAlias"
- android:targetActivity="com.android.virtualization.terminal.MainActivity"
- android:exported="true"
- android:enabled="false" >
+ <activity android:name=".SettingsActivity">
+ </activity>
+ <activity android:name=".SettingsDiskResizeActivity">
+ </activity>
+ <activity android:name=".SettingsPortForwardingActivity">
+ </activity>
+ <activity android:name=".SettingsRecoveryActivity">
+ </activity>
+ <property
+ android:name="android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED"
+ android:value="true" />
+ <provider
+ android:name="androidx.startup.InitializationProvider"
+ android:authorities="${applicationId}.androidx-startup"
+ android:exported="false"
+ tools:node="merge">
+ <meta-data
+ android:name="${applicationId}.SplitInitializer"
+ android:value="androidx.startup" />
+ </provider>
+ <activity android:name=".InstallerActivity"
+ android:exported="false">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
- </activity-alias>
+ </activity>
<service
android:name="com.android.virtualization.vmlauncher.VmLauncherService"
- android:enabled="true"
android:exported="false"
android:foregroundServiceType="specialUse">
<property
diff --git a/android/LinuxInstaller/assets/.gitkeep b/android/TerminalApp/assets/.gitkeep
similarity index 100%
rename from android/LinuxInstaller/assets/.gitkeep
rename to android/TerminalApp/assets/.gitkeep
diff --git a/android/TerminalApp/assets/client.p12 b/android/TerminalApp/assets/client.p12
new file mode 100644
index 0000000..f1f5820
--- /dev/null
+++ b/android/TerminalApp/assets/client.p12
Binary files differ
diff --git a/android/TerminalApp/generate_assets.sh b/android/TerminalApp/generate_assets.sh
new file mode 100755
index 0000000..4001bfd
--- /dev/null
+++ b/android/TerminalApp/generate_assets.sh
@@ -0,0 +1,27 @@
+#!/bin/bash
+set -e
+
+if [ "$#" -ne 1 ]; then
+ echo "$0 <image.raw path>"
+ echo "image.raw can be built with packages/modules/Virtualization/build/debian/build.sh"
+ exit 1
+fi
+image_raw_path=$(realpath $1)
+pushd $(dirname $0) > /dev/null
+tempdir=$(mktemp -d)
+asset_dir=./assets/linux
+mkdir -p ${asset_dir}
+echo Copy files...
+pushd ${tempdir} > /dev/null
+cp "${image_raw_path}" ${tempdir}
+tar czvS -f images.tar.gz $(basename ${image_raw_path})
+popd > /dev/null
+cp vm_config.json ${asset_dir}
+mv ${tempdir}/images.tar.gz ${asset_dir}
+echo Calculating hash...
+hash=$(cat ${asset_dir}/images.tar.gz ${asset_dir}/vm_config.json | sha1sum | cut -d' ' -f 1)
+echo ${hash} > ${asset_dir}/hash
+popd > /dev/null
+echo Cleaning up...
+rm -rf ${tempdir}
+
diff --git a/android/LinuxInstaller/java/com/android/virtualization/linuxinstaller/MainActivity.java b/android/TerminalApp/java/com/android/virtualization/terminal/InstallerActivity.java
similarity index 72%
rename from android/LinuxInstaller/java/com/android/virtualization/linuxinstaller/MainActivity.java
rename to android/TerminalApp/java/com/android/virtualization/terminal/InstallerActivity.java
index 1d875cb..1c739e2 100644
--- a/android/LinuxInstaller/java/com/android/virtualization/linuxinstaller/MainActivity.java
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/InstallerActivity.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2024 The Android Open Source Project
+ * Copyright 2024 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.
@@ -14,15 +14,10 @@
* limitations under the License.
*/
-package com.android.virtualization.linuxinstaller;
+package com.android.virtualization.terminal;
import android.annotation.WorkerThread;
import android.app.Activity;
-import android.content.ComponentName;
-import android.content.Intent;
-import android.content.pm.ActivityInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
import android.os.Bundle;
import android.os.Environment;
import android.os.SystemProperties;
@@ -39,13 +34,11 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
-import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
-public class MainActivity extends Activity {
+public class InstallerActivity extends Activity {
private static final String TAG = "LinuxInstaller";
- private static final String ACTION_VM_TERMINAL = "android.virtualization.VM_TERMINAL";
private static final Path DEST_DIR =
Path.of(Environment.getExternalStorageDirectory().getPath(), "linux");
@@ -59,20 +52,17 @@
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
+ setResult(RESULT_CANCELED);
+
+ setContentView(R.layout.activity_installer);
executorService.execute(this::installLinuxImage);
}
private void installLinuxImage() {
- ComponentName vmTerminalComponent = resolve(getPackageManager(), ACTION_VM_TERMINAL);
- if (vmTerminalComponent == null) {
- updateStatus("Failed to resolve VM terminal");
- return;
- }
-
if (!hasLocalAssets()) {
updateStatus("No local assets");
+ setResult(RESULT_CANCELED, null);
return;
}
try {
@@ -81,13 +71,9 @@
Log.e(TAG, "failed to update image", e);
return;
}
- updateStatus("Enabling terminal app...");
- getPackageManager()
- .setComponentEnabledSetting(
- vmTerminalComponent,
- PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
- PackageManager.DONT_KILL_APP);
updateStatus("Done.");
+ setResult(RESULT_OK);
+ finish();
}
@WorkerThread
@@ -136,10 +122,10 @@
throw new RuntimeException("Internal error: destDir shouldn't be null");
}
- SystemProperties.set("debug.custom_vm_setup.path", destDir);
- SystemProperties.set("debug.custom_vm_setup.done", "false");
- SystemProperties.set("debug.custom_vm_setup.start", "true");
- while (!SystemProperties.getBoolean("debug.custom_vm_setup.done", false)) {
+ SystemProperties.set("debug.linux_vm_setup.path", destDir);
+ SystemProperties.set("debug.linux_vm_setup.done", "false");
+ SystemProperties.set("debug.linux_vm_setup.start", "true");
+ while (!SystemProperties.getBoolean("debug.linux_vm_setup.done", false)) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
@@ -190,18 +176,4 @@
statusView.append(line + "\n");
});
}
-
- private ComponentName resolve(PackageManager pm, String action) {
- Intent intent = new Intent(action);
- List<ResolveInfo> resolveInfos = pm.queryIntentActivities(intent, PackageManager.MATCH_ALL);
- if (resolveInfos.size() != 1) {
- Log.w(
- TAG,
- "Failed to resolve activity, action=" + action + ", resolved=" + resolveInfos);
- return null;
- }
- ActivityInfo activityInfo = resolveInfos.getFirst().activityInfo;
- // MainActivityAlias shows in Launcher
- return new ComponentName(activityInfo.packageName, activityInfo.name + "Alias");
- }
}
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java b/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java
index a6723fb..612da12 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java
@@ -15,83 +15,326 @@
*/
package com.android.virtualization.terminal;
-import android.app.Activity;
import android.content.ClipData;
import android.content.ClipboardManager;
+import android.content.Context;
+import android.content.Intent;
+import android.net.http.SslError;
import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
+import android.system.ErrnoException;
+import android.system.Os;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
+import android.view.View;
+import android.view.accessibility.AccessibilityManager;
+import android.webkit.ClientCertRequest;
+import android.webkit.SslErrorHandler;
import android.webkit.WebChromeClient;
+import android.webkit.WebResourceError;
+import android.webkit.WebResourceRequest;
import android.webkit.WebView;
import android.webkit.WebViewClient;
-import android.widget.TextView;
import android.widget.Toast;
+import androidx.appcompat.app.AppCompatActivity;
+
import com.android.virtualization.vmlauncher.VmLauncherServices;
-public class MainActivity extends Activity implements VmLauncherServices.VmLauncherServiceCallback {
+import com.google.android.material.appbar.MaterialToolbar;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.RandomAccessFile;
+import java.net.InetAddress;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.UnknownHostException;
+import java.security.Key;
+import java.security.KeyStore;
+import java.security.PrivateKey;
+import java.security.cert.Certificate;
+import java.security.cert.X509Certificate;
+
+public class MainActivity extends AppCompatActivity
+ implements VmLauncherServices.VmLauncherServiceCallback,
+ AccessibilityManager.TouchExplorationStateChangeListener {
+
private static final String TAG = "VmTerminalApp";
- private String mVmIpAddr;
+ private static final String VM_ADDR = "192.168.0.2";
+ private static final int TTYD_PORT = 7681;
+ private static final int REQUEST_CODE_INSTALLER = 0x33;
+
+ private X509Certificate[] mCertificates;
+ private PrivateKey mPrivateKey;
private WebView mWebView;
+ private AccessibilityManager mAccessibilityManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- Toast.makeText(this, R.string.vm_creation_message, Toast.LENGTH_SHORT).show();
- VmLauncherServices.startVmLauncherService(this, this);
+
+ checkForUpdate();
+ try {
+ // No resize for now.
+ long newSizeInBytes = 0;
+ diskResize(this, newSizeInBytes);
+ } catch (IOException e) {
+ Log.e(TAG, "Failed to resize disk", e);
+ Toast.makeText(this, "Error resizing disk: " + e.getMessage(), Toast.LENGTH_LONG)
+ .show();
+ }
setContentView(R.layout.activity_headless);
+
+ MaterialToolbar toolbar = (MaterialToolbar) findViewById(R.id.toolbar);
+ setSupportActionBar(toolbar);
mWebView = (WebView) findViewById(R.id.webview);
mWebView.getSettings().setDatabaseEnabled(true);
mWebView.getSettings().setDomStorageEnabled(true);
mWebView.getSettings().setJavaScriptEnabled(true);
mWebView.setWebChromeClient(new WebChromeClient());
+
+ mAccessibilityManager = getSystemService(AccessibilityManager.class);
+ mAccessibilityManager.addTouchExplorationStateChangeListener(this);
+
+ connectToTerminalService();
+ readClientCertificate();
+ }
+
+ private URL getTerminalServiceUrl() {
+ boolean needsAccessibility = mAccessibilityManager.isTouchExplorationEnabled();
+ String file = "/";
+ String query = needsAccessibility ? "?screenReaderMode=true" : "";
+
+ try {
+ return new URL("https", VM_ADDR, TTYD_PORT, file + query);
+ } catch (MalformedURLException e) {
+ // this cannot happen
+ return null;
+ }
+ }
+
+ private void readClientCertificate() {
+ // TODO(b/363235314): instead of using the key in asset, it should be generated in runtime
+ // and then provisioned in the vm via virtio-fs
+ try (InputStream keystoreFileStream =
+ getClass().getResourceAsStream("/assets/client.p12")) {
+ KeyStore keyStore = KeyStore.getInstance("PKCS12");
+ String password = "1234";
+ String alias = "1";
+
+ keyStore.load(keystoreFileStream, password != null ? password.toCharArray() : null);
+ Key key = keyStore.getKey(alias, password.toCharArray());
+ if (key instanceof PrivateKey) {
+ mPrivateKey = (PrivateKey) key;
+ Certificate cert = keyStore.getCertificate(alias);
+ mCertificates = new X509Certificate[1];
+ mCertificates[0] = (X509Certificate) cert;
+ }
+ } catch (Exception e) {
+ Log.e(TAG, e.getMessage());
+ }
+ }
+
+ private void connectToTerminalService() {
+ Log.i(TAG, "URL=" + getTerminalServiceUrl().toString());
mWebView.setWebViewClient(
new WebViewClient() {
@Override
- public boolean shouldOverrideUrlLoading(WebView view, String url) {
- view.loadUrl(url);
- return true;
+ public boolean shouldOverrideUrlLoading(
+ WebView view, WebResourceRequest request) {
+ return false;
+ }
+
+ @Override
+ public void onReceivedError(
+ WebView view, WebResourceRequest request, WebResourceError error) {
+ switch (error.getErrorCode()) {
+ case WebViewClient.ERROR_CONNECT:
+ case WebViewClient.ERROR_HOST_LOOKUP:
+ view.reload();
+ return;
+ default:
+ String url = request.getUrl().toString();
+ CharSequence msg = error.getDescription();
+ Log.e(TAG, "Failed to load " + url + ": " + msg);
+ }
+ }
+
+ @Override
+ public void onPageFinished(WebView view, String url) {
+ URL loadedUrl = null;
+ try {
+ loadedUrl = new URL(url);
+ } catch (MalformedURLException e) {
+ // cannot happen.
+ }
+ Log.i(TAG, "on page finished. URL=" + loadedUrl);
+ if (getTerminalServiceUrl().toString().equals(url)) {
+ android.os.Trace.endAsyncSection("executeTerminal", 0);
+ view.setVisibility(View.VISIBLE);
+ }
+ }
+
+ @Override
+ public void onReceivedClientCertRequest(
+ WebView view, ClientCertRequest request) {
+ if (mPrivateKey != null && mCertificates != null) {
+ request.proceed(mPrivateKey, mCertificates);
+ return;
+ }
+ super.onReceivedClientCertRequest(view, request);
+ }
+
+ @Override
+ public void onReceivedSslError(
+ WebView view, SslErrorHandler handler, SslError error) {
+ // ttyd uses self-signed certificate
+ handler.proceed();
}
});
+ new Thread(
+ () -> {
+ waitUntilVmStarts();
+ runOnUiThread(
+ () -> mWebView.loadUrl(getTerminalServiceUrl().toString()));
+ })
+ .start();
+ }
+
+ private void diskResize(Context context, long sizeInBytes) throws IOException {
+ try {
+ if (sizeInBytes == 0) {
+ return;
+ }
+ File file = getPartitionFile(context, "root_part");
+ String filePath = file.getAbsolutePath();
+ Log.d(TAG, "Disk-resize in progress for partition: " + filePath);
+
+ long currentSize = Os.stat(filePath).st_size;
+ runE2fsck(filePath);
+ if (sizeInBytes > currentSize) {
+ allocateSpace(file, sizeInBytes);
+ }
+
+ resizeFilesystem(filePath, sizeInBytes);
+ } catch (ErrnoException e) {
+ Log.e(TAG, "ErrnoException during disk resize", e);
+ throw new IOException("ErrnoException during disk resize", e);
+ } catch (IOException e) {
+ Log.e(TAG, "Failed to resize disk", e);
+ throw e;
+ }
+ }
+
+ private static File getPartitionFile(Context context, String fileName)
+ throws FileNotFoundException {
+ File file = new File(context.getFilesDir(), fileName);
+ if (!file.exists()) {
+ Log.d(TAG, fileName + " - file not found");
+ throw new FileNotFoundException("File not found: " + fileName);
+ }
+ return file;
+ }
+
+ private static void allocateSpace(File file, long sizeInBytes) throws IOException {
+ try {
+ RandomAccessFile raf = new RandomAccessFile(file, "rw");
+ FileDescriptor fd = raf.getFD();
+ Os.posix_fallocate(fd, 0, sizeInBytes);
+ raf.close();
+ Log.d(TAG, "Allocated space to: " + sizeInBytes + " bytes");
+ } catch (ErrnoException e) {
+ Log.e(TAG, "Failed to allocate space", e);
+ throw new IOException("Failed to allocate space", e);
+ }
+ }
+
+ private static void runE2fsck(String filePath) throws IOException {
+ try {
+ runCommand("/system/bin/e2fsck", "-f", filePath);
+ Log.d(TAG, "e2fsck completed: " + filePath);
+ } catch (IOException e) {
+ Log.e(TAG, "Failed to run e2fsck", e);
+ throw e;
+ }
+ }
+
+ private static void resizeFilesystem(String filePath, long sizeInBytes) throws IOException {
+ long sizeInMB = sizeInBytes / (1024 * 1024);
+ if (sizeInMB == 0) {
+ Log.e(TAG, "Invalid size: " + sizeInBytes + " bytes");
+ throw new IllegalArgumentException("Size cannot be zero MB");
+ }
+ String sizeArg = sizeInMB + "M";
+ try {
+ runCommand("/system/bin/resize2fs", filePath, sizeArg);
+ Log.d(TAG, "resize2fs completed: " + filePath + ", size: " + sizeArg);
+ } catch (IOException e) {
+ Log.e(TAG, "Failed to run resize2fs", e);
+ throw e;
+ }
+ }
+
+ private static void runCommand(String... command) throws IOException {
+ try {
+ Process process = new ProcessBuilder(command).redirectErrorStream(true).start();
+ process.waitFor();
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ throw new IOException("Command interrupted", e);
+ }
+ }
+
+ private static void waitUntilVmStarts() {
+ InetAddress addr = null;
+ try {
+ addr = InetAddress.getByName(VM_ADDR);
+ } catch (UnknownHostException e) {
+ // this can never happen.
+ }
+ try {
+ while (!addr.isReachable(10000)) {}
+ } catch (IOException e) {
+ // give up on network error
+ throw new RuntimeException(e);
+ }
+ return;
}
@Override
protected void onDestroy() {
+ getSystemService(AccessibilityManager.class).removeTouchExplorationStateChangeListener(this);
VmLauncherServices.stopVmLauncherService(this);
super.onDestroy();
}
- private void gotoURL(String url) {
- runOnUiThread(() -> mWebView.loadUrl(url));
- }
-
+ @Override
public void onVmStart() {
Log.i(TAG, "onVmStart()");
}
+ @Override
public void onVmStop() {
Toast.makeText(this, R.string.vm_stop_message, Toast.LENGTH_SHORT).show();
Log.i(TAG, "onVmStop()");
finish();
}
+ @Override
public void onVmError() {
Toast.makeText(this, R.string.vm_error_message, Toast.LENGTH_SHORT).show();
Log.i(TAG, "onVmError()");
finish();
}
+ @Override
public void onIpAddrAvailable(String ipAddr) {
- mVmIpAddr = ipAddr;
- ((TextView) findViewById(R.id.ip_addr_textview)).setText(mVmIpAddr);
-
- // TODO(b/359523803): Use AVF API to be notified when shell is ready instead of using dealy
- new Handler(Looper.getMainLooper())
- .postDelayed(() -> gotoURL("http://" + mVmIpAddr + ":7681"), 2000);
+ // TODO: remove this
}
@Override
@@ -101,17 +344,51 @@
}
@Override
- public boolean onMenuItemSelected(int featureId, MenuItem item) {
+ public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == R.id.copy_ip_addr) {
// TODO(b/340126051): remove this menu item when port forwarding is supported.
getSystemService(ClipboardManager.class)
- .setPrimaryClip(ClipData.newPlainText("A VM's IP address", mVmIpAddr));
+ .setPrimaryClip(ClipData.newPlainText("A VM's IP address", VM_ADDR));
return true;
} else if (id == R.id.stop_vm) {
VmLauncherServices.stopVmLauncherService(this);
+ mWebView.setVisibility(View.INVISIBLE);
+ return true;
+
+ } else if (id == R.id.menu_item_settings) {
+ Intent intent = new Intent(this, SettingsActivity.class);
+ this.startActivity(intent);
return true;
}
- return super.onMenuItemSelected(featureId, item);
+ return super.onOptionsItemSelected(item);
+ }
+
+ @Override
+ public void onTouchExplorationStateChanged(boolean enabled) {
+ connectToTerminalService();
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+
+ if (requestCode == REQUEST_CODE_INSTALLER) {
+ if (resultCode != RESULT_OK) {
+ Log.e(TAG, "Failed to start VM. Installer returned error.");
+ finish();
+ }
+ startVm();
+ }
+ }
+
+ private void checkForUpdate() {
+ Intent intent = new Intent(this, InstallerActivity.class);
+ startActivityForResult(intent, REQUEST_CODE_INSTALLER);
+ }
+
+ private void startVm() {
+ android.os.Trace.beginAsyncSection("executeTerminal", 0);
+ VmLauncherServices.startVmLauncherService(this, this);
}
}
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/SettingsActivity.kt b/android/TerminalApp/java/com/android/virtualization/terminal/SettingsActivity.kt
new file mode 100644
index 0000000..dccfea3
--- /dev/null
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/SettingsActivity.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2024 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.virtualization.terminal
+
+import android.os.Bundle
+import androidx.appcompat.app.AppCompatActivity
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+
+class SettingsActivity : AppCompatActivity() {
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.settings_activity)
+
+ val settingsItems = arrayOf(
+ SettingsItem(
+ resources.getString(R.string.settings_disk_resize_title),
+ resources.getString(R.string.settings_disk_resize_sub_title),
+ R.drawable.baseline_storage_24,
+ SettingsItemEnum.DiskResize
+ ),
+ SettingsItem(
+ resources.getString(R.string.settings_port_forwarding_title),
+ resources.getString(R.string.settings_port_forwarding_sub_title),
+ R.drawable.baseline_call_missed_outgoing_24,
+ SettingsItemEnum.PortForwarding
+ ),
+ SettingsItem(
+ resources.getString(R.string.settings_recovery_title),
+ resources.getString(R.string.settings_recovery_sub_title),
+ R.drawable.baseline_settings_backup_restore_24,
+ SettingsItemEnum.Recovery
+ ),
+ )
+ val settingsListItemAdapter = SettingsItemAdapter(settingsItems)
+
+ val recyclerView: RecyclerView = findViewById(R.id.settings_list_recycler_view)
+ recyclerView.layoutManager = LinearLayoutManager(this)
+ recyclerView.adapter = settingsListItemAdapter
+ }
+}
\ No newline at end of file
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/SettingsDiskResizeActivity.kt b/android/TerminalApp/java/com/android/virtualization/terminal/SettingsDiskResizeActivity.kt
new file mode 100644
index 0000000..1b14ef2
--- /dev/null
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/SettingsDiskResizeActivity.kt
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2024 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.virtualization.terminal
+
+import android.os.Bundle
+import android.os.FileUtils
+import android.widget.TextView
+import android.widget.Toast
+import android.text.style.RelativeSizeSpan
+import android.text.Spannable
+import android.text.SpannableString
+import android.text.Spanned
+import android.text.format.Formatter
+import android.text.TextUtils
+import androidx.appcompat.app.AppCompatActivity
+import androidx.core.view.isVisible
+import com.google.android.material.button.MaterialButton
+import com.google.android.material.slider.Slider
+
+import java.util.regex.Matcher
+import java.util.regex.Pattern
+
+class SettingsDiskResizeActivity : AppCompatActivity() {
+ private val maxDiskSize: Float = 256F
+ private val numberPattern: Pattern = Pattern.compile("[\\d]*[\\Ù«.,]?[\\d]+");
+ private var diskSize: Float = 104F
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.settings_disk_resize)
+ val diskSizeText = findViewById<TextView>(R.id.settings_disk_resize_resize_gb_assigned)
+ val diskMaxSizeText = findViewById<TextView>(R.id.settings_disk_resize_resize_gb_max)
+ diskMaxSizeText.text = getString(R.string.settings_disk_resize_resize_gb_max_format,
+ localizedFileSize(maxDiskSize));
+
+ val diskSizeSlider = findViewById<Slider>(R.id.settings_disk_resize_disk_size_slider)
+ diskSizeSlider.setValueTo(maxDiskSize)
+ val cancelButton = findViewById<MaterialButton>(R.id.settings_disk_resize_cancel_button)
+ val resizeButton = findViewById<MaterialButton>(R.id.settings_disk_resize_resize_button)
+ diskSizeSlider.value = diskSize
+ diskSizeText.text = enlargeFontOfNumber(
+ getString(R.string.settings_disk_resize_resize_gb_assigned_format,
+ localizedFileSize(diskSize)))
+
+ diskSizeSlider.addOnChangeListener { _, value, _ ->
+ diskSizeText.text = enlargeFontOfNumber(
+ getString(R.string.settings_disk_resize_resize_gb_assigned_format,
+ localizedFileSize(value)))
+ cancelButton.isVisible = true
+ resizeButton.isVisible = true
+ }
+ cancelButton.setOnClickListener {
+ diskSizeSlider.value = diskSize
+ cancelButton.isVisible = false
+ resizeButton.isVisible = false
+ }
+
+ resizeButton.setOnClickListener {
+ diskSize = diskSizeSlider.value
+ cancelButton.isVisible = false
+ resizeButton.isVisible = false
+ Toast.makeText(this@SettingsDiskResizeActivity, R.string.settings_disk_resize_resize_message, Toast.LENGTH_SHORT)
+ .show()
+ }
+ }
+
+ fun localizedFileSize(sizeGb: Float): String {
+ // formatShortFileSize() uses SI unit (i.e. kB = 1000 bytes),
+ // so covert sizeGb with "GB" instead of "GIB".
+ val bytes = FileUtils.parseSize(sizeGb.toLong().toString() + "GB")
+ return Formatter.formatShortFileSize(this, bytes)
+ }
+
+ fun enlargeFontOfNumber(summary: CharSequence): CharSequence {
+ if (TextUtils.isEmpty(summary)) {
+ return ""
+ }
+
+ val matcher = numberPattern.matcher(summary);
+ if (matcher.find()) {
+ val spannableSummary = SpannableString(summary)
+ spannableSummary.setSpan(
+ RelativeSizeSpan(2f),
+ matcher.start(),
+ matcher.end(),
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
+ return spannableSummary
+ }
+ return summary
+ }
+}
\ No newline at end of file
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/SettingsItem.kt b/android/TerminalApp/java/com/android/virtualization/terminal/SettingsItem.kt
new file mode 100644
index 0000000..e1723a7
--- /dev/null
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/SettingsItem.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2024 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.virtualization.terminal
+
+enum class SettingsItemEnum {
+ DiskResize, PortForwarding, Recovery
+}
+
+class SettingsItem(
+ val title: String,
+ val subTitle: String,
+ val icon: Int,
+ val settingsItemEnum: SettingsItemEnum
+) {
+}
\ No newline at end of file
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/SettingsItemAdapter.kt b/android/TerminalApp/java/com/android/virtualization/terminal/SettingsItemAdapter.kt
new file mode 100644
index 0000000..86f5c92
--- /dev/null
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/SettingsItemAdapter.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2024 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.virtualization.terminal
+
+import android.content.Intent
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ImageView
+import android.widget.TextView
+import androidx.recyclerview.widget.RecyclerView
+import com.google.android.material.card.MaterialCardView
+
+class SettingsItemAdapter(private val dataSet: Array<SettingsItem>) :
+ RecyclerView.Adapter<SettingsItemAdapter.ViewHolder>() {
+
+ class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
+ val card: MaterialCardView = view.findViewById(R.id.settings_list_item_card)
+ val icon: ImageView = view.findViewById(R.id.settings_list_item_icon)
+ val title: TextView = view.findViewById(R.id.settings_list_item_title)
+ val subTitle: TextView = view.findViewById(R.id.settings_list_item_sub_title)
+ }
+
+ override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): ViewHolder {
+ val view = LayoutInflater.from(viewGroup.context)
+ .inflate(R.layout.settings_list_item, viewGroup, false)
+ return ViewHolder(view)
+ }
+
+ override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {
+ viewHolder.icon.setImageResource(dataSet[position].icon)
+ viewHolder.title.text = dataSet[position].title
+ viewHolder.subTitle.text = dataSet[position].subTitle
+
+ viewHolder.card.setOnClickListener { view ->
+ val intent = Intent(
+ viewHolder.itemView.context,
+ when (dataSet[position].settingsItemEnum) {
+ SettingsItemEnum.DiskResize -> SettingsDiskResizeActivity::class.java
+ SettingsItemEnum.PortForwarding -> SettingsPortForwardingActivity::class.java
+ SettingsItemEnum.Recovery -> SettingsRecoveryActivity::class.java
+ }
+ )
+ view.context.startActivity(intent)
+ }
+ }
+
+ override fun getItemCount() = dataSet.size
+}
\ No newline at end of file
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/SettingsPortForwardingActivity.kt b/android/TerminalApp/java/com/android/virtualization/terminal/SettingsPortForwardingActivity.kt
new file mode 100644
index 0000000..6c36cc8
--- /dev/null
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/SettingsPortForwardingActivity.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2024 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.virtualization.terminal
+
+import android.os.Bundle
+import androidx.appcompat.app.AppCompatActivity
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+
+class SettingsPortForwardingActivity : AppCompatActivity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.settings_port_forwarding)
+
+ val settingsPortForwardingItems = arrayOf(
+ SettingsPortForwardingItem(8080, true),
+ SettingsPortForwardingItem(443, false),
+ SettingsPortForwardingItem(80, false)
+ )
+
+ val settingsPortForwardingAdapter =
+ SettingsPortForwardingAdapter(settingsPortForwardingItems)
+
+ val recyclerView: RecyclerView = findViewById(R.id.settings_port_forwarding_recycler_view)
+ recyclerView.layoutManager = LinearLayoutManager(this)
+ recyclerView.adapter = settingsPortForwardingAdapter
+ }
+}
\ No newline at end of file
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/SettingsPortForwardingAdapter.kt b/android/TerminalApp/java/com/android/virtualization/terminal/SettingsPortForwardingAdapter.kt
new file mode 100644
index 0000000..1fa38e3
--- /dev/null
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/SettingsPortForwardingAdapter.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2024 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.virtualization.terminal
+
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.TextView
+import androidx.recyclerview.widget.RecyclerView
+import com.google.android.material.materialswitch.MaterialSwitch
+
+class SettingsPortForwardingAdapter(private val dataSet: Array<SettingsPortForwardingItem>) :
+ RecyclerView.Adapter<SettingsPortForwardingAdapter.ViewHolder>() {
+
+ class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
+ val enabledSwitch: MaterialSwitch =
+ view.findViewById(R.id.settings_port_forwarding_item_enabled_switch)
+ val port: TextView = view.findViewById(R.id.settings_port_forwarding_item_port)
+ }
+
+ override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): ViewHolder {
+ val view = LayoutInflater.from(viewGroup.context)
+ .inflate(R.layout.settings_port_forwarding_item, viewGroup, false)
+ return ViewHolder(view)
+ }
+
+ override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {
+ viewHolder.port.text = dataSet[position].port.toString()
+ viewHolder.enabledSwitch.isChecked = dataSet[position].enabled
+ }
+
+ override fun getItemCount() = dataSet.size
+}
\ No newline at end of file
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/SettingsPortForwardingItem.kt b/android/TerminalApp/java/com/android/virtualization/terminal/SettingsPortForwardingItem.kt
new file mode 100644
index 0000000..599e377
--- /dev/null
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/SettingsPortForwardingItem.kt
@@ -0,0 +1,18 @@
+/*
+ * Copyright (C) 2024 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.virtualization.terminal
+
+class SettingsPortForwardingItem(val port: Int, val enabled: Boolean) {}
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/SettingsRecoveryActivity.kt b/android/TerminalApp/java/com/android/virtualization/terminal/SettingsRecoveryActivity.kt
new file mode 100644
index 0000000..7256015
--- /dev/null
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/SettingsRecoveryActivity.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2024 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.virtualization.terminal
+
+import android.os.Bundle
+import android.widget.Toast
+import androidx.appcompat.app.AppCompatActivity
+import com.google.android.material.card.MaterialCardView
+
+class SettingsRecoveryActivity : AppCompatActivity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.settings_recovery)
+ val resetCard = findViewById<MaterialCardView>(R.id.settings_recovery_reset_card)
+ resetCard.setOnClickListener {
+ Toast.makeText(this@SettingsRecoveryActivity, R.string.settings_recovery_reset_message, Toast.LENGTH_SHORT).show()
+ }
+ }
+}
\ No newline at end of file
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/SplitInitializer.kt b/android/TerminalApp/java/com/android/virtualization/terminal/SplitInitializer.kt
new file mode 100644
index 0000000..cb917bd
--- /dev/null
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/SplitInitializer.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2024 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.virtualization.terminal
+
+import android.content.Context
+import androidx.startup.Initializer
+import androidx.window.embedding.RuleController
+
+class SplitInitializer : Initializer<RuleController> {
+
+ override fun create(context: Context): RuleController {
+ return RuleController.getInstance(context).apply {
+ setRules(RuleController.parseRules(context, R.xml.main_split_config))
+ }
+ }
+
+ override fun dependencies(): List<Class<out Initializer<*>>> {
+ return emptyList()
+ }
+}
\ No newline at end of file
diff --git a/android/FerrochromeApp/custom_vm_setup.rc b/android/TerminalApp/linux_vm_setup.rc
similarity index 77%
rename from android/FerrochromeApp/custom_vm_setup.rc
rename to android/TerminalApp/linux_vm_setup.rc
index 68f370e..ac91532 100644
--- a/android/FerrochromeApp/custom_vm_setup.rc
+++ b/android/TerminalApp/linux_vm_setup.rc
@@ -1,4 +1,4 @@
-# Copyright (C) 2024 The Android Open Source Project
+# Copyright 2024 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.
@@ -12,12 +12,12 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-service custom_vm_setup /system_ext/bin/custom_vm_setup
+service linux_vm_setup /system/bin/linux_vm_setup
user shell
group shell media_rw
disabled
oneshot
seclabel u:r:shell:s0
-on property:debug.custom_vm_setup.start=true
- start custom_vm_setup
+on property:debug.linux_vm_setup.start=true
+ start linux_vm_setup
diff --git a/android/FerrochromeApp/custom_vm_setup.sh b/android/TerminalApp/linux_vm_setup.sh
similarity index 82%
rename from android/FerrochromeApp/custom_vm_setup.sh
rename to android/TerminalApp/linux_vm_setup.sh
index df1a3a6..6a93f6f 100644
--- a/android/FerrochromeApp/custom_vm_setup.sh
+++ b/android/TerminalApp/linux_vm_setup.sh
@@ -7,7 +7,7 @@
}
function install() {
- src_dir=$(getprop debug.custom_vm_setup.path)
+ src_dir=$(getprop debug.linux_vm_setup.path)
src_dir=${src_dir/#\/storage\/emulated\//\/data\/media\/}
dst_dir=/data/local/tmp/
@@ -26,7 +26,7 @@
rm ${src_dir}/vm_config.json
}
-setprop debug.custom_vm_setup.done false
+setprop debug.linux_vm_setup.done false
install
-setprop debug.custom_vm_setup.start false
-setprop debug.custom_vm_setup.done true
+setprop debug.linux_vm_setup.start false
+setprop debug.linux_vm_setup.done true
diff --git a/android/TerminalApp/res/drawable/baseline_call_missed_outgoing_24.xml b/android/TerminalApp/res/drawable/baseline_call_missed_outgoing_24.xml
new file mode 100644
index 0000000..597c317
--- /dev/null
+++ b/android/TerminalApp/res/drawable/baseline_call_missed_outgoing_24.xml
@@ -0,0 +1,5 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android" android:autoMirrored="true" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
+
+ <path android:fillColor="@android:color/white" android:pathData="M3,8.41l9,9l7,-7V15h2V7h-8v2h4.59L12,14.59L4.41,7L3,8.41z"/>
+
+</vector>
diff --git a/android/TerminalApp/res/drawable/baseline_settings_backup_restore_24.xml b/android/TerminalApp/res/drawable/baseline_settings_backup_restore_24.xml
new file mode 100644
index 0000000..22b23ba
--- /dev/null
+++ b/android/TerminalApp/res/drawable/baseline_settings_backup_restore_24.xml
@@ -0,0 +1,5 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
+
+ <path android:fillColor="@android:color/white" android:pathData="M14,12c0,-1.1 -0.9,-2 -2,-2s-2,0.9 -2,2 0.9,2 2,2 2,-0.9 2,-2zM12,3c-4.97,0 -9,4.03 -9,9L0,12l4,4 4,-4L5,12c0,-3.87 3.13,-7 7,-7s7,3.13 7,7 -3.13,7 -7,7c-1.51,0 -2.91,-0.49 -4.06,-1.3l-1.42,1.44C8.04,20.3 9.94,21 12,21c4.97,0 9,-4.03 9,-9s-4.03,-9 -9,-9z"/>
+
+</vector>
diff --git a/android/TerminalApp/res/drawable/baseline_storage_24.xml b/android/TerminalApp/res/drawable/baseline_storage_24.xml
new file mode 100644
index 0000000..6e52e3f
--- /dev/null
+++ b/android/TerminalApp/res/drawable/baseline_storage_24.xml
@@ -0,0 +1,5 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
+
+ <path android:fillColor="@android:color/white" android:pathData="M2,20h20v-4L2,16v4zM4,17h2v2L4,19v-2zM2,4v4h20L22,4L2,4zM6,7L4,7L4,5h2v2zM2,14h20v-4L2,10v4zM4,11h2v2L4,13v-2z"/>
+
+</vector>
diff --git a/android/TerminalApp/res/layout/activity_headless.xml b/android/TerminalApp/res/layout/activity_headless.xml
index 3fe5271..9211799 100644
--- a/android/TerminalApp/res/layout/activity_headless.xml
+++ b/android/TerminalApp/res/layout/activity_headless.xml
@@ -7,14 +7,36 @@
android:orientation="vertical"
android:fitsSystemWindows="true"
tools:context=".MainActivity">
- <TextView
- android:id="@+id/ip_addr_textview"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content" />
- <WebView
- android:id="@+id/webview"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_marginBottom="5dp" />
+ <com.google.android.material.appbar.MaterialToolbar
+ android:id="@+id/toolbar"
+ android:layout_width="match_parent"
+ android:layout_height="?attr/actionBarSize"
+ app:layout_constraintTop_toTopOf="parent"/>
+ <FrameLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <LinearLayout
+ android:orientation="vertical"
+ android:gravity="center"
+ android:layout_gravity="center"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
+ <TextView
+ android:text="@string/vm_creation_message"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="5dp"/>
+ <ProgressBar
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+ </LinearLayout>
+ <WebView
+ android:id="@+id/webview"
+ android:layout_marginBottom="5dp"
+ android:layout_gravity="fill"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:visibility="invisible"/>
+ </FrameLayout>
</LinearLayout>
diff --git a/android/FerrochromeApp/res/layout/activity_ferrochrome.xml b/android/TerminalApp/res/layout/activity_installer.xml
similarity index 100%
rename from android/FerrochromeApp/res/layout/activity_ferrochrome.xml
rename to android/TerminalApp/res/layout/activity_installer.xml
diff --git a/android/TerminalApp/res/layout/settings_activity.xml b/android/TerminalApp/res/layout/settings_activity.xml
new file mode 100644
index 0000000..b1acf23
--- /dev/null
+++ b/android/TerminalApp/res/layout/settings_activity.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:fitsSystemWindows="true">
+
+ <com.google.android.material.search.SearchBar
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"/>
+
+ <androidx.recyclerview.widget.RecyclerView
+ android:id="@+id/settings_list_recycler_view"
+ android:layout_marginHorizontal="16dp"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+</LinearLayout>
\ No newline at end of file
diff --git a/android/TerminalApp/res/layout/settings_disk_resize.xml b/android/TerminalApp/res/layout/settings_disk_resize.xml
new file mode 100644
index 0000000..f868b28
--- /dev/null
+++ b/android/TerminalApp/res/layout/settings_disk_resize.xml
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:orientation="vertical"
+ android:layout_marginStart="24dp"
+ android:layout_marginEnd="24dp"
+ android:layout_marginTop="24dp"
+ android:fitsSystemWindows="true">
+
+ <TextView
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:text="@string/settings_disk_resize_title"
+ android:textSize="48sp"
+ android:layout_marginBottom="24dp"/>
+
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <TextView
+ android:id="@+id/settings_disk_resize_resize_gb_assigned"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:textSize="14sp"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintBottom_toTopOf="@+id/settings_disk_resize_disk_size_slider"/>
+
+ <TextView
+ android:id="@+id/settings_disk_resize_resize_gb_max"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:textSize="14sp"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintBottom_toTopOf="@+id/settings_disk_resize_disk_size_slider"/>
+
+ <com.google.android.material.slider.Slider
+ android:id="@+id/settings_disk_resize_disk_size_slider"
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:layout_marginBottom="36dp"
+ app:tickVisible="false"
+ android:valueFrom="0"
+ android:stepSize="4"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent" />
+
+ <com.google.android.material.button.MaterialButton
+ android:id="@+id/settings_disk_resize_cancel_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/settings_disk_resize_resize_cancel"
+ android:visibility="invisible"
+ android:layout_marginHorizontal="8dp"
+ app:layout_constraintTop_toTopOf="@+id/settings_disk_resize_disk_size_slider"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toStartOf="@+id/settings_disk_resize_resize_button" />
+
+ <com.google.android.material.button.MaterialButton
+ android:id="@+id/settings_disk_resize_resize_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/settings_disk_resize_resize_restart_vm_to_apply"
+ android:visibility="invisible"
+ android:layout_marginHorizontal="8dp"
+ app:layout_constraintTop_toTopOf="@+id/settings_disk_resize_disk_size_slider"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent" />
+ </androidx.constraintlayout.widget.ConstraintLayout>
+</LinearLayout>
\ No newline at end of file
diff --git a/android/TerminalApp/res/layout/settings_list_item.xml b/android/TerminalApp/res/layout/settings_list_item.xml
new file mode 100644
index 0000000..89f2d82
--- /dev/null
+++ b/android/TerminalApp/res/layout/settings_list_item.xml
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="utf-8"?>
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:gravity="center_vertical"
+ android:layout_height="wrap_content">
+
+ <com.google.android.material.card.MaterialCardView
+ android:id="@+id/settings_list_item_card"
+ app:strokeWidth="0dp"
+ app:cardCornerRadius="28dp"
+ app:checkedIcon="@null"
+ android:focusable="true"
+ android:checkable="true"
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent">
+
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:layout_width="match_parent"
+ android:layout_height="88dp"
+ android:layout_marginStart="24dp"
+ android:layout_marginEnd="16dp">
+
+ <com.google.android.material.imageview.ShapeableImageView
+ android:id="@+id/settings_list_item_icon"
+ android:layout_width="24dp"
+ android:layout_height="24dp"
+ android:layout_marginEnd="24dp"
+ android:scaleType="centerCrop"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintLeft_toLeftOf="parent" />
+
+ <TextView
+ android:id="@+id/settings_list_item_title"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="20dp"
+ android:layout_marginStart="24dp"
+ android:textSize="20sp"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toTopOf="@+id/settings_list_item_sub_title"
+ app:layout_constraintStart_toEndOf="@id/settings_list_item_icon"
+ app:layout_constraintEnd_toEndOf="parent" />
+
+ <TextView
+ android:id="@+id/settings_list_item_sub_title"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:textSize="14sp"
+ android:layout_marginBottom="20dp"
+ android:layout_marginStart="24dp"
+ app:layout_constraintTop_toBottomOf="@+id/settings_list_item_title"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toEndOf="@id/settings_list_item_icon"
+ app:layout_constraintEnd_toEndOf="parent" />
+ </androidx.constraintlayout.widget.ConstraintLayout>
+ </com.google.android.material.card.MaterialCardView>
+</FrameLayout>
\ No newline at end of file
diff --git a/android/TerminalApp/res/layout/settings_port_forwarding.xml b/android/TerminalApp/res/layout/settings_port_forwarding.xml
new file mode 100644
index 0000000..1d68907
--- /dev/null
+++ b/android/TerminalApp/res/layout/settings_port_forwarding.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:layout_marginStart="24dp"
+ android:layout_marginEnd="24dp"
+ android:layout_marginTop="24dp"
+ android:fitsSystemWindows="true">
+
+ <TextView
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:text="@string/settings_port_forwarding_title"
+ android:textSize="48sp"
+ android:layout_marginBottom="24dp"/>
+
+ <androidx.recyclerview.widget.RecyclerView
+ android:id="@+id/settings_port_forwarding_recycler_view"
+ android:layout_marginHorizontal="16dp"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+</LinearLayout>
\ No newline at end of file
diff --git a/android/TerminalApp/res/layout/settings_port_forwarding_item.xml b/android/TerminalApp/res/layout/settings_port_forwarding_item.xml
new file mode 100644
index 0000000..9e5981e
--- /dev/null
+++ b/android/TerminalApp/res/layout/settings_port_forwarding_item.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ app:layout_constraintCircleRadius="@dimen/material_emphasis_medium"
+ xmlns:app="http://schemas.android.com/apk/res-auto">
+
+ <TextView
+ android:id="@+id/settings_port_forwarding_item_port"
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintLeft_toLeftOf="parent"/>
+
+ <com.google.android.material.materialswitch.MaterialSwitch
+ android:id="@+id/settings_port_forwarding_item_enabled_switch"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintRight_toRightOf="parent" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/android/TerminalApp/res/layout/settings_recovery.xml b/android/TerminalApp/res/layout/settings_recovery.xml
new file mode 100644
index 0000000..e18c8a6
--- /dev/null
+++ b/android/TerminalApp/res/layout/settings_recovery.xml
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:orientation="vertical"
+ android:layout_marginEnd="24dp"
+ android:layout_marginTop="24dp"
+ android:fitsSystemWindows="true">
+
+ <TextView
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:text="@string/settings_recovery_title"
+ android:textSize="48sp"
+ android:layout_marginStart="24dp"
+ android:layout_marginBottom="24dp"/>
+
+ <com.google.android.material.card.MaterialCardView
+ android:id="@+id/settings_recovery_reset_card"
+ app:strokeWidth="0dp"
+ app:cardCornerRadius="0dp"
+ app:checkedIcon="@null"
+ android:focusable="true"
+ android:checkable="true"
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent">
+
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:layout_width="match_parent"
+ android:layout_height="88dp"
+ android:layout_marginEnd="16dp"
+ android:layout_marginStart="24dp">
+
+ <TextView
+ android:id="@+id/settings_recovery_reset_title"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="20dp"
+ android:layout_marginStart="24dp"
+ android:textSize="20sp"
+ android:text="@string/settings_recovery_reset_title"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toTopOf="@+id/settings_recovery_reset_sub_title"
+ app:layout_constraintLeft_toLeftOf="parent" />
+
+ <TextView
+ android:id="@+id/settings_recovery_reset_sub_title"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:textSize="14sp"
+ android:layout_marginBottom="20dp"
+ android:layout_marginStart="24dp"
+ android:text="@string/settings_recovery_reset_sub_title"
+ app:layout_constraintTop_toBottomOf="@+id/settings_recovery_reset_title"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintLeft_toLeftOf="parent" />
+ </androidx.constraintlayout.widget.ConstraintLayout>
+ </com.google.android.material.card.MaterialCardView>
+</LinearLayout>
\ No newline at end of file
diff --git a/android/TerminalApp/res/menu/main_menu.xml b/android/TerminalApp/res/menu/main_menu.xml
index cc34cda..9c83923 100644
--- a/android/TerminalApp/res/menu/main_menu.xml
+++ b/android/TerminalApp/res/menu/main_menu.xml
@@ -4,4 +4,6 @@
android:title="Copy the IP address"/>
<item android:id="@+id/stop_vm"
android:title="Stop the existing VM instance"/>
+ <item android:id="@+id/menu_item_settings"
+ android:title="Settings"/>
</menu>
\ No newline at end of file
diff --git a/android/TerminalApp/res/values/dimens.xml b/android/TerminalApp/res/values/dimens.xml
new file mode 100644
index 0000000..e6ed461
--- /dev/null
+++ b/android/TerminalApp/res/values/dimens.xml
@@ -0,0 +1,3 @@
+<resources>
+ <dimen name="activity_split_ratio">0.3</dimen>
+</resources>
\ No newline at end of file
diff --git a/android/TerminalApp/res/values/integers.xml b/android/TerminalApp/res/values/integers.xml
new file mode 100644
index 0000000..0c7d2b9
--- /dev/null
+++ b/android/TerminalApp/res/values/integers.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <integer name="split_min_width">720</integer>
+</resources>
\ No newline at end of file
diff --git a/android/TerminalApp/res/values/strings.xml b/android/TerminalApp/res/values/strings.xml
index 79da7cd..d3670d2 100644
--- a/android/TerminalApp/res/values/strings.xml
+++ b/android/TerminalApp/res/values/strings.xml
@@ -16,8 +16,45 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+
+ <!-- Application name of this terminal app shown in the launcher. This app provides computer terminal to connect to virtual machine. [CHAR LIMIT=16] -->
<string name="app_name">Terminal</string>
- <string name="vm_creation_message">Virtual machine is booting. Please wait.</string>
- <string name="vm_stop_message">Virtual machine is stopped. Exiting.</string>
- <string name="vm_error_message">Virtual machine crashed. Exiting.</string>
+
+ <!-- Toast message to notify that preparing terminal to start [CHAR LIMIT=none] -->
+ <string name="vm_creation_message">Preparing terminal</string>
+ <!-- Toast message to notify that terminal is stopping [CHAR LIMIT=none] -->
+ <string name="vm_stop_message">Stopping terminal</string>
+ <!-- Toast message to notify that terminal is crashed [CHAR LIMIT=none] -->
+ <string name="vm_error_message">Terminal crashed</string>
+
+ <!-- Settings memu title for resizing disk of the virtual machine. [CHAR LIMIT=none] -->
+ <string name="settings_disk_resize_title">Disk Resize</string>
+ <!-- Settings memu subtitle for resizing disk of the virtual machine. [CHAR LIMIT=none] -->
+ <string name="settings_disk_resize_sub_title">Resize / Rootfs</string>
+ <!-- Toast message after new disk size is set. [CHAR LIMIT=none] -->
+ <string name="settings_disk_resize_resize_message">Disk size set</string>
+ <!-- Settings menu option description format of the current disk size. [CHAR LIMIT=none] -->
+ <string name="settings_disk_resize_resize_gb_assigned_format"><xliff:g id="assigned_size" example="10GB">%1$s</xliff:g> assigned</string>
+ <!-- Settings menu option description format of the maximum resizable disk size. [CHAR LIMIT=none] -->
+ <string name="settings_disk_resize_resize_gb_max_format"><xliff:g id="max_size" example="256GB">%1$s</xliff:g> max</string>
+ <!-- Settings menu button to cancel disk resize. [CHAR LIMIT=32] -->
+ <string name="settings_disk_resize_resize_cancel">Cancel</string>
+ <!-- Settings menu button to apply change that requires to restart VM (abbrev of virtual machine). [CHAR LIMIT=64] -->
+ <string name="settings_disk_resize_resize_restart_vm_to_apply">Restart VM to apply</string>
+
+ <!-- Settings menu title for 'port forwarding' [CHAR LIMIT=none] -->
+ <string name="settings_port_forwarding_title">Port Forwarding</string>
+ <!-- Settings menu subtitle for 'port forwarding' [CHAR LIMIT=none] -->
+ <string name="settings_port_forwarding_sub_title">Configure port forwarding</string>
+
+ <!-- Settings menu title for recoverying image [CHAR LIMIT=none] -->
+ <string name="settings_recovery_title">Recovery</string>
+ <!-- Settings menu subtitle for recoverying image [CHAR LIMIT=none] -->
+ <string name="settings_recovery_sub_title">Partition Recovery options</string>
+ <!-- Settings menu title for resetting the virtual machine image [CHAR LIMIT=none] -->
+ <string name="settings_recovery_reset_title">Change to Initial version</string>
+ <!-- Settings menu subtitle for resetting the virtual machine image [CHAR LIMIT=none] -->
+ <string name="settings_recovery_reset_sub_title">Remove all</string>
+ <!-- Toast message for reset is completed [CHAR LIMIT=none] -->
+ <string name="settings_recovery_reset_message">VM reset</string>
</resources>
diff --git a/android/TerminalApp/res/xml/main_split_config.xml b/android/TerminalApp/res/xml/main_split_config.xml
new file mode 100644
index 0000000..f51e7ea
--- /dev/null
+++ b/android/TerminalApp/res/xml/main_split_config.xml
@@ -0,0 +1,37 @@
+<resources xmlns:window="http://schemas.android.com/apk/res-auto">
+
+ <!-- Define a split for the named activities. -->
+ <ActivityRule window:alwaysExpand="true">
+ <ActivityFilter window:activityName=".MainActivity" />
+ </ActivityRule>
+
+ <SplitPairRule
+ window:clearTop="true"
+ window:finishPrimaryWithSecondary="adjacent"
+ window:finishSecondaryWithPrimary="always"
+ window:splitLayoutDirection="locale"
+ window:splitMaxAspectRatioInPortrait="alwaysAllow"
+ window:splitMinWidthDp="@integer/split_min_width"
+ window:splitRatio="@dimen/activity_split_ratio">
+ <SplitPairFilter
+ window:primaryActivityName="com.android.virtualization.terminal.SettingsActivity"
+ window:secondaryActivityName="com.android.virtualization.terminal.SettingsDiskResizeActivity" />
+ <SplitPairFilter
+ window:primaryActivityName="com.android.virtualization.terminal.SettingsActivity"
+ window:secondaryActivityName="com.android.virtualization.terminal.SettingsPortForwardingActivity" />
+ <SplitPairFilter
+ window:primaryActivityName="com.android.virtualization.terminal.SettingsActivity"
+ window:secondaryActivityName="com.android.virtualization.terminal.SettingsRecoveryActivity" />
+ </SplitPairRule>
+
+ <SplitPlaceholderRule
+ window:placeholderActivityName="com.android.virtualization.terminal.SettingsDiskResizeActivity"
+ window:splitLayoutDirection="locale"
+ window:splitMaxAspectRatioInPortrait="alwaysAllow"
+ window:splitMinWidthDp="@integer/split_min_width"
+ window:splitRatio="@dimen/activity_split_ratio">
+ window:stickyPlaceholder="false">
+ <ActivityFilter
+ window:activityName="com.android.virtualization.terminal.SettingsActivity"/>
+ </SplitPlaceholderRule>
+</resources>
\ No newline at end of file
diff --git a/android/LinuxInstaller/linux_image_builder/vm_config.json b/android/TerminalApp/vm_config.json
similarity index 87%
rename from android/LinuxInstaller/linux_image_builder/vm_config.json
rename to android/TerminalApp/vm_config.json
index 21462b8..474e9c3 100644
--- a/android/LinuxInstaller/linux_image_builder/vm_config.json
+++ b/android/TerminalApp/vm_config.json
@@ -1,8 +1,9 @@
+
{
"name": "debian",
"disks": [
{
- "image": "/data/local/tmp/debian.img",
+ "image": "/data/local/tmp/image.raw",
"partitions": [],
"writable": true
}
@@ -17,3 +18,4 @@
"console_input_device": "ttyS0",
"network": true
}
+
diff --git a/android/virtmgr/src/aidl.rs b/android/virtmgr/src/aidl.rs
index 87fb611..23652d2 100644
--- a/android/virtmgr/src/aidl.rs
+++ b/android/virtmgr/src/aidl.rs
@@ -33,7 +33,7 @@
CpuTopology::CpuTopology,
DiskImage::DiskImage,
InputDevice::InputDevice,
- IVirtualMachine::{BnVirtualMachine, IVirtualMachine},
+ IVirtualMachine::{self, BnVirtualMachine},
IVirtualMachineCallback::IVirtualMachineCallback,
IVirtualizationService::IVirtualizationService,
Partition::Partition,
@@ -62,9 +62,8 @@
use apkverify::{HashAlgorithm, V4Signature};
use avflog::LogResult;
use binder::{
- self, wait_for_interface, BinderFeatures, ExceptionCode, Interface, ParcelFileDescriptor,
- Status, StatusCode, Strong,
- IntoBinderResult,
+ self, wait_for_interface, Accessor, BinderFeatures, ConnectionInfo, ExceptionCode, Interface, ParcelFileDescriptor,
+ SpIBinder, Status, StatusCode, Strong, IntoBinderResult,
};
use cstr::cstr;
use glob::glob;
@@ -90,7 +89,7 @@
use std::sync::{Arc, Mutex, Weak, LazyLock};
use vbmeta::VbMetaImage;
use vmconfig::{VmConfig, get_debug_level};
-use vsock::VsockStream;
+use vsock::{VsockAddr, VsockStream};
use zip::ZipArchive;
/// The unique ID of a VM used (together with a port number) for vsock communication.
@@ -98,6 +97,9 @@
pub const BINDER_SERVICE_IDENTIFIER: &str = "android.system.virtualizationservice";
+/// Vsock privileged ports are below this number.
+const VSOCK_PRIV_PORT_MAX: u32 = 1024;
+
/// The size of zero.img.
/// Gaps in composite disk images are filled with a shared zero.img.
const ZERO_FILLER_SIZE: u64 = 4096;
@@ -221,7 +223,8 @@
console_out_fd: Option<&ParcelFileDescriptor>,
console_in_fd: Option<&ParcelFileDescriptor>,
log_fd: Option<&ParcelFileDescriptor>,
- ) -> binder::Result<Strong<dyn IVirtualMachine>> {
+ dump_dt_fd: Option<&ParcelFileDescriptor>,
+ ) -> binder::Result<Strong<dyn IVirtualMachine::IVirtualMachine>> {
let mut is_protected = false;
let ret = self.create_vm_internal(
config,
@@ -229,6 +232,7 @@
console_in_fd,
log_fd,
&mut is_protected,
+ dump_dt_fd,
);
write_vm_creation_stats(config, is_protected, &ret);
ret
@@ -485,7 +489,8 @@
console_in_fd: Option<&ParcelFileDescriptor>,
log_fd: Option<&ParcelFileDescriptor>,
is_protected: &mut bool,
- ) -> binder::Result<Strong<dyn IVirtualMachine>> {
+ dump_dt_fd: Option<&ParcelFileDescriptor>,
+ ) -> binder::Result<Strong<dyn IVirtualMachine::IVirtualMachine>> {
let requester_uid = get_calling_uid();
let requester_debug_pid = get_calling_pid();
@@ -527,6 +532,7 @@
clone_or_prepare_logger_fd(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(log_fd, format!("Log({})", cid))?;
+ let dump_dt_fd = dump_dt_fd.map(clone_file).transpose()?;
// Counter to generate unique IDs for temporary image files.
let mut next_temporary_image_id = 0;
@@ -744,6 +750,7 @@
audio_config,
no_balloon: config.noBalloon,
usb_config,
+ dump_dt_fd,
};
let instance = Arc::new(
VmInstance::new(
@@ -1326,14 +1333,14 @@
}
impl VirtualMachine {
- fn create(instance: Arc<VmInstance>) -> Strong<dyn IVirtualMachine> {
+ fn create(instance: Arc<VmInstance>) -> Strong<dyn IVirtualMachine::IVirtualMachine> {
BnVirtualMachine::new_binder(VirtualMachine { instance }, BinderFeatures::default())
}
}
impl Interface for VirtualMachine {}
-impl IVirtualMachine for VirtualMachine {
+impl IVirtualMachine::IVirtualMachine for VirtualMachine {
fn getCid(&self) -> binder::Result<i32> {
// Don't check permission. The owner of the VM might have passed this binder object to
// others.
@@ -1394,19 +1401,48 @@
fn connectVsock(&self, port: i32) -> binder::Result<ParcelFileDescriptor> {
if !matches!(&*self.instance.vm_state.lock().unwrap(), VmState::Running { .. }) {
- return Err(anyhow!("VM is not running")).or_service_specific_exception(-1);
+ return Err(Status::new_service_specific_error_str(
+ IVirtualMachine::ERROR_UNEXPECTED,
+ Some("Virtual Machine is not running"),
+ ));
}
let port = port as u32;
- if port < 1024 {
- return Err(anyhow!("Can't connect to privileged port {port}"))
- .or_service_specific_exception(-1);
+ if port < VSOCK_PRIV_PORT_MAX {
+ return Err(Status::new_service_specific_error_str(
+ IVirtualMachine::ERROR_UNEXPECTED,
+ Some("Can't connect to privileged port {port}"),
+ ));
}
let stream = VsockStream::connect_with_cid_port(self.instance.cid, port)
.context("Failed to connect")
- .or_service_specific_exception(-1)?;
+ .or_service_specific_exception(IVirtualMachine::ERROR_UNEXPECTED)?;
Ok(vsock_stream_to_pfd(stream))
}
+ fn createAccessorBinder(&self, name: &str, port: i32) -> binder::Result<SpIBinder> {
+ if !matches!(&*self.instance.vm_state.lock().unwrap(), VmState::Running { .. }) {
+ return Err(Status::new_service_specific_error_str(
+ IVirtualMachine::ERROR_UNEXPECTED,
+ Some("Virtual Machine is not running"),
+ ));
+ }
+ let port = port as u32;
+ if port < VSOCK_PRIV_PORT_MAX {
+ return Err(Status::new_service_specific_error_str(
+ IVirtualMachine::ERROR_UNEXPECTED,
+ Some("Can't connect to privileged port {port}"),
+ ));
+ }
+ let cid = self.instance.cid;
+ let get_connection_info =
+ move |_instance: &str| Some(ConnectionInfo::Vsock(VsockAddr::new(cid, port)));
+ let accessor = Accessor::new(name, get_connection_info);
+ accessor
+ .as_binder()
+ .context("The newly created Accessor should always have a binder")
+ .or_service_specific_exception(IVirtualMachine::ERROR_UNEXPECTED)
+ }
+
fn setHostConsoleName(&self, ptsname: &str) -> binder::Result<()> {
self.instance.vm_context.global_context.setHostConsoleName(ptsname)
}
diff --git a/android/virtmgr/src/crosvm.rs b/android/virtmgr/src/crosvm.rs
index b2be736..25271f8 100644
--- a/android/virtmgr/src/crosvm.rs
+++ b/android/virtmgr/src/crosvm.rs
@@ -135,6 +135,7 @@
pub audio_config: Option<AudioConfig>,
pub no_balloon: bool,
pub usb_config: UsbConfig,
+ pub dump_dt_fd: Option<File>,
}
#[derive(Debug)]
@@ -985,6 +986,11 @@
// Keep track of what file descriptors should be mapped to the crosvm process.
let mut preserved_fds = config.indirect_files.into_iter().map(|f| f.into()).collect();
+ if let Some(dump_dt_fd) = config.dump_dt_fd {
+ let dump_dt_fd = add_preserved_fd(&mut preserved_fds, dump_dt_fd);
+ command.arg("--dump-device-tree-blob").arg(dump_dt_fd);
+ }
+
// Setup the serial devices.
// 1. uart device: used as the output device by bootloaders and as early console by linux
// 2. uart device: used to report the reason for the VM failing.
diff --git a/android/virtmgr/src/payload.rs b/android/virtmgr/src/payload.rs
index 81e02b7..5811314 100644
--- a/android/virtmgr/src/payload.rs
+++ b/android/virtmgr/src/payload.rs
@@ -178,14 +178,9 @@
let pm =
wait_for_interface::<dyn IPackageManagerNative>(PACKAGE_MANAGER_NATIVE_SERVICE)
.context("Failed to get service when prefer_staged is set.")?;
- let staged =
- pm.getStagedApexModuleNames().context("getStagedApexModuleNames failed")?;
- for name in staged {
- if let Some(staged_apex_info) =
- pm.getStagedApexInfo(&name).context("getStagedApexInfo failed")?
- {
- list.override_staged_apex(&staged_apex_info)?;
- }
+ let staged = pm.getStagedApexInfos().context("getStagedApexInfos failed")?;
+ for apex in staged {
+ list.override_staged_apex(&apex)?;
}
}
Ok(list)
diff --git a/android/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualMachine.aidl b/android/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualMachine.aidl
index afa25e2..e52222a 100644
--- a/android/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualMachine.aidl
+++ b/android/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualMachine.aidl
@@ -19,6 +19,13 @@
import android.system.virtualizationservice.VirtualMachineState;
interface IVirtualMachine {
+ /**
+ * Encountered an unexpected error. This is an implementation detail and the client
+ * can do nothing about it.
+ * This is used as a Service Specific Exception.
+ */
+ const int ERROR_UNEXPECTED = -1;
+
/** Get the CID allocated to the VM. */
int getCid();
@@ -48,6 +55,19 @@
/** Open a vsock connection to the CID of the VM on the given port. */
ParcelFileDescriptor connectVsock(int port);
+ /**
+ * Create an Accessor in libbinder that will open a vsock connection
+ * to the CID of the VM on the given port.
+ *
+ * \param instance name of the service that the accessor is responsible for.
+ * This is the same instance that we expect clients to use when trying
+ * to get the service with the ServiceManager APIs.
+ *
+ * \return IBinder of the IAccessor on success, or throws a service specific exception
+ * on error. See the ERROR_* values above.
+ */
+ IBinder createAccessorBinder(String instance, int port);
+
/** Set the name of the peer end (ptsname) of the host console. */
void setHostConsoleName(in @utf8InCpp String pathname);
diff --git a/android/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualizationService.aidl b/android/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualizationService.aidl
index 234d8d0..0c3f6b7 100644
--- a/android/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualizationService.aidl
+++ b/android/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualizationService.aidl
@@ -35,11 +35,14 @@
* `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.
+ * `dumpDtFd` is the file where to dump the VM's device tree. It is only used in
+ * debugging/testing.
*/
IVirtualMachine createVm(in VirtualMachineConfig config,
in @nullable ParcelFileDescriptor consoleOutFd,
in @nullable ParcelFileDescriptor consoleInFd,
- in @nullable ParcelFileDescriptor osLogFd);
+ in @nullable ParcelFileDescriptor osLogFd,
+ in @nullable ParcelFileDescriptor dumpDtFd);
/**
* Allocate an instance_id to the (newly created) VM.
diff --git a/android/vm/src/main.rs b/android/vm/src/main.rs
index 609bbdf..81ca8fa 100644
--- a/android/vm/src/main.rs
+++ b/android/vm/src/main.rs
@@ -114,6 +114,10 @@
#[cfg(debuggable_vms_improvements)]
#[arg(long)]
enable_earlycon: bool,
+
+ /// Path to file to dump VM device tree.
+ #[arg(long)]
+ dump_device_tree: Option<PathBuf>,
}
impl DebugConfig {
diff --git a/android/vm/src/run.rs b/android/vm/src/run.rs
index 823546f..0e1f4cc 100644
--- a/android/vm/src/run.rs
+++ b/android/vm/src/run.rs
@@ -203,6 +203,7 @@
config.debug.console.as_ref().map(|p| p.as_ref()),
config.debug.console_in.as_ref().map(|p| p.as_ref()),
config.debug.log.as_ref().map(|p| p.as_ref()),
+ config.debug.dump_device_tree.as_ref().map(|p| p.as_ref()),
)
}
@@ -284,6 +285,7 @@
config.debug.console.as_ref().map(|p| p.as_ref()),
config.debug.console_in.as_ref().map(|p| p.as_ref()),
config.debug.log.as_ref().map(|p| p.as_ref()),
+ config.debug.dump_device_tree.as_ref().map(|p| p.as_ref()),
)
}
@@ -306,6 +308,7 @@
console_out_path: Option<&Path>,
console_in_path: Option<&Path>,
log_path: Option<&Path>,
+ dump_device_tree: Option<&Path>,
) -> Result<(), Error> {
let console_out = if let Some(console_out_path) = console_out_path {
Some(File::create(console_out_path).with_context(|| {
@@ -330,9 +333,17 @@
} else {
Some(duplicate_fd(io::stdout())?)
};
+ let dump_dt = if let Some(dump_device_tree) = dump_device_tree {
+ Some(File::create(dump_device_tree).with_context(|| {
+ format!("Failed to open file to dump device tree: {:?}", dump_device_tree)
+ })?)
+ } else {
+ None
+ };
let callback = Box::new(Callback {});
- let vm = VmInstance::create(service, config, console_out, console_in, log, Some(callback))
- .context("Failed to create VM")?;
+ let vm =
+ VmInstance::create(service, config, console_out, console_in, log, dump_dt, Some(callback))
+ .context("Failed to create VM")?;
vm.start().context("Failed to start VM")?;
let debug_level = get_debug_level(config).unwrap_or(DebugLevel::NONE);
diff --git a/android/vm_demo_native/main.cpp b/android/vm_demo_native/main.cpp
index bc42036..d7ff02e 100644
--- a/android/vm_demo_native/main.cpp
+++ b/android/vm_demo_native/main.cpp
@@ -226,8 +226,10 @@
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));
+ ScopedFileDescriptor dump_dt_fd(-1);
- ScopedAStatus ret = service.createVm(config, console_out_fd, console_in_fd, log_fd, &vm);
+ ScopedAStatus ret =
+ service.createVm(config, console_out_fd, console_in_fd, log_fd, dump_dt_fd, &vm);
if (!ret.isOk()) {
return Error() << "Failed to create VM";
}
diff --git a/build/apex/Android.bp b/build/apex/Android.bp
index 4916df7..4b69660 100644
--- a/build/apex/Android.bp
+++ b/build/apex/Android.bp
@@ -80,6 +80,11 @@
}),
}
+vintf_fragment {
+ name: "virtualizationservice.xml",
+ src: "virtualizationservice.xml",
+}
+
apex_defaults {
name: "com.android.virt_avf_enabled",
@@ -166,10 +171,14 @@
true: "AndroidManifest.xml",
default: unset,
}),
- vintf_fragments: select(soong_config_variable("ANDROID", "avf_remote_attestation_enabled"), {
+ vintf_fragment_modules: select(soong_config_variable("ANDROID", "avf_remote_attestation_enabled"), {
"true": ["virtualizationservice.xml"],
default: unset,
}),
+ required: select(release_flag("RELEASE_AVF_SUPPORT_CUSTOM_VM_WITH_PARAVIRTUALIZED_DEVICES"), {
+ true: ["linux_vm_setup"],
+ default: [],
+ }),
}
apex_defaults {
diff --git a/build/apex/product_packages.mk b/build/apex/product_packages.mk
index b2a4ca2..0646e67 100644
--- a/build/apex/product_packages.mk
+++ b/build/apex/product_packages.mk
@@ -26,11 +26,13 @@
com.android.compos \
features_com.android.virt.xml
-# TODO(b/207336449): Figure out how to get these off /system
+ifneq (true, $(RELEASE_INSTALL_APEX_SYSTEMSERVER_DEXPREOPT_SAME_PARTITION))
PRODUCT_ARTIFACT_PATH_REQUIREMENT_ALLOWED_LIST := \
system/framework/oat/%@service-compos.jar@classes.odex \
system/framework/oat/%@service-compos.jar@classes.vdex \
+endif
+
PRODUCT_APEX_SYSTEM_SERVER_JARS := com.android.compos:service-compos
PRODUCT_SYSTEM_EXT_PROPERTIES := ro.config.isolated_compilation_enabled=true
@@ -71,7 +73,3 @@
$(error RELEASE_AVF_ENABLE_EARLY_VM can only be enabled in trunk_staging until b/357025924 is fixed)
endif
endif
-
-ifdef RELEASE_AVF_SUPPORT_CUSTOM_VM_WITH_PARAVIRTUALIZED_DEVICES
- PRODUCT_PACKAGES += LinuxInstallerAppStub
-endif
diff --git a/build/debian/build.sh b/build/debian/build.sh
index 3d3820a..71a9d3b 100755
--- a/build/debian/build.sh
+++ b/build/debian/build.sh
@@ -2,15 +2,15 @@
# This is a script to build a Debian image that can run in a VM created via AVF.
# TODOs:
-# - Support x86_64 architecture
# - Add Android-specific packages via a new class
# - Use a stable release from debian-cloud-images
show_help() {
- echo Usage: $0 [OPTION]... [FILE]
- echo Builds a debian image and save it to FILE.
- echo Options:
- echo -h Pring usage and this help message and exit.
+ echo "Usage: sudo $0 [OPTION]... [FILE]"
+ echo "Builds a debian image and save it to FILE. [sudo is required]"
+ echo "Options:"
+ echo "-h Print usage and this help message and exit."
+ echo "-a ARCH Architecture of the image [default is aarch64]"
}
check_sudo() {
@@ -21,81 +21,125 @@
}
parse_options() {
- while getopts ":h" option; do
+ while getopts "ha:" option; do
case ${option} in
h)
show_help
exit;;
+ a)
+ if [[ "$OPTARG" != "aarch64" && "$OPTARG" != "x86_64" ]]; then
+ echo "Invalid architecture: $OPTARG"
+ exit
+ fi
+ arch="$OPTARG"
+ if [[ "$arch" == "x86_64" ]]; then
+ debian_arch="amd64"
+ fi
+ ;;
+ *)
+ echo "Invalid option: $OPTARG"
+ exit
+ ;;
esac
done
- if [ -n "$1" ]; then
- built_image=$1
+ if [[ "${*:$OPTIND:1}" ]]; then
+ built_image="${*:$OPTIND:1}"
fi
}
install_prerequisites() {
apt update
- DEBIAN_FRONTEND=noninteractive \
- apt install --no-install-recommends --assume-yes \
- ca-certificates \
- debsums \
- dosfstools \
- fai-server \
- fai-setup-storage \
- fdisk \
- make \
- python3 \
- python3-libcloud \
- python3-marshmallow \
- python3-pytest \
- python3-yaml \
- qemu-utils \
- udev \
- qemu-system-arm \
+ packages=(
+ binfmt-support
+ build-essential
+ ca-certificates
+ curl
+ debsums
+ dosfstools
+ fai-server
+ fai-setup-storage
+ fdisk
+ make
+ protobuf-compiler
+ python3
+ python3-libcloud
+ python3-marshmallow
+ python3-pytest
+ python3-yaml
qemu-user-static
+ qemu-utils
+ sudo
+ udev
+ )
+ if [[ "$arch" == "aarch64" ]]; then
+ packages+=(
+ gcc-aarch64-linux-gnu
+ libc6-dev-arm64-cross
+ qemu-system-arm
+ )
+ else
+ packages+=(
+ qemu-system
+ )
+ fi
+ DEBIAN_FRONTEND=noninteractive \
+ apt install --no-install-recommends --assume-yes "${packages[@]}"
- sed -i s/losetup\ -f/losetup\ -P\ -f/g /usr/sbin/fai-diskimage
- sed -i 's/wget \$/wget -t 0 \$/g' /usr/share/debootstrap/functions
+ if [ ! -f $"HOME"/.cargo/bin/cargo ]; then
+ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
+ fi
- apt install --no-install-recommends --assume-yes curl
- # just for testing
- echo libseccomp: $(curl -is https://deb.debian.org/debian/pool/main/libs/libseccomp/libseccomp2_2.5.4-1+deb12u1_arm64.deb | head -n 1)
- echo libsemanage-common: $(curl -is https://deb.debian.org/debian/pool/main/libs/libsemanage/libsemanage-common_3.4-1_all.deb | head -n 1)
- echo libpcre2: $(curl -is https://deb.debian.org/debian/pool/main/p/pcre2/libpcre2-8-0_10.42-1_arm64.deb | head -n 1)
+ source "$HOME"/.cargo/env
+ rustup target add "${arch}"-unknown-linux-gnu
}
download_debian_cloud_image() {
local ver=master
local prj=debian-cloud-images
- local url=https://salsa.debian.org/cloud-team/${prj}/-/archive/${ver}/${prj}-${ver}.tar.gz
- local outdir=${debian_cloud_image}
+ local url="https://salsa.debian.org/cloud-team/${prj}/-/archive/${ver}/${prj}-${ver}.tar.gz"
+ local outdir="${debian_cloud_image}"
- mkdir -p ${outdir}
- wget -O - ${url} | tar xz -C ${outdir} --strip-components=1
+ mkdir -p "${outdir}"
+ wget -O - "${url}" | tar xz -C "${outdir}" --strip-components=1
+}
+
+build_rust_binary_and_copy() {
+ pushd "$(dirname "$0")/../../guest/$1" > /dev/null
+ RUSTFLAGS="-C linker=${arch}-linux-gnu-gcc" cargo build \
+ --target "${arch}-unknown-linux-gnu" \
+ --target-dir "${workdir}/$1"
+ mkdir -p "${dst}/files/usr/local/bin/$1"
+ cp "${workdir}/$1/${arch}-unknown-linux-gnu/debug/$1" "${dst}/files/usr/local/bin/$1/AVF"
+ chmod 777 "${dst}/files/usr/local/bin/$1/AVF"
+ popd > /dev/null
}
copy_android_config() {
- local src=$(dirname $0)/fai_config
- local dst=${config_space}
+ local src="$(dirname "$0")/fai_config"
+ local dst="${config_space}"
- cp -R ${src}/* ${dst}
- cp $(dirname $0)/image.yaml ${resources_dir}
+ cp -R "${src}"/* "${dst}"
+ cp "$(dirname "$0")/image.yaml" "${resources_dir}"
local ttyd_version=1.7.7
- local url=https://github.com/tsl0922/ttyd/releases/download/${ttyd_version}/ttyd.aarch64
- mkdir -p ${dst}/files/usr/local/bin/ttyd
- wget ${url} -O ${dst}/files/usr/local/bin/ttyd/AVF
- chmod 777 ${dst}/files/usr/local/bin/ttyd/AVF
+ local url="https://github.com/tsl0922/ttyd/releases/download/${ttyd_version}/ttyd.${arch}"
+ mkdir -p "${dst}/files/usr/local/bin/ttyd"
+ wget "${url}" -O "${dst}/files/usr/local/bin/ttyd/AVF"
+ chmod 777 "${dst}/files/usr/local/bin/ttyd/AVF"
+
+ build_rust_binary_and_copy forwarder_guest
+ build_rust_binary_and_copy forwarder_guest_launcher
+ build_rust_binary_and_copy ip_addr_reporter
}
run_fai() {
- local out=${built_image}
- make -C ${debian_cloud_image} image_bookworm_nocloud_arm64
- mv ${debian_cloud_image}/image_bookworm_nocloud_arm64.raw ${out}
+ local out="${built_image}"
+ make -C "${debian_cloud_image}" "image_bookworm_nocloud_${debian_arch}"
+ mv "${debian_cloud_image}/image_bookworm_nocloud_${debian_arch}.raw" "${out}"
}
clean_up() {
- rm -rf ${workdir}
+ rm -rf "${workdir}"
}
set -e
@@ -107,8 +151,10 @@
debian_version=bookworm
config_space=${debian_cloud_image}/config_space/${debian_version}
resources_dir=${debian_cloud_image}/src/debian_cloud_images/resources
+arch=aarch64
+debian_arch=arm64
+parse_options "$@"
check_sudo
-parse_options $@
install_prerequisites
download_debian_cloud_image
copy_android_config
diff --git a/build/debian/build_in_container.sh b/build/debian/build_in_container.sh
new file mode 100755
index 0000000..fd1a975
--- /dev/null
+++ b/build/debian/build_in_container.sh
@@ -0,0 +1,24 @@
+#!/bin/bash
+
+if [ -z "$ANDROID_BUILD_TOP" ]; then echo "forgot to source build/envsetup.sh?" && exit 1; fi
+
+arch=aarch64
+while getopts "a:" option; do
+ case ${option} in
+ a)
+ if [[ "$OPTARG" != "aarch64" && "$OPTARG" != "x86_64" ]]; then
+ echo "Invalid architecture: $OPTARG"
+ exit
+ fi
+ arch="$OPTARG"
+ ;;
+ *)
+ echo "Invalid option: $OPTARG"
+ exit
+ ;;
+ esac
+done
+
+docker run --privileged -it --workdir /root/Virtualization/build/debian -v \
+ "$ANDROID_BUILD_TOP/packages/modules/Virtualization:/root/Virtualization" -v \
+ /dev:/dev ubuntu:22.04 /root/Virtualization/build/debian/build.sh -a "$arch"
diff --git a/build/debian/fai_config/files/etc/systemd/system/ip_addr_reporter.service/AVF b/build/debian/fai_config/files/etc/systemd/system/ip_addr_reporter.service/AVF
new file mode 100644
index 0000000..7d163fb
--- /dev/null
+++ b/build/debian/fai_config/files/etc/systemd/system/ip_addr_reporter.service/AVF
@@ -0,0 +1,13 @@
+[Unit]
+Description=ip report service
+After=syslog.target
+After=network.target
+Requires=ttyd.service
+[Service]
+ExecStart=/usr/local/bin/ip_addr_reporter
+Type=simple
+Restart=on-failure
+User=root
+Group=root
+[Install]
+WantedBy=multi-user.target
diff --git a/build/debian/fai_config/files/etc/systemd/system/ttyd.service/AVF b/build/debian/fai_config/files/etc/systemd/system/ttyd.service/AVF
index f71557d..0aab770 100644
--- a/build/debian/fai_config/files/etc/systemd/system/ttyd.service/AVF
+++ b/build/debian/fai_config/files/etc/systemd/system/ttyd.service/AVF
@@ -3,7 +3,7 @@
After=syslog.target
After=network.target
[Service]
-ExecStart=/usr/local/bin/ttyd -W screen -aAxR -S main login
+ExecStart=/usr/local/bin/ttyd --ssl --ssl-cert /etc/ttyd/server.crt --ssl-key /etc/ttyd/server.key --ssl-ca /etc/ttyd/ca.crt -W screen -aAxR -S main login -f droid
Type=simple
Restart=always
User=root
diff --git a/build/debian/fai_config/files/etc/systemd/system/vsockip.service/AVF b/build/debian/fai_config/files/etc/systemd/system/vsockip.service/AVF
deleted file mode 100644
index a29020b..0000000
--- a/build/debian/fai_config/files/etc/systemd/system/vsockip.service/AVF
+++ /dev/null
@@ -1,12 +0,0 @@
-[Unit]
-Description=vsock ip service
-After=syslog.target
-After=network.target
-[Service]
-ExecStart=/usr/bin/python3 /usr/local/bin/vsock.py
-Type=simple
-Restart=always
-User=root
-Group=root
-[Install]
-WantedBy=multi-user.target
diff --git a/build/debian/fai_config/files/etc/ttyd/ca.crt/AVF b/build/debian/fai_config/files/etc/ttyd/ca.crt/AVF
new file mode 100644
index 0000000..90d8c0e
--- /dev/null
+++ b/build/debian/fai_config/files/etc/ttyd/ca.crt/AVF
@@ -0,0 +1,21 @@
+-----BEGIN CERTIFICATE-----
+MIIDhzCCAm+gAwIBAgIUQkvURjf6sU5aJ7oK9usHnJHsc/owDQYJKoZIhvcNAQEL
+BQAwUzELMAkGA1UEBhMCQ04xCzAJBgNVBAgMAkdEMQswCQYDVQQHDAJTWjETMBEG
+A1UECgwKQWNtZSwgSW5jLjEVMBMGA1UEAwwMQWNtZSBSb290IENBMB4XDTI0MTAx
+NDAxMjgzN1oXDTI1MTAxNDAxMjgzN1owUzELMAkGA1UEBhMCQ04xCzAJBgNVBAgM
+AkdEMQswCQYDVQQHDAJTWjETMBEGA1UECgwKQWNtZSwgSW5jLjEVMBMGA1UEAwwM
+QWNtZSBSb290IENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtjgS
+ePtWI6xARLzM1bUMvqtWwY4ci4TzcOcfLfV5Eqbb135NSBKQ+Q2IAguc2Bl3ZVRE
+08GhQ9XJOo+mp2SUY/8+SJpCVhVlWvF6LwXd8X5pZ9GCem0FXY02kMr5ZiTs/CN2
+LZIyJKgXCT/5208on+BbiNp0pk2Pz1nDOdpxvkDJ8UKRWLwqCAEM/rcN1Lc00aln
+N/Rfi/CQE+MDAmhuy/nxr37ldqhkN+xM4bhNs1bjyVposKtbmFUY/SD3ca5CMawU
+E3l5hZ5kfua7lelEPVhvNYJcxffVO0fPNEbUKr1WsPLrnidqegcU8bml1BoCphgA
+qzoxD0rZniqMsom/vwIDAQABo1MwUTAdBgNVHQ4EFgQUZOHF7/arn8ODqEj1Wifk
+dEA5TFkwHwYDVR0jBBgwFoAUZOHF7/arn8ODqEj1WifkdEA5TFkwDwYDVR0TAQH/
+BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAVets3IybnIycAtajxpJygdji/95t
+ikdyWbi8lrszC0E5bCR9XPQKnqx/svKYrEVQNihH/nZ6TlTv0f3b77+92sVlmQfl
+a3KKI6qIgcqNEO2lHYsS+cPeBmaM6WXcEPe6gEnan1i5N16B9g9ntY4lOg8Z4roR
+2lVVCCNwabyBxb5oQDsN1IDeJ7JRRZqGGduDSZTvdd36GqNhMvXQjluyJCCFd1Hv
+IwwJmAR2GMUQU8Eoa+zGzW1Inf1YJytTu8SeQ+hYy2QCG88vZigJdifmhETDDz9Q
+xQjp1SCNIBxFHY2voqtiJtfupN5pVieECZS42pbVHMIAUOk7BmNcEWnSKw==
+-----END CERTIFICATE-----
diff --git a/build/debian/fai_config/files/etc/ttyd/server.crt/AVF b/build/debian/fai_config/files/etc/ttyd/server.crt/AVF
new file mode 100644
index 0000000..b4ca829
--- /dev/null
+++ b/build/debian/fai_config/files/etc/ttyd/server.crt/AVF
@@ -0,0 +1,21 @@
+-----BEGIN CERTIFICATE-----
+MIIDiTCCAnGgAwIBAgIUasfD1K/4tJHwNRXL2kdSD9VbeSwwDQYJKoZIhvcNAQEL
+BQAwUzELMAkGA1UEBhMCQ04xCzAJBgNVBAgMAkdEMQswCQYDVQQHDAJTWjETMBEG
+A1UECgwKQWNtZSwgSW5jLjEVMBMGA1UEAwwMQWNtZSBSb290IENBMB4XDTI0MTAx
+NDAxMjgzOFoXDTI1MTAxNDAxMjgzOFowUDELMAkGA1UEBhMCQ04xCzAJBgNVBAgM
+AkdEMQswCQYDVQQHDAJTWjETMBEGA1UECgwKQWNtZSwgSW5jLjESMBAGA1UEAwwJ
+bG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3+uVF4TP
+jUjfL8vJlECAN1rLFK8lDuOUv52VCrW7MXMfGYlA4nk1OKDjygnZIpET6I9cTfCG
+Xiwad6bU6Oqy4MZ2i338F+eERrGpkitSQ7QRqZannjBIDFxXZvJpMTJDIWNCmz+P
+K2VcvCh8im2tJA66wJogUcVmJBugNqleqxFcxPvXOdBdWBK7JYOcb4J643eLX6+D
+X6v2QTlKXfihouVC8wAzbw9HHmOVb7ono1rV7xpcFrOyBiDGVSgEteiB8l26iXA9
+fExkb0rUzHjlgvb/l8/nGAaQHd0eE+/SGd4tXvs9KHX6XJh/PI0ExTsDIBDcuVOt
+2YzXeuM6zzrKLQIDAQABo1gwVjAUBgNVHREEDTALgglsb2NhbGhvc3QwHQYDVR0O
+BBYEFHpFYqFC/AEOfWfdZmpy5YBZfgR2MB8GA1UdIwQYMBaAFGThxe/2q5/Dg6hI
+9Von5HRAOUxZMA0GCSqGSIb3DQEBCwUAA4IBAQBQspP3wo3yzcPWuFk4lRyo7zpF
+JfBBX0UU1Z0MQfIGxLC2YtRvxobRqwLcKUKQjBqUuRdukleOaVVFeXb/HI9vY3ji
+9PfUb2UJ4O3z3pdSK0EwXbkCidtUflRLvPG6dgBrXyLOqxBqA5lWR2ds5HRAMRAi
+eXfDkJTmNOAQAnPgM+35FBgmhh6axG+bUudvvVoA8ca+zW9i1R6/vblxYJ6bhmw0
+8s+uoAX6FXcZ0YFOGdhcpJmnbiRd3D0VVacjc3b9pjFOI8d3bh9pR47p0kVOaRsh
+aAG3gZhyMPOgbYceCjfzND5YhycDI+MzPo/JOYdhHGGJawoh1nP94QNPan6J
+-----END CERTIFICATE-----
diff --git a/build/debian/fai_config/files/etc/ttyd/server.key/AVF b/build/debian/fai_config/files/etc/ttyd/server.key/AVF
new file mode 100644
index 0000000..37933b2
--- /dev/null
+++ b/build/debian/fai_config/files/etc/ttyd/server.key/AVF
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDf65UXhM+NSN8v
+y8mUQIA3WssUryUO45S/nZUKtbsxcx8ZiUDieTU4oOPKCdkikRPoj1xN8IZeLBp3
+ptTo6rLgxnaLffwX54RGsamSK1JDtBGplqeeMEgMXFdm8mkxMkMhY0KbP48rZVy8
+KHyKba0kDrrAmiBRxWYkG6A2qV6rEVzE+9c50F1YErslg5xvgnrjd4tfr4Nfq/ZB
+OUpd+KGi5ULzADNvD0ceY5VvuiejWtXvGlwWs7IGIMZVKAS16IHyXbqJcD18TGRv
+StTMeOWC9v+Xz+cYBpAd3R4T79IZ3i1e+z0odfpcmH88jQTFOwMgENy5U63ZjNd6
+4zrPOsotAgMBAAECggEAARJYlD12ch5WM2aDrPOGOAtREOfP7CCwWcMiOfBP72iR
+Y9Vipxmuz16nwTJ22F7HvPsdPOUo1cFtWhim2Aqr/ZxuT4Ce9oVrk6iDwRdeuYdY
+cIhtChvJi+p0ggMcuyzp90+3AYXxynsOlCufMjSNGaqvYUsNEXnJFSgiKr7mgbIO
+J0VU1Wrquw7N58RKL+T3xEvE7uO3QpLOim2MbfRSVq/JGNxqAGw0/uxtjFs7Vtf9
+z44e/ULeYDS7zMj6cMggxQp5nfzcboGoNVUEDgYjOzqXCe4cG0n3XfN7GJhaS1ZF
+tPd8l4Ch0IrT4hs5uVFaMdFbj+er7mvmqfTVytrRmQKBgQD2kVB53EKhqxgvz2N7
+bAJglOLd6FWKsWlLMSdER0/4dRVRMIBxnYWgQ0gaRc4TM7oyKOl3MDF9jdDne5KJ
+cnfzFoH2GD6VBQRr0mFmV1UV6oHEjDJBasMo/1Vw3TJ4oZgZpYpJjrDmPWZqHUs4
+I79TdvJrNFSmk3MGVFjatLIq5QKBgQDofHpHfBeRCn2Z3OOkiAN5V53n5deZl6Jt
+lGTsrXKpEzRTre4LWZojoB9hiGjptZkXHA2HW90RiV9OHhTa8W9ZntLnOnWc5RUn
+Tzh14KupjsBQm/gE8SuqHSDx1mxTnIUo0W28d/Beecri5KfaoEY+wxZXOeQy5JFR
+ec/AhU4FqQKBgGhVzUwDnF502+M/SsVrSwY7elSUf74UnI2o2wjVdE2anc6hS3jI
+Q0cxsU0MxMrzVJLtJP2+cvLCE+ggLj3jJkbC+3N7ht/gI6LMf1KjGeoQNaFKAeoU
+l0i94xXDRBwvpQEVP5MowkprKO82PiIfXlKfPq2Gk1t5gW7oOkExvULRAoGBAK7R
+051nec0uJ06I5IE3ae1X7jyP//TWKmTeHpo+vybWcxWth3/va9H4OUC9M67ySGEx
+ThcIBA+IzirOwf31aTbqEEuiEQje1m5NyvYQ8OS6nHDBJ9qHg78S0lAoXiLtYtBT
+04HSauSQDvlY2cOzm77cMjN7K9b9Oy0aPRfW5dmpAoGAGesq4Ojky4crpi0H1O7n
+cMuIAzaPozsMx7iSrhUe69fwVFiMkEKR6ems01DmjYwPb6DtxCieaRlGbd9E8oIZ
+y6n+Uh9Qbc5sDhPMsys6NyKOv/A6rkn49/etr40f0Z5g9g/d2+qtwoAXjo3sSPuW
+7iqbruRjbKUaJKzdpIqOKD0=
+-----END PRIVATE KEY-----
diff --git a/build/debian/fai_config/files/usr/local/bin/vsock.py/AVF b/build/debian/fai_config/files/usr/local/bin/vsock.py/AVF
deleted file mode 100755
index 292d953..0000000
--- a/build/debian/fai_config/files/usr/local/bin/vsock.py/AVF
+++ /dev/null
@@ -1,57 +0,0 @@
-#!/usr/bin/env python3
-
-import socket
-
-# Constants for vsock (from linux/vm_sockets.h)
-AF_VSOCK = 40
-SOCK_STREAM = 1
-VMADDR_CID_ANY = -1
-
-def get_local_ip():
- """Retrieves the first IPv4 address found on the system.
-
- Returns:
- str: The local IPv4 address, or '127.0.0.1' if no IPv4 address is found.
- """
-
- s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
- try:
- s.connect(('8.8.8.8', 80))
- ip = s.getsockname()[0]
- except Exception:
- ip = '127.0.0.1'
- finally:
- s.close()
- return ip
-
-def main():
- PORT = 1024
-
- # Create a vsock socket
- server_socket = socket.socket(AF_VSOCK, SOCK_STREAM)
-
- # Bind the socket to the server address
- server_address = (VMADDR_CID_ANY, PORT)
- server_socket.bind(server_address)
-
- # Listen for incoming connections
- server_socket.listen(1)
- print(f"VSOCK server listening on port {PORT}...")
-
- while True:
- # Accept a connection
- connection, client_address = server_socket.accept()
- print(f"Connection from: {client_address}")
-
- try:
- # Get the local IP address
- local_ip = get_local_ip()
-
- # Send the IP address to the client
- connection.sendall(local_ip.encode())
- finally:
- # Close the connection
- connection.close()
-
-if __name__ == "__main__":
- main()
diff --git a/build/debian/fai_config/hooks/extrbase.BASE b/build/debian/fai_config/hooks/extrbase.BASE
deleted file mode 100755
index 05d1e96..0000000
--- a/build/debian/fai_config/hooks/extrbase.BASE
+++ /dev/null
@@ -1,6 +0,0 @@
-#!/bin/sh
-set -euE
-
-touch "${LOGDIR}/skip.extrbase"
-
-debootstrap --verbose --variant minbase --arch "$DEBOOTSTRAP_ARCH" "$SUITE" "$FAI_ROOT" "$DEBOOTSTRAP_MIRROR"
diff --git a/build/debian/fai_config/hooks/partition.ARM64 b/build/debian/fai_config/hooks/partition.ARM64
deleted file mode 100755
index b3b603b..0000000
--- a/build/debian/fai_config/hooks/partition.ARM64
+++ /dev/null
@@ -1,53 +0,0 @@
-#!/bin/sh
-set -eu
-touch $LOGDIR/skip.partition
-
-set -- $disklist
-device=/dev/$1
-
-wait_for_device() {
- for s in $(seq 10); do
- if [ -e "$1" ]; then
- break
- fi
- sleep 1
- done
-}
-
-sfdisk "$device" << EOF
-label: gpt
-unit: sectors
-
-# EFI system
-p15 : start=2048, size=260096, type="EFI System", uuid=${PARTUUID_ESP}
-# Linux
-p1 : start=262144, type="Linux root (ARM-64)", uuid=${PARTUUID_ROOT}
-EOF
-
-file=$(losetup -O BACK-FILE ${device} | tail -1)
-
-root_offset=$(parted -m ${device} unit B print | awk -F '[B:]' '/1:/{ print $2 }')
-root_size=$( parted -m ${device} unit B print | awk -F '[B:]' '/1:/{ print $6 }')
-efi_offset=$( parted -m ${device} unit B print | awk -F '[B:]' '/15:/{ print $2 }')
-efi_size=$( parted -m ${device} unit B print | awk -F '[B:]' '/15:/{ print $6 }')
-device_root=$(losetup -o ${root_offset} --sizelimit ${root_size} --show -f ${file})
-device_efi=$(losetup -o ${efi_offset} --sizelimit ${efi_size} --show -f ${file})
-rm -f ${device}p1
-rm -f ${device}p15
-ln -sf ${device_root} ${device}p1
-ln -sf ${device_efi} ${device}p15
-
-ls -al /dev/loop*
-losetup -a -l
-parted ${device} unit B print
-
-partprobe "$device"
-
-wait_for_device "$device_root"
-mkfs.ext4 -U "$FSUUID_ROOT" "$device_root"
-tune2fs -c 0 -i 0 "$device_root"
-
-wait_for_device "$device_efi"
-mkfs.vfat "$device_efi"
-
-parted ${device} unit B print
diff --git a/build/debian/fai_config/scripts/AVF/10-systemd b/build/debian/fai_config/scripts/AVF/10-systemd
index e04a562..6a106c6 100755
--- a/build/debian/fai_config/scripts/AVF/10-systemd
+++ b/build/debian/fai_config/scripts/AVF/10-systemd
@@ -1,6 +1,8 @@
#!/bin/bash
+chmod +x $target/usr/local/bin/forwarder_guest
+chmod +x $target/usr/local/bin/forwarder_guest_launcher
+chmod +x $target/usr/local/bin/ip_addr_reporter
chmod +x $target/usr/local/bin/ttyd
-chmod +x $target/usr/local/bin/vsock.py
ln -s /etc/systemd/system/ttyd.service $target/etc/systemd/system/multi-user.target.wants/ttyd.service
-ln -s /etc/systemd/system/vsockip.service $target/etc/systemd/system/multi-user.target.wants/vsockip.service
\ No newline at end of file
+ln -s /etc/systemd/system/ip_addr_reporter.service $target/etc/systemd/system/multi-user.target.wants/ip_addr_reporter.service
diff --git a/build/debian/fai_config/scripts/AVF/20-useradd b/build/debian/fai_config/scripts/AVF/20-useradd
new file mode 100755
index 0000000..9fbcd43
--- /dev/null
+++ b/build/debian/fai_config/scripts/AVF/20-useradd
@@ -0,0 +1,4 @@
+#!/bin/bash
+
+$ROOTCMD useradd -m -u 1000 -N -G sudo droid
+$ROOTCMD echo 'droid ALL=(ALL) NOPASSWD:ALL' >> $target/etc/sudoers
\ No newline at end of file
diff --git a/build/debian/kokoro/gcp_ubuntu_docker/aarch64/build.sh b/build/debian/kokoro/gcp_ubuntu_docker/aarch64/build.sh
new file mode 100644
index 0000000..c1524dd
--- /dev/null
+++ b/build/debian/kokoro/gcp_ubuntu_docker/aarch64/build.sh
@@ -0,0 +1,12 @@
+#!/bin/bash
+
+set -e
+
+cd "${KOKORO_ARTIFACTS_DIR}/git/avf/build/debian/"
+sudo losetup -D
+grep vmx /proc/cpuinfo || true
+sudo ./build.sh
+tar czvS -f ${KOKORO_ARTIFACTS_DIR}/images.tar.gz image.raw
+
+mkdir -p ${KOKORO_ARTIFACTS_DIR}/logs
+sudo cp -r /var/log/fai/* ${KOKORO_ARTIFACTS_DIR}/logs || true
diff --git a/build/debian/kokoro/gcp_ubuntu_docker/continuous.cfg b/build/debian/kokoro/gcp_ubuntu_docker/aarch64/continuous.cfg
similarity index 65%
rename from build/debian/kokoro/gcp_ubuntu_docker/continuous.cfg
rename to build/debian/kokoro/gcp_ubuntu_docker/aarch64/continuous.cfg
index d92031e..e836eea 100644
--- a/build/debian/kokoro/gcp_ubuntu_docker/continuous.cfg
+++ b/build/debian/kokoro/gcp_ubuntu_docker/aarch64/continuous.cfg
@@ -4,4 +4,11 @@
# Location of the bash script. Should have value <git_on_borg_scm.name>/<path_from_repository_root>.
# git_on_borg_scm.name is specified in the job configuration (next section).
-build_file: "avf/build/debian/kokoro/gcp_ubuntu_docker/build.sh"
+build_file: "avf/build/debian/kokoro/gcp_ubuntu_docker/aarch64/build.sh"
+
+action {
+ define_artifacts {
+ regex: "images.tar.gz"
+ regex: "logs/**"
+ }
+}
diff --git a/build/debian/kokoro/gcp_ubuntu_docker/continuous.cfg b/build/debian/kokoro/gcp_ubuntu_docker/aarch64/hourly.cfg
similarity index 64%
copy from build/debian/kokoro/gcp_ubuntu_docker/continuous.cfg
copy to build/debian/kokoro/gcp_ubuntu_docker/aarch64/hourly.cfg
index d92031e..e836eea 100644
--- a/build/debian/kokoro/gcp_ubuntu_docker/continuous.cfg
+++ b/build/debian/kokoro/gcp_ubuntu_docker/aarch64/hourly.cfg
@@ -4,4 +4,11 @@
# Location of the bash script. Should have value <git_on_borg_scm.name>/<path_from_repository_root>.
# git_on_borg_scm.name is specified in the job configuration (next section).
-build_file: "avf/build/debian/kokoro/gcp_ubuntu_docker/build.sh"
+build_file: "avf/build/debian/kokoro/gcp_ubuntu_docker/aarch64/build.sh"
+
+action {
+ define_artifacts {
+ regex: "images.tar.gz"
+ regex: "logs/**"
+ }
+}
diff --git a/build/debian/kokoro/gcp_ubuntu_docker/presubmit.cfg b/build/debian/kokoro/gcp_ubuntu_docker/aarch64/presubmit.cfg
similarity index 65%
rename from build/debian/kokoro/gcp_ubuntu_docker/presubmit.cfg
rename to build/debian/kokoro/gcp_ubuntu_docker/aarch64/presubmit.cfg
index d92031e..e836eea 100644
--- a/build/debian/kokoro/gcp_ubuntu_docker/presubmit.cfg
+++ b/build/debian/kokoro/gcp_ubuntu_docker/aarch64/presubmit.cfg
@@ -4,4 +4,11 @@
# Location of the bash script. Should have value <git_on_borg_scm.name>/<path_from_repository_root>.
# git_on_borg_scm.name is specified in the job configuration (next section).
-build_file: "avf/build/debian/kokoro/gcp_ubuntu_docker/build.sh"
+build_file: "avf/build/debian/kokoro/gcp_ubuntu_docker/aarch64/build.sh"
+
+action {
+ define_artifacts {
+ regex: "images.tar.gz"
+ regex: "logs/**"
+ }
+}
diff --git a/build/debian/kokoro/gcp_ubuntu_docker/build.sh b/build/debian/kokoro/gcp_ubuntu_docker/build.sh
deleted file mode 100644
index c5745d0..0000000
--- a/build/debian/kokoro/gcp_ubuntu_docker/build.sh
+++ /dev/null
@@ -1,13 +0,0 @@
-#!/bin/bash
-
-set -e
-
-cd "${KOKORO_ARTIFACTS_DIR}/git/avf/build/debian/"
-
-# FAI needs it
-pyenv install 3.10
-pyenv global 3.10
-python --version
-
-sudo losetup -D
-sudo -E ./build.sh
diff --git a/build/debian/kokoro/gcp_ubuntu_docker/x86_64/build.sh b/build/debian/kokoro/gcp_ubuntu_docker/x86_64/build.sh
new file mode 100644
index 0000000..ab675f0
--- /dev/null
+++ b/build/debian/kokoro/gcp_ubuntu_docker/x86_64/build.sh
@@ -0,0 +1,12 @@
+#!/bin/bash
+
+set -e
+
+cd "${KOKORO_ARTIFACTS_DIR}/git/avf/build/debian/"
+sudo losetup -D
+grep vmx /proc/cpuinfo || true
+sudo ./build.sh -a x86_64
+tar czvS -f ${KOKORO_ARTIFACTS_DIR}/images.tar.gz image.raw
+
+mkdir -p ${KOKORO_ARTIFACTS_DIR}/logs
+sudo cp -r /var/log/fai/* ${KOKORO_ARTIFACTS_DIR}/logs || true
diff --git a/build/debian/kokoro/gcp_ubuntu_docker/continuous.cfg b/build/debian/kokoro/gcp_ubuntu_docker/x86_64/continuous.cfg
similarity index 65%
copy from build/debian/kokoro/gcp_ubuntu_docker/continuous.cfg
copy to build/debian/kokoro/gcp_ubuntu_docker/x86_64/continuous.cfg
index d92031e..a5e8aeb 100644
--- a/build/debian/kokoro/gcp_ubuntu_docker/continuous.cfg
+++ b/build/debian/kokoro/gcp_ubuntu_docker/x86_64/continuous.cfg
@@ -4,4 +4,11 @@
# Location of the bash script. Should have value <git_on_borg_scm.name>/<path_from_repository_root>.
# git_on_borg_scm.name is specified in the job configuration (next section).
-build_file: "avf/build/debian/kokoro/gcp_ubuntu_docker/build.sh"
+build_file: "avf/build/debian/kokoro/gcp_ubuntu_docker/x86_64/build.sh"
+
+action {
+ define_artifacts {
+ regex: "images.tar.gz"
+ regex: "logs/**"
+ }
+}
diff --git a/build/debian/kokoro/gcp_ubuntu_docker/continuous.cfg b/build/debian/kokoro/gcp_ubuntu_docker/x86_64/hourly.cfg
similarity index 65%
copy from build/debian/kokoro/gcp_ubuntu_docker/continuous.cfg
copy to build/debian/kokoro/gcp_ubuntu_docker/x86_64/hourly.cfg
index d92031e..a5e8aeb 100644
--- a/build/debian/kokoro/gcp_ubuntu_docker/continuous.cfg
+++ b/build/debian/kokoro/gcp_ubuntu_docker/x86_64/hourly.cfg
@@ -4,4 +4,11 @@
# Location of the bash script. Should have value <git_on_borg_scm.name>/<path_from_repository_root>.
# git_on_borg_scm.name is specified in the job configuration (next section).
-build_file: "avf/build/debian/kokoro/gcp_ubuntu_docker/build.sh"
+build_file: "avf/build/debian/kokoro/gcp_ubuntu_docker/x86_64/build.sh"
+
+action {
+ define_artifacts {
+ regex: "images.tar.gz"
+ regex: "logs/**"
+ }
+}
diff --git a/build/debian/kokoro/gcp_ubuntu_docker/continuous.cfg b/build/debian/kokoro/gcp_ubuntu_docker/x86_64/presubmit.cfg
similarity index 65%
copy from build/debian/kokoro/gcp_ubuntu_docker/continuous.cfg
copy to build/debian/kokoro/gcp_ubuntu_docker/x86_64/presubmit.cfg
index d92031e..a5e8aeb 100644
--- a/build/debian/kokoro/gcp_ubuntu_docker/continuous.cfg
+++ b/build/debian/kokoro/gcp_ubuntu_docker/x86_64/presubmit.cfg
@@ -4,4 +4,11 @@
# Location of the bash script. Should have value <git_on_borg_scm.name>/<path_from_repository_root>.
# git_on_borg_scm.name is specified in the job configuration (next section).
-build_file: "avf/build/debian/kokoro/gcp_ubuntu_docker/build.sh"
+build_file: "avf/build/debian/kokoro/gcp_ubuntu_docker/x86_64/build.sh"
+
+action {
+ define_artifacts {
+ regex: "images.tar.gz"
+ regex: "logs/**"
+ }
+}
diff --git a/docs/vm_remote_attestation.md b/docs/vm_remote_attestation.md
index ee20591..2ee0fae 100644
--- a/docs/vm_remote_attestation.md
+++ b/docs/vm_remote_attestation.md
@@ -126,7 +126,7 @@
To support VM remote attestation, vendors must include an RKP VM marker in their
DICE certificates. This marker should be present from the early boot stage
-within the TEE and continue through to the last DICE certificate before
+within the TEE and continue through to the leaf DICE certificate before
[pvmfw][pvmfw] takes over.
![RKP VM DICE chain][rkpvm-dice-chain]
@@ -140,6 +140,20 @@
server because it will lack the RKP VM marker that pvmfw would have added in a
genuine RKP VM boot process.
+### Testing
+
+To ensure the correct implementation and usage of RKP VM markers, we've
+incorporated comprehensive checks into various xTS tests (e.g.,
+`VtsHalRemotelyProvisionedComponentTargetTest`).
+
+These tests validate the following conditions:
+
+- The RKP VM DICE chain must have a continuous presence of at least two RKP VM
+ markers, extending to the leaf DICE certificate.
+- Non-RKP VM DICE chains must not have a continuous presence of two or more RKP
+ VM markers, preventing non-RKP VM chains from being incorrectly identified as
+ RKP VM chains.
+
[pvmfw]: ../guest/pvmfw/README.md
[rkpvm-dice-chain]: img/rkpvm-dice-chain.png
diff --git a/guest/forwarder_guest/Cargo.toml b/guest/forwarder_guest/Cargo.toml
new file mode 100644
index 0000000..65f57cf
--- /dev/null
+++ b/guest/forwarder_guest/Cargo.toml
@@ -0,0 +1,11 @@
+[package]
+name = "forwarder_guest"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+clap = { version = "4.5.19", features = ["derive"] }
+forwarder = { path = "../../libs/libforwarder" }
+poll_token_derive = "0.1.0"
+remain = "0.2.14"
+vmm-sys-util = "0.12.1"
diff --git a/guest/forwarder_guest/src/main.rs b/guest/forwarder_guest/src/main.rs
new file mode 100644
index 0000000..6ebd4ef
--- /dev/null
+++ b/guest/forwarder_guest/src/main.rs
@@ -0,0 +1,123 @@
+// Copyright 2024 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.
+
+// Copied from ChromiumOS with relicensing:
+// src/platform2/vm_tools/chunnel/src/bin/chunnel.rs
+
+//! Guest-side stream socket forwarder
+
+use std::fmt;
+use std::result;
+
+use clap::Parser;
+use forwarder::forwarder::{ForwarderError, ForwarderSession};
+use forwarder::stream::{StreamSocket, StreamSocketError};
+use poll_token_derive::PollToken;
+use vmm_sys_util::poll::{PollContext, PollToken};
+
+#[remain::sorted]
+#[derive(Debug)]
+enum Error {
+ ConnectSocket(StreamSocketError),
+ Forward(ForwarderError),
+ PollContextAdd(vmm_sys_util::errno::Error),
+ PollContextDelete(vmm_sys_util::errno::Error),
+ PollContextNew(vmm_sys_util::errno::Error),
+ PollWait(vmm_sys_util::errno::Error),
+}
+
+type Result<T> = result::Result<T, Error>;
+
+impl fmt::Display for Error {
+ #[remain::check]
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ use self::Error::*;
+
+ #[remain::sorted]
+ match self {
+ ConnectSocket(e) => write!(f, "failed to connect socket: {}", e),
+ Forward(e) => write!(f, "failed to forward traffic: {}", e),
+ PollContextAdd(e) => write!(f, "failed to add fd to poll context: {}", e),
+ PollContextDelete(e) => write!(f, "failed to delete fd from poll context: {}", e),
+ PollContextNew(e) => write!(f, "failed to create poll context: {}", e),
+ PollWait(e) => write!(f, "failed to wait for poll: {}", e),
+ }
+ }
+}
+
+fn run_forwarder(local_stream: StreamSocket, remote_stream: StreamSocket) -> Result<()> {
+ #[derive(PollToken)]
+ enum Token {
+ LocalStreamReadable,
+ RemoteStreamReadable,
+ }
+ let poll_ctx: PollContext<Token> = PollContext::new().map_err(Error::PollContextNew)?;
+ poll_ctx.add(&local_stream, Token::LocalStreamReadable).map_err(Error::PollContextAdd)?;
+ poll_ctx.add(&remote_stream, Token::RemoteStreamReadable).map_err(Error::PollContextAdd)?;
+
+ let mut forwarder = ForwarderSession::new(local_stream, remote_stream);
+
+ loop {
+ let events = poll_ctx.wait().map_err(Error::PollWait)?;
+
+ for event in events.iter_readable() {
+ match event.token() {
+ Token::LocalStreamReadable => {
+ let shutdown = forwarder.forward_from_local().map_err(Error::Forward)?;
+ if shutdown {
+ poll_ctx
+ .delete(forwarder.local_stream())
+ .map_err(Error::PollContextDelete)?;
+ }
+ }
+ Token::RemoteStreamReadable => {
+ let shutdown = forwarder.forward_from_remote().map_err(Error::Forward)?;
+ if shutdown {
+ poll_ctx
+ .delete(forwarder.remote_stream())
+ .map_err(Error::PollContextDelete)?;
+ }
+ }
+ }
+ }
+ if forwarder.is_shut_down() {
+ return Ok(());
+ }
+ }
+}
+
+#[derive(Parser)]
+/// Flags for running command
+pub struct Args {
+ /// Local socket address
+ #[arg(long)]
+ #[arg(alias = "local")]
+ local_sockaddr: String,
+
+ /// Remote socket address
+ #[arg(long)]
+ #[arg(alias = "remote")]
+ remote_sockaddr: String,
+}
+
+// TODO(b/370897694): Support forwarding for datagram socket
+fn main() -> Result<()> {
+ let args = Args::parse();
+
+ let local_stream = StreamSocket::connect(&args.local_sockaddr).map_err(Error::ConnectSocket)?;
+ let remote_stream =
+ StreamSocket::connect(&args.remote_sockaddr).map_err(Error::ConnectSocket)?;
+
+ run_forwarder(local_stream, remote_stream)
+}
diff --git a/guest/forwarder_guest_launcher/Cargo.toml b/guest/forwarder_guest_launcher/Cargo.toml
new file mode 100644
index 0000000..bf0c0ed
--- /dev/null
+++ b/guest/forwarder_guest_launcher/Cargo.toml
@@ -0,0 +1,13 @@
+[package]
+name = "forwarder_guest_launcher"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+clap = { version = "4.5.20", features = ["derive"] }
+prost = "0.13.3"
+tokio = { version = "1.40.0", features = ["rt-multi-thread"] }
+tonic = "0.12.3"
+
+[build-dependencies]
+tonic-build = "0.12.3"
diff --git a/guest/forwarder_guest_launcher/build.rs b/guest/forwarder_guest_launcher/build.rs
new file mode 100644
index 0000000..c923747
--- /dev/null
+++ b/guest/forwarder_guest_launcher/build.rs
@@ -0,0 +1,18 @@
+// Copyright 2024 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.
+
+fn main() -> Result<(), Box<dyn std::error::Error>> {
+ tonic_build::compile_protos("../../libs/debian_service/proto/DebianService.proto")?;
+ Ok(())
+}
diff --git a/guest/forwarder_guest_launcher/src/main.rs b/guest/forwarder_guest_launcher/src/main.rs
new file mode 100644
index 0000000..4042fe5
--- /dev/null
+++ b/guest/forwarder_guest_launcher/src/main.rs
@@ -0,0 +1,50 @@
+// Copyright 2024 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.
+
+//! Launcher of forwarder_guest
+
+use clap::Parser;
+use debian_service::debian_service_client::DebianServiceClient;
+use debian_service::Empty;
+use tonic::transport::Endpoint;
+use tonic::Request;
+
+mod debian_service {
+ tonic::include_proto!("com.android.virtualization.vmlauncher.proto");
+}
+
+#[derive(Parser)]
+/// Flags for running command
+pub struct Args {
+ /// Host IP address
+ #[arg(long)]
+ #[arg(alias = "host")]
+ host_addr: String,
+}
+
+#[tokio::main]
+async fn main() -> Result<(), Box<dyn std::error::Error>> {
+ let args = Args::parse();
+ let addr = format!("https://{}:12000", args.host_addr);
+
+ let channel = Endpoint::from_shared(addr)?.connect().await?;
+ let mut client = DebianServiceClient::new(channel);
+ let mut res_stream =
+ client.open_forwarding_request_queue(Request::new(Empty {})).await?.into_inner();
+
+ while let Some(response) = res_stream.message().await? {
+ println!("Response from the host: {:?}", response);
+ }
+ Ok(())
+}
diff --git a/guest/ip_addr_reporter/.gitignore b/guest/ip_addr_reporter/.gitignore
new file mode 100644
index 0000000..ea8c4bf
--- /dev/null
+++ b/guest/ip_addr_reporter/.gitignore
@@ -0,0 +1 @@
+/target
diff --git a/guest/ip_addr_reporter/Cargo.toml b/guest/ip_addr_reporter/Cargo.toml
new file mode 100644
index 0000000..e255eaf
--- /dev/null
+++ b/guest/ip_addr_reporter/Cargo.toml
@@ -0,0 +1,13 @@
+[package]
+name = "ip_addr_reporter"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+netdev = "0.31.0"
+prost = "0.13.3"
+tokio = { version = "1.40.0", features = ["rt-multi-thread"] }
+tonic = "0.12.3"
+
+[build-dependencies]
+tonic-build = "0.12.3"
diff --git a/guest/ip_addr_reporter/build.rs b/guest/ip_addr_reporter/build.rs
new file mode 100644
index 0000000..e3939d4
--- /dev/null
+++ b/guest/ip_addr_reporter/build.rs
@@ -0,0 +1,7 @@
+fn main() -> Result<(), Box<dyn std::error::Error>> {
+ let proto_file = "../../libs/debian_service/proto/DebianService.proto";
+
+ tonic_build::compile_protos(proto_file).unwrap();
+
+ Ok(())
+}
diff --git a/guest/ip_addr_reporter/src/main.rs b/guest/ip_addr_reporter/src/main.rs
new file mode 100644
index 0000000..5784a83
--- /dev/null
+++ b/guest/ip_addr_reporter/src/main.rs
@@ -0,0 +1,26 @@
+use api::debian_service_client::DebianServiceClient;
+use api::IpAddr;
+
+pub mod api {
+ tonic::include_proto!("com.android.virtualization.vmlauncher.proto");
+}
+
+#[tokio::main]
+async fn main() -> Result<(), String> {
+ let gateway_ip_addr = netdev::get_default_gateway()?.ipv4[0];
+ let ip_addr = netdev::get_default_interface()?.ipv4[0].addr();
+ const PORT: i32 = 12000;
+
+ let server_addr = format!("http://{}:{}", gateway_ip_addr.to_string(), PORT);
+
+ println!("local ip addr: {}", ip_addr.to_string());
+ println!("coonect to grpc server {}", server_addr);
+
+ let mut client = DebianServiceClient::connect(server_addr).await.map_err(|e| e.to_string())?;
+
+ let request = tonic::Request::new(IpAddr { addr: ip_addr.to_string() });
+
+ let response = client.report_vm_ip_addr(request).await.map_err(|e| e.to_string())?;
+ println!("response from server: {:?}", response);
+ Ok(())
+}
diff --git a/guest/microdroid_manager/Android.bp b/guest/microdroid_manager/Android.bp
index 9c9a3d0..1824c20 100644
--- a/guest/microdroid_manager/Android.bp
+++ b/guest/microdroid_manager/Android.bp
@@ -43,7 +43,6 @@
"libmicrodroid_payload_config",
"libmicrodroid_uids",
"libnix",
- "libonce_cell",
"libopenssl",
"libprotobuf",
"librpcbinder_rs",
diff --git a/guest/port_listener/build.sh b/guest/port_listener/build.sh
new file mode 100755
index 0000000..a1d0205
--- /dev/null
+++ b/guest/port_listener/build.sh
@@ -0,0 +1,54 @@
+#!/bin/bash
+
+set -e
+
+check_sudo() {
+ if [ "$EUID" -ne 0 ]; then
+ echo "Please run as root."
+ exit
+ fi
+}
+
+install_prerequisites() {
+ apt update
+ apt install --no-install-recommends --assume-yes \
+ bpftool \
+ clang \
+ libbpf-dev \
+ libgoogle-glog-dev \
+ libstdc++-14-dev
+}
+
+build_port_listener() {
+ cp $(dirname $0)/src/* ${workdir}
+ out_dir=${PWD}
+ pushd ${workdir}
+ bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h
+ clang \
+ -O2 \
+ -Wall \
+ -target bpf \
+ -g \
+ -c listen_tracker.ebpf.c \
+ -o listen_tracker.ebpf.o
+ bpftool gen skeleton listen_tracker.ebpf.o > listen_tracker.skel.h
+ clang++ \
+ -O2 \
+ -Wall \
+ -lbpf \
+ -lglog \
+ -o port_listener \
+ main.cc
+ cp port_listener ${out_dir}
+ popd
+}
+
+clean_up() {
+ rm -rf ${workdir}
+}
+trap clean_up EXIT
+workdir=$(mktemp -d)
+
+check_sudo
+install_prerequisites
+build_port_listener
diff --git a/guest/port_listener/src/common.h b/guest/port_listener/src/common.h
new file mode 100644
index 0000000..d6e507c
--- /dev/null
+++ b/guest/port_listener/src/common.h
@@ -0,0 +1,31 @@
+// Copyright 2024 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.
+
+// Copied from ChromiumOS with relicensing:
+// src/platform2/vm_tools/port_listener/common.h
+
+#ifndef VM_TOOLS_PORT_LISTENER_COMMON_H_
+#define VM_TOOLS_PORT_LISTENER_COMMON_H_
+
+enum State {
+ kPortListenerUp,
+ kPortListenerDown,
+};
+
+struct event {
+ enum State state;
+ uint16_t port;
+};
+
+#endif // VM_TOOLS_PORT_LISTENER_COMMON_H_
diff --git a/guest/port_listener/src/listen_tracker.ebpf.c b/guest/port_listener/src/listen_tracker.ebpf.c
new file mode 100644
index 0000000..9e98aad
--- /dev/null
+++ b/guest/port_listener/src/listen_tracker.ebpf.c
@@ -0,0 +1,80 @@
+// Copyright 2024 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.
+
+// Copied from ChromiumOS with relicensing:
+// src/platform2/vm_tools/port_listener/listen_tracker.ebpf.c
+
+// bpf_helpers.h uses types defined here
+#include <bpf/bpf_helpers.h>
+
+#include "common.h"
+#include "vmlinux.h"
+
+// For some reason 6.1 doesn't include these symbols in the debug build
+// so they don't get included in vmlinux.h. These features have existed since
+// well before 6.1.
+#define BPF_F_NO_PREALLOC (1U << 0)
+#define BPF_ANY 0
+
+struct {
+ __uint(type, BPF_MAP_TYPE_RINGBUF);
+ __uint(max_entries, 1 << 24);
+} events SEC(".maps");
+
+struct {
+ __uint(type, BPF_MAP_TYPE_HASH);
+ __type(key, struct sock*);
+ __type(value, __u8);
+ __uint(max_entries, 65535);
+ __uint(map_flags, BPF_F_NO_PREALLOC);
+} sockmap SEC(".maps");
+
+const __u8 set_value = 0;
+
+SEC("tp/sock/inet_sock_set_state")
+int tracepoint_inet_sock_set_state(struct trace_event_raw_inet_sock_set_state* ctx) {
+ // We don't support anything other than TCP.
+ if (ctx->protocol != IPPROTO_TCP) {
+ return 0;
+ }
+ struct sock* sk = (struct sock*)ctx->skaddr;
+ // If we're transitioning away from LISTEN but we don't know about this
+ // socket yet then don't report anything.
+ if (ctx->oldstate == BPF_TCP_LISTEN && bpf_map_lookup_elem(&sockmap, &sk) == NULL) {
+ return 0;
+ }
+ // If we aren't transitioning to or from TCP_LISTEN then we don't care.
+ if (ctx->newstate != BPF_TCP_LISTEN && ctx->oldstate != BPF_TCP_LISTEN) {
+ return 0;
+ }
+
+ struct event* ev;
+ ev = bpf_ringbuf_reserve(&events, sizeof(*ev), 0);
+ if (!ev) {
+ return 0;
+ }
+ ev->port = ctx->sport;
+
+ if (ctx->newstate == BPF_TCP_LISTEN) {
+ bpf_map_update_elem(&sockmap, &sk, &set_value, BPF_ANY);
+ ev->state = kPortListenerUp;
+ }
+ if (ctx->oldstate == BPF_TCP_LISTEN) {
+ bpf_map_delete_elem(&sockmap, &sk);
+ ev->state = kPortListenerDown;
+ }
+ bpf_ringbuf_submit(ev, 0);
+
+ return 0;
+}
diff --git a/guest/port_listener/src/main.cc b/guest/port_listener/src/main.cc
new file mode 100644
index 0000000..1caceaf
--- /dev/null
+++ b/guest/port_listener/src/main.cc
@@ -0,0 +1,167 @@
+// Copyright 2024 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.
+
+// Copied from ChromiumOS with relicensing:
+// src/platform2/vm_tools/port_listener/main.cc
+
+#include <bpf/libbpf.h>
+#include <bpf/libbpf_legacy.h>
+#include <glog/logging.h>
+#include <linux/vm_sockets.h> // Needs to come after sys/socket.h
+#include <sys/socket.h>
+
+#include <memory>
+#include <unordered_map>
+
+#include "common.h"
+#include "listen_tracker.skel.h"
+
+typedef std::unordered_map<int, int> port_usage_map;
+
+namespace port_listener {
+namespace {
+
+int HandleEvent(void* ctx, void* const data, size_t size) {
+ port_usage_map* map = reinterpret_cast<port_usage_map*>(ctx);
+ const struct event* ev = (struct event*)data;
+
+ switch (ev->state) {
+ case kPortListenerUp:
+ (*map)[ev->port]++;
+ break;
+
+ case kPortListenerDown:
+ if ((*map)[ev->port] > 0) {
+ (*map)[ev->port]--;
+ } else {
+ LOG(INFO) << "Received down event while port count was 0; ignoring";
+ }
+
+ break;
+
+ default:
+ LOG(ERROR) << "Unknown event state " << ev->state;
+ }
+
+ LOG(INFO) << "Listen event: port=" << ev->port << " state=" << ev->state;
+
+ return 0;
+}
+
+typedef std::unique_ptr<struct ring_buffer, decltype(&ring_buffer__free)> ring_buffer_ptr;
+typedef std::unique_ptr<listen_tracker_ebpf, decltype(&listen_tracker_ebpf__destroy)>
+ listen_tracker_ptr;
+
+// BPFProgram tracks the state and resources of the listen_tracker BPF program.
+class BPFProgram {
+public:
+ // Default movable but not copyable.
+ BPFProgram(BPFProgram&& other) = default;
+ BPFProgram(const BPFProgram& other) = delete;
+ BPFProgram& operator=(BPFProgram&& other) = default;
+ BPFProgram& operator=(const BPFProgram& other) = delete;
+
+ // Load loads the listen_tracker BPF program and prepares it for polling. On
+ // error nullptr is returned.
+ static std::unique_ptr<BPFProgram> Load() {
+ auto* skel = listen_tracker_ebpf__open();
+ if (!skel) {
+ PLOG(ERROR) << "Failed to open listen_tracker BPF skeleton";
+ return nullptr;
+ }
+ listen_tracker_ptr skeleton(skel, listen_tracker_ebpf__destroy);
+
+ int err = listen_tracker_ebpf__load(skeleton.get());
+ if (err) {
+ PLOG(ERROR) << "Failed to load listen_tracker BPF program";
+ return nullptr;
+ }
+
+ auto map = std::make_unique<port_usage_map>();
+ auto* rb = ring_buffer__new(bpf_map__fd(skel->maps.events), HandleEvent, map.get(), NULL);
+ if (!rb) {
+ PLOG(ERROR) << "Failed to open ring buffer for listen_tracker";
+ return nullptr;
+ }
+ ring_buffer_ptr ringbuf(rb, ring_buffer__free);
+
+ err = listen_tracker_ebpf__attach(skeleton.get());
+ if (err) {
+ PLOG(ERROR) << "Failed to attach listen_tracker";
+ return nullptr;
+ }
+
+ return std::unique_ptr<BPFProgram>(
+ new BPFProgram(std::move(skeleton), std::move(ringbuf), std::move(map)));
+ }
+
+ // Poll waits for the listen_tracker BPF program to post a new event to the
+ // ring buffer. BPFProgram handles integrating this new event into the
+ // port_usage map and callers should consult port_usage() after Poll returns
+ // for the latest data.
+ const bool Poll() {
+ int err = ring_buffer__poll(rb_.get(), -1);
+ if (err < 0) {
+ LOG(ERROR) << "Error polling ring buffer ret=" << err;
+ return false;
+ }
+
+ return true;
+ }
+
+ const port_usage_map& port_usage() { return *port_usage_; }
+
+private:
+ BPFProgram(listen_tracker_ptr&& skeleton, ring_buffer_ptr&& rb,
+ std::unique_ptr<port_usage_map>&& port_usage)
+ : skeleton_(std::move(skeleton)),
+ rb_(std::move(rb)),
+ port_usage_(std::move(port_usage)) {}
+
+ listen_tracker_ptr skeleton_;
+ ring_buffer_ptr rb_;
+ std::unique_ptr<port_usage_map> port_usage_;
+};
+
+} // namespace
+} // namespace port_listener
+
+int main(int argc, char** argv) {
+ google::InitGoogleLogging(argv[0]);
+ libbpf_set_strict_mode(LIBBPF_STRICT_ALL);
+
+ // Load our BPF program.
+ auto program = port_listener::BPFProgram::Load();
+ if (program == nullptr) {
+ LOG(ERROR) << "Failed to load BPF program";
+ return EXIT_FAILURE;
+ }
+
+ // main loop: poll for listen updates
+ for (;;) {
+ if (!program->Poll()) {
+ LOG(ERROR) << "Failure while polling BPF program";
+ return EXIT_FAILURE;
+ }
+ // port_usage will be updated with the latest usage data
+
+ for (auto it : program->port_usage()) {
+ if (it.second <= 0) {
+ continue;
+ }
+ // TODO(b/340126051): Add listening TCP4 ports.
+ }
+ // TODO(b/340126051): Notify port information to the guest agent.
+ }
+}
diff --git a/guest/pvmfw/Android.bp b/guest/pvmfw/Android.bp
index b502af6..bcd3e42 100644
--- a/guest/pvmfw/Android.bp
+++ b/guest/pvmfw/Android.bp
@@ -19,9 +19,8 @@
"libcstr",
"libdiced_open_dice_nostd",
"libfdtpci",
- "liblibfdt",
+ "liblibfdt_nostd",
"liblog_rust_nostd",
- "libonce_cell_nostd",
"libpvmfw_avb_nostd",
"libpvmfw_embedded_key",
"libpvmfw_fdt_template",
diff --git a/guest/pvmfw/README.md b/guest/pvmfw/README.md
index 58ba10c..3ffa3f0 100644
--- a/guest/pvmfw/README.md
+++ b/guest/pvmfw/README.md
@@ -233,10 +233,10 @@
[header]: src/config.rs
[DTBO]: https://android.googlesource.com/platform/external/dtc/+/refs/heads/main/Documentation/dt-object-internal.txt
-[debug_policy]: ../docs/debug/README.md#debug-policy
-[device_assignment]: ../docs/device_assignment.md
+[debug_policy]: ../../docs/debug/README.md#debug-policy
+[device_assignment]: ../../docs/device_assignment.md
[secretkeeper_key]: https://android.googlesource.com/platform/system/secretkeeper/+/refs/heads/main/README.md#secretkeeper-public-key
-[vendor_hashtree_digest]: ../microdroid/README.md#verification-of-vendor-image
+[vendor_hashtree_digest]: ../../build/microdroid/README.md#verification-of-vendor-image
#### Virtual Platform DICE Chain Handover
@@ -402,7 +402,7 @@
};
```
-[dt.md]: ../docs/device_trees.md#avf_specific-properties-and-nodes
+[dt.md]: ../../docs/device_trees.md#avf_specific-properties-and-nodes
### Guest Image Signing
diff --git a/guest/pvmfw/avb/Android.bp b/guest/pvmfw/avb/Android.bp
index 558152d..f97a713 100644
--- a/guest/pvmfw/avb/Android.bp
+++ b/guest/pvmfw/avb/Android.bp
@@ -43,6 +43,7 @@
":test_image_with_duplicated_capability",
":test_image_with_rollback_index_5",
":test_image_with_multiple_capabilities",
+ ":test_image_with_all_capabilities",
":unsigned_test_image",
],
prefer_rlib: true,
@@ -218,3 +219,17 @@
},
],
}
+
+avb_add_hash_footer {
+ name: "test_image_with_all_capabilities",
+ src: ":unsigned_test_image",
+ partition_name: "boot",
+ private_key: ":pvmfw_sign_key",
+ salt: "4231",
+ props: [
+ {
+ name: "com.android.virt.cap",
+ value: "remote_attest|secretkeeper_protection|supports_uefi_boot",
+ },
+ ],
+}
diff --git a/guest/pvmfw/avb/src/verify.rs b/guest/pvmfw/avb/src/verify.rs
index 038b1d6..bd700ce 100644
--- a/guest/pvmfw/avb/src/verify.rs
+++ b/guest/pvmfw/avb/src/verify.rs
@@ -70,6 +70,11 @@
RemoteAttest,
/// Secretkeeper protected secrets.
SecretkeeperProtection,
+ /// UEFI support for booting guest kernel.
+ SupportsUefiBoot,
+ /// (internal)
+ #[allow(non_camel_case_types)] // TODO: Use mem::variant_count once stable.
+ _VARIANT_COUNT,
}
impl Capability {
@@ -77,6 +82,9 @@
const REMOTE_ATTEST: &'static [u8] = b"remote_attest";
const SECRETKEEPER_PROTECTION: &'static [u8] = b"secretkeeper_protection";
const SEPARATOR: u8 = b'|';
+ const SUPPORTS_UEFI_BOOT: &'static [u8] = b"supports_uefi_boot";
+ /// Number of supported capabilites.
+ pub const COUNT: usize = Self::_VARIANT_COUNT as usize;
/// Returns the capabilities indicated in `descriptor`, or error if the descriptor has
/// unexpected contents.
@@ -91,6 +99,7 @@
let cap = match v {
Self::REMOTE_ATTEST => Self::RemoteAttest,
Self::SECRETKEEPER_PROTECTION => Self::SecretkeeperProtection,
+ Self::SUPPORTS_UEFI_BOOT => Self::SupportsUefiBoot,
_ => return Err(PvmfwVerifyError::UnknownVbmetaProperty),
};
if res.contains(&cap) {
diff --git a/guest/pvmfw/avb/tests/api_test.rs b/guest/pvmfw/avb/tests/api_test.rs
index 8683e69..01c13d4 100644
--- a/guest/pvmfw/avb/tests/api_test.rs
+++ b/guest/pvmfw/avb/tests/api_test.rs
@@ -38,6 +38,7 @@
const TEST_IMG_WITH_INITRD_AND_NON_INITRD_DESC_PATH: &str =
"test_image_with_initrd_and_non_initrd_desc.img";
const TEST_IMG_WITH_MULTIPLE_CAPABILITIES: &str = "test_image_with_multiple_capabilities.img";
+const TEST_IMG_WITH_ALL_CAPABILITIES: &str = "test_image_with_all_capabilities.img";
const UNSIGNED_TEST_IMG_PATH: &str = "unsigned_test.img";
const RANDOM_FOOTER_POS: usize = 30;
@@ -418,3 +419,22 @@
assert!(verified_boot_data.has_capability(Capability::SecretkeeperProtection));
Ok(())
}
+
+#[test]
+fn payload_with_all_capabilities() -> Result<()> {
+ let public_key = load_trusted_public_key()?;
+ let verified_boot_data = verify_payload(
+ &fs::read(TEST_IMG_WITH_ALL_CAPABILITIES)?,
+ /* initrd= */ None,
+ &public_key,
+ )
+ .map_err(|e| anyhow!("Verification failed. Error: {}", e))?;
+
+ assert!(verified_boot_data.has_capability(Capability::RemoteAttest));
+ assert!(verified_boot_data.has_capability(Capability::SecretkeeperProtection));
+ assert!(verified_boot_data.has_capability(Capability::SupportsUefiBoot));
+ // Fail if this test doesn't actually cover all supported capabilities.
+ assert_eq!(Capability::COUNT, 3);
+
+ Ok(())
+}
diff --git a/guest/rialto/Android.bp b/guest/rialto/Android.bp
index 4c18bf9..eeb5b2d 100644
--- a/guest/rialto/Android.bp
+++ b/guest/rialto/Android.bp
@@ -15,7 +15,7 @@
"libcstr",
"libdiced_open_dice_nostd",
"libfdtpci",
- "liblibfdt",
+ "liblibfdt_nostd",
"liblog_rust_nostd",
"libservice_vm_comm_nostd",
"libservice_vm_fake_chain_nostd",
diff --git a/guest/rialto/tests/test.rs b/guest/rialto/tests/test.rs
index 582b69e..7ec5647 100644
--- a/guest/rialto/tests/test.rs
+++ b/guest/rialto/tests/test.rs
@@ -335,6 +335,14 @@
let virtmgr = vmclient::VirtualizationService::new().context("Failed to spawn VirtMgr")?;
let service = virtmgr.connect().context("Failed to connect to VirtMgr")?;
info!("Connected to VirtMgr for service VM");
- VmInstance::create(service.as_ref(), &config, console, /* consoleIn */ None, log, None)
- .context("Failed to create VM")
+ VmInstance::create(
+ service.as_ref(),
+ &config,
+ console,
+ /* consoleIn */ None,
+ log,
+ /* dump_dt */ None,
+ None,
+ )
+ .context("Failed to create VM")
}
diff --git a/guest/vmbase_example/Android.bp b/guest/vmbase_example/Android.bp
index ff7bd83..49a6d69 100644
--- a/guest/vmbase_example/Android.bp
+++ b/guest/vmbase_example/Android.bp
@@ -12,7 +12,7 @@
"libcstr",
"libdiced_open_dice_nostd",
"libfdtpci",
- "liblibfdt",
+ "liblibfdt_nostd",
"liblog_rust_nostd",
"libvirtio_drivers",
"libvmbase",
diff --git a/libs/debian_service/Android.bp b/libs/debian_service/Android.bp
new file mode 100644
index 0000000..0495825
--- /dev/null
+++ b/libs/debian_service/Android.bp
@@ -0,0 +1,56 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+JAVA_LITE_PROTO_CMD = "mkdir -p $(genDir)/gen && " +
+ "$(location aprotoc) --java_opt=annotate_code=false " +
+ "-Iexternal/protobuf/src " +
+ "-Ipackages/modules/Virtualization/libs/debian_service/proto " +
+ "--plugin=protoc-gen-grpc-java=$(location protoc-gen-grpc-java-plugin) " +
+ "--grpc-java_out=lite:$(genDir)/gen $(in) && " +
+ "$(location soong_zip) -o $(out) -C $(genDir)/gen -D $(genDir)/gen"
+
+java_genrule {
+ name: "debian-service-stub-lite",
+ tools: [
+ "aprotoc",
+ "protoc-gen-grpc-java-plugin",
+ "soong_zip",
+ ],
+ cmd: JAVA_LITE_PROTO_CMD,
+ srcs: [
+ "proto/*.proto",
+ ":libprotobuf-internal-protos",
+ ],
+ out: [
+ "protos.srcjar",
+ ],
+}
+
+java_library {
+ name: "debian-service-grpclib-lite",
+ proto: {
+ type: "lite",
+ include_dirs: [
+ "external/protobuf/src",
+ "external/protobuf/java",
+ ],
+ },
+ srcs: [
+ ":debian-service-stub-lite",
+ "proto/*.proto",
+ ":libprotobuf-internal-protos",
+ ],
+ libs: ["javax_annotation-api_1.3.2"],
+ static_libs: [
+ "libprotobuf-java-lite",
+ "grpc-java-core-android",
+ "grpc-java-okhttp-client-lite",
+ "guava",
+ ],
+ sdk_version: "current",
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.virt",
+ ],
+}
diff --git a/libs/debian_service/proto/DebianService.proto b/libs/debian_service/proto/DebianService.proto
new file mode 100644
index 0000000..5e3286a
--- /dev/null
+++ b/libs/debian_service/proto/DebianService.proto
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+
+syntax = "proto3";
+
+package com.android.virtualization.vmlauncher.proto;
+
+option java_package = "com.android.virtualization.vmlauncher.proto";
+option java_multiple_files = true;
+
+service DebianService {
+ rpc ReportVmIpAddr (IpAddr) returns (ReportVmIpAddrResponse) {}
+ rpc OpenForwardingRequestQueue (Empty) returns (stream ForwardingRequestItem) {}
+}
+
+message Empty {}
+
+message IpAddr {
+ string addr = 1;
+}
+
+message ReportVmIpAddrResponse {
+ bool success = 1;
+}
+
+message ForwardingRequestItem {
+ int32 guest_tcp_port = 1;
+ int32 vsock_port = 2;
+}
diff --git a/libs/dice/driver/Android.bp b/libs/dice/driver/Android.bp
index c93bd7d..baed21d 100644
--- a/libs/dice/driver/Android.bp
+++ b/libs/dice/driver/Android.bp
@@ -22,7 +22,6 @@
"liblibc",
"liblog_rust",
"libnix",
- "libonce_cell",
"libopenssl",
"libthiserror",
"libserde_cbor",
diff --git a/libs/fdtpci/Android.bp b/libs/fdtpci/Android.bp
index e12c24f..d7a5da3 100644
--- a/libs/fdtpci/Android.bp
+++ b/libs/fdtpci/Android.bp
@@ -11,7 +11,7 @@
defaults: ["avf_build_flags_rust"],
srcs: ["src/lib.rs"],
rustlibs: [
- "liblibfdt",
+ "liblibfdt_nostd",
"liblog_rust_nostd",
"libvirtio_drivers",
],
diff --git a/libs/framework-virtualization/README.md b/libs/framework-virtualization/README.md
index 0dd7e64..a742ccc 100644
--- a/libs/framework-virtualization/README.md
+++ b/libs/framework-virtualization/README.md
@@ -1,12 +1,12 @@
# Android Virtualization Framework API
These Java APIs allow an app to configure and run a Virtual Machine running
-[Microdroid](../build/microdroid/README.md) and execute native code from the app (the
+[Microdroid](../../build/microdroid/README.md) and execute native code from the app (the
payload) within it.
-There is more information on AVF [here](../README.md). To see how to package the
+There is more information on AVF [here](../../README.md). To see how to package the
payload code that is to run inside a VM, and the native API available to it, see
-the [VM Payload API](../libs/libvm_payload/README.md)
+the [VM Payload API](../libvm_payload/README.md)
The API classes are all in the
[`android.system.virtualmachine`](src/android/system/virtualmachine) package.
diff --git a/libs/framework-virtualization/src/android/system/virtualmachine/VirtualMachine.java b/libs/framework-virtualization/src/android/system/virtualmachine/VirtualMachine.java
index 3b16a8a..b278610 100644
--- a/libs/framework-virtualization/src/android/system/virtualmachine/VirtualMachine.java
+++ b/libs/framework-virtualization/src/android/system/virtualmachine/VirtualMachine.java
@@ -1578,7 +1578,8 @@
: createVirtualMachineConfigForAppFrom(vmConfig, service);
mVirtualMachine =
- service.createVm(vmConfigParcel, consoleOutFd, consoleInFd, mLogWriter);
+ service.createVm(
+ vmConfigParcel, consoleOutFd, consoleInFd, mLogWriter, null);
mVirtualMachine.registerCallback(new CallbackTranslator(service));
if (mMemoryManagementCallbacks != null) {
mContext.registerComponentCallbacks(mMemoryManagementCallbacks);
diff --git a/libs/libcompos_common/compos_client.rs b/libs/libcompos_common/compos_client.rs
index 107f8d0..316eaa9 100644
--- a/libs/libcompos_common/compos_client.rs
+++ b/libs/libcompos_common/compos_client.rs
@@ -152,6 +152,7 @@
console_fd,
/* console_in_fd */ None,
log_fd,
+ /* dump_dt */ None,
Some(callback),
)
.context("Failed to create VM")?;
diff --git a/libs/libfdt/Android.bp b/libs/libfdt/Android.bp
index b2e7b2b..09f288d 100644
--- a/libs/libfdt/Android.bp
+++ b/libs/libfdt/Android.bp
@@ -19,14 +19,14 @@
dylib: {
enabled: false,
},
- static_libs: [
+ header_libs: [
"libfdt",
],
apex_available: ["com.android.virt"],
}
-rust_library_rlib {
- name: "liblibfdt",
+rust_defaults {
+ name: "liblibfdt_defaults",
crate_name: "libfdt",
defaults: ["avf_build_flags_rust"],
srcs: [
@@ -34,23 +34,36 @@
":liblibfdt_bindgen",
],
edition: "2021",
- no_stdlibs: true,
- prefer_rlib: true,
- stdlibs: [
- "libcore.rust_sysroot",
- ],
rustlibs: [
"libcstr",
"liblibfdt_bindgen",
"libstatic_assertions",
"libzerocopy_nostd",
],
+}
+
+rust_library_rlib {
+ name: "liblibfdt",
+ defaults: ["liblibfdt_defaults"],
whole_static_libs: [
"libfdt",
],
apex_available: ["com.android.virt"],
}
+rust_library_rlib {
+ name: "liblibfdt_nostd",
+ defaults: ["liblibfdt_defaults"],
+ no_stdlibs: true,
+ prefer_rlib: true,
+ stdlibs: [
+ "libcore.rust_sysroot",
+ ],
+ whole_static_libs: [
+ "libfdt_baremetal",
+ ],
+}
+
rust_test {
name: "liblibfdt.integration_test",
crate_name: "libfdt_test",
diff --git a/libs/libforwarder/Android.bp b/libs/libforwarder/Android.bp
new file mode 100644
index 0000000..48307e7
--- /dev/null
+++ b/libs/libforwarder/Android.bp
@@ -0,0 +1,15 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_library {
+ name: "libforwarder",
+ crate_name: "forwarder",
+ edition: "2021",
+ srcs: ["src/lib.rs"],
+ rustlibs: [
+ "liblibc",
+ "libvsock",
+ ],
+ proc_macros: ["libremain"],
+}
diff --git a/libs/libforwarder/Cargo.toml b/libs/libforwarder/Cargo.toml
new file mode 100644
index 0000000..9f3f341
--- /dev/null
+++ b/libs/libforwarder/Cargo.toml
@@ -0,0 +1,9 @@
+[package]
+name = "forwarder"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+libc = "0.2.159"
+remain = "0.2.14"
+vsock = "0.5.1"
diff --git a/libs/libforwarder/src/forwarder.rs b/libs/libforwarder/src/forwarder.rs
new file mode 100644
index 0000000..3600ab2
--- /dev/null
+++ b/libs/libforwarder/src/forwarder.rs
@@ -0,0 +1,170 @@
+// Copyright 2024 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.
+
+// Copied from ChromiumOS with relicensing:
+// src/platform2/vm_tools/chunnel/src/forwarder.rs
+
+//! This module contains forwarding mechanism between stream sockets.
+
+use std::fmt;
+use std::io::{self, Read, Write};
+use std::result;
+
+use crate::stream::StreamSocket;
+
+// This was picked arbitrarily. crosvm doesn't yet use VIRTIO_NET_F_MTU, so there's no reason to
+// opt for massive 65535 byte frames.
+const MAX_FRAME_SIZE: usize = 8192;
+
+/// Errors that can be encountered by a ForwarderSession.
+#[remain::sorted]
+#[derive(Debug)]
+pub enum ForwarderError {
+ /// An io::Error was encountered while reading from a stream.
+ ReadFromStream(io::Error),
+ /// An io::Error was encountered while shutting down writes on a stream.
+ ShutDownStream(io::Error),
+ /// An io::Error was encountered while writing to a stream.
+ WriteToStream(io::Error),
+}
+
+type Result<T> = result::Result<T, ForwarderError>;
+
+impl fmt::Display for ForwarderError {
+ #[remain::check]
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ use self::ForwarderError::*;
+
+ #[remain::sorted]
+ match self {
+ ReadFromStream(e) => write!(f, "failed to read from stream: {}", e),
+ ShutDownStream(e) => write!(f, "failed to shut down stream: {}", e),
+ WriteToStream(e) => write!(f, "failed to write to stream: {}", e),
+ }
+ }
+}
+
+/// A ForwarderSession owns stream sockets that it forwards traffic between.
+pub struct ForwarderSession {
+ local: StreamSocket,
+ remote: StreamSocket,
+}
+
+fn forward(from_stream: &mut StreamSocket, to_stream: &mut StreamSocket) -> Result<bool> {
+ let mut buf = [0u8; MAX_FRAME_SIZE];
+
+ let count = from_stream.read(&mut buf).map_err(ForwarderError::ReadFromStream)?;
+ if count == 0 {
+ to_stream.shut_down_write().map_err(ForwarderError::ShutDownStream)?;
+ return Ok(true);
+ }
+
+ to_stream.write_all(&buf[..count]).map_err(ForwarderError::WriteToStream)?;
+ Ok(false)
+}
+
+impl ForwarderSession {
+ /// Creates a forwarder session from a local and remote stream socket.
+ pub fn new(local: StreamSocket, remote: StreamSocket) -> Self {
+ ForwarderSession { local, remote }
+ }
+
+ /// Forwards traffic from the local socket to the remote socket.
+ /// Returns true if the local socket has reached EOF and the
+ /// remote socket has been shut down for further writes.
+ pub fn forward_from_local(&mut self) -> Result<bool> {
+ forward(&mut self.local, &mut self.remote)
+ }
+
+ /// Forwards traffic from the remote socket to the local socket.
+ /// Returns true if the remote socket has reached EOF and the
+ /// local socket has been shut down for further writes.
+ pub fn forward_from_remote(&mut self) -> Result<bool> {
+ forward(&mut self.remote, &mut self.local)
+ }
+
+ /// Returns a reference to the local stream socket.
+ pub fn local_stream(&self) -> &StreamSocket {
+ &self.local
+ }
+
+ /// Returns a reference to the remote stream socket.
+ pub fn remote_stream(&self) -> &StreamSocket {
+ &self.remote
+ }
+
+ /// Returns true if both sockets are completely shut down and the session can be dropped.
+ pub fn is_shut_down(&self) -> bool {
+ self.local.is_shut_down() && self.remote.is_shut_down()
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use std::io::{Read, Write};
+ use std::net::Shutdown;
+ use std::os::unix::net::UnixStream;
+
+ #[test]
+ fn forward_unix() {
+ // Local streams.
+ let (mut london, folkestone) = UnixStream::pair().unwrap();
+ // Remote streams.
+ let (coquelles, mut paris) = UnixStream::pair().unwrap();
+
+ // Connect the local and remote sockets via the chunnel.
+ let mut forwarder = ForwarderSession::new(folkestone.into(), coquelles.into());
+
+ // Put some traffic in from London.
+ let greeting = b"hello";
+ london.write_all(greeting).unwrap();
+
+ // Expect forwarding from the local end not to have reached EOF.
+ assert!(!forwarder.forward_from_local().unwrap());
+ let mut salutation = [0u8; 8];
+ let count = paris.read(&mut salutation).unwrap();
+ assert_eq!(greeting.len(), count);
+ assert_eq!(greeting, &salutation[..count]);
+
+ // Shut the local socket down. The forwarder should detect this and perform a shutdown,
+ // which will manifest as an EOF when reading.
+ london.shutdown(Shutdown::Write).unwrap();
+ assert!(forwarder.forward_from_local().unwrap());
+ assert_eq!(paris.read(&mut salutation).unwrap(), 0);
+
+ // Don't consider the forwarder shut down until both ends are.
+ assert!(!forwarder.is_shut_down());
+
+ // Forward traffic from the remote end.
+ let salutation = b"bonjour";
+ paris.write_all(salutation).unwrap();
+
+ // Expect forwarding from the remote end not to have reached EOF.
+ assert!(!forwarder.forward_from_remote().unwrap());
+ let mut greeting = [0u8; 8];
+ let count = london.read(&mut greeting).unwrap();
+ assert_eq!(salutation.len(), count);
+ assert_eq!(salutation, &greeting[..count]);
+
+ // Shut the remote socket down. The forwarder should detect this and perform a shutdown,
+ // which will manifest as an EOF when reading.
+ paris.shutdown(Shutdown::Write).unwrap();
+ assert!(forwarder.forward_from_remote().unwrap());
+ assert_eq!(london.read(&mut greeting).unwrap(), 0);
+
+ // The forwarder should now be considered shut down.
+ assert!(forwarder.is_shut_down());
+ }
+}
diff --git a/libs/libforwarder/src/lib.rs b/libs/libforwarder/src/lib.rs
new file mode 100644
index 0000000..bcce689
--- /dev/null
+++ b/libs/libforwarder/src/lib.rs
@@ -0,0 +1,21 @@
+// Copyright 2024 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.
+
+// Copied from ChromiumOS with relicensing:
+// src/platform2/vm_tools/chunnel/src/lib.rs
+
+//! Library for stream socket forwarding.
+
+pub mod forwarder;
+pub mod stream;
diff --git a/libs/libforwarder/src/stream.rs b/libs/libforwarder/src/stream.rs
new file mode 100644
index 0000000..d8c7f51
--- /dev/null
+++ b/libs/libforwarder/src/stream.rs
@@ -0,0 +1,263 @@
+// Copyright 2024 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.
+
+// Copied from ChromiumOS with relicensing:
+// src/platform2/vm_tools/chunnel/src/stream.rs
+
+//! This module provides abstraction of various stream socket type.
+
+use std::fmt;
+use std::io;
+use std::net::TcpStream;
+use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd};
+use std::os::unix::net::UnixStream;
+use std::result;
+
+use libc::{self, c_void, shutdown, EPIPE, SHUT_WR};
+use vsock::VsockAddr;
+use vsock::VsockStream;
+
+/// Parse a vsock SocketAddr from a string. vsock socket addresses are of the form
+/// "vsock:cid:port".
+pub fn parse_vsock_addr(addr: &str) -> result::Result<VsockAddr, io::Error> {
+ let components: Vec<&str> = addr.split(':').collect();
+ if components.len() != 3 || components[0] != "vsock" {
+ return Err(io::Error::from_raw_os_error(libc::EINVAL));
+ }
+
+ Ok(VsockAddr::new(
+ components[1].parse().map_err(|_| io::Error::from_raw_os_error(libc::EINVAL))?,
+ components[2].parse().map_err(|_| io::Error::from_raw_os_error(libc::EINVAL))?,
+ ))
+}
+
+/// StreamSocket provides a generic abstraction around any connection-oriented stream socket.
+/// The socket will be closed when StreamSocket is dropped, but writes to the socket can also
+/// be shut down manually.
+pub struct StreamSocket {
+ fd: RawFd,
+ shut_down: bool,
+}
+
+impl StreamSocket {
+ /// Connects to the given socket address. Supported socket types are vsock, unix, and TCP.
+ pub fn connect(sockaddr: &str) -> result::Result<StreamSocket, StreamSocketError> {
+ const UNIX_PREFIX: &str = "unix:";
+ const VSOCK_PREFIX: &str = "vsock:";
+
+ if sockaddr.starts_with(VSOCK_PREFIX) {
+ let addr = parse_vsock_addr(sockaddr)
+ .map_err(|e| StreamSocketError::ConnectVsock(sockaddr.to_string(), e))?;
+ let vsock_stream = VsockStream::connect(&addr)
+ .map_err(|e| StreamSocketError::ConnectVsock(sockaddr.to_string(), e))?;
+ Ok(vsock_stream.into())
+ } else if sockaddr.starts_with(UNIX_PREFIX) {
+ let (_prefix, sock_path) = sockaddr.split_at(UNIX_PREFIX.len());
+ let unix_stream = UnixStream::connect(sock_path)
+ .map_err(|e| StreamSocketError::ConnectUnix(sockaddr.to_string(), e))?;
+ Ok(unix_stream.into())
+ } else {
+ // Assume this is a TCP stream.
+ let tcp_stream = TcpStream::connect(sockaddr)
+ .map_err(|e| StreamSocketError::ConnectTcp(sockaddr.to_string(), e))?;
+ Ok(tcp_stream.into())
+ }
+ }
+
+ /// Shuts down writes to the socket using shutdown(2).
+ pub fn shut_down_write(&mut self) -> io::Result<()> {
+ // SAFETY:
+ // Safe because no memory is modified and the return value is checked.
+ let ret = unsafe { shutdown(self.fd, SHUT_WR) };
+ if ret < 0 {
+ return Err(io::Error::last_os_error());
+ }
+
+ self.shut_down = true;
+ Ok(())
+ }
+
+ /// Returns true if the socket has been shut down for writes, false otherwise.
+ pub fn is_shut_down(&self) -> bool {
+ self.shut_down
+ }
+}
+
+impl io::Read for StreamSocket {
+ fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
+ // SAFETY:
+ // Safe because this will only modify the contents of |buf| and we check the return value.
+ let ret = unsafe { libc::read(self.fd, buf.as_mut_ptr() as *mut c_void, buf.len()) };
+ if ret < 0 {
+ return Err(io::Error::last_os_error());
+ }
+
+ Ok(ret as usize)
+ }
+}
+
+impl io::Write for StreamSocket {
+ fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+ // SAFETY:
+ // Safe because this doesn't modify any memory and we check the return value.
+ let ret = unsafe { libc::write(self.fd, buf.as_ptr() as *const c_void, buf.len()) };
+ if ret < 0 {
+ // If a write causes EPIPE then the socket is shut down for writes.
+ let err = io::Error::last_os_error();
+ if let Some(errno) = err.raw_os_error() {
+ if errno == EPIPE {
+ self.shut_down = true
+ }
+ }
+
+ return Err(err);
+ }
+
+ Ok(ret as usize)
+ }
+
+ fn flush(&mut self) -> io::Result<()> {
+ // No buffered data so nothing to do.
+ Ok(())
+ }
+}
+
+impl AsRawFd for StreamSocket {
+ fn as_raw_fd(&self) -> RawFd {
+ self.fd
+ }
+}
+
+impl From<TcpStream> for StreamSocket {
+ fn from(stream: TcpStream) -> Self {
+ StreamSocket { fd: stream.into_raw_fd(), shut_down: false }
+ }
+}
+
+impl From<UnixStream> for StreamSocket {
+ fn from(stream: UnixStream) -> Self {
+ StreamSocket { fd: stream.into_raw_fd(), shut_down: false }
+ }
+}
+
+impl From<VsockStream> for StreamSocket {
+ fn from(stream: VsockStream) -> Self {
+ StreamSocket { fd: stream.into_raw_fd(), shut_down: false }
+ }
+}
+
+impl FromRawFd for StreamSocket {
+ unsafe fn from_raw_fd(fd: RawFd) -> Self {
+ StreamSocket { fd, shut_down: false }
+ }
+}
+
+impl Drop for StreamSocket {
+ fn drop(&mut self) {
+ // SAFETY:
+ // Safe because this doesn't modify any memory and we are the only
+ // owner of the file descriptor.
+ unsafe { libc::close(self.fd) };
+ }
+}
+
+/// Error enums for StreamSocket.
+#[remain::sorted]
+#[derive(Debug)]
+pub enum StreamSocketError {
+ /// Error on connecting TCP socket.
+ ConnectTcp(String, io::Error),
+ /// Error on connecting unix socket.
+ ConnectUnix(String, io::Error),
+ /// Error on connecting vsock socket.
+ ConnectVsock(String, io::Error),
+}
+
+impl fmt::Display for StreamSocketError {
+ #[remain::check]
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ use self::StreamSocketError::*;
+
+ #[remain::sorted]
+ match self {
+ ConnectTcp(sockaddr, e) => {
+ write!(f, "failed to connect to TCP sockaddr {}: {}", sockaddr, e)
+ }
+ ConnectUnix(sockaddr, e) => {
+ write!(f, "failed to connect to unix sockaddr {}: {}", sockaddr, e)
+ }
+ ConnectVsock(sockaddr, e) => {
+ write!(f, "failed to connect to vsock sockaddr {}: {}", sockaddr, e)
+ }
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use std::io::{Read, Write};
+ use std::net::TcpListener;
+ use std::os::unix::net::{UnixListener, UnixStream};
+ use tempfile::TempDir;
+
+ #[test]
+ fn sock_connect_tcp() {
+ let listener = TcpListener::bind("127.0.0.1:0").unwrap();
+ let sockaddr = format!("127.0.0.1:{}", listener.local_addr().unwrap().port());
+
+ let _stream = StreamSocket::connect(&sockaddr).unwrap();
+ }
+
+ #[test]
+ fn sock_connect_unix() {
+ let tempdir = TempDir::new().unwrap();
+ let path = tempdir.path().to_owned().join("test.sock");
+ let _listener = UnixListener::bind(&path).unwrap();
+
+ let unix_addr = format!("unix:{}", path.to_str().unwrap());
+ let _stream = StreamSocket::connect(&unix_addr).unwrap();
+ }
+
+ #[test]
+ fn invalid_sockaddr() {
+ assert!(StreamSocket::connect("this is not a valid sockaddr").is_err());
+ }
+
+ #[test]
+ fn shut_down_write() {
+ let (unix_stream, _dummy) = UnixStream::pair().unwrap();
+ let mut stream: StreamSocket = unix_stream.into();
+
+ stream.write_all(b"hello").unwrap();
+
+ stream.shut_down_write().unwrap();
+
+ assert!(stream.is_shut_down());
+ assert!(stream.write(b"goodbye").is_err());
+ }
+
+ #[test]
+ fn read_from_shut_down_sock() {
+ let (unix_stream1, unix_stream2) = UnixStream::pair().unwrap();
+ let mut stream1: StreamSocket = unix_stream1.into();
+ let mut stream2: StreamSocket = unix_stream2.into();
+
+ stream1.shut_down_write().unwrap();
+
+ // Reads from the other end of the socket should now return EOF.
+ let mut buf = Vec::new();
+ assert_eq!(stream2.read_to_end(&mut buf).unwrap(), 0);
+ }
+}
diff --git a/libs/libservice_vm_manager/src/lib.rs b/libs/libservice_vm_manager/src/lib.rs
index d7b4dd6..0f322bb 100644
--- a/libs/libservice_vm_manager/src/lib.rs
+++ b/libs/libservice_vm_manager/src/lib.rs
@@ -244,8 +244,9 @@
let console_out = Some(android_log_fd()?);
let console_in = None;
let log = Some(android_log_fd()?);
+ let dump_dt = None;
let callback = None;
- VmInstance::create(service.as_ref(), &config, console_out, console_in, log, callback)
+ VmInstance::create(service.as_ref(), &config, console_out, console_in, log, dump_dt, callback)
.context("Failed to create service VM")
}
diff --git a/libs/libservice_vm_requests/src/rkp.rs b/libs/libservice_vm_requests/src/rkp.rs
index e2be11b..7de7cd5 100644
--- a/libs/libservice_vm_requests/src/rkp.rs
+++ b/libs/libservice_vm_requests/src/rkp.rs
@@ -63,8 +63,7 @@
const CSR_PAYLOAD_SCHEMA_V3: u8 = 3;
const AUTH_REQ_SCHEMA_V1: u8 = 1;
-// TODO(b/300624493): Add a new certificate type for AVF CSR.
-const CERTIFICATE_TYPE: &str = "keymint";
+const CERTIFICATE_TYPE: &str = "rkp-vm";
/// Builds the CSR described in:
///
diff --git a/libs/libvm_payload/README.md b/libs/libvm_payload/README.md
index 8ef1bac..bbcfc61 100644
--- a/libs/libvm_payload/README.md
+++ b/libs/libvm_payload/README.md
@@ -9,7 +9,7 @@
available in the VM, and only 64 bit code is supported.
To create a VM and run the payload from Android see the [AVF Java
-APIs](../libs/framework-virtualization/README.md).
+APIs](../framework-virtualization/README.md).
## Entry point
diff --git a/libs/libvmbase/Android.bp b/libs/libvmbase/Android.bp
index ee12e85..e634c18 100644
--- a/libs/libvmbase/Android.bp
+++ b/libs/libvmbase/Android.bp
@@ -81,7 +81,7 @@
"libbuddy_system_allocator",
"libcstr",
"libfdtpci",
- "liblibfdt",
+ "liblibfdt_nostd",
"liblog_rust_nostd",
"libonce_cell_nostd",
"libsmccc",
diff --git a/libs/libvmclient/src/lib.rs b/libs/libvmclient/src/lib.rs
index ce7d5a5..13630c0 100644
--- a/libs/libvmclient/src/lib.rs
+++ b/libs/libvmclient/src/lib.rs
@@ -208,14 +208,21 @@
console_out: Option<File>,
console_in: Option<File>,
log: Option<File>,
+ dump_dt: Option<File>,
callback: Option<Box<dyn VmCallback + Send + Sync>>,
) -> BinderResult<Self> {
let console_out = console_out.map(ParcelFileDescriptor::new);
let console_in = console_in.map(ParcelFileDescriptor::new);
let log = log.map(ParcelFileDescriptor::new);
+ let dump_dt = dump_dt.map(ParcelFileDescriptor::new);
- let vm =
- service.createVm(config, console_out.as_ref(), console_in.as_ref(), log.as_ref())?;
+ let vm = service.createVm(
+ config,
+ console_out.as_ref(),
+ console_in.as_ref(),
+ log.as_ref(),
+ dump_dt.as_ref(),
+ )?;
let cid = vm.getCid()?;
diff --git a/libs/service-compos/Android.bp b/libs/service-compos/Android.bp
index 3dcf8be..053680c 100644
--- a/libs/service-compos/Android.bp
+++ b/libs/service-compos/Android.bp
@@ -37,4 +37,7 @@
libs: ["services"],
sdk_version: "",
installable: true,
+
+ // The dexpreopt artifacts of service-compos will be installed in /system_ext
+ system_ext_specific: true,
}
diff --git a/libs/service-compos/java/com/android/server/compos/IsolatedCompilationService.java b/libs/service-compos/java/com/android/server/compos/IsolatedCompilationService.java
index 95e365d..ab8a4cf 100644
--- a/libs/service-compos/java/com/android/server/compos/IsolatedCompilationService.java
+++ b/libs/service-compos/java/com/android/server/compos/IsolatedCompilationService.java
@@ -104,7 +104,7 @@
packageNative.registerStagedApexObserver(observer);
// In the unlikely event that an APEX has been staged before we get here, we may
// have to schedule compilation immediately.
- observer.checkModules(packageNative.getStagedApexModuleNames());
+ observer.checkModules(packageNative.getStagedApexInfos());
} catch (RemoteException e) {
Log.e(TAG, "Failed to initialize observer", e);
}
@@ -118,26 +118,21 @@
@Override
public void onApexStaged(ApexStagedEvent event) {
Log.d(TAG, "onApexStaged");
- checkModules(event.stagedApexModuleNames);
+ checkModules(event.stagedApexInfos);
}
- void checkModules(String[] moduleNames) {
+ void checkModules(StagedApexInfo[] stagedApexInfos) {
if (IsolatedCompilationJobService.isStagedApexJobScheduled(mScheduler)) {
Log.d(TAG, "Job already scheduled");
// We're going to run anyway, we don't need to check this update
return;
}
boolean needCompilation = false;
- for (String moduleName : moduleNames) {
- try {
- StagedApexInfo apexInfo = mPackageNative.getStagedApexInfo(moduleName);
- if (apexInfo != null && apexInfo.hasClassPathJars) {
- Log.i(TAG, "Classpath affecting module updated: " + moduleName);
- needCompilation = true;
- break;
- }
- } catch (RemoteException e) {
- Log.w(TAG, "Failed to get getStagedApexInfo for " + moduleName);
+ for (StagedApexInfo apexInfo : stagedApexInfos) {
+ if (apexInfo != null && apexInfo.hasClassPathJars) {
+ Log.i(TAG, "Classpath affecting module updated: " + apexInfo.moduleName);
+ needCompilation = true;
+ break;
}
}
if (needCompilation) {
diff --git a/libs/service-virtualization/src/com/android/system/virtualmachine/VirtualizationSystemService.java b/libs/service-virtualization/src/com/android/system/virtualmachine/VirtualizationSystemService.java
index 241eef4..998389b 100644
--- a/libs/service-virtualization/src/com/android/system/virtualmachine/VirtualizationSystemService.java
+++ b/libs/service-virtualization/src/com/android/system/virtualmachine/VirtualizationSystemService.java
@@ -21,6 +21,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.net.LinkAddress;
import android.net.TetheringManager;
import android.net.TetheringManager.StartTetheringCallback;
import android.net.TetheringManager.TetheringRequest;
@@ -157,8 +158,11 @@
@Override
public void enableVmTethering() {
+ LinkAddress local = new LinkAddress("192.168.0.1/24");
+ LinkAddress client = new LinkAddress("192.168.0.2/24");
final TetheringRequest tr =
new TetheringRequest.Builder(TetheringManager.TETHERING_VIRTUAL)
+ .setStaticIpv4Addresses(local, client)
.setConnectivityScope(TetheringManager.CONNECTIVITY_SCOPE_GLOBAL)
.build();
diff --git a/libs/vm_launcher_lib/Android.bp b/libs/vm_launcher_lib/Android.bp
index cb6fc9e..5267508 100644
--- a/libs/vm_launcher_lib/Android.bp
+++ b/libs/vm_launcher_lib/Android.bp
@@ -12,6 +12,7 @@
platform_apis: true,
static_libs: [
"gson",
+ "debian-service-grpclib-lite",
],
libs: [
"framework-virtualization.impl",
diff --git a/libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/DebianServiceImpl.java b/libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/DebianServiceImpl.java
new file mode 100644
index 0000000..ccc0ed6
--- /dev/null
+++ b/libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/DebianServiceImpl.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2024 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.virtualization.vmlauncher;
+
+import android.util.Log;
+
+import com.android.virtualization.vmlauncher.proto.DebianServiceGrpc;
+import com.android.virtualization.vmlauncher.proto.Empty;
+import com.android.virtualization.vmlauncher.proto.ForwardingRequestItem;
+import com.android.virtualization.vmlauncher.proto.IpAddr;
+import com.android.virtualization.vmlauncher.proto.ReportVmIpAddrResponse;
+
+import io.grpc.stub.StreamObserver;
+
+class DebianServiceImpl extends DebianServiceGrpc.DebianServiceImplBase {
+ public static final String TAG = "DebianService";
+ private final DebianServiceCallback mCallback;
+
+ protected DebianServiceImpl(DebianServiceCallback callback) {
+ super();
+ mCallback = callback;
+ }
+
+ @Override
+ public void reportVmIpAddr(
+ IpAddr request, StreamObserver<ReportVmIpAddrResponse> responseObserver) {
+ Log.d(DebianServiceImpl.TAG, "reportVmIpAddr: " + request.toString());
+ mCallback.onIpAddressAvailable(request.getAddr());
+ ReportVmIpAddrResponse reply = ReportVmIpAddrResponse.newBuilder().setSuccess(true).build();
+ responseObserver.onNext(reply);
+ responseObserver.onCompleted();
+ }
+
+ @Override
+ public void openForwardingRequestQueue(
+ Empty request, StreamObserver<ForwardingRequestItem> responseObserver) {
+ Log.d(DebianServiceImpl.TAG, "OpenForwardingRequestQueue");
+
+ // TODO(b/340126051): Bring information from forwarder_host.
+
+ responseObserver.onCompleted();
+ }
+
+ protected interface DebianServiceCallback {
+ void onIpAddressAvailable(String ipAddr);
+ }
+}
diff --git a/libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/VmLauncherService.java b/libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/VmLauncherService.java
index 5e78f99..3d5c345 100644
--- a/libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/VmLauncherService.java
+++ b/libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/VmLauncherService.java
@@ -22,25 +22,23 @@
import android.app.Service;
import android.content.Intent;
import android.os.Bundle;
-import android.os.Handler;
import android.os.IBinder;
-import android.os.Looper;
-import android.os.ParcelFileDescriptor;
import android.os.ResultReceiver;
import android.system.virtualmachine.VirtualMachine;
import android.system.virtualmachine.VirtualMachineConfig;
import android.system.virtualmachine.VirtualMachineException;
import android.util.Log;
-import java.io.BufferedReader;
-import java.io.FileInputStream;
+import io.grpc.InsecureServerCredentials;
+import io.grpc.Server;
+import io.grpc.okhttp.OkHttpServerBuilder;
+
import java.io.IOException;
-import java.io.InputStreamReader;
import java.nio.file.Path;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
-public class VmLauncherService extends Service {
+public class VmLauncherService extends Service implements DebianServiceImpl.DebianServiceCallback {
private static final String TAG = "VmLauncherService";
// TODO: this path should be from outside of this service
private static final String VM_CONFIG_PATH = "/data/local/tmp/vm_config.json";
@@ -54,6 +52,7 @@
private ExecutorService mExecutorService;
private VirtualMachine mVirtualMachine;
private ResultReceiver mResultReceiver;
+ private Server mServer;
@Override
public IBinder onBind(Intent intent) {
@@ -87,7 +86,10 @@
Runner runner;
try {
+ android.os.Trace.beginSection("vmCreate");
runner = Runner.create(this, config);
+ android.os.Trace.endSection();
+ android.os.Trace.beginAsyncSection("debianBoot", 0);
} catch (VirtualMachineException e) {
Log.e(TAG, "cannot create runner", e);
stopSelf();
@@ -113,10 +115,9 @@
startForeground();
mResultReceiver.send(RESULT_START, null);
- if (config.getCustomImageConfig().useNetwork()) {
- Handler handler = new Handler(Looper.getMainLooper());
- gatherIpAddrFromVm(handler);
- }
+
+ startDebianServer();
+
return START_NOT_STICKY;
}
@@ -134,6 +135,7 @@
mExecutorService = null;
mVirtualMachine = null;
}
+ stopDebianServer();
}
private boolean isVmRunning() {
@@ -141,34 +143,38 @@
&& mVirtualMachine.getStatus() == VirtualMachine.STATUS_RUNNING;
}
- // TODO(b/359523803): Use AVF API to get ip addr when it exists
- private void gatherIpAddrFromVm(Handler handler) {
- handler.postDelayed(
- () -> {
- if (!isVmRunning()) {
- Log.d(TAG, "A virtual machine instance isn't running");
- return;
- }
- int INTERNAL_VSOCK_SERVER_PORT = 1024;
- try (ParcelFileDescriptor pfd =
- mVirtualMachine.connectVsock(INTERNAL_VSOCK_SERVER_PORT)) {
- try (BufferedReader input =
- new BufferedReader(
- new InputStreamReader(
- new FileInputStream(pfd.getFileDescriptor())))) {
- String vmIpAddr = input.readLine().strip();
- Bundle b = new Bundle();
- b.putString(KEY_VM_IP_ADDR, vmIpAddr);
- mResultReceiver.send(RESULT_IPADDR, b);
- return;
- } catch (IOException e) {
- Log.e(TAG, e.toString());
- }
- } catch (Exception e) {
- Log.e(TAG, e.toString());
- }
- gatherIpAddrFromVm(handler);
- },
- 1000);
+ private void startDebianServer() {
+ new Thread(
+ () -> {
+ // TODO(b/372666638): gRPC for java doesn't support vsock for now.
+ // In addition, let's consider using a dynamic port and SSL(and client
+ // certificate)
+ int port = 12000;
+ try {
+ mServer =
+ OkHttpServerBuilder.forPort(
+ port, InsecureServerCredentials.create())
+ .addService(new DebianServiceImpl(this))
+ .build()
+ .start();
+ } catch (IOException e) {
+ Log.d(TAG, "grpc server error", e);
+ }
+ })
+ .start();
+ }
+
+ private void stopDebianServer() {
+ if (mServer != null) {
+ mServer.shutdown();
+ }
+ }
+
+ @Override
+ public void onIpAddressAvailable(String ipAddr) {
+ android.os.Trace.endAsyncSection("debianBoot", 0);
+ Bundle b = new Bundle();
+ b.putString(VmLauncherService.KEY_VM_IP_ADDR, ipAddr);
+ mResultReceiver.send(VmLauncherService.RESULT_IPADDR, b);
}
}
diff --git a/microfuchsia/microfuchsiad/src/instance_starter.rs b/microfuchsia/microfuchsiad/src/instance_starter.rs
index 15fcc06..61a024f 100644
--- a/microfuchsia/microfuchsiad/src/instance_starter.rs
+++ b/microfuchsia/microfuchsiad/src/instance_starter.rs
@@ -90,6 +90,7 @@
console_out,
console_in,
/* log= */ None,
+ /* dump_dt= */ None,
None,
)
.context("Failed to create VM")?;
diff --git a/tests/hostside/AndroidTest.xml b/tests/hostside/AndroidTest.xml
index 18728ad..f77def3 100644
--- a/tests/hostside/AndroidTest.xml
+++ b/tests/hostside/AndroidTest.xml
@@ -28,4 +28,8 @@
<test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
<option name="jar" value="MicrodroidHostTestCases.jar" />
</test>
+
+ <!-- Controller that will skip the module if a native bridge situation is detected -->
+ <!-- For example: module wants to run arm and device is x86 -->
+ <object type="module_controller" class="com.android.tradefed.testtype.suite.module.NativeBridgeModuleController" />
</configuration>
diff --git a/tests/testapk/AndroidTest.xml b/tests/testapk/AndroidTest.xml
index 22cd0dc..e490da4 100644
--- a/tests/testapk/AndroidTest.xml
+++ b/tests/testapk/AndroidTest.xml
@@ -39,4 +39,8 @@
<option name="shell-timeout" value="300000" />
<option name="test-timeout" value="300000" />
</test>
+
+ <!-- Controller that will skip the module if a native bridge situation is detected -->
+ <!-- For example: module wants to run arm and device is x86 -->
+ <object type="module_controller" class="com.android.tradefed.testtype.suite.module.NativeBridgeModuleController" />
</configuration>
diff --git a/tests/vm_accessor/README.md b/tests/vm_accessor/README.md
index c85cf3c..8b0eb2a 100644
--- a/tests/vm_accessor/README.md
+++ b/tests/vm_accessor/README.md
@@ -1,15 +1,16 @@
# Demo for serving a service in a VM
You can implement a service in a VM, and let client in the Android can use it
-as if it's in the Android. To do so, implement IAccessor.
+as if it's in the Android. To do so, use libbinder's IAccessor.
-IAccessor allows AIDL service in a VM can be accessed via servicemanager.
-To do so, VM owners should also provide IAccessor implementation. servicemanager
-will connect to the IAccessor and get the binder of the service in a VM with it.
+IAccessor allows AIDL service in a VM to be accessed via servicemanager.
+To do so, VM owners should also provide IAccessor through libbinder's service
+manager APIs. servicemanager will connect to the IAccessor and get the binder
+of the service in a VM with it.
com.android.virt.accessor_demo apex contains the minimum setup for IAccessor as
follows:
- - accessor_demo: Sample implementation of IAccessor, which is expected to
+ - accessor_demo: Sample implementation using IAccessor, which is expected to
launch VM and returns the Vsock connection of service in the VM.
- AccessorVmApp: Sample app that conatins VM payload. Provides the actual
implementation of service in a VM.
diff --git a/tests/vm_accessor/accessor/Android.bp b/tests/vm_accessor/accessor/Android.bp
index 7c0ee6d..8055f91 100644
--- a/tests/vm_accessor/accessor/Android.bp
+++ b/tests/vm_accessor/accessor/Android.bp
@@ -14,7 +14,6 @@
],
rustlibs: [
"android.system.virtualizationservice-rust",
- "android.os.accessor-rust",
"libanyhow",
"libandroid_logger",
"libbinder_rs",
diff --git a/tests/vm_accessor/accessor/src/accessor.rs b/tests/vm_accessor/accessor/src/accessor.rs
deleted file mode 100644
index 966bffb..0000000
--- a/tests/vm_accessor/accessor/src/accessor.rs
+++ /dev/null
@@ -1,56 +0,0 @@
-// Copyright 2024, 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.
-
-//! IAcessor implementation.
-//! TODO: Keep this in proper places, so other pVMs can use this.
-//! TODO: Allows to customize VMs for launching. (e.g. port, ...)
-
-use android_os_accessor::aidl::android::os::IAccessor::IAccessor;
-use binder::{self, Interface, ParcelFileDescriptor};
-use log::info;
-use std::time::Duration;
-use vmclient::VmInstance;
-
-// Note: Do not use LazyServiceGuard here, to make this service and VM are quit
-// when nobody references it.
-// TODO(b/353492849): Do not use IAccessor directly.
-#[derive(Debug)]
-pub struct Accessor {
- // Note: we can't simply keep reference by specifying lifetime to Accessor,
- // because 'trait Interface' requires 'static.
- vm: VmInstance,
- port: i32,
- instance: String,
-}
-
-impl Accessor {
- pub fn new(vm: VmInstance, port: i32, instance: &str) -> Self {
- Self { vm, port, instance: instance.into() }
- }
-}
-
-impl Interface for Accessor {}
-
-impl IAccessor for Accessor {
- fn addConnection(&self) -> binder::Result<ParcelFileDescriptor> {
- self.vm.wait_until_ready(Duration::from_secs(20)).unwrap();
-
- info!("VM is ready. Connecting to service via port {}", self.port);
-
- self.vm.vm.connectVsock(self.port)
- }
- fn getInstanceName(&self) -> binder::Result<String> {
- Ok(self.instance.clone())
- }
-}
diff --git a/tests/vm_accessor/accessor/src/main.rs b/tests/vm_accessor/accessor/src/main.rs
index 49f5794..db53d8e 100644
--- a/tests/vm_accessor/accessor/src/main.rs
+++ b/tests/vm_accessor/accessor/src/main.rs
@@ -14,16 +14,14 @@
//! Android VM control tool.
-mod accessor;
mod run;
-use accessor::Accessor;
-use android_os_accessor::aidl::android::os::IAccessor::BnAccessor;
use anyhow::Error;
use anyhow::{anyhow, bail};
-use binder::{BinderFeatures, ProcessState};
+use binder::ProcessState;
use log::info;
use run::run_vm;
+use std::time::Duration;
// Private contract between IAccessor impl and VM service.
const PORT: i32 = 5678;
@@ -40,11 +38,13 @@
);
let vm = run_vm()?;
+ vm.wait_until_ready(Duration::from_secs(20)).unwrap();
+ let accessor = vm.vm.createAccessorBinder(SERVICE_NAME, PORT).unwrap();
+
+ let accessor_delegator = binder::delegate_accessor(SERVICE_NAME, accessor).unwrap();
// If you want to serve multiple services in a VM, then register Accessor impls multiple times.
- let accessor = Accessor::new(vm, PORT, SERVICE_NAME);
- let accessor_binder = BnAccessor::new_binder(accessor, BinderFeatures::default());
- binder::register_lazy_service(SERVICE_NAME, accessor_binder.as_binder()).map_err(|e| {
+ binder::register_lazy_service(SERVICE_NAME, accessor_delegator).map_err(|e| {
anyhow!("Failed to register lazy service, service={SERVICE_NAME}, err={e:?}",)
})?;
info!("service {SERVICE_NAME} is registered as lazy service");
diff --git a/tests/vm_accessor/accessor/src/run.rs b/tests/vm_accessor/accessor/src/run.rs
index 932baab..6dcc507 100644
--- a/tests/vm_accessor/accessor/src/run.rs
+++ b/tests/vm_accessor/accessor/src/run.rs
@@ -128,6 +128,7 @@
Some(android_log_fd()?), /* console_out */
None, /* console_in */
Some(android_log_fd()?), /* log */
+ None, /* dump_dt */
Some(Box::new(Callback {})),
)
.context("Failed to create VM")?;
diff --git a/tests/vmbase_example/src/main.rs b/tests/vmbase_example/src/main.rs
index e0563b7..34a2b0b 100644
--- a/tests/vmbase_example/src/main.rs
+++ b/tests/vmbase_example/src/main.rs
@@ -119,6 +119,7 @@
Some(console),
/* consoleIn */ None,
Some(log_writer),
+ /* dump_dt */ None,
None,
)
.context("Failed to create VM")?;