Merge "Use sockaddr_vm instead of VsockAddr for libbinder" into main
diff --git a/OWNERS b/OWNERS
index afd2555..81217f3 100644
--- a/OWNERS
+++ b/OWNERS
@@ -30,8 +30,6 @@
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
diff --git a/android/LinuxInstaller/Android.bp b/android/LinuxInstaller/Android.bp
deleted file mode 100644
index f7994ef..0000000
--- a/android/LinuxInstaller/Android.bp
+++ /dev/null
@@ -1,62 +0,0 @@
-package {
- default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-java_defaults {
- name: "LinuxVmPayloadInstaller",
- init_rc: [":linux_vm_setup.rc"],
- required: ["linux_vm_setup"],
- system_ext_specific: true,
- platform_apis: true,
- privileged: true,
-}
-
-android_app {
- name: "LinuxInstallerApp",
- srcs: ["java/**/*.java"],
- resource_dirs: ["res"],
- asset_dirs: ["assets"],
- manifest: "AndroidManifest.xml",
- defaults: ["LinuxVmPayloadInstaller"],
- 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: ["LinuxVmPayloadInstaller"],
- 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",
-}
-
-filegroup {
- name: "linux_vm_setup.rc",
- srcs: ["linux_vm_setup.rc"],
-}
-
-sh_binary {
- name: "linux_vm_setup",
- src: "linux_vm_setup.sh",
- system_ext_specific: true,
- host_supported: false,
-}
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/java/com/android/virtualization/linuxinstaller/MainActivity.java b/android/LinuxInstaller/java/com/android/virtualization/linuxinstaller/MainActivity.java
deleted file mode 100644
index 0351f97..0000000
--- a/android/LinuxInstaller/java/com/android/virtualization/linuxinstaller/MainActivity.java
+++ /dev/null
@@ -1,207 +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.linuxinstaller;
-
-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;
-import android.text.TextUtils;
-import android.util.Log;
-import android.widget.TextView;
-
-import libcore.io.Streams;
-
-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 MainActivity 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");
-
- private static final String ASSET_DIR = "linux";
- private static final String HASH_FILE_NAME = "hash";
- private static final Path HASH_FILE = Path.of(DEST_DIR.toString(), HASH_FILE_NAME);
-
- ExecutorService executorService = Executors.newSingleThreadExecutor();
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
-
- 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");
- return;
- }
- try {
- updateImageIfNeeded();
- } catch (IOException e) {
- 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.");
- }
-
- @WorkerThread
- private boolean hasLocalAssets() {
- try {
- String[] files = getAssets().list(ASSET_DIR);
- return files != null && files.length > 0;
- } catch (IOException e) {
- Log.e(TAG, "there is an error during listing up assets", e);
- return false;
- }
- }
-
- @WorkerThread
- private void updateImageIfNeeded() throws IOException {
- if (!isUpdateNeeded()) {
- Log.d(TAG, "No update needed.");
- return;
- }
-
- try {
- if (Files.notExists(DEST_DIR)) {
- Files.createDirectory(DEST_DIR);
- }
-
- updateStatus("Copying images...");
- String[] files = getAssets().list(ASSET_DIR);
- for (String file : files) {
- updateStatus(file);
- Path dst = Path.of(DEST_DIR.toString(), file);
- updateFile(getAssets().open(ASSET_DIR + "/" + file), dst);
- }
- } catch (IOException e) {
- Log.e(TAG, "Error while updating image: " + e);
- updateStatus("Failed to update image.");
- throw e;
- }
- extractImages(DEST_DIR.toAbsolutePath().toString());
- }
-
- @WorkerThread
- private void extractImages(String destDir) throws IOException {
- updateStatus("Extracting images...");
-
- if (TextUtils.isEmpty(destDir)) {
- throw new RuntimeException("Internal error: destDir shouldn't be null");
- }
-
- 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) {
- Log.e(TAG, "Error while extracting image: " + e);
- updateStatus("Failed to extract image.");
- throw new IOException("extracting image is interrupted", e);
- }
- }
- }
-
- @WorkerThread
- private boolean isUpdateNeeded() {
- Path[] pathsToCheck = {DEST_DIR, HASH_FILE};
- for (Path p : pathsToCheck) {
- if (Files.notExists(p)) {
- Log.d(TAG, p.toString() + " does not exist.");
- return true;
- }
- }
-
- try {
- String installedHash = readAll(new FileInputStream(HASH_FILE.toFile()));
- String updatedHash = readAll(getAssets().open(ASSET_DIR + "/" + HASH_FILE_NAME));
- if (installedHash.equals(updatedHash)) {
- return false;
- }
- Log.d(TAG, "Hash mismatch. Installed: " + installedHash + " Updated: " + updatedHash);
- } catch (IOException e) {
- Log.e(TAG, "Error while checking hash: " + e);
- }
- return true;
- }
-
- private static String readAll(InputStream input) throws IOException {
- return Streams.readFully(new InputStreamReader(input)).strip();
- }
-
- private static void updateFile(InputStream input, Path path) throws IOException {
- try (input) {
- Files.copy(input, path, StandardCopyOption.REPLACE_EXISTING);
- }
- }
-
- private void updateStatus(String line) {
- runOnUiThread(
- () -> {
- TextView statusView = findViewById(R.id.status_txt_view);
- 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/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/linux_vm_setup.rc b/android/LinuxInstaller/linux_vm_setup.rc
deleted file mode 100644
index 9264d96..0000000
--- a/android/LinuxInstaller/linux_vm_setup.rc
+++ /dev/null
@@ -1,23 +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.
-
-service linux_vm_setup /system_ext/bin/linux_vm_setup
- user shell
- group shell media_rw
- disabled
- oneshot
- seclabel u:r:shell:s0
-
-on property:debug.linux_vm_setup.start=true
- start linux_vm_setup
diff --git a/android/LinuxInstaller/linux_vm_setup.sh b/android/LinuxInstaller/linux_vm_setup.sh
deleted file mode 100644
index 6a93f6f..0000000
--- a/android/LinuxInstaller/linux_vm_setup.sh
+++ /dev/null
@@ -1,32 +0,0 @@
-#!/system/bin/sh
-
-function round_up() {
- num=$1
- div=$2
- echo $((( (( ${num} / ${div} ) + 1) * ${div} )))
-}
-
-function install() {
- src_dir=$(getprop debug.linux_vm_setup.path)
- src_dir=${src_dir/#\/storage\/emulated\//\/data\/media\/}
- dst_dir=/data/local/tmp/
-
- cat $(find ${src_dir} -name "images.tar.gz*" | sort) | tar xz -C ${dst_dir}
- cp -u ${src_dir}/vm_config.json ${dst_dir}
- chmod 666 ${dst_dir}/*
-
- if [ -f ${dst_dir}state.img ]; then
- # increase the size of state.img to the multiple of 4096
- num_blocks=$(du -b -K ${dst_dir}state.img | cut -f 1)
- required_num_blocks=$(round_up ${num_blocks} 4)
- additional_blocks=$((( ${required_num_blocks} - ${num_blocks} )))
- dd if=/dev/zero bs=512 count=${additional_blocks} >> ${dst_dir}state.img
- fi
- rm ${src_dir}/images.tar.gz*
- rm ${src_dir}/vm_config.json
-}
-
-setprop debug.linux_vm_setup.done false
-install
-setprop debug.linux_vm_setup.start false
-setprop debug.linux_vm_setup.done true
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/.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 d91af2f..932ca76 100644
--- a/android/TerminalApp/Android.bp
+++ b/android/TerminalApp/Android.bp
@@ -9,6 +9,7 @@
"java/**/*.kt",
],
resource_dirs: ["res"],
+ asset_dirs: ["assets"],
static_libs: [
"vm_launcher_lib",
"androidx-constraintlayout_constraintlayout",
diff --git a/android/TerminalApp/AndroidManifest.xml b/android/TerminalApp/AndroidManifest.xml
index f09412e..105e454 100644
--- a/android/TerminalApp/AndroidManifest.xml
+++ b/android/TerminalApp/AndroidManifest.xml
@@ -3,6 +3,7 @@
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" />
@@ -12,15 +13,18 @@
<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:theme="@style/Theme.Material3.DayNight.NoActionBar"
- android:usesCleartextTraffic="true">
+ 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>
@@ -44,20 +48,15 @@
android:name="${applicationId}.SplitInitializer"
android:value="androidx.startup" />
</provider>
- <activity-alias
- android:name=".MainActivityAlias"
- android:targetActivity="com.android.virtualization.terminal.MainActivity"
- android:exported="true"
- android:enabled="false" >
+ <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/java/com/android/virtualization/terminal/InstallerActivity.java b/android/TerminalApp/java/com/android/virtualization/terminal/InstallerActivity.java
new file mode 100644
index 0000000..a49ea72
--- /dev/null
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/InstallerActivity.java
@@ -0,0 +1,71 @@
+/*
+ * 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.
+ */
+
+package com.android.virtualization.terminal;
+
+import android.app.Activity;
+import android.os.Build;
+import android.os.Bundle;
+import android.util.Log;
+import android.widget.TextView;
+
+import com.android.virtualization.vmlauncher.InstallUtils;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+public class InstallerActivity extends Activity {
+ private static final String TAG = "LinuxInstaller";
+
+ ExecutorService executorService = Executors.newSingleThreadExecutor();
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setResult(RESULT_CANCELED);
+
+ setContentView(R.layout.activity_installer);
+
+ executorService.execute(this::installLinuxImage);
+ }
+
+ private void installLinuxImage() {
+ Log.d(TAG, "installLinuxImage");
+ // Installing from sdcard is supported only in debuggable build.
+ if (Build.isDebuggable()) {
+ updateStatus("try /sdcard/linux/images.tar.gz");
+ if (InstallUtils.installImageFromExternalStorage(this)) {
+ Log.d(TAG, "success / sdcard");
+ updateStatus("image is installed from /sdcard/linux/images.tar.gz");
+ setResult(RESULT_OK);
+ finish();
+ return;
+ }
+ Log.d(TAG, "fail / sdcard");
+ updateStatus("There is no /sdcard/linux/images.tar.gz");
+ }
+ setResult(RESULT_CANCELED, null);
+ finish();
+ }
+
+ private void updateStatus(String line) {
+ runOnUiThread(
+ () -> {
+ TextView statusView = findViewById(R.id.status_txt_view);
+ statusView.append(line + "\n");
+ });
+ }
+}
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java b/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java
index 846f975..20b4c96 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java
@@ -15,38 +15,78 @@
*/
package com.android.virtualization.terminal;
-import android.content.ClipData;
-import android.content.ClipboardManager;
+import android.content.Context;
import android.content.Intent;
+import android.net.http.SslError;
+import android.os.Build;
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.InstallUtils;
import com.android.virtualization.vmlauncher.VmLauncherServices;
import com.google.android.material.appbar.MaterialToolbar;
-public class MainActivity extends AppCompatActivity implements
- VmLauncherServices.VmLauncherServiceCallback {
+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);
+
+ boolean launchInstaller = installIfNecessary();
+ 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);
@@ -57,49 +97,249 @@
mWebView.getSettings().setDomStorageEnabled(true);
mWebView.getSettings().setJavaScriptEnabled(true);
mWebView.setWebChromeClient(new WebChromeClient());
+
+ mAccessibilityManager = getSystemService(AccessibilityManager.class);
+ mAccessibilityManager.addTouchExplorationStateChangeListener(this);
+
+ connectToTerminalService();
+ readClientCertificate();
+
+ // if installer is launched, it will be handled in onActivityResult
+ if (!launchInstaller) {
+ startVm();
+ }
+ }
+
+ 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
@@ -111,20 +351,49 @@
@Override
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));
- return true;
- } else if (id == R.id.stop_vm) {
- VmLauncherServices.stopVmLauncherService(this);
- return true;
-
- } else if (id == R.id.menu_item_settings) {
+ if (id == R.id.menu_item_settings) {
Intent intent = new Intent(this, SettingsActivity.class);
this.startActivity(intent);
return true;
}
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 boolean installIfNecessary() {
+ // If payload from external storage exists(only for debuggable build) or there is no
+ // installed image, launch installer activity.
+ if ((Build.isDebuggable() && InstallUtils.payloadFromExternalStorageExists())
+ || !InstallUtils.isImageInstalled(this)) {
+ Intent intent = new Intent(this, InstallerActivity.class);
+ startActivityForResult(intent, REQUEST_CODE_INSTALLER);
+ return true;
+ }
+ return false;
+ }
+
+ private void startVm() {
+ if (!InstallUtils.isImageInstalled(this)) {
+ return;
+ }
+ android.os.Trace.beginAsyncSection("executeTerminal", 0);
+ VmLauncherServices.startVmLauncherService(this, this);
+ }
}
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/SettingsDiskResizeActivity.kt b/android/TerminalApp/java/com/android/virtualization/terminal/SettingsDiskResizeActivity.kt
index 4be291f..1b14ef2 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/SettingsDiskResizeActivity.kt
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/SettingsDiskResizeActivity.kt
@@ -16,27 +16,49 @@
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_disk_size)
+ 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)
- diskSizeText.text = diskSize.toInt().toString()
diskSizeSlider.value = diskSize
+ diskSizeText.text = enlargeFontOfNumber(
+ getString(R.string.settings_disk_resize_resize_gb_assigned_format,
+ localizedFileSize(diskSize)))
diskSizeSlider.addOnChangeListener { _, value, _ ->
- diskSizeText.text = value.toInt().toString()
+ diskSizeText.text = enlargeFontOfNumber(
+ getString(R.string.settings_disk_resize_resize_gb_assigned_format,
+ localizedFileSize(value)))
cancelButton.isVisible = true
resizeButton.isVisible = true
}
@@ -54,4 +76,29 @@
.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/res/drawable/ic_settings.xml b/android/TerminalApp/res/drawable/ic_settings.xml
new file mode 100644
index 0000000..4bcd4aa
--- /dev/null
+++ b/android/TerminalApp/res/drawable/ic_settings.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="960"
+ android:viewportHeight="960"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M370,880L354,752Q341,747 329.5,740Q318,733 307,725L188,775L78,585L181,507Q180,500 180,493.5Q180,487 180,480Q180,473 180,466.5Q180,460 181,453L78,375L188,185L307,235Q318,227 330,220Q342,213 354,208L370,80L590,80L606,208Q619,213 630.5,220Q642,227 653,235L772,185L882,375L779,453Q780,460 780,466.5Q780,473 780,480Q780,487 780,493.5Q780,500 778,507L881,585L771,775L653,725Q642,733 630,740Q618,747 606,752L590,880L370,880ZM440,800L519,800L533,694Q564,686 590.5,670.5Q617,655 639,633L738,674L777,606L691,541Q696,527 698,511.5Q700,496 700,480Q700,464 698,448.5Q696,433 691,419L777,354L738,286L639,328Q617,305 590.5,289.5Q564,274 533,266L520,160L441,160L427,266Q396,274 369.5,289.5Q343,305 321,327L222,286L183,354L269,418Q264,433 262,448Q260,463 260,480Q260,496 262,511Q264,526 269,541L183,606L222,674L321,632Q343,655 369.5,670.5Q396,686 427,694L440,800ZM482,620Q540,620 581,579Q622,538 622,480Q622,422 581,381Q540,340 482,340Q423,340 382.5,381Q342,422 342,480Q342,538 382.5,579Q423,620 482,620ZM480,480L480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480L480,480Z"/>
+</vector>
diff --git a/android/TerminalApp/res/layout/activity_headless.xml b/android/TerminalApp/res/layout/activity_headless.xml
index f786a0f..7baaf5c 100644
--- a/android/TerminalApp/res/layout/activity_headless.xml
+++ b/android/TerminalApp/res/layout/activity_headless.xml
@@ -12,14 +12,32 @@
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_constraintTop_toTopOf="parent"/>
- <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" />
+ <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">
+ <com.google.android.material.textview.MaterialTextView
+ android:text="@string/vm_creation_message"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="5dp"/>
+ <com.google.android.material.progressindicator.CircularProgressIndicator
+ android:indeterminate="true"
+ 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/LinuxInstaller/res/layout/activity_main.xml b/android/TerminalApp/res/layout/activity_installer.xml
similarity index 100%
rename from android/LinuxInstaller/res/layout/activity_main.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
index b1acf23..4fc6229 100644
--- a/android/TerminalApp/res/layout/settings_activity.xml
+++ b/android/TerminalApp/res/layout/settings_activity.xml
@@ -5,10 +5,6 @@
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"
diff --git a/android/TerminalApp/res/layout/settings_disk_resize.xml b/android/TerminalApp/res/layout/settings_disk_resize.xml
index 3c09f52..f868b28 100644
--- a/android/TerminalApp/res/layout/settings_disk_resize.xml
+++ b/android/TerminalApp/res/layout/settings_disk_resize.xml
@@ -21,27 +21,19 @@
android:layout_height="match_parent">
<TextView
- android:id="@+id/settings_disk_resize_disk_size"
+ android:id="@+id/settings_disk_resize_resize_gb_assigned"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
- android:textSize="36sp"
- app:layout_constraintLeft_toLeftOf="parent"
+ 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:text="@string/settings_disk_resize_resize_gb_assigned"
android:textSize="14sp"
- app:layout_constraintLeft_toRightOf="@+id/settings_disk_resize_disk_size"
- app:layout_constraintBottom_toTopOf="@+id/settings_disk_resize_disk_size_slider"/>
-
- <TextView
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- android:text="@string/settings_disk_resize_resize_gb_total"
- android:textSize="14sp"
- app:layout_constraintRight_toRightOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toTopOf="@+id/settings_disk_resize_disk_size_slider"/>
<com.google.android.material.slider.Slider
@@ -51,7 +43,6 @@
android:layout_marginBottom="36dp"
app:tickVisible="false"
android:valueFrom="0"
- android:valueTo="256"
android:stepSize="4"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />
@@ -65,7 +56,7 @@
android:layout_marginHorizontal="8dp"
app:layout_constraintTop_toTopOf="@+id/settings_disk_resize_disk_size_slider"
app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintRight_toLeftOf="@+id/settings_disk_resize_resize_button" />
+ app:layout_constraintEnd_toStartOf="@+id/settings_disk_resize_resize_button" />
<com.google.android.material.button.MaterialButton
android:id="@+id/settings_disk_resize_resize_button"
@@ -76,6 +67,6 @@
android:layout_marginHorizontal="8dp"
app:layout_constraintTop_toTopOf="@+id/settings_disk_resize_disk_size_slider"
app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintRight_toRightOf="parent" />
+ app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</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 9c83923..0fee90e 100644
--- a/android/TerminalApp/res/menu/main_menu.xml
+++ b/android/TerminalApp/res/menu/main_menu.xml
@@ -1,9 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
-<menu xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:id="@+id/copy_ip_addr"
- android:title="Copy the IP address"/>
- <item android:id="@+id/stop_vm"
- android:title="Stop the existing VM instance"/>
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto">
<item android:id="@+id/menu_item_settings"
- android:title="Settings"/>
-</menu>
\ No newline at end of file
+ android:icon="@drawable/ic_settings"
+ android:title="@string/action_settings"
+ app:showAsAction="always"/>
+</menu>
diff --git a/android/TerminalApp/res/values/strings.xml b/android/TerminalApp/res/values/strings.xml
index c3a3348..1cbaee8 100644
--- a/android/TerminalApp/res/values/strings.xml
+++ b/android/TerminalApp/res/values/strings.xml
@@ -16,25 +16,48 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <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>
+ <!-- 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>
+
+ <!-- Action bar icon name for the settings view CHAR LIMIT=none] -->
+ <string name="action_settings">Settings</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>
- <string name="settings_disk_resize_resize_gb_assigned">GB Assigned</string>
- <string name="settings_disk_resize_resize_gb_total">256 GB total</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/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/build/apex/Android.bp b/build/apex/Android.bp
index 4916df7..f794239 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,7 +171,7 @@
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,
}),
diff --git a/build/apex/product_packages.mk b/build/apex/product_packages.mk
index c678693..0646e67 100644
--- a/build/apex/product_packages.mk
+++ b/build/apex/product_packages.mk
@@ -73,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 af5084b..b4e8b2f 100755
--- a/build/debian/build.sh
+++ b/build/debian/build.sh
@@ -60,6 +60,7 @@
fai-setup-storage
fdisk
make
+ protobuf-compiler
python3
python3-libcloud
python3-marshmallow
@@ -81,25 +82,22 @@
qemu-system
)
fi
+
+ # TODO(b/365955006): remove these lines when uboot supports x86_64 EFI application
+ if [[ "$arch" == "x86_64" ]]; then
+ packages+=(
+ libguestfs-tools
+ )
+ fi
DEBIAN_FRONTEND=noninteractive \
apt install --no-install-recommends --assume-yes "${packages[@]}"
-
if [ ! -f $"HOME"/.cargo/bin/cargo ]; then
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
fi
source "$HOME"/.cargo/env
rustup target add "${arch}"-unknown-linux-gnu
-
- 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
-
- 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_"${debian_arch}".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_"${debian_arch}".deb | head -n 1)"
}
download_debian_cloud_image() {
@@ -112,6 +110,17 @@
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}"
@@ -125,14 +134,9 @@
wget "${url}" -O "${dst}/files/usr/local/bin/ttyd/AVF"
chmod 777 "${dst}/files/usr/local/bin/ttyd/AVF"
- pushd "$(dirname "$0")/forwarder_guest" > /dev/null
- RUSTFLAGS="-C linker=${arch}-linux-gnu-gcc" cargo build \
- --target "${arch}-unknown-linux-gnu" \
- --target-dir "${workdir}/forwarder_guest"
- mkdir -p "${dst}/files/usr/local/bin/forwarder_guest"
- cp "${workdir}/forwarder_guest/${arch}-unknown-linux-gnu/debug/forwarder_guest" "${dst}/files/usr/local/bin/forwarder_guest/AVF"
- chmod 777 "${dst}/files/usr/local/bin/forwarder_guest/AVF"
- popd > /dev/null
+ 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() {
@@ -163,3 +167,18 @@
copy_android_config
run_fai
fdisk -l image.raw
+images=(image.raw)
+# TODO(b/365955006): remove these lines when uboot supports x86_64 EFI application
+if [[ "$arch" == "x86_64" ]]; then
+ virt-get-kernel -a image.raw
+ mv vmlinuz* vmlinuz
+ mv initrd.img* initrd.img
+ images+=(
+ vmlinuz
+ initrd.img
+ )
+fi
+
+cp $(dirname $0)/vm_config.json.${arch} vm_config.json
+# --sparse option isn't supported in apache-commons-compress
+tar czv -f images.tar.gz ${images[@]} vm_config.json
\ No newline at end of file
diff --git a/build/debian/build_in_container.sh b/build/debian/build_in_container.sh
index 555ce90..fd1a975 100755
--- a/build/debian/build_in_container.sh
+++ b/build/debian/build_in_container.sh
@@ -19,6 +19,6 @@
esac
done
-docker run --privileged -it -v \
+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/scripts/AVF/10-systemd b/build/debian/fai_config/scripts/AVF/10-systemd
index d33b92a..6a106c6 100755
--- a/build/debian/fai_config/scripts/AVF/10-systemd
+++ b/build/debian/fai_config/scripts/AVF/10-systemd
@@ -1,7 +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..7a1523a
--- /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
+sudo mv images.tar.gz ${KOKORO_ARTIFACTS_DIR} || true
+
+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 73%
copy from build/debian/kokoro/gcp_ubuntu_docker/continuous.cfg
copy to build/debian/kokoro/gcp_ubuntu_docker/aarch64/continuous.cfg
index 97ebd5d..e836eea 100644
--- a/build/debian/kokoro/gcp_ubuntu_docker/continuous.cfg
+++ b/build/debian/kokoro/gcp_ubuntu_docker/aarch64/continuous.cfg
@@ -4,10 +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: "image.tar.gz"
+ regex: "images.tar.gz"
+ regex: "logs/**"
}
}
diff --git a/build/debian/kokoro/gcp_ubuntu_docker/hourly.cfg b/build/debian/kokoro/gcp_ubuntu_docker/aarch64/hourly.cfg
similarity index 73%
copy from build/debian/kokoro/gcp_ubuntu_docker/hourly.cfg
copy to build/debian/kokoro/gcp_ubuntu_docker/aarch64/hourly.cfg
index 97ebd5d..e836eea 100644
--- a/build/debian/kokoro/gcp_ubuntu_docker/hourly.cfg
+++ b/build/debian/kokoro/gcp_ubuntu_docker/aarch64/hourly.cfg
@@ -4,10 +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: "image.tar.gz"
+ 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/presubmit.cfg
similarity index 72%
copy from build/debian/kokoro/gcp_ubuntu_docker/continuous.cfg
copy to build/debian/kokoro/gcp_ubuntu_docker/aarch64/presubmit.cfg
index 97ebd5d..e836eea 100644
--- a/build/debian/kokoro/gcp_ubuntu_docker/continuous.cfg
+++ b/build/debian/kokoro/gcp_ubuntu_docker/aarch64/presubmit.cfg
@@ -4,10 +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: "image.tar.gz"
+ 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 4cc4769..0000000
--- a/build/debian/kokoro/gcp_ubuntu_docker/build.sh
+++ /dev/null
@@ -1,9 +0,0 @@
-#!/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}/image.tar.gz image.raw
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..66e3d64
--- /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
+sudo mv images.tar.gz ${KOKORO_ARTIFACTS_DIR} || true
+
+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 73%
rename from build/debian/kokoro/gcp_ubuntu_docker/continuous.cfg
rename to build/debian/kokoro/gcp_ubuntu_docker/x86_64/continuous.cfg
index 97ebd5d..a5e8aeb 100644
--- a/build/debian/kokoro/gcp_ubuntu_docker/continuous.cfg
+++ b/build/debian/kokoro/gcp_ubuntu_docker/x86_64/continuous.cfg
@@ -4,10 +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: "image.tar.gz"
+ regex: "images.tar.gz"
+ regex: "logs/**"
}
}
diff --git a/build/debian/kokoro/gcp_ubuntu_docker/hourly.cfg b/build/debian/kokoro/gcp_ubuntu_docker/x86_64/hourly.cfg
similarity index 73%
rename from build/debian/kokoro/gcp_ubuntu_docker/hourly.cfg
rename to build/debian/kokoro/gcp_ubuntu_docker/x86_64/hourly.cfg
index 97ebd5d..a5e8aeb 100644
--- a/build/debian/kokoro/gcp_ubuntu_docker/hourly.cfg
+++ b/build/debian/kokoro/gcp_ubuntu_docker/x86_64/hourly.cfg
@@ -4,10 +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: "image.tar.gz"
+ 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 72%
copy from build/debian/kokoro/gcp_ubuntu_docker/continuous.cfg
copy to build/debian/kokoro/gcp_ubuntu_docker/x86_64/presubmit.cfg
index 97ebd5d..a5e8aeb 100644
--- a/build/debian/kokoro/gcp_ubuntu_docker/continuous.cfg
+++ b/build/debian/kokoro/gcp_ubuntu_docker/x86_64/presubmit.cfg
@@ -4,10 +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: "image.tar.gz"
+ regex: "images.tar.gz"
+ regex: "logs/**"
}
}
diff --git a/android/LinuxInstaller/linux_image_builder/vm_config.json b/build/debian/vm_config.json.aarch64
similarity index 87%
rename from android/LinuxInstaller/linux_image_builder/vm_config.json
rename to build/debian/vm_config.json.aarch64
index 21462b8..9f9295c 100644
--- a/android/LinuxInstaller/linux_image_builder/vm_config.json
+++ b/build/debian/vm_config.json.aarch64
@@ -2,7 +2,7 @@
"name": "debian",
"disks": [
{
- "image": "/data/local/tmp/debian.img",
+ "image": "$PAYLOAD_DIR/image.raw",
"partitions": [],
"writable": true
}
diff --git a/android/LinuxInstaller/linux_image_builder/vm_config.json b/build/debian/vm_config.json.x86_64
similarity index 69%
copy from android/LinuxInstaller/linux_image_builder/vm_config.json
copy to build/debian/vm_config.json.x86_64
index 21462b8..2fb9faa 100644
--- a/android/LinuxInstaller/linux_image_builder/vm_config.json
+++ b/build/debian/vm_config.json.x86_64
@@ -2,11 +2,14 @@
"name": "debian",
"disks": [
{
- "image": "/data/local/tmp/debian.img",
+ "image": "$PAYLOAD_DIR/image.raw",
"partitions": [],
"writable": true
}
],
+ "kernel": "$PAYLOAD_DIR/vmlinuz",
+ "initrd": "$PAYLOAD_DIR/initrd.img",
+ "params": "root=/dev/vda1",
"protected": false,
"cpu_topology": "match_host",
"platform_version": "~1.0",
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/build/debian/forwarder_guest/Cargo.toml b/guest/forwarder_guest/Cargo.toml
similarity index 79%
rename from build/debian/forwarder_guest/Cargo.toml
rename to guest/forwarder_guest/Cargo.toml
index e70dcd4..65f57cf 100644
--- a/build/debian/forwarder_guest/Cargo.toml
+++ b/guest/forwarder_guest/Cargo.toml
@@ -5,7 +5,7 @@
[dependencies]
clap = { version = "4.5.19", features = ["derive"] }
-forwarder = { path = "../../../libs/libforwarder" }
+forwarder = { path = "../../libs/libforwarder" }
poll_token_derive = "0.1.0"
remain = "0.2.14"
vmm-sys-util = "0.12.1"
diff --git a/build/debian/forwarder_guest/src/main.rs b/guest/forwarder_guest/src/main.rs
similarity index 100%
rename from build/debian/forwarder_guest/src/main.rs
rename to guest/forwarder_guest/src/main.rs
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/build/debian/port_listener/build.sh b/guest/port_listener/build.sh
similarity index 100%
rename from build/debian/port_listener/build.sh
rename to guest/port_listener/build.sh
diff --git a/build/debian/port_listener/src/common.h b/guest/port_listener/src/common.h
similarity index 100%
rename from build/debian/port_listener/src/common.h
rename to guest/port_listener/src/common.h
diff --git a/build/debian/port_listener/src/listen_tracker.ebpf.c b/guest/port_listener/src/listen_tracker.ebpf.c
similarity index 99%
rename from build/debian/port_listener/src/listen_tracker.ebpf.c
rename to guest/port_listener/src/listen_tracker.ebpf.c
index 030ded0..9e98aad 100644
--- a/build/debian/port_listener/src/listen_tracker.ebpf.c
+++ b/guest/port_listener/src/listen_tracker.ebpf.c
@@ -16,11 +16,10 @@
// src/platform2/vm_tools/port_listener/listen_tracker.ebpf.c
// bpf_helpers.h uses types defined here
-#include "vmlinux.h"
-
#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
diff --git a/build/debian/port_listener/src/main.cc b/guest/port_listener/src/main.cc
similarity index 99%
rename from build/debian/port_listener/src/main.cc
rename to guest/port_listener/src/main.cc
index b0b0979..1caceaf 100644
--- a/build/debian/port_listener/src/main.cc
+++ b/guest/port_listener/src/main.cc
@@ -18,9 +18,8 @@
#include <bpf/libbpf.h>
#include <bpf/libbpf_legacy.h>
#include <glog/logging.h>
-#include <sys/socket.h>
-
#include <linux/vm_sockets.h> // Needs to come after sys/socket.h
+#include <sys/socket.h>
#include <memory>
#include <unordered_map>
diff --git a/guest/pvmfw/Android.bp b/guest/pvmfw/Android.bp
index d0d309b..bcd3e42 100644
--- a/guest/pvmfw/Android.bp
+++ b/guest/pvmfw/Android.bp
@@ -21,7 +21,6 @@
"libfdtpci",
"liblibfdt_nostd",
"liblog_rust_nostd",
- "libonce_cell_nostd",
"libpvmfw_avb_nostd",
"libpvmfw_embedded_key",
"libpvmfw_fdt_template",
diff --git a/guest/rialto/tests/test.rs b/guest/rialto/tests/test.rs
index 7ec5647..09f9cc3 100644
--- a/guest/rialto/tests/test.rs
+++ b/guest/rialto/tests/test.rs
@@ -25,7 +25,10 @@
use bssl_avf::{rand_bytes, sha256, EcKey, PKey};
use client_vm_csr::generate_attestation_key_and_csr;
use coset::{CborSerializable, CoseMac0, CoseSign};
-use hwtrust::{rkp, session::Session};
+use hwtrust::{
+ rkp,
+ session::{RkpInstance, Session},
+};
use log::{info, warn};
use service_vm_comm::{
ClientVmAttestationParams, Csr, CsrPayload, EcdsaP256KeyPair, GenerateCertificateRequestParams,
@@ -206,7 +209,8 @@
let vm_component = vm_component.decode_as::<asn1::SequenceOf<asn1::Any, 4>>().unwrap();
assert_eq!(4, vm_component.len());
let name = vm_component.get(0).unwrap().decode_as::<asn1::Utf8StringRef>().unwrap();
- assert_eq!(expected_component.name, name.as_ref());
+ let name_str: &str = name.as_ref();
+ assert_eq!(expected_component.name, name_str);
let version = vm_component.get(1).unwrap().decode_as::<u64>().unwrap();
assert_eq!(expected_component.version, version);
let code_hash = vm_component.get(2).unwrap().decode_as::<asn1::OctetString>().unwrap();
@@ -285,6 +289,7 @@
fn check_csr(csr: Vec<u8>) -> Result<()> {
let mut session = Session::default();
session.set_allow_any_mode(true);
+ session.set_rkp_instance(RkpInstance::Avf);
let _csr = rkp::Csr::from_cbor(&session, &csr[..]).context("Failed to parse CSR")?;
Ok(())
}
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/libservice_vm_fake_chain/src/service_vm.rs b/libs/libservice_vm_fake_chain/src/service_vm.rs
index 9bd831d..86fd3ea 100644
--- a/libs/libservice_vm_fake_chain/src/service_vm.rs
+++ b/libs/libservice_vm_fake_chain/src/service_vm.rs
@@ -116,6 +116,7 @@
component_name: Some(cstr!("Protected VM firmware")),
component_version: Some(1),
resettable: true,
+ rkp_vm_marker: true,
..Default::default()
};
let config_descriptor = retry_bcc_format_config_descriptor(&config_values)?;
@@ -158,6 +159,7 @@
component_name: Some(cstr!("vm_entry")),
component_version: Some(12),
resettable: true,
+ rkp_vm_marker: true,
..Default::default()
};
let config_descriptor = retry_bcc_format_config_descriptor(&config_values)?;
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/libvmbase/src/entry.rs b/libs/libvmbase/src/entry.rs
index 99f28fc..f442a32 100644
--- a/libs/libvmbase/src/entry.rs
+++ b/libs/libvmbase/src/entry.rs
@@ -56,8 +56,7 @@
/// This is the entry point to the Rust code, called from the binary entry point in `entry.S`.
#[no_mangle]
extern "C" fn rust_entry(x0: u64, x1: u64, x2: u64, x3: u64) -> ! {
- // SAFETY: Only called once, from here, and inaccessible to client code.
- unsafe { heap::init() };
+ heap::init();
if try_console_init().is_err() {
// Don't panic (or log) here to avoid accessing the console.
diff --git a/libs/libvmbase/src/heap.rs b/libs/libvmbase/src/heap.rs
index 99c06aa..3a4e198 100644
--- a/libs/libvmbase/src/heap.rs
+++ b/libs/libvmbase/src/heap.rs
@@ -22,39 +22,78 @@
use core::ffi::c_void;
use core::mem;
use core::num::NonZeroUsize;
+use core::ops::Range;
use core::ptr;
use core::ptr::NonNull;
use buddy_system_allocator::LockedHeap;
+use spin::{
+ mutex::{SpinMutex, SpinMutexGuard},
+ Once,
+};
/// Configures the size of the global allocator.
#[macro_export]
macro_rules! configure_heap {
($len:expr) => {
- static mut __HEAP_ARRAY: [u8; $len] = [0; $len];
- #[export_name = "HEAP"]
- // SAFETY: HEAP will only be accessed once as mut, from init().
- static mut __HEAP: &'static mut [u8] = unsafe { &mut __HEAP_ARRAY };
+ static __HEAP: $crate::heap::HeapArray<{ $len }> = $crate::heap::HeapArray::new();
+ #[export_name = "get_heap"]
+ fn __get_heap() -> &'static mut [u8] {
+ __HEAP.get()
+ }
};
}
+/// An array to be used as a heap.
+///
+/// This should be stored in a static variable to have the appropriate lifetime.
+pub struct HeapArray<const SIZE: usize> {
+ array: SpinMutex<[u8; SIZE]>,
+}
+
+impl<const SIZE: usize> HeapArray<SIZE> {
+ /// Creates a new empty heap array.
+ #[allow(clippy::new_without_default)]
+ pub const fn new() -> Self {
+ Self { array: SpinMutex::new([0; SIZE]) }
+ }
+
+ /// Gets the heap as a slice.
+ ///
+ /// Panics if called more than once.
+ pub fn get(&self) -> &mut [u8] {
+ SpinMutexGuard::leak(self.array.try_lock().expect("Page heap was already taken"))
+ .as_mut_slice()
+ }
+}
+
extern "Rust" {
- /// Slice used by the global allocator, configured using configure_heap!().
- static mut HEAP: &'static mut [u8];
+ /// Gets slice used by the global allocator, configured using configure_heap!().
+ ///
+ /// Panics if called more than once.
+ fn get_heap() -> &'static mut [u8];
}
#[global_allocator]
static HEAP_ALLOCATOR: LockedHeap<32> = LockedHeap::<32>::new();
+/// The range of addresses used for the heap.
+static HEAP_RANGE: Once<Range<usize>> = Once::new();
+
/// Initialize the global allocator.
///
-/// # Safety
-///
-/// Must be called no more than once.
-pub(crate) unsafe fn init() {
- // SAFETY: Nothing else accesses this memory, and we hand it over to the heap to manage and
- // never touch it again. The heap is locked, so there cannot be any races.
- let (start, size) = unsafe { (HEAP.as_mut_ptr() as usize, HEAP.len()) };
+/// Panics if called more than once.
+pub(crate) fn init() {
+ // SAFETY: This is in fact a safe Rust function.
+ let heap = unsafe { get_heap() };
+
+ HEAP_RANGE.call_once(|| {
+ let range = heap.as_ptr_range();
+ range.start as usize..range.end as usize
+ });
+
+ let start = heap.as_mut_ptr() as usize;
+ let size = heap.len();
let mut heap = HEAP_ALLOCATOR.lock();
// SAFETY: We are supplying a valid memory range, and we only do this once.
@@ -107,10 +146,9 @@
/// errors.
unsafe extern "C" fn free(ptr: *mut c_void) {
let Some(ptr) = NonNull::new(ptr) else { return };
- // SAFETY: The contents of the HEAP slice may change, but the address range never does.
- let heap_range = unsafe { HEAP.as_ptr_range() };
+ let heap_range = HEAP_RANGE.get().expect("free called before heap was initialised");
assert!(
- heap_range.contains(&(ptr.as_ptr() as *const u8)),
+ heap_range.contains(&(ptr.as_ptr() as usize)),
"free() called on a pointer that is not part of the HEAP: {ptr:?}"
);
// SAFETY: ptr is non-null and was allocated by allocate, which prepends a correctly aligned
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..f47f6b6 100644
--- a/libs/vm_launcher_lib/Android.bp
+++ b/libs/vm_launcher_lib/Android.bp
@@ -12,6 +12,8 @@
platform_apis: true,
static_libs: [
"gson",
+ "debian-service-grpclib-lite",
+ "apache-commons-compress",
],
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/InstallUtils.java b/libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/InstallUtils.java
new file mode 100644
index 0000000..eb6dd77
--- /dev/null
+++ b/libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/InstallUtils.java
@@ -0,0 +1,141 @@
+/*
+ * 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.content.Context;
+import android.os.Environment;
+import android.util.Log;
+
+import org.apache.commons.compress.archivers.ArchiveEntry;
+import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
+import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Function;
+
+public class InstallUtils {
+ private static final String TAG = InstallUtils.class.getSimpleName();
+
+ private static final String VM_CONFIG_FILENAME = "vm_config.json";
+ private static final String COMPRESSED_PAYLOAD_FILENAME = "images.tar.gz";
+ private static final String PAYLOAD_DIR = "linux";
+
+ public static String getVmConfigPath(Context context) {
+ return new File(context.getFilesDir(), PAYLOAD_DIR)
+ .toPath()
+ .resolve(VM_CONFIG_FILENAME)
+ .toString();
+ }
+
+ public static boolean isImageInstalled(Context context) {
+ return Files.exists(Path.of(getVmConfigPath(context)));
+ }
+
+ private static Path getPayloadPath() {
+ File payloadDir = Environment.getExternalStoragePublicDirectory(PAYLOAD_DIR);
+ if (payloadDir == null) {
+ Log.d(TAG, "no payload dir: " + payloadDir);
+ return null;
+ }
+ Path payloadPath = payloadDir.toPath().resolve(COMPRESSED_PAYLOAD_FILENAME);
+ return payloadPath;
+ }
+
+ public static boolean payloadFromExternalStorageExists() {
+ return Files.exists(getPayloadPath());
+ }
+
+ public static boolean installImageFromExternalStorage(Context context) {
+ if (!payloadFromExternalStorageExists()) {
+ Log.d(TAG, "no artifact file from external storage");
+ }
+ Path payloadPath = getPayloadPath();
+ try (BufferedInputStream inputStream =
+ new BufferedInputStream(Files.newInputStream(payloadPath));
+ TarArchiveInputStream tar =
+ new TarArchiveInputStream(new GzipCompressorInputStream(inputStream))) {
+ ArchiveEntry entry;
+ Path baseDir = new File(context.getFilesDir(), PAYLOAD_DIR).toPath();
+ Files.createDirectories(baseDir);
+ while ((entry = tar.getNextEntry()) != null) {
+ Path extractTo = baseDir.resolve(entry.getName());
+ if (entry.isDirectory()) {
+ Files.createDirectories(extractTo);
+ } else {
+ Files.copy(tar, extractTo, StandardCopyOption.REPLACE_EXISTING);
+ }
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "installation failed", e);
+ return false;
+ }
+ if (!isImageInstalled(context)) {
+ return false;
+ }
+
+ if (!resolvePathInVmConfig(context)) {
+ Log.d(TAG, "resolving path failed");
+ try {
+ Files.deleteIfExists(Path.of(getVmConfigPath(context)));
+ } catch (IOException e) {
+ return false;
+ }
+ return false;
+ }
+
+ // remove payload if installation is done.
+ try {
+ Files.deleteIfExists(payloadPath);
+ } catch (IOException e) {
+ Log.d(TAG, "failed to remove installed payload", e);
+ }
+
+ return true;
+ }
+
+ private static Function<String, String> getReplacer(Context context) {
+ Map<String, String> rules = new HashMap<>();
+ rules.put("\\$PAYLOAD_DIR", new File(context.getFilesDir(), PAYLOAD_DIR).toString());
+ return (s) -> {
+ for (Map.Entry<String, String> rule : rules.entrySet()) {
+ s = s.replaceAll(rule.getKey(), rule.getValue());
+ }
+ return s;
+ };
+ }
+
+ private static boolean resolvePathInVmConfig(Context context) {
+ try {
+ String replacedVmConfig =
+ String.join(
+ "\n",
+ Files.readAllLines(Path.of(getVmConfigPath(context))).stream()
+ .map(getReplacer(context))
+ .toList());
+ Files.write(Path.of(getVmConfigPath(context)), replacedVmConfig.getBytes());
+ return true;
+ } catch (IOException e) {
+ return false;
+ }
+ }
+}
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..a59cc3d 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,28 +22,24 @@
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";
private static final int RESULT_START = 0;
private static final int RESULT_STOP = 1;
@@ -54,6 +50,7 @@
private ExecutorService mExecutorService;
private VirtualMachine mVirtualMachine;
private ResultReceiver mResultReceiver;
+ private Server mServer;
@Override
public IBinder onBind(Intent intent) {
@@ -82,12 +79,15 @@
}
mExecutorService = Executors.newCachedThreadPool();
- ConfigJson json = ConfigJson.from(VM_CONFIG_PATH);
+ ConfigJson json = ConfigJson.from(InstallUtils.getVmConfigPath(this));
VirtualMachineConfig config = json.toConfig(this);
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 +113,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 +133,7 @@
mExecutorService = null;
mVirtualMachine = null;
}
+ stopDebianServer();
}
private boolean isVmRunning() {
@@ -141,34 +141,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/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java b/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
index 2d55d66..ffcf338 100644
--- a/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
+++ b/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
@@ -36,6 +36,7 @@
import android.cts.statsdatom.lib.ReportUtils;
import com.android.compatibility.common.util.CddTest;
+import com.android.compatibility.common.util.PropertyUtil;
import com.android.compatibility.common.util.VsrTest;
import com.android.microdroid.test.common.ProcessUtil;
import com.android.microdroid.test.host.CommandRunner;
@@ -437,9 +438,8 @@
@VsrTest(requirements = {"VSR-7.1-001.008"})
public void UpgradedPackageIsAcceptedWithSecretkeeper() throws Exception {
// Preconditions
- assumeVmTypeSupported(true);
- assumeUpdatableVmSupported();
-
+ assumeVmTypeSupported(true); // Non-protected VMs may not support upgrades
+ ensureUpdatableVmSupported();
getDevice().uninstallPackage(PACKAGE_NAME);
getDevice().installPackage(findTestFile(APK_NAME), /* reinstall= */ true);
ensureProtectedMicrodroidBootsSuccessfully(INSTANCE_ID_FILE, INSTANCE_IMG);
@@ -1392,10 +1392,16 @@
&& device.doesFileExist("/sys/bus/platform/drivers/vfio-platform"));
}
- private void assumeUpdatableVmSupported() throws DeviceNotAvailableException {
- assumeTrue(
- "This test is only applicable if if Updatable VMs are supported",
- isUpdatableVmSupported());
+ private void ensureUpdatableVmSupported() throws DeviceNotAvailableException {
+ if (PropertyUtil.isVendorApiLevelAtLeast(getAndroidDevice(), 202504)) {
+ assertTrue(
+ "Missing Updatable VM support, have you declared Secretkeeper interface?",
+ isUpdatableVmSupported());
+ } else {
+ assumeTrue(
+ "Vendor API lower than 202504 may not support Updatable VM",
+ isUpdatableVmSupported());
+ }
}
private TestDevice getAndroidDevice() {