summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorErwin Jansen <jansene@google.com>2024-03-26 11:21:31 -0700
committerErwin Jansen <jansene@google.com>2024-03-26 12:01:37 -0700
commit2df9a028719c965be4ad1754cff446d2124fdd1d (patch)
tree32e150b401ba68f551a6f81bc840fff1f452bfca
parent1d2472876b64b71f713c035af10a3bc9b35aa47f (diff)
downloadgoldfish-2df9a028719c965be4ad1754cff446d2124fdd1d.tar.gz
Bring in cpu and files library from emu-master-dev
This brings in the cpu identifying tools and a set of file tools from emu-master-dev to parse avds. Change-Id: I276e234a1f8428a22d2ae00c3279ed54ba9dff09
-rw-r--r--cpu/BUILD.bazel42
-rw-r--r--cpu/README.MD34
-rw-r--r--cpu/include/android/goldfish/cpu/CpuAccelerator.h103
-rw-r--r--cpu/include/android/goldfish/cpu/cpu_accelerator.h116
-rw-r--r--cpu/src/android/goldfish/cpu/AppleCpuAccelerator.h50
-rw-r--r--cpu/src/android/goldfish/cpu/CpuAccelerator.cpp1087
-rw-r--r--cpu/src/android/goldfish/cpu/x86_cpuid.cpp262
-rw-r--r--cpu/src/android/goldfish/cpu/x86_cpuid.h158
-rw-r--r--cpu/test/android/goldfish/cpu/x86_cpuid_unittest.cpp118
-rw-r--r--files/BUILD.bazel43
-rw-r--r--files/include/android/goldfish/ConfigDirs.h101
-rw-r--r--files/include/android/goldfish/IniFile.h173
-rw-r--r--files/include/android/goldfish/ParameterList.h92
-rw-r--r--files/include/android/goldfish/avd/Avd.h131
-rw-r--r--files/include/android/goldfish/bootconfig.h29
-rw-r--r--files/src/android/goldfish/ConfigDirs.cpp316
-rw-r--r--files/src/android/goldfish/EncryptionKeyImg.cpp37
-rw-r--r--files/src/android/goldfish/Ext4.cpp0
-rw-r--r--files/src/android/goldfish/FileShareOpen.cpp156
-rw-r--r--files/src/android/goldfish/IniFile.cpp652
-rw-r--r--files/src/android/goldfish/ParameterList.cpp96
-rw-r--r--files/src/android/goldfish/RingStreambuf.cpp219
-rw-r--r--files/src/android/goldfish/avd/Avd.cpp234
-rw-r--r--files/src/android/goldfish/avd/keys.h100
-rw-r--r--files/src/android/goldfish/bootconfig.cpp131
-rw-r--r--files/test/android/goldfish/Avd_unittest.cpp63
-rw-r--r--files/test/android/goldfish/ConfigDirs_unittest.cpp169
-rw-r--r--files/test/android/goldfish/FileShareOpen_unittest.cpp149
-rw-r--r--files/test/android/goldfish/IniFile_unittest.cpp581
-rw-r--r--files/test/android/goldfish/ParameterList_unittest.cpp122
-rw-r--r--files/test/android/goldfish/bootconfig_unittest.cpp92
31 files changed, 5656 insertions, 0 deletions
diff --git a/cpu/BUILD.bazel b/cpu/BUILD.bazel
new file mode 100644
index 0000000..1d92e04
--- /dev/null
+++ b/cpu/BUILD.bazel
@@ -0,0 +1,42 @@
+cc_library(
+ name = "cpu",
+ srcs = glob([
+ "src/android/goldfish/cpu/*.cpp",
+ "src/android/goldfish/cpu/*.h",
+ ]),
+ hdrs = glob([
+ "include/android/goldfish/cpu/*.h",
+ ]),
+ includes = [
+ "include",
+ "src",
+ ],
+ # linkopts = ["-undefined error"],
+ visibility = ["//visibility:public"],
+ deps = [
+ "//hardware/generic/goldfish/logging:backend",
+ "//hardware/generic/goldfish/system",
+ "//hardware/google/aemu/base:aemu-base",
+ "@com_google_absl//absl/status:statusor",
+ "@com_google_absl//absl/strings",
+ "@com_google_absl//absl/strings:str_format",
+ ],
+)
+
+cc_test(
+ name = "cpu_unittests",
+ srcs = glob(
+ [
+ "test/**/*.cpp",
+ "test/**/*.h",
+ ],
+ exclude = ["test/**/Win32"],
+ ),
+ linkopts = ["-undefined error"],
+ deps = [
+ ":cpu",
+ "//hardware/generic/goldfish/logging:backend",
+ "//hardware/generic/goldfish/system:test-headers",
+ "@com_google_googletest//:gtest_main",
+ ],
+)
diff --git a/cpu/README.MD b/cpu/README.MD
new file mode 100644
index 0000000..d7ca202
--- /dev/null
+++ b/cpu/README.MD
@@ -0,0 +1,34 @@
+# Android CPU Acceleration and Virtualization Utilities**
+
+This package provides utilities essential for determining hardware acceleration capabilities and CPU features. It's designed to aid in making informed decisions about the best acceleration approach for optimal Android emulation.
+
+## Key Functionalities
+
+* **Acceleration Status Detection:**
+ * Determines whether hardware acceleration is available on the host machine.
+ * Identifies supported acceleration technologies (e.g., KVM, HAXM, Hypervisor.framework, WHPX)
+ * Provides detailed status information and explanations to guide developers.
+
+* **CPU Feature Identification:**
+ * Reports CPU vendor (Intel, AMD, or others).
+ * Detects whether the CPU supports virtualization technologies and advanced instruction sets (SSE, SSE2, SSE3, etc.)
+ * Determines if the host is running in a virtual machine environment.
+
+* **Hypervisor Support:**
+ * Checks the status of the Hyper-V hypervisor on Windows systems.
+ * Checks the availability and support for Apple's Hypervisor.framework on macOS systems.
+
+## Example Usage
+
+```c++
+#include "android/goldfish/cpu/CpuAccelerator.h"
+
+int main() {
+ if (GetCurrentCpuAccelerator() != CPU_ACCELERATOR_NONE) {
+ std::cout << "Hardware acceleration is available: "
+ << GetCurrentCpuAcceleratorStatus() << std::endl;
+ } else {
+ std::cout << "Hardware acceleration is not supported: "
+ << GetCurrentCpuAcceleratorStatus() << std::endl;
+ }
+}
diff --git a/cpu/include/android/goldfish/cpu/CpuAccelerator.h b/cpu/include/android/goldfish/cpu/CpuAccelerator.h
new file mode 100644
index 0000000..f6509ce
--- /dev/null
+++ b/cpu/include/android/goldfish/cpu/CpuAccelerator.h
@@ -0,0 +1,103 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// This software is licensed under the terms of the GNU General Public
+// License version 2, as published by the Free Software Foundation, and
+// may be copied, distributed, and modified under those terms.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+#pragma once
+
+#include <cstdlib>
+
+#include <string>
+#include <utility>
+
+#include "aemu/base/Version.h"
+#include "android/goldfish/cpu/cpu_accelerator.h"
+
+namespace android::goldfish {
+
+// The list of CPU emulation acceleration technologies supported by the
+// Android emulator.
+// CPU_ACCELERATOR_NONE means no acceleration is supported on this machine.
+//
+// CPU_ACCELERATOR_KVM means Linux KVM, which requires a specific driver
+// to be installed and that /dev/kvm is properly accessible by the current
+// user.
+//
+// CPU_ACCELERATOR_HAX means Intel's Hardware Accelerated eXecution,
+// which can be installed on Windows and OS X machines running on an
+// Intel processor.
+//
+// CPU_ACCELERATOR_HVF means Apple's Hypervisor.framework, which
+// requires an Intel Mac running OS X 10.10+.
+//
+// CPU_ACCELERATOR_WHPX means Windows Hypervisor Platform.
+//
+enum CpuAccelerator {
+ CPU_ACCELERATOR_NONE = 0,
+ CPU_ACCELERATOR_KVM,
+ CPU_ACCELERATOR_HAX,
+ CPU_ACCELERATOR_HVF,
+ CPU_ACCELERATOR_WHPX,
+ CPU_ACCELERATOR_AEHD,
+ CPU_ACCELERATOR_MAX,
+};
+
+// Returns whether or not the CPU supports all modern x86
+// virtualization features, so that we don't have to
+// use SMP = 1.
+bool hasModernX86VirtualizationFeatures();
+
+// Return the CPU accelerator technology usable on the current machine.
+// This only returns a non-CPU_ACCELERATOR_NONE if corresponding accelerator
+// can be used properly. Otherwise it will return CPU_ACCELERATOR_NONE.
+CpuAccelerator GetCurrentCpuAccelerator();
+void ResetCurrentCpuAccelerator(CpuAccelerator accel);
+
+// Returns whether or not the accelerator |type| is suppored
+// on the current system.
+bool GetCurrentAcceleratorSupport(CpuAccelerator type);
+
+// Return an ASCII string describing the state of the current CPU
+// acceleration on this machine. If GetCurrentCpuAccelerator() returns
+// CPU_ACCELERATOR_NONE this will contain a small explanation why
+// the accelerator cannot be used.
+std::string GetCurrentCpuAcceleratorStatus();
+
+// Return the version for the CPU accelerator technology usable
+// on the current machine.
+android::base::Version GetCurrentCpuAcceleratorVersion();
+
+// Convert CpuAccelerator to string type
+std::string CpuAcceleratorToString(CpuAccelerator type);
+
+// Return an status code describing the state of the current CPU
+// acceleration on this machine. If GetCurrentCpuAccelerator() returns
+// CPU_ACCELERATOR_NONE this will contain a small explanation why
+// the accelerator cannot be used.
+AndroidCpuAcceleration GetCurrentCpuAcceleratorStatusCode();
+
+// For unit testing/debugging purpose only, must be called before
+// GetCurrentCpuAccelerator().
+void SetCurrentCpuAcceleratorForTesting(CpuAccelerator accel,
+ AndroidCpuAcceleration status_code,
+ const char *status);
+
+// Returns the Hyper-V configuration of the current system
+// and a short message describing it.
+std::pair<AndroidHyperVStatus, std::string> GetHyperVStatus();
+
+// Returns a set of AndroidCpuInfoFlags describing the CPU capabilities
+// (and a text explanation as well)
+std::pair<AndroidCpuInfoFlags, std::string> GetCpuInfo();
+
+// For testing
+base::Version parseMacOSVersionString(const std::string &str,
+ std::string *status);
+
+} // namespace android::goldfish
diff --git a/cpu/include/android/goldfish/cpu/cpu_accelerator.h b/cpu/include/android/goldfish/cpu/cpu_accelerator.h
new file mode 100644
index 0000000..c329af5
--- /dev/null
+++ b/cpu/include/android/goldfish/cpu/cpu_accelerator.h
@@ -0,0 +1,116 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// This software is licensed under the terms of the GNU General Public
+// License version 2, as published by the Free Software Foundation, and
+// may be copied, distributed, and modified under those terms.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+#pragma once
+
+#include "android/utils/compiler.h"
+
+ANDROID_BEGIN_HEADER
+
+// don't change these numbers
+// Android Studio depends on them
+using AndroidCpuAcceleration = enum {
+ ANDROID_CPU_ACCELERATION_READY = 0, // Acceleration is available
+ ANDROID_CPU_ACCELERATION_NESTED_NOT_SUPPORTED =
+ 1, // HAXM doesn't support nested VM
+ ANDROID_CPU_ACCELERATION_INTEL_REQUIRED =
+ 2, // HAXM requires GeniuneIntel processor
+ ANDROID_CPU_ACCELERATION_NO_CPU_SUPPORT =
+ 3, // CPU doesn't support required features (VT-x or SVM)
+ ANDROID_CPU_ACCELERATION_NO_CPU_VTX_SUPPORT = 4, // CPU doesn't support VT-x
+ ANDROID_CPU_ACCELERATION_NO_CPU_NX_SUPPORT = 5, // CPU doesn't support NX
+ ANDROID_CPU_ACCELERATION_ACCEL_NOT_INSTALLED =
+ 6, // KVM/HAXM package not installed
+ ANDROID_CPU_ACCELERATION_ACCEL_OBSOLETE = 7, // HAXM package is obsolete
+ ANDROID_CPU_ACCELERATION_DEV_NOT_FOUND =
+ 8, // /dev/kvm is not found: VT disabled in BIOS or KVM kernel module not
+ // loaded
+ ANDROID_CPU_ACCELERATION_VT_DISABLED =
+ 9, // HAXM is installed but VT disabled in BIOS
+ ANDROID_CPU_ACCELERATION_NX_DISABLED =
+ 10, // HAXM is installed but NX disabled in BIOS
+ ANDROID_CPU_ACCELERATION_DEV_PERMISSION =
+ 11, // /dev/kvm or HAXM device: permission denied
+ ANDROID_CPU_ACCELERATION_DEV_OPEN_FAILED =
+ 12, // /dev/kvm or HAXM device: open failed
+ ANDROID_CPU_ACCELERATION_DEV_IOCTL_FAILED =
+ 13, // /dev/kvm or HAXM device: ioctl failed
+ ANDROID_CPU_ACCELERATION_DEV_OBSOLETE =
+ 14, // KVM or HAXM supported API is too old
+ ANDROID_CPU_ACCELERATION_HYPERV_ENABLED =
+ 15, // HyperV must be disabled, unless WHPX is available
+ ANDROID_CPU_ACCELERATION_ERROR = 138, // Some other error occurred
+};
+
+// ditto
+using AndroidHyperVStatus = enum {
+ ANDROID_HYPERV_ABSENT = 0, // No hyper-V found
+ ANDROID_HYPERV_INSTALLED = 1, // Hyper-V is installed but not running
+ ANDROID_HYPERV_RUNNING = 2, // Hyper-V is up and running
+ ANDROID_HYPERV_ERROR = 100, // Failed to detect status
+};
+
+// For any possible Android Studio Hypervisor.framework detection
+using AndroidHVFStatus = enum {
+ ANDROID_HVF_UNSUPPORTED = 0, // No Hypervisor.framework support on host system
+ ANDROID_HVF_SUPPORTED = 1, // Hypervisor.framework is supported
+ ANDROID_HVF_ERROR = 100, // Failed to detect status
+};
+
+// +1, cpu info
+using AndroidCpuInfoFlags = enum {
+ ANDROID_CPU_INFO_FAILED = 0, // this is the value to return if something
+ // went wrong
+
+ ANDROID_CPU_INFO_AMD = 1 << 0, // AMD CPU
+ ANDROID_CPU_INFO_INTEL = 1 << 1, // Intel CPU
+ ANDROID_CPU_INFO_OTHER = 1 << 2, // Other CPU manufacturer
+ ANDROID_CPU_INFO_VM = 1 << 3, // Running in a VM
+ ANDROID_CPU_INFO_VIRT_SUPPORTED = 1 << 4, // CPU supports
+ // virtualization technologies
+
+ ANDROID_CPU_INFO_32_BIT = 1 << 5, // 32-bit CPU, really!
+ ANDROID_CPU_INFO_64_BIT_32_BIT_OS = 1 << 6, // 32-bit OS on a 64-bit CPU
+ ANDROID_CPU_INFO_64_BIT = 1 << 7, // 64-bit CPU
+ ANDROID_CPU_INFO_APPLE = 1 << 8, // Apple CPU (such as M1, M2, ...)
+
+};
+
+/* Returns ANDROID_CPU_ACCELERATION_READY if CPU acceleration is
+ * possible on this machine. If |status| is not NULL, on exit,
+ * |*status| will be set to a heap-allocated string describing
+ * the status of acceleration, to be freed by the caller.
+ */
+AndroidCpuAcceleration androidCpuAcceleration_getStatus(char **status);
+
+using AndroidCpuAccelerator = enum {
+ ANDROID_CPU_ACCELERATOR_NONE = 0,
+ ANDROID_CPU_ACCELERATOR_KVM,
+ ANDROID_CPU_ACCELERATOR_HAX,
+ ANDROID_CPU_ACCELERATOR_HVF,
+ ANDROID_CPU_ACCELERATOR_WHPX,
+ ANDROID_CPU_ACCELERATOR_AEHD,
+ ANDROID_CPU_ACCELERATOR_MAX,
+};
+
+bool androidCpuAcceleration_hasModernX86VirtualizationFeatures();
+
+/* Returns the auto-selected CPU accelerator. */
+AndroidCpuAccelerator androidCpuAcceleration_getAccelerator();
+
+/* Returns support status of the cpu accelerator |type| on the current machine.
+ */
+bool androidCpuAcceleration_isAcceleratorSupported(AndroidCpuAccelerator type);
+
+/* Resets the current cpu accelerator to reflect current status. */
+void androidCpuAcceleration_resetCpuAccelerator(AndroidCpuAccelerator type);
+
+ANDROID_END_HEADER
diff --git a/cpu/src/android/goldfish/cpu/AppleCpuAccelerator.h b/cpu/src/android/goldfish/cpu/AppleCpuAccelerator.h
new file mode 100644
index 0000000..bd49ee8
--- /dev/null
+++ b/cpu/src/android/goldfish/cpu/AppleCpuAccelerator.h
@@ -0,0 +1,50 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// This software is licensed under the terms of the GNU General Public
+// License version 2, as published by the Free Software Foundation, and
+// may be copied, distributed, and modified under those terms.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+#pragma once
+
+#include <cstdlib>
+#include <string>
+
+#include "android/goldfish/cpu//cpu_accelerator.h"
+
+namespace android::goldfish {
+
+#ifdef __APPLE__
+
+// This function takes a string in the format "VERSION=1.2.3" and returns an
+// integer that represents the dotted version number. Invalid strings are
+// converted to -1.
+// Any characters before "VERSION=" are ignored.
+// Non-numeric characters at the end are ignored.
+//
+// Examples:
+// "VERSION=1.2.4" -> 0x01020004
+// "VERSION=1.2" -> 0x01020000
+// "1.2.4" -> -1
+// "asfd" -> -1
+
+int32_t cpuAcceleratorParseVersionScript(const std::string &version_script);
+
+// This function searches one or more kernel extension directories for the
+// specified version file.
+// The version file is read and parsed if found
+// cpuAcceleratorParseVersionScript
+// returns 0 - HAXM not installed
+// returns -1 - HAXM corrupt or too old
+// returns 0x01020004 for version version number e.g. "1.2.4"
+int32_t cpuAcceleratorGetHaxVersion(const char *kext_dir[],
+ const size_t kext_dir_count,
+ const char *version_file);
+
+#endif
+
+} // namespace android::goldfish
diff --git a/cpu/src/android/goldfish/cpu/CpuAccelerator.cpp b/cpu/src/android/goldfish/cpu/CpuAccelerator.cpp
new file mode 100644
index 0000000..a8b9a35
--- /dev/null
+++ b/cpu/src/android/goldfish/cpu/CpuAccelerator.cpp
@@ -0,0 +1,1087 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// This software is licensed under the terms of the GNU General Public
+// License version 2, as published by the Free Software Foundation, and
+// may be copied, distributed, and modified under those terms.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+#define CPU_ACCELERATOR_PRIVATE
+#include "android/goldfish/cpu/CpuAccelerator.h"
+
+#include "absl//strings/str_format.h"
+#ifdef _WIN32
+#define WIN32_LEAN_AND_MEAN 1
+#include <windows.h>
+#include <winioctl.h>
+#else
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+
+#include <cstring>
+#ifndef _MSC_VER
+#include <unistd.h>
+#endif
+#endif
+
+#include <cstdio>
+
+#include "aemu/base/Compiler.h"
+#include "aemu/base/Log.h"
+#include "aemu/base/StringFormat.h"
+#include "aemu/base/files/ScopedFd.h"
+#include "aemu/base/memory/ScopedPtr.h"
+#include "aemu/base/misc/FileUtils.h"
+#include "aemu/base/misc/StringUtils.h"
+#include "android/base/system/System.h"
+#include "android/goldfish/cpu/cpu_accelerator.h"
+#include "android/goldfish/cpu/x86_cpuid.h"
+#include "host-common/FeatureControl.h"
+
+#ifdef _WIN32
+#include "aemu/base/files/PathUtils.h"
+#include "aemu/base/files/ScopedFileHandle.h"
+#include "aemu/base/system/Win32UnicodeString.h"
+#include "aemu/base/system/Win32Utils.h"
+#include "android/windows_installer.h"
+#endif
+
+#ifdef __APPLE__
+#include "android/goldfish/cpu/AppleCpuAccelerator.h"
+#endif
+
+#include <array>
+#include <string_view>
+
+// NOTE: This source file must be independent of the rest of QEMU, as such
+// it should not include / reuse any QEMU source file or function
+// related to KVM or HAX.
+
+#ifdef __linux__
+#define HAVE_KVM 1
+
+#elif defined(_WIN32)
+#define HAVE_WHPX 1
+#define HAVE_AEHD 1
+#define HAVE_HAX 1
+
+#elif defined(__APPLE__)
+#define HAVE_HVF 1
+#ifdef __arm64__
+#define APPLE_SILICON 1
+#endif
+
+#else
+#error "Unsupported host platform!"
+#endif
+
+namespace android::goldfish {
+
+using base::Version;
+
+// For detecting that the cpu can run with fast virtualization
+// without requiring workarounds such as resetting SMP=1.
+//
+// Technically, we need rdmsr to detect ept/ug support,
+// but that instruction is only available
+// as root. So just say yes if the processor has features that were
+// only introduced the same time as the feature.
+// EPT: popcnt
+// UG: aes + pclmulqsq
+bool hasModernX86VirtualizationFeatures() {
+ uint32_t cpuid_function1_ecx;
+ android_get_x86_cpuid(1, 0, nullptr, nullptr, &cpuid_function1_ecx, nullptr);
+
+ uint32_t popcnt_support = 1 << 23;
+ uint32_t aes_support = 1 << 25;
+ uint32_t pclmulqsq_support = 1 << 1;
+
+ bool eptSupport = (cpuid_function1_ecx & popcnt_support) != 0u;
+ bool ugSupport = ((cpuid_function1_ecx & aes_support) != 0u) &&
+ ((cpuid_function1_ecx & pclmulqsq_support) != 0u);
+
+ return eptSupport && ugSupport;
+}
+
+struct GlobalState {
+ bool probed;
+ bool testing;
+ CpuAccelerator accel;
+ char status[256];
+ char version[256];
+ AndroidCpuAcceleration status_code;
+ std::array<bool, CPU_ACCELERATOR_MAX> supported_accelerators;
+};
+
+GlobalState gGlobals = {false, false, CPU_ACCELERATOR_NONE,
+ {'\0'}, {'\0'}, ANDROID_CPU_ACCELERATION_ERROR,
+ {}};
+
+// Windows Hypervisor Platform (WHPX) support
+
+#if HAVE_WHPX
+
+#include <WinHvEmulation.h>
+#include <WinHvPlatform.h>
+#include <windows.h>
+
+#define WHPX_DBG(...) VERBOSE_PRINT(init, __VA_ARGS__)
+
+static bool isOkToTryWHPX() {
+ return featurecontrol::isEnabled(featurecontrol::WindowsHypervisorPlatform);
+}
+
+AndroidCpuAcceleration ProbeWHPX(std::string *status) {
+ HRESULT hr;
+ WHV_CAPABILITY whpx_cap;
+ UINT32 whpx_cap_size;
+ bool acc_available = true;
+ HMODULE hWinHvPlatform;
+
+ typedef HRESULT(WINAPI * WHvGetCapability_t)(WHV_CAPABILITY_CODE, VOID *,
+ UINT32, UINT32 *);
+
+ WHPX_DBG("Checking whether Windows Hypervisor Platform (WHPX) is "
+ "available.");
+
+ hWinHvPlatform = LoadLibraryW(L"WinHvPlatform.dll");
+ if (hWinHvPlatform) {
+ WHPX_DBG("WinHvPlatform.dll found. Looking for WHvGetCapability...");
+ WHvGetCapability_t f_WHvGetCapability =
+ (WHvGetCapability_t)GetProcAddress(hWinHvPlatform, "WHvGetCapability");
+ if (f_WHvGetCapability) {
+ WHPX_DBG("WHvGetCapability found. Querying WHPX capabilities...");
+ hr = f_WHvGetCapability(WHvCapabilityCodeHypervisorPresent, &whpx_cap,
+ sizeof(whpx_cap), &whpx_cap_size);
+ if (FAILED(hr) || !whpx_cap.HypervisorPresent) {
+ WHPX_DBG("WHvGetCapability failed. hr=0x%08lx "
+ "whpx_cap.HypervisorPresent? %d\n",
+ hr, whpx_cap.HypervisorPresent);
+ StringAppendFormat(status, "WHPX: No accelerator found, hr=%08lx.", hr);
+ acc_available = false;
+ }
+ } else {
+ WHPX_DBG("Could not load library function 'WHvGetCapability'.");
+ status->assign("Could not load library function 'WHvGetCapability'.");
+ acc_available = false;
+ }
+ } else {
+ WHPX_DBG("Could not load library WinHvPlatform.dll");
+ status->assign("Could not load library 'WinHvPlatform.dll'.");
+ acc_available = false;
+ }
+
+ if (hWinHvPlatform)
+ FreeLibrary(hWinHvPlatform);
+
+ if (!acc_available) {
+ WHPX_DBG("WHPX is either not available or not installed.");
+ return ANDROID_CPU_ACCELERATION_ACCEL_NOT_INSTALLED;
+ }
+
+ auto ver = android::base::Win32Utils::getWindowsVersion();
+ if (!ver) {
+ WHPX_DBG("Could not extract Windows version.");
+ status->assign("Could not extract Windows version.");
+ return ANDROID_CPU_ACCELERATION_ERROR;
+ }
+
+ char version_str[32];
+ snprintf(version_str, sizeof(version_str), "%lu.%lu.%lu", ver->dwMajorVersion,
+ ver->dwMinorVersion, ver->dwBuildNumber);
+
+ WHPX_DBG("WHPX (%s) is installed and usable.", version_str);
+ StringAppendFormat(status, "WHPX (%s) is installed and usable.", version_str);
+ GlobalState *g = &gGlobals;
+ ::snprintf(g->version, sizeof(g->version), "%s", version_str);
+ return ANDROID_CPU_ACCELERATION_READY;
+}
+
+#endif // HAVE_WHPX
+
+/////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////
+/////
+///// Linux KVM support.
+/////
+/////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////
+
+#if HAVE_KVM
+
+#include <linux/kvm.h>
+
+#include "android/emulation/kvm_env.h"
+
+// Return true iff KVM is installed and usable on this machine.
+// |*status| will be set to a small status string explaining the
+// status of KVM on success or failure.
+AndroidCpuAcceleration ProbeKVM(std::string *status) {
+ const char *kvm_device = getenv(KVM_DEVICE_NAME_ENV);
+ if (NULL == kvm_device) {
+ kvm_device = "/dev/kvm";
+ }
+ // Check that kvm device exists.
+ if (::android_access(kvm_device, F_OK)) {
+ // kvm device does not exist
+ bool cpu_ok = android_get_x86_cpuid_vmx_support() ||
+ android_get_x86_cpuid_svm_support();
+ if (!cpu_ok) {
+ status->assign("KVM requires a CPU that supports vmx or svm");
+ return ANDROID_CPU_ACCELERATION_NO_CPU_SUPPORT;
+ }
+ StringAppendFormat(status,
+ "%s is not found: VT disabled in BIOS or KVM kernel "
+ "module not loaded",
+ kvm_device);
+ return ANDROID_CPU_ACCELERATION_DEV_NOT_FOUND;
+ }
+
+ // Check that kvm device can be opened.
+ if (::android_access(kvm_device, R_OK)) {
+ const char *kEtcGroupsPath = "/etc/group";
+ std::string etcGroupsKvmLine("LINE_NOT_FOUND");
+ const auto fileContents = android::readFileIntoString(kEtcGroupsPath);
+
+ if (fileContents) {
+ base::split<std::string>(*fileContents, std::string("\n"),
+ [&etcGroupsKvmLine](const std::string &line) {
+ if (!strncmp("kvm:", line.data(), 4)) {
+ etcGroupsKvmLine = line.data();
+ }
+ });
+ }
+
+ StringAppendFormat(
+ status,
+ "This user doesn't have permissions to use KVM (%s).\n"
+ "The KVM line in /etc/group is: [%s]\n"
+ "\n"
+ "If the current user has KVM permissions,\n"
+ "the KVM line in /etc/group should end with \":\" followed by "
+ "your username.\n"
+ "\n"
+ "If we see LINE_NOT_FOUND, the kvm group may need to be "
+ "created along with permissions:\n"
+ " sudo groupadd -r kvm\n"
+ " # Then ensure /lib/udev/rules.d/50-udev-default.rules "
+ "contains something like:\n"
+ " # KERNEL==\"kvm\", GROUP=\"kvm\", MODE=\"0660\"\n"
+ " # and then run:\n"
+ " sudo gpasswd -a $USER kvm\n"
+ "\n"
+ "If we see kvm:... but no username at the end, running the "
+ "following command may allow KVM access:\n"
+ " sudo gpasswd -a $USER kvm\n"
+ "\n"
+ "You may need to log out and back in for changes to take "
+ "effect.\n",
+ kvm_device, etcGroupsKvmLine.c_str());
+
+ // There are issues with ensuring the above is actually printed. Print
+ // it now.
+ fprintf(stderr, "%s: %s\n", __func__, status->c_str());
+ return ANDROID_CPU_ACCELERATION_DEV_PERMISSION;
+ }
+
+ // Open the file.
+ ScopedFd fd(TEMP_FAILURE_RETRY(open(kvm_device, O_RDWR)));
+ if (!fd.valid()) {
+ StringAppendFormat(status, "Could not open %s : %s", kvm_device,
+ strerror(errno));
+ return ANDROID_CPU_ACCELERATION_DEV_OPEN_FAILED;
+ }
+
+ // Extract KVM version number.
+ int version = ::ioctl(fd.get(), KVM_GET_API_VERSION, 0);
+ if (version < 0) {
+ status->assign("Could not extract KVM version: ");
+ status->append(strerror(errno));
+ return ANDROID_CPU_ACCELERATION_DEV_IOCTL_FAILED;
+ }
+
+ // Compare to minimum supported version
+ status->clear();
+
+ if (version < KVM_API_VERSION) {
+ StringAppendFormat(status,
+ "KVM version too old: %d (expected at least %d)\n",
+ version, KVM_API_VERSION);
+ return ANDROID_CPU_ACCELERATION_DEV_OBSOLETE;
+ }
+
+ // Profit!
+ StringAppendFormat(status, "KVM (version %d) is installed and usable.",
+ version);
+ GlobalState *g = &gGlobals;
+ ::snprintf(g->version, sizeof(g->version), "%d", version);
+ return ANDROID_CPU_ACCELERATION_READY;
+}
+
+#endif // HAVE_KVM
+
+#if HAVE_HAX
+
+#define HAXM_INSTALLER_VERSION_MINIMUM 0x06000001
+#define HAXM_INSTALLER_VERSION_MINIMUM_APPLE 0x6020001
+#define HAXM_INSTALLER_VERSION_RECOMMENDED 0x7060005
+#define HAXM_INSTALLER_VERSION_INCOMPATIBLE 0x7080000
+
+std::string cpuAcceleratorFormatVersion(int32_t version) {
+ if (version < 0) {
+ return "<invalid>";
+ }
+ char buf[16]; // strlen("127.255.65535")+1 = 14
+ int32_t revision = version & 0xffff;
+ version >>= 16;
+ int32_t minor = version & 0xff;
+ version >>= 8;
+ int32_t major = version & 0x7f;
+ snprintf(buf, sizeof(buf), "%i.%i.%i", major, minor, revision);
+ return buf;
+}
+
+// Version numbers for the HAX kernel module.
+// |compat_version| is the minimum API version supported by the module.
+// |current_version| is its current API version.
+struct HaxModuleVersion {
+ uint32_t compat_version;
+ uint32_t current_version;
+};
+
+/*
+ * ProbeHaxCpu: returns ANDROID_CPU_ACCELERATION_READY if the CPU supports
+ * HAXM requirements.
+ *
+ * Otherwise returns some other AndroidCpuAcceleration status and sets
+ * |status| to a user-understandable error string
+ */
+AndroidCpuAcceleration ProbeHaxCpu(std::string *status) {
+ char vendor_id[16];
+ android_get_x86_cpuid_vendor_id(vendor_id, sizeof(vendor_id));
+
+ if (!android_get_x86_cpuid_vmx_support()) {
+ status->assign(
+ "Android Emulator requires an Intel processor with VT-x and NX "
+ "support. "
+ "(VT-x is not supported)");
+ return ANDROID_CPU_ACCELERATION_NO_CPU_VTX_SUPPORT;
+ }
+
+ if (!android_get_x86_cpuid_nx_support()) {
+ status->assign(
+ "Android Emulator requires an Intel processor with VT-x and NX "
+ "support. "
+ "(NX is not supported)");
+ return ANDROID_CPU_ACCELERATION_NO_CPU_NX_SUPPORT;
+ }
+
+ return ANDROID_CPU_ACCELERATION_READY;
+}
+
+/////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////
+/////
+///// Windows HAX support.
+/////
+/////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////
+
+#include "android/base/system/System.h"
+
+using ::android::base::System;
+using base::ScopedFileHandle;
+using namespace android;
+
+// Windows IOCTL code to extract HAX kernel module version.
+#define HAX_DEVICE_TYPE 0x4000
+#define HAX_IOCTL_VERSION \
+ CTL_CODE(HAX_DEVICE_TYPE, 0x900, METHOD_BUFFERED, FILE_ANY_ACCESS)
+#define HAX_IOCTL_CAPABILITY \
+ CTL_CODE(HAX_DEVICE_TYPE, 0x910, METHOD_BUFFERED, FILE_ANY_ACCESS)
+
+// The minimum API version supported by the Android emulator.
+#define HAX_MIN_VERSION 3 // 6.0.0
+
+// IMPORTANT: Keep in sync with target-i386/hax-interface.h
+struct hax_capabilityinfo {
+ /* bit 0: 1 - HAXM is working
+ * 0 - HAXM is not working possibly because VT/NX is disabled
+ NX means Non-eXecution, aks. XD (eXecution Disable)
+ * bit 1: 1 - HAXM has hard limit on how many RAM can be used as guest RAM
+ * 0 - HAXM has no memory limitation
+ */
+#define HAX_CAP_STATUS_WORKING 0x1
+#define HAX_CAP_STATUS_NOTWORKING 0x0
+#define HAX_CAP_WORKSTATUS_MASK 0x1
+#define HAX_CAP_MEMQUOTA 0x2
+ uint16_t wstatus;
+ /*
+ * valid when HAXM is not working
+ * bit 0: HAXM is not working because VT is not enabeld
+ * bit 1: HAXM is not working because NX not enabled
+ */
+#define HAX_CAP_FAILREASON_VT 0x1
+#define HAX_CAP_FAILREASON_NX 0x2
+ uint16_t winfo;
+ uint32_t pad;
+ uint64_t mem_quota;
+};
+
+AndroidCpuAcceleration ProbeHAX(std::string *status) {
+ status->clear();
+
+ int32_t haxm_installer_version = 0;
+
+ AndroidCpuAcceleration cpu = ProbeHaxCpu(status);
+ if (cpu != ANDROID_CPU_ACCELERATION_READY)
+ return cpu;
+
+ std::string HaxInstallerCheck =
+ System::get()->envGet("HAXM_BYPASS_INSTALLER_CHECK");
+ if (HaxInstallerCheck.compare("1")) {
+ const char *productDisplayName =
+ u8"Intel® Hardware Accelerated Execution Manager";
+ haxm_installer_version = WindowsInstaller::getVersion(productDisplayName);
+ if (haxm_installer_version == 0) {
+ status->assign("HAXM is not installed on this machine");
+ return ANDROID_CPU_ACCELERATION_ACCEL_NOT_INSTALLED;
+ }
+
+ if (haxm_installer_version < HAXM_INSTALLER_VERSION_MINIMUM) {
+ StringAppendFormat(
+ status, "HAXM must be updated (version %s < %s).",
+ cpuAcceleratorFormatVersion(haxm_installer_version),
+ cpuAcceleratorFormatVersion(HAXM_INSTALLER_VERSION_MINIMUM));
+ return ANDROID_CPU_ACCELERATION_ACCEL_OBSOLETE;
+ }
+
+ if (haxm_installer_version >= HAXM_INSTALLER_VERSION_INCOMPATIBLE) {
+ StringAppendFormat(status,
+ "HAXM (version %s) is not compatible with the "
+ "android emulator. Version 7.6.5 is recommended.",
+ cpuAcceleratorFormatVersion(haxm_installer_version));
+ return ANDROID_CPU_ACCELERATION_ACCEL_OBSOLETE;
+ }
+ }
+
+ // 1) Try to find the HAX kernel module.
+ ScopedFileHandle hax(CreateFile("\\\\.\\HAX", GENERIC_READ | GENERIC_WRITE, 0,
+ NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL,
+ NULL));
+ if (!hax.valid()) {
+ DWORD err = GetLastError();
+ if (err == ERROR_FILE_NOT_FOUND) {
+ status->assign("Unable to open HAXM device: ERROR_FILE_NOT_FOUND");
+ return ANDROID_CPU_ACCELERATION_DEV_NOT_FOUND;
+ } else if (err == ERROR_ACCESS_DENIED) {
+ status->assign("Unable to open HAXM device: ERROR_ACCESS_DENIED");
+ return ANDROID_CPU_ACCELERATION_DEV_PERMISSION;
+ }
+ StringAppendFormat(status, "Opening HAX kernel module failed: %u", err);
+ return ANDROID_CPU_ACCELERATION_DEV_OPEN_FAILED;
+ }
+
+ // 2) Extract the module's version.
+ HaxModuleVersion hax_version;
+
+ DWORD dSize = 0;
+ BOOL ret =
+ DeviceIoControl(hax.get(), HAX_IOCTL_VERSION, NULL, 0, &hax_version,
+ sizeof(hax_version), &dSize, (LPOVERLAPPED)NULL);
+ if (!ret) {
+ DWORD err = GetLastError();
+ StringAppendFormat(status, "Could not extract HAX module version: %u", err);
+ return ANDROID_CPU_ACCELERATION_DEV_IOCTL_FAILED;
+ }
+
+ // 3) Check that it is the right version.
+ if (hax_version.current_version < HAX_MIN_VERSION) {
+ StringAppendFormat(status,
+ "HAX version (%d) is too old (need at least %d).",
+ hax_version.current_version, HAX_MIN_VERSION);
+ return ANDROID_CPU_ACCELERATION_DEV_OBSOLETE;
+ }
+
+ hax_capabilityinfo cap = {};
+ ret = DeviceIoControl(hax.get(), HAX_IOCTL_CAPABILITY, NULL, 0, &cap,
+ sizeof(cap), &dSize, (LPOVERLAPPED)NULL);
+
+ if (!ret) {
+ DWORD err = GetLastError();
+ StringAppendFormat(status, "Could not extract HAX capability: %u", err);
+ return ANDROID_CPU_ACCELERATION_DEV_IOCTL_FAILED;
+ }
+
+ if ((cap.wstatus & HAX_CAP_WORKSTATUS_MASK) == HAX_CAP_STATUS_NOTWORKING) {
+ if (cap.winfo & HAX_CAP_FAILREASON_VT) {
+ status->assign("VT feature disabled in BIOS/UEFI");
+ return ANDROID_CPU_ACCELERATION_VT_DISABLED;
+ } else if (cap.winfo & HAX_CAP_FAILREASON_NX) {
+ status->assign("NX feature disabled in BIOS/UEFI");
+ return ANDROID_CPU_ACCELERATION_NX_DISABLED;
+ }
+ }
+
+ // 4) Profit!
+ StringAppendFormat(status, "HAXM version %s (%d) is installed and usable.",
+ cpuAcceleratorFormatVersion(haxm_installer_version),
+ hax_version.current_version);
+ if (haxm_installer_version > HAXM_INSTALLER_VERSION_RECOMMENDED)
+ StringAppendFormat(status,
+ " Warning: HAXM version greater than 7.6.5 is not "
+ "recommended. Some AVDs may fail to boot.");
+ GlobalState *g = &gGlobals;
+ ::snprintf(g->version, sizeof(g->version), "%s",
+ cpuAcceleratorFormatVersion(haxm_installer_version).c_str());
+ return ANDROID_CPU_ACCELERATION_READY;
+}
+#endif // HAVE_HAX
+
+/////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////
+/////
+///// Darwin Hypervisor.framework support.
+/////
+/////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////
+
+#if HAVE_HVF
+
+using android::base::System;
+Version currentMacOSVersion(std::string *status) {
+ std::string osProductVersion = System::get()->getOsName();
+ return parseMacOSVersionString(osProductVersion, status);
+}
+
+AndroidCpuAcceleration ProbeHVF(std::string *status) {
+ status->clear();
+
+ AndroidCpuAcceleration res = ANDROID_CPU_ACCELERATION_NO_CPU_SUPPORT;
+
+ // Hypervisor.framework is only supported on OS X 10.10 and above.
+ auto macOsVersion = currentMacOSVersion(status);
+ if (macOsVersion < Version(10, 10, 0)) {
+ status->append("Hypervisor.Framework is only supported"
+ "on OS X 10.10 and above");
+ return res;
+ }
+
+#ifndef APPLE_SILICON
+ // HVF need EPT and UG
+ if (!android::hasModernX86VirtualizationFeatures()) {
+ status->assign("CPU doesn't support EPT and/or UG features "
+ "needed for Hypervisor.Framework");
+ return res;
+ }
+#endif
+
+ // HVF supported
+ res = ANDROID_CPU_ACCELERATION_READY;
+
+ GlobalState *g = &gGlobals;
+ int maj = macOsVersion.component<Version::kMajor>();
+ int min = macOsVersion.component<Version::kMinor>();
+ ::snprintf(g->version, sizeof(g->version), "%d.%d", maj, min);
+ status->append(
+ absl::StrFormat("Hypervisor.Framework OS X Version %d.%d", maj, min));
+ return res;
+}
+#endif // HAVE_HVF
+
+// Windows AEHD hypervisor support
+
+#if HAVE_AEHD
+
+#include "android/base/system/System.h"
+
+using ::android::base::System;
+
+// Windows IOCTL code to extract AEHD version.
+#define FILE_DEVICE_AEHD 0xE3E3
+#define AEHD_GET_API_VERSION \
+ CTL_CODE(FILE_DEVICE_AEHD, 0x00, METHOD_BUFFERED, FILE_ANY_ACCESS)
+
+/*
+ * ProbeAEHDCpu: returns ANDROID_CPU_ACCELERATION_READY if the CPU supports
+ * AEHD requirements.
+ *
+ * Otherwise returns some other AndroidCpuAcceleration status and sets
+ * |status| to a user-understandable error string
+ */
+AndroidCpuAcceleration ProbeAEHDCpu(std::string *status) {
+ char vendor_id[16];
+ android_get_x86_cpuid_vendor_id(vendor_id, sizeof(vendor_id));
+
+ auto cpu_vendor_type = android_get_x86_cpuid_vendor_id_type(vendor_id);
+ if (cpu_vendor_type == VENDOR_ID_AMD &&
+ !android_get_x86_cpuid_svm_support() ||
+ cpu_vendor_type == VENDOR_ID_INTEL &&
+ !android_get_x86_cpuid_vmx_support()) {
+ status->assign(
+ "Android Emulator requires an Intel/AMD processor with virtualization "
+ "extension support. "
+ "(Virtualization extension is not supported)");
+ return ANDROID_CPU_ACCELERATION_NO_CPU_SUPPORT;
+ }
+
+ return ANDROID_CPU_ACCELERATION_READY;
+}
+
+AndroidCpuAcceleration ProbeAEHD(std::string *status) {
+ status->clear();
+
+ AndroidCpuAcceleration cpu = ProbeAEHDCpu(status);
+ if (cpu != ANDROID_CPU_ACCELERATION_READY)
+ return cpu;
+
+ ScopedFileHandle aehd(CreateFile("\\\\.\\AEHD", GENERIC_READ | GENERIC_WRITE,
+ 0, NULL, CREATE_ALWAYS,
+ FILE_ATTRIBUTE_NORMAL, NULL));
+ if (aehd.valid())
+ goto success;
+ ScopedFileHandle gvm(CreateFile("\\\\.\\gvm", GENERIC_READ | GENERIC_WRITE, 0,
+ NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL,
+ NULL));
+ if (!aehd.valid() && !gvm.valid()) {
+ DWORD err = GetLastError();
+ if (err == ERROR_FILE_NOT_FOUND) {
+ status->assign("Android Emulator hypervisor driver is not installed"
+ " on this machine");
+ return ANDROID_CPU_ACCELERATION_ACCEL_NOT_INSTALLED;
+ } else if (err == ERROR_ACCESS_DENIED) {
+ status->assign("Unable to open AEHD device: ERROR_ACCESS_DENIED");
+ return ANDROID_CPU_ACCELERATION_DEV_PERMISSION;
+ }
+ StringAppendFormat(status,
+ "Opening Android Emulator hypervisor driver"
+ " failed: %u",
+ err);
+ return ANDROID_CPU_ACCELERATION_DEV_OPEN_FAILED;
+ }
+
+success:
+ int version;
+
+ DWORD dSize = 0;
+ BOOL ret = DeviceIoControl(aehd.valid() ? aehd.get() : gvm.get(),
+ AEHD_GET_API_VERSION, NULL, 0, &version,
+ sizeof(version), &dSize, (LPOVERLAPPED)NULL);
+ if (!ret) {
+ DWORD err = GetLastError();
+ StringAppendFormat(status, "Could not extract AEHD version: %u", err);
+ return ANDROID_CPU_ACCELERATION_DEV_IOCTL_FAILED;
+ }
+
+ // Profit!
+ StringAppendFormat(status, "AEHD (version %d.%d) is installed and usable.",
+ version >> 16, version & 0xFFFF);
+ GlobalState *g = &gGlobals;
+ ::snprintf(g->version, sizeof(g->version), "%d", version);
+ return ANDROID_CPU_ACCELERATION_READY;
+}
+
+#endif // HAVE_AEHD
+
+CpuAccelerator GetCurrentCpuAccelerator() {
+ GlobalState *g = &gGlobals;
+
+ if (g->probed || g->testing) {
+ return g->accel;
+ }
+
+ g->supported_accelerators = {};
+
+ std::string status;
+ AndroidCpuAcceleration status_code = ANDROID_CPU_ACCELERATION_ERROR;
+
+#if !HAVE_KVM
+#if APPLE_SILICON
+ // don't check
+#else
+ if (!android::hasModernX86VirtualizationFeatures()) {
+ // TODO: Support snapshots when UG is not supported.
+ fprintf(stderr, "Warning: Quick Boot / Snapshots not supported on this "
+ "machine. "
+ "A CPU with EPT + UG features is currently needed. "
+ "We will address this in a future release.\n");
+ featurecontrol::setEnabledOverride(featurecontrol::FastSnapshotV1, false);
+ }
+#endif
+#endif
+
+#if HAVE_KVM
+ status_code = ProbeKVM(&status);
+ if (status_code == ANDROID_CPU_ACCELERATION_READY) {
+ g->accel = CPU_ACCELERATOR_KVM;
+ g->supported_accelerators[CPU_ACCELERATOR_KVM] = true;
+ }
+#elif HAVE_HAX || HAVE_HVF || HAVE_WHPX || HAVE_AEHD
+ auto hvStatus = GetHyperVStatus();
+ if (hvStatus.first == ANDROID_HYPERV_RUNNING) {
+ status = "Please disable Hyper-V before using the Android Emulator. "
+ "Start a command prompt as Administrator, run 'bcdedit /set "
+ "hypervisorlaunchtype off', reboot.";
+ status_code = ANDROID_CPU_ACCELERATION_HYPERV_ENABLED;
+#if HAVE_WHPX
+ auto ver = android::base::Win32Utils::getWindowsVersion();
+ if (isOkToTryWHPX()) {
+ if (ver && ver->dwMajorVersion >= 10 && ver->dwBuildNumber >= 17134) {
+ status_code = ProbeWHPX(&status);
+ if (status_code == ANDROID_CPU_ACCELERATION_READY) {
+ g->accel = CPU_ACCELERATOR_WHPX;
+ g->supported_accelerators[CPU_ACCELERATOR_WHPX] = true;
+ } else {
+ status = "Hyper-V detected and Windows Hypervisor Platform "
+ "is available. "
+ "Please ensure both the \"Hyper-V\" and \"Windows "
+ "Hypervisor Platform\" "
+ "features enabled in \"Turn Windows features on "
+ "or off\".";
+ }
+ } else {
+ status = "Hyper-V detected and Windows Hypervisor Platform is "
+ "not available. "
+ "Please either disable Hyper-V or upgrade to Windows "
+ "10 1803 or later. "
+ "To disable Hyper-V, start a command prompt as "
+ "Administrator, run "
+ "'bcdedit /set hypervisorlaunchtype off', reboot. "
+ "If upgrading OS to Windows 10 1803 or later, please "
+ "ensure both "
+ "the \"Hyper-V\" and \"Windows Hypervisor Platform\" "
+ "features enabled "
+ "in \"Turn Windows features on or off\".";
+ status_code = ANDROID_CPU_ACCELERATION_HYPERV_ENABLED;
+ }
+ }
+#endif
+ } else {
+
+#ifndef APPLE_SILICON
+ char vendor_id[16];
+ CpuVendorIdType vid_type;
+
+ android_get_x86_cpuid_vendor_id(vendor_id, sizeof(vendor_id));
+ vid_type = android_get_x86_cpuid_vendor_id_type(vendor_id);
+ if (vid_type != VENDOR_ID_AMD && vid_type != VENDOR_ID_INTEL) {
+ StringAppendFormat(&status,
+ "Android Emulator requires an Intel or AMD processor "
+ "with "
+ "virtualization extension support. Your CPU: '%s'",
+ vendor_id);
+ status_code = ANDROID_CPU_ACCELERATION_NO_CPU_SUPPORT;
+ } else {
+#if HAVE_AEHD
+ status_code = ProbeAEHD(&status);
+ if (status_code == ANDROID_CPU_ACCELERATION_READY) {
+ g->accel = CPU_ACCELERATOR_AEHD;
+ g->supported_accelerators[CPU_ACCELERATOR_AEHD] = true;
+ }
+#endif
+#if HAVE_HAX
+ if (status_code != ANDROID_CPU_ACCELERATION_READY &&
+ vid_type == VENDOR_ID_INTEL) {
+ std::string statusHax;
+ AndroidCpuAcceleration status_code_HAX;
+
+ status_code_HAX = ProbeHAX(&statusHax);
+ if (status_code_HAX == ANDROID_CPU_ACCELERATION_READY) {
+ g->accel = CPU_ACCELERATOR_HAX;
+ g->supported_accelerators[CPU_ACCELERATOR_HAX] = true;
+ status = statusHax;
+ status_code = status_code_HAX;
+ }
+ }
+#endif
+ }
+#endif
+
+#if HAVE_HVF
+
+ std::string statusHvf;
+ AndroidCpuAcceleration status_code_HVF = ProbeHVF(&statusHvf);
+ if (status_code_HVF == ANDROID_CPU_ACCELERATION_READY) {
+#ifdef APPLE_SILICON
+ g->accel = CPU_ACCELERATOR_HVF;
+ g->supported_accelerators[CPU_ACCELERATOR_KVM] = false;
+#endif
+ g->supported_accelerators[CPU_ACCELERATOR_HVF] = true;
+ // TODO(jansene): Switch to HVF as default option if/when
+ // appropriate.
+ if (status_code != ANDROID_CPU_ACCELERATION_READY) {
+ g->accel = CPU_ACCELERATOR_HVF;
+ status_code = status_code_HVF;
+ status = statusHvf;
+ }
+ }
+#endif // HAVE_HVF
+ }
+#else // !HAVE_KVM && !(HAVE_HAX || HAVE_HVF || HAVE_AEHD || HAVE_WHPX)
+ status = "This system does not support CPU acceleration.";
+#endif // !HAVE_KVM && !(HAVE_HAX || HAVE_HVF || HAVE_AEHD || HAVE_WHPX)
+
+ // Print HAXM deprecation message
+ if (g->accel == CPU_ACCELERATOR_HAX) {
+ fprintf(stderr, "HAXM is deprecated and not supported by Intel "
+ "any more. Please download and install Android "
+ "Emulator Hypervisor Driver for AMD Processors, "
+ "which also supports Intel Processors. Installing "
+ "from SDK Manager is coming soon.\n");
+ }
+
+ // cache status
+ g->probed = true;
+ g->status_code = status_code;
+ ::snprintf(g->status, sizeof(g->status), "%s", status.c_str());
+
+ return g->accel;
+}
+
+void ResetCurrentCpuAccelerator(CpuAccelerator accel) {
+ GlobalState *g = &gGlobals;
+ g->accel = accel;
+}
+
+bool GetCurrentAcceleratorSupport(CpuAccelerator type) {
+ GlobalState *g = &gGlobals;
+
+ if (!g->probed && !g->testing) {
+ // Force detection of the current CPU accelerator.
+ GetCurrentCpuAccelerator();
+ }
+
+ return g->supported_accelerators[type];
+}
+
+std::string GetCurrentCpuAcceleratorStatus() {
+ GlobalState *g = &gGlobals;
+
+ if (!g->probed && !g->testing) {
+ // Force detection of the current CPU accelerator.
+ GetCurrentCpuAccelerator();
+ }
+
+ return {g->status};
+}
+
+AndroidCpuAcceleration GetCurrentCpuAcceleratorStatusCode() {
+ GlobalState *g = &gGlobals;
+
+ if (!g->probed && !g->testing) {
+ // Force detection of the current CPU accelerator.
+ GetCurrentCpuAccelerator();
+ }
+
+ return g->status_code;
+}
+
+void SetCurrentCpuAcceleratorForTesting(CpuAccelerator accel,
+ AndroidCpuAcceleration status_code,
+ const char *status) {
+ GlobalState *g = &gGlobals;
+
+ g->testing = true;
+ g->accel = accel;
+ g->status_code = status_code;
+ ::snprintf(g->status, sizeof(g->status), "%s", status);
+}
+
+std::pair<AndroidHyperVStatus, std::string> GetHyperVStatus() {
+#ifndef _WIN32
+ // this was easy
+ return std::make_pair(ANDROID_HYPERV_ABSENT, "Hyper-V runs only on Windows");
+#else // _WIN32
+ char vendor_id[16];
+ android_get_x86_cpuid_vmhost_vendor_id(vendor_id, sizeof(vendor_id));
+ const auto vmType = android_get_x86_cpuid_vendor_vmhost_type(vendor_id);
+ if (vmType == VENDOR_VM_HYPERV) {
+ // The simple part: there's currently a Hyper-V hypervisor running.
+ // Let's find out if we're in a host or guest.
+ // Hyper-V has a CPUID function 0x40000003 which returns a set of
+ // supported features in ebx register. Ebx[0] is a 'CreatePartitions'
+ // feature bit, which is only enabled in host as of now
+ uint32_t ebx;
+ android_get_x86_cpuid(0x40000003, 0, nullptr, &ebx, nullptr, nullptr);
+ if (ebx & 0x1) {
+ return std::make_pair(ANDROID_HYPERV_RUNNING, "Hyper-V is enabled");
+ } else {
+ // TODO: update this part when Hyper-V officially implements
+ // nesting support
+ return std::make_pair(
+ ANDROID_HYPERV_ABSENT,
+ "Running in a guest Hyper-V VM, Hyper-V is not supported");
+ }
+ } else if (vmType != VENDOR_VM_NOTVM) {
+ // some CPUs may return something strange even if we're not under a VM,
+ // so let's double-check it
+ if (android_get_x86_cpuid_is_vcpu()) {
+ return std::make_pair(ANDROID_HYPERV_ABSENT,
+ "Running in a guest VM, Hyper-V is not supported");
+ }
+ }
+
+ using android::base::PathUtils;
+ using android::base::Win32UnicodeString;
+
+ // Now the hard part: we know Hyper-V is not running. We need to find out if
+ // it's installed.
+ // The only reliable way of detecting it is to query the list of optional
+ // features through the WMI and check if Hyper-V is installed there. But it
+ // runs for tens of seconds, and can be even slower under memory pressure.
+ // Instead, let's take a shortcut: Hyper-V engine file is vmms.exe. If it's
+ // installed it has to be in system32 directory. So we can just check if
+ // it's there.
+ Win32UnicodeString winPath(MAX_PATH);
+ UINT size = ::GetWindowsDirectoryW(winPath.data(), winPath.size() + 1);
+ if (size > winPath.size()) {
+ winPath.resize(size);
+ size = ::GetWindowsDirectoryW(winPath.data(), winPath.size() + 1);
+ }
+ if (size == 0) {
+ // Last chance call
+ winPath = L"C:\\Windows";
+ } else if (winPath.size() != size) {
+ winPath.resize(size);
+ }
+
+#ifdef __x86_64__
+ const std::string sysPath = PathUtils::join(winPath.toString(), "System32");
+#else
+ // For the 32-bit application everything's a little bit more complicated:
+ // the main Hyper-V executable is 64-bit on 64-bit OS; but we're running
+ // under file system redirector which redirects access into 32-bit System32.
+ // even more: if we're running under 32-bit Windows, there's no 64-bit
+ // directory. So we need to select the proper one here.
+ // First, try a symlink which only exists on 64-bit Windows and leads to
+ // the native, 64-bit directory
+ std::string sysPath = PathUtils::join(winPath.toString(), "Sysnative");
+
+ // check only if path exists: path_is_dir() would fail as it's not a
+ // directory but a symlink
+ if (!path_exists(sysPath.c_str())) {
+ // If it doesn't exist, we're on 32-bit Windows and let's just use
+ // the plain old System32
+ sysPath = PathUtils::join(winPath.toString(), "System32");
+ }
+#endif
+ const std::string hyperVExe = PathUtils::join(sysPath, "vmms.exe");
+
+ if (path_is_regular(hyperVExe.c_str())) {
+ // hyper-v is installed but not running
+ return std::make_pair(ANDROID_HYPERV_INSTALLED, "Hyper-V is disabled");
+ }
+
+ // not a slightest sign of it
+ return std::make_pair(ANDROID_HYPERV_ABSENT, "Hyper-V is not installed");
+#endif // _WIN32
+}
+
+std::pair<AndroidCpuInfoFlags, std::string> GetCpuInfo() {
+ int flags = 0;
+ std::string status;
+
+ char vendor_id[13];
+ android_get_x86_cpuid_vendor_id(vendor_id, sizeof(vendor_id));
+ switch (android_get_x86_cpuid_vendor_id_type(vendor_id)) {
+ case VENDOR_ID_AMD:
+ flags |= ANDROID_CPU_INFO_AMD;
+ status += "AMD CPU\n";
+ if (android_get_x86_cpuid_svm_support()) {
+ flags |= ANDROID_CPU_INFO_VIRT_SUPPORTED;
+ status += "Virtualization is supported\n";
+ }
+ break;
+ case VENDOR_ID_INTEL:
+ flags |= ANDROID_CPU_INFO_INTEL;
+ status += "Intel CPU\n";
+ if (android_get_x86_cpuid_vmx_support()) {
+ flags |= ANDROID_CPU_INFO_VIRT_SUPPORTED;
+ status += "Virtualization is supported\n";
+ }
+ break;
+ default:
+#ifdef APPLE_SILICON
+ flags |= ANDROID_CPU_INFO_APPLE;
+ status += "Apple CPU\n";
+ status += "Virtualization is supported\n"; // we have not found
+ // otherwise on apple cpu
+#else
+ flags |= ANDROID_CPU_INFO_OTHER;
+ status += "Other CPU: ";
+ status += vendor_id;
+#endif
+ status += '\n';
+ break;
+ }
+
+ if (android_get_x86_cpuid_is_vcpu()) {
+ if (android_get_x86_cpuid_hyperv_root()) {
+ status += "Hyper-V Root Partition\n";
+ } else {
+ status += "Inside a VM\n";
+ flags |= ANDROID_CPU_INFO_VM;
+ }
+ } else {
+ status += "Bare metal\n";
+ }
+
+ flags |= ANDROID_CPU_INFO_64_BIT_32_BIT_OS;
+ status += "64-bit CPU, 32-bit OS\n";
+
+ return std::make_pair(static_cast<AndroidCpuInfoFlags>(flags), status);
+}
+
+std::string CpuAcceleratorToString(CpuAccelerator type) {
+ switch (type) {
+ case CPU_ACCELERATOR_KVM:
+ return "kvm";
+ case CPU_ACCELERATOR_HAX:
+ return "hax (deprecated)";
+ case CPU_ACCELERATOR_HVF:
+ return "hvf";
+ case CPU_ACCELERATOR_WHPX:
+ return "whpx";
+ case CPU_ACCELERATOR_AEHD:
+ return "aehd";
+ case CPU_ACCELERATOR_NONE:
+ case CPU_ACCELERATOR_MAX:
+ return "tcg";
+ }
+ return "";
+}
+
+Version GetCurrentCpuAcceleratorVersion() {
+ GlobalState *g = &gGlobals;
+
+ if (!g->probed && !g->testing) {
+ // Force detection of the current CPU accelerator.
+ GetCurrentCpuAccelerator();
+ }
+ return (g->accel != CPU_ACCELERATOR_NONE) ? Version(g->version)
+ : Version::invalid();
+}
+
+Version parseMacOSVersionString(const std::string &str, std::string *status) {
+ size_t pos = str.rfind(' ');
+ if (strncmp("Error: ", str.c_str(), 7) == 0 || pos == std::string::npos) {
+ status->append(absl::StrFormat(
+ "Internal error: failed to parse OS version '%s'", str));
+ return {0, 0, 0};
+ }
+
+ auto ver = Version(std::string(str.c_str() + pos + 1, str.size() - pos - 1));
+ if (!ver.isValid()) {
+ status->append(absl::StrFormat(
+ "Internal error: failed to parse OS version '%s'", str));
+ return {0, 0, 0};
+ }
+
+ return ver;
+}
+
+} // namespace android::goldfish
diff --git a/cpu/src/android/goldfish/cpu/x86_cpuid.cpp b/cpu/src/android/goldfish/cpu/x86_cpuid.cpp
new file mode 100644
index 0000000..12265ae
--- /dev/null
+++ b/cpu/src/android/goldfish/cpu/x86_cpuid.cpp
@@ -0,0 +1,262 @@
+/*
+** Copyright (c) 2014, Intel Corporation
+** Copyright (c) 2015, Google, Inc.
+**
+** This software is licensed under the terms of the GNU General Public
+** License version 2, as published by the Free Software Foundation, and
+** may be copied, distributed, and modified under those terms.
+**
+** This program is distributed in the hope that it will be useful,
+** but WITHOUT ANY WARRANTY; without even the implied warranty of
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+** GNU General Public License for more details.
+*/
+
+#include "android/goldfish/cpu/x86_cpuid.h"
+
+#include <string.h>
+
+uint32_t android_get_x86_cpuid_function_max() {
+ uint32_t function_max;
+ android_get_x86_cpuid(0, 0, &function_max, nullptr, nullptr, nullptr);
+ return function_max;
+}
+
+uint32_t android_get_x86_cpuid_extended_function_max() {
+ uint32_t function_max;
+ android_get_x86_cpuid(0x80000000, 0, &function_max, nullptr, nullptr,
+ nullptr);
+ return function_max;
+}
+
+void android_get_x86_cpuid(uint32_t function, uint32_t count, uint32_t *eax,
+ uint32_t *ebx, uint32_t *ecx, uint32_t *edx) {
+#if defined(__x86_64__) || defined(__i386__)
+ uint32_t vec[4];
+
+#ifdef __x86_64__
+ asm volatile("cpuid"
+ : "=a"(vec[0]), "=b"(vec[1]), "=c"(vec[2]), "=d"(vec[3])
+ : "0"(function), "c"(count)
+ : "cc");
+#else /* !__x86_64__ */
+ asm volatile("pusha \n\t"
+ "cpuid \n\t"
+ "mov %%eax, 0(%2) \n\t"
+ "mov %%ebx, 4(%2) \n\t"
+ "mov %%ecx, 8(%2) \n\t"
+ "mov %%edx, 12(%2) \n\t"
+ "popa"
+ :
+ : "a"(function), "c"(count), "S"(vec)
+ : "memory", "cc");
+#endif /* !__x86_64__ */
+
+ if (eax) {
+ *eax = vec[0];
+ }
+ if (ebx) {
+ *ebx = vec[1];
+ }
+ if (ecx) {
+ *ecx = vec[2];
+ }
+ if (edx) {
+ *edx = vec[3];
+ }
+#endif /* defined(__x86_64__) || defined(__i386__) */
+}
+
+bool android_get_x86_cpuid_vendor_id_is_vmhost(const char *vendor_id) {
+ static const char *const VMHostCPUID[] = {
+ "VMwareVMware", // VMware
+ "KVMKVMKVM", // KVM
+ "VBoxVBoxVBox", // VirtualBox
+ "Microsoft Hv", // Microsoft Hyper-V or Windows Virtual PC
+ "XenVMMXenVMM", // Xen HVM
+ };
+
+ const int VMHostCPUIDCount = sizeof(VMHostCPUID) / sizeof(VMHostCPUID[0]);
+ for (int i = 0; i < VMHostCPUIDCount; i++) {
+ /* I don't think HAXM supports nesting */
+ if (strcmp(vendor_id, VMHostCPUID[i]) == 0)
+ return true;
+ }
+ return false;
+}
+
+void android_get_x86_cpuid_vendor_id(char *buf, size_t buf_len) {
+ memset(buf, 0, buf_len);
+ if (buf_len < 13) {
+ return;
+ }
+ android_get_x86_cpuid(0, 0, nullptr, (uint32_t *)buf, (uint32_t *)(buf + 8),
+ (uint32_t *)(buf + 4));
+}
+
+void android_get_x86_cpuid_vmhost_vendor_id(char *buf, size_t buf_len) {
+ char _buf[13] = {0};
+
+ memset(buf, 0, buf_len);
+ if (buf_len < 13) {
+ return;
+ }
+ // Check hypervisor flags before querying leaf 0x40000x00
+ // Some platform will return garbage for leaf 0x400000x00
+ if (!android_get_x86_cpuid_is_vcpu()) {
+ return;
+ }
+ android_get_x86_cpuid(0x40000000, 0, nullptr, (uint32_t *)buf,
+ (uint32_t *)(buf + 4), (uint32_t *)(buf + 8));
+ // KVM/Xen with Hyper-V enlightenment will present "Microsoft Hv"
+ // on leaf 0x40000000. Get id from 0x40000100
+ if (strcmp(buf, "Microsoft Hv") == 0) {
+ android_get_x86_cpuid(0x40000100, 0, nullptr, (uint32_t *)_buf,
+ (uint32_t *)(_buf + 4), (uint32_t *)(_buf + 8));
+ if (_buf[0])
+ memcpy(buf, _buf, 13);
+ }
+}
+
+CpuVendorIdType android_get_x86_cpuid_vendor_id_type(const char *vendor_id) {
+ if (!vendor_id) {
+ return VENDOR_ID_OTHER;
+ }
+
+ if (strcmp(vendor_id, "AuthenticAMD") == 0) {
+ return VENDOR_ID_AMD;
+ }
+ if (strcmp(vendor_id, "GenuineIntel") == 0) {
+ return VENDOR_ID_INTEL;
+ }
+ if (strcmp(vendor_id, "VIA VIA VIA ") == 0) {
+ return VENDOR_ID_VIA;
+ }
+ if (android_get_x86_cpuid_vendor_id_is_vmhost(vendor_id)) {
+ return VENDOR_ID_VM;
+ }
+
+ return VENDOR_ID_OTHER;
+}
+
+CpuVendorVmType
+android_get_x86_cpuid_vendor_vmhost_type(const char *vendor_id) {
+ if (!vendor_id) {
+ return VENDOR_VM_OTHER;
+ }
+
+ if (vendor_id[0] == 0) {
+ return VENDOR_VM_NOTVM;
+ }
+ if (strcmp(vendor_id, "VMWareVMWare") == 0) {
+ return VENDOR_VM_VMWARE;
+ }
+ if (strcmp(vendor_id, "KVMKVMKVM") == 0) {
+ return VENDOR_VM_KVM;
+ }
+ if (strcmp(vendor_id, "VBoxVBoxVBox") == 0) {
+ return VENDOR_VM_VBOX;
+ }
+ if (strcmp(vendor_id, "Microsoft Hv") == 0) {
+ return VENDOR_VM_HYPERV;
+ }
+ if (strcmp(vendor_id, "XenVMMXenVMM") == 0) {
+ return VENDOR_VM_XEN;
+ }
+
+ return VENDOR_VM_OTHER;
+}
+
+bool android_get_x86_cpuid_vmx_support() {
+ uint32_t cpuid_function1_ecx;
+ android_get_x86_cpuid(1, 0, nullptr, nullptr, &cpuid_function1_ecx, nullptr);
+
+ const uint32_t CPUID_1_ECX_VMX = (1 << 5); // Intel VMX support
+ if ((cpuid_function1_ecx & CPUID_1_ECX_VMX) != 0) {
+ // now we need to confirm that this is an Intel CPU
+ char vendor_id[13];
+ android_get_x86_cpuid_vendor_id(vendor_id, sizeof(vendor_id));
+ if (android_get_x86_cpuid_vendor_id_type(vendor_id) == VENDOR_ID_INTEL) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool android_get_x86_cpuid_svm_support() {
+ uint32_t cpuid_ecx;
+ android_get_x86_cpuid(0x80000001, 0, nullptr, nullptr, &cpuid_ecx, nullptr);
+
+ const uint32_t CPUID_ECX_SVM = (1 << 2); // AMD SVM support
+ if ((cpuid_ecx & CPUID_ECX_SVM) != 0) {
+ // make sure it's an AMD processor
+ char vendor_id[13];
+ android_get_x86_cpuid_vendor_id(vendor_id, sizeof(vendor_id));
+ if (android_get_x86_cpuid_vendor_id_type(vendor_id) == VENDOR_ID_AMD) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool android_get_x86_cpuid_is_vcpu() {
+ uint32_t cpuid_function1_ecx;
+ android_get_x86_cpuid(1, 0, nullptr, nullptr, &cpuid_function1_ecx, nullptr);
+
+ // Hypervisors are supposed to indicate their presence with a bit in the
+ // output of running CPUID when eax=1: if the output ECX has bit 31 set
+ // (cite: https://lwn.net/Articles/301888/ ).
+ const uint32_t CPUID_1_ECX_VCPU = (1 << 31); // This is a virtual CPU
+ return (cpuid_function1_ecx & CPUID_1_ECX_VCPU) != 0;
+}
+
+bool android_get_x86_cpuid_nx_support() {
+ if (android_get_x86_cpuid_extended_function_max() < 0x80000001)
+ return false;
+
+ uint32_t cpuid_80000001_edx;
+ android_get_x86_cpuid(0x80000001, 0, nullptr, nullptr, nullptr,
+ &cpuid_80000001_edx);
+
+ const uint32_t CPUID_80000001_EDX_NX = (1 << 20); // NX support
+ return (cpuid_80000001_edx & CPUID_80000001_EDX_NX) != 0;
+}
+
+bool android_get_x86_cpuid_is_64bit_capable() {
+#ifdef __x86_64__
+ return true; // this was easy
+#else
+ const uint32_t kBitnessSupportFunc = 0x80000001;
+
+ const auto maxFunc = android_get_x86_cpuid_extended_function_max();
+ if (maxFunc < kBitnessSupportFunc) {
+ return false; // if it's that old, it is not a 64-bit one
+ }
+
+ uint32_t edx = 0;
+ android_get_x86_cpuid(kBitnessSupportFunc, 0, nullptr, nullptr, nullptr,
+ &edx);
+ // bit 29 is the 64-bit mode support
+ return (edx & (1 << 29)) != 0;
+#endif
+}
+
+bool android_get_x86_cpuid_hyperv_root() {
+ char hv_vendor_id[16] = {};
+ android_get_x86_cpuid_vmhost_vendor_id(hv_vendor_id, sizeof(hv_vendor_id));
+ auto vmhost = android_get_x86_cpuid_vendor_vmhost_type(hv_vendor_id);
+ if (vmhost != VENDOR_VM_HYPERV) {
+ return false;
+ }
+
+ // The linux kernel uses CPUID LEAF 0x40000000 EBX BIT 12 to check whether
+ // this is a Hyper-V Root Partition. Use the same method here.
+ uint32_t cpuid_function40000003_ebx;
+ android_get_x86_cpuid(0x40000003, 0, nullptr, &cpuid_function40000003_ebx,
+ nullptr, nullptr);
+ const uint32_t HV_PARTITION_PRIVILEGE_CPUMANAGEMENT = (1 << 12);
+ return (cpuid_function40000003_ebx & HV_PARTITION_PRIVILEGE_CPUMANAGEMENT) !=
+ 0;
+}
diff --git a/cpu/src/android/goldfish/cpu/x86_cpuid.h b/cpu/src/android/goldfish/cpu/x86_cpuid.h
new file mode 100644
index 0000000..5c2e171
--- /dev/null
+++ b/cpu/src/android/goldfish/cpu/x86_cpuid.h
@@ -0,0 +1,158 @@
+/*
+** Copyright (c) 2014, Intel Corporation
+**
+** This software is licensed under the terms of the GNU General Public
+** License version 2, as published by the Free Software Foundation, and
+** may be copied, distributed, and modified under those terms.
+**
+** This program is distributed in the hope that it will be useful,
+** but WITHOUT ANY WARRANTY; without even the implied warranty of
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+** GNU General Public License for more details.
+*/
+
+#pragma once
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#include "android/utils/compiler.h"
+
+ANDROID_BEGIN_HEADER
+
+/*
+ * android_get_x86_cpuid_function_max: retrieve x86 CPUID function max
+ * supported by this processor. This is corresponds to the value passed
+ * to android_get_x86_cpuid |function| parameter
+ */
+uint32_t android_get_x86_cpuid_function_max();
+
+/*
+ * android_get_x86_cpuid_extended_function_max: retrieve x86 CPUID extended
+ * function max supported by this processor. This is corresponds to the
+ * value passed to android_get_x86_cpuid |function| parameter
+ */
+uint32_t android_get_x86_cpuid_extended_function_max();
+
+/* The following list of CPUID features is based on Table 1 in section 'ABI
+ * Management' of NDK Programmer's Guide (Android NDK r10c). */
+/* Applicable when calling CPUID with EAX=1 */
+#define CPUID_EDX_MMX (1 << 23)
+#define CPUID_EDX_SSE (1 << 25)
+#define CPUID_EDX_SSE2 (1 << 26)
+#define CPUID_ECX_SSE3 (1 << 0)
+#define CPUID_ECX_SSSE3 (1 << 9)
+#define CPUID_ECX_SSE41 (1 << 19)
+#define CPUID_ECX_SSE42 (1 << 20)
+#define CPUID_ECX_POPCNT (1 << 23)
+
+/*
+ * android_get_x86_cpuid: retrieve x86 CPUID for host CPU.
+ *
+ * Executes the x86 CPUID instruction on the host CPU with the given
+ * parameters, and saves the results in the given locations. Does nothing on
+ * non-x86 hosts.
+ *
+ * |function| is the 'CPUID leaf' (the EAX parameter to CPUID), and |count|
+ * the 'CPUID sub-leaf' (the ECX parameter to CPUID), givne as input
+ *
+ * parameters. |eax|,|ebx|,|ecx| and |edx| are optional pointers to variables
+ * that will be set on exit to the value of the corresponding register;
+ * if one of this parameter is NULL, it is ignored.
+ */
+void android_get_x86_cpuid(uint32_t function, uint32_t count, uint32_t *eax,
+ uint32_t *ebx, uint32_t *ecx, uint32_t *edx);
+
+/*
+ * android_get_x86_cpuid_vendor_id: retrieve x86 CPUID vendor id as a null
+ * terminated string
+ *
+ * examples: "GenuineIntel" "AuthenticAMD" "VMwareVMware"
+ *
+ * |vendor_id_len| - must be at least 13 bytes
+ */
+void android_get_x86_cpuid_vendor_id(char *vendor_id, size_t vendor_id_len);
+
+// android_get_x86_cpuid_vmhost_vendor_id: get the vendor ID for the
+// currently running hypervisor. If there's no hypervisor, empty string is
+// returned
+//
+// examples: "VMWareVMWare" "KVMKVMKVM" "VBoxVBoxVBox", ""
+//
+// |vendor_id| - an output buffer, is always null-terminated after the call
+// |vendor_id_len| - must be at least 13 bytes
+//
+void android_get_x86_cpuid_vmhost_vendor_id(char *vendor_id,
+ size_t vendor_id_len);
+
+// Possible CPU vendor ID types
+// Some VMs report their own CPU vendor ID instead of the real hardware,
+// in that case VENDOR_ID_VM is used
+typedef enum {
+ VENDOR_ID_AMD,
+ VENDOR_ID_INTEL,
+ VENDOR_ID_VIA,
+ VENDOR_ID_VM,
+ VENDOR_ID_OTHER,
+} CpuVendorIdType;
+
+// Possible VM Vendor IDs
+// Special values:
+// VENDOR_VM_NOTVM - not a VM, the string is known to identify a real CPU
+// VENDOR_VM_OTHER - have no idea of the meaning of the vendor ID string
+typedef enum {
+ VENDOR_VM_VMWARE,
+ VENDOR_VM_VBOX,
+ VENDOR_VM_HYPERV,
+ VENDOR_VM_KVM,
+ VENDOR_VM_XEN,
+ VENDOR_VM_NOTVM,
+ VENDOR_VM_OTHER
+} CpuVendorVmType;
+
+// Returns the type of vendor ID
+CpuVendorIdType android_get_x86_cpuid_vendor_id_type(const char *vendor_id);
+CpuVendorVmType android_get_x86_cpuid_vendor_vmhost_type(const char *vendor_id);
+
+/*
+ * android_get_x86_vendor_id_vmhost: identify known VM vendor ids by the
+ * CPU vendorID - do not confuse with VMHost vendorID
+ *
+ * Returns 1 if |vendor_id| retrieved from cpuid is one of four known VM
+ * host vendor id strings. This is just another way of writing this:
+ * bool res = android_get_x86_cpuid_vendor_id_type(vendor_id) == VENDOR_ID_VM;
+ */
+bool android_get_x86_cpuid_vendor_id_is_vmhost(const char *vendor_id);
+
+/*
+ * android_get_x86_cpuid_vmx_support: returns 1 if the CPU supports Intel
+ * VM-x features, returns 0 otherwise
+ */
+bool android_get_x86_cpuid_vmx_support();
+
+/*
+ * android_get_x86_cpuid_svm_support: returns 1 if the CPU supports AMD
+ * SVM features, returns 0 otherwise
+ */
+bool android_get_x86_cpuid_svm_support();
+
+/*
+ * android_get_x86_cpuid_nx_support: returns 1 if the CPU supports Intel
+ * NX (no execute) features, returns 0 otherwise
+ */
+bool android_get_x86_cpuid_nx_support();
+
+/*
+ * android_get_x86_cpuid_is_vcpu: returns 1 if the CPU is a running under
+ * a Hypervisor
+ */
+bool android_get_x86_cpuid_is_vcpu();
+
+// Returns true if the CPU supports AMD64 instruction set.
+bool android_get_x86_cpuid_is_64bit_capable();
+
+// Returns true if the CPU is running inside a Hyper-V Root Partition
+bool android_get_x86_cpuid_hyperv_root();
+
+ANDROID_END_HEADER
diff --git a/cpu/test/android/goldfish/cpu/x86_cpuid_unittest.cpp b/cpu/test/android/goldfish/cpu/x86_cpuid_unittest.cpp
new file mode 100644
index 0000000..c32a372
--- /dev/null
+++ b/cpu/test/android/goldfish/cpu/x86_cpuid_unittest.cpp
@@ -0,0 +1,118 @@
+// Copyright (c) 2014, Intel Corporation
+//
+// This software is licensed under the terms of the GNU General Public
+// License version 2, as published by the Free Software Foundation, and
+// may be copied, distributed, and modified under those terms.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+#include <gtest/gtest.h>
+
+#include "android/goldfish/cpu/x86_cpuid.h"
+
+#include <stdio.h>
+
+namespace android {
+namespace utils {
+
+// Applicable when calling CPUID with EAX=0x80000001
+// Long mode, which is one of the features that distinguish x86_64 from x86
+static const uint32_t CPUID_EDX_LM = 1 << 29;
+
+// Prints to stdout a line stating whether a feature is supported or not.
+static void PrintCpuFeatureTestResult(const char *feature, bool supported) {
+ printf("%s -%s supported\n", feature, supported ? "" : " not");
+}
+
+// Not really a test. Prints a summary of the host CPU's ABI compatibility for
+// manual verification.
+TEST(x86_cpuid, Default) {
+ uint32_t ecx = 0, edx = 0;
+
+ // Call CPUID with EAX=1 and ECX=0 to get CPU feature bits.
+ android_get_x86_cpuid(1, 0, nullptr, nullptr, &ecx, &edx);
+
+ if (!ecx && !edx) {
+ // Both output variables are unchanged, which probably means that
+ // android_get_x86_cpuid(..) was compiled to a no-op.
+ printf("Host CPU architecture is neither x86 nor x86_64!\n");
+ } else {
+ // EDX returned by CPUID (EAX=1) should never be zero.
+ EXPECT_TRUE(edx);
+
+ uint32_t edx2 = 0;
+
+ // Call CPUID with EAX=0x80000001 and ECX=0 to get extended CPU feature
+ // bits.
+ android_get_x86_cpuid(0x80000001, 0, nullptr, nullptr, nullptr, &edx2);
+
+ // Long mode is a distinguishing feature of x86_64.
+ bool isX86_64 = edx2 & CPUID_EDX_LM;
+ printf("Host CPU architecture is %s.\n", isX86_64 ? "x86_64" : "x86");
+
+ printf("ABI compatibility:\n");
+
+ // See Table 1 in section 'ABI Management' of NDK Programmer's Guide
+ // (Android NDK r10c) for the features required by x86/x86_64 ABIs.
+ PrintCpuFeatureTestResult("MMX", edx & CPUID_EDX_MMX);
+ PrintCpuFeatureTestResult("SSE", edx & CPUID_EDX_SSE);
+ PrintCpuFeatureTestResult("SSE2", edx & CPUID_EDX_SSE2);
+ PrintCpuFeatureTestResult("SSE3", ecx & CPUID_ECX_SSE3);
+ PrintCpuFeatureTestResult("SSSE3", ecx & CPUID_ECX_SSSE3);
+ if (isX86_64) {
+ PrintCpuFeatureTestResult("SSE4.1", ecx & CPUID_ECX_SSE41);
+ PrintCpuFeatureTestResult("SSE4.2", ecx & CPUID_ECX_SSE42);
+ PrintCpuFeatureTestResult("POPCNT", ecx & CPUID_ECX_POPCNT);
+ }
+ }
+}
+
+TEST(x86_cpuid, android_get_x86_cpuid_vendor_id_is_vmhost) {
+ EXPECT_TRUE(android_get_x86_cpuid_vendor_id_is_vmhost("KVMKVMKVM"));
+ EXPECT_TRUE(android_get_x86_cpuid_vendor_id_is_vmhost("Microsoft Hv"));
+ EXPECT_TRUE(android_get_x86_cpuid_vendor_id_is_vmhost("VMwareVMware"));
+ EXPECT_TRUE(android_get_x86_cpuid_vendor_id_is_vmhost("XenVMMXenVMM"));
+ EXPECT_FALSE(android_get_x86_cpuid_vendor_id_is_vmhost("GenuineIntel"));
+}
+
+TEST(x86_cpuid, android_get_x86_cpuid_vmx_support) {
+#if defined(__aarch64__) && defined(__APPLE__)
+ GTEST_SKIP() << "Skipping test for mac_arm_x64 bots.";
+#endif
+ EXPECT_LE(0x80000001, android_get_x86_cpuid_extended_function_max());
+ // We can't really expect anything here: e.g. some buildbots have the
+ // virtualization support disabled. So just call these to make sure they
+ // don't crash.
+ android_get_x86_cpuid_vmx_support();
+ android_get_x86_cpuid_svm_support();
+ EXPECT_TRUE(android_get_x86_cpuid_nx_support());
+}
+
+TEST(x86_cpuid, android_get_x86_cpuid_vendor_id_type) {
+ EXPECT_EQ(VENDOR_ID_AMD,
+ android_get_x86_cpuid_vendor_id_type("AuthenticAMD"));
+ EXPECT_EQ(VENDOR_ID_INTEL,
+ android_get_x86_cpuid_vendor_id_type("GenuineIntel"));
+ EXPECT_EQ(VENDOR_ID_VM, android_get_x86_cpuid_vendor_id_type("KVMKVMKVM"));
+ EXPECT_EQ(VENDOR_ID_OTHER,
+ android_get_x86_cpuid_vendor_id_type("any other string"));
+}
+
+TEST(x86_cpuid, android_get_x86_cpuid_vendor_vmhost_type) {
+ EXPECT_EQ(VENDOR_VM_VMWARE,
+ android_get_x86_cpuid_vendor_vmhost_type("VMWareVMWare"));
+ EXPECT_EQ(VENDOR_VM_HYPERV,
+ android_get_x86_cpuid_vendor_vmhost_type("Microsoft Hv"));
+ EXPECT_EQ(VENDOR_VM_KVM,
+ android_get_x86_cpuid_vendor_vmhost_type("KVMKVMKVM"));
+ EXPECT_EQ(VENDOR_VM_VBOX,
+ android_get_x86_cpuid_vendor_vmhost_type("VBoxVBoxVBox"));
+ EXPECT_EQ(VENDOR_VM_NOTVM, android_get_x86_cpuid_vendor_vmhost_type(""));
+ EXPECT_EQ(VENDOR_VM_OTHER, android_get_x86_cpuid_vendor_vmhost_type("blah"));
+}
+
+} // namespace utils
+} // namespace android
diff --git a/files/BUILD.bazel b/files/BUILD.bazel
new file mode 100644
index 0000000..bfc2cbd
--- /dev/null
+++ b/files/BUILD.bazel
@@ -0,0 +1,43 @@
+cc_library(
+ name = "files",
+ srcs = glob([
+ "src/android/**/*.cpp",
+ "src/**/*.h",
+ ]),
+ hdrs = glob([
+ "include/android/goldfish/*.h",
+ "include/android/goldfish/avd/*.h",
+ ]),
+ includes = [
+ "include",
+ "src",
+ ],
+ # linkopts = ["-undefined error"],
+ visibility = ["//visibility:public"],
+ deps = [
+ "//hardware/generic/goldfish/logging:backend",
+ "//hardware/generic/goldfish/system",
+ "//hardware/google/aemu/base:aemu-base",
+ "@com_google_absl//absl/status:statusor",
+ "@com_google_absl//absl/strings",
+ "@com_google_absl//absl/strings:str_format",
+ ],
+)
+
+cc_test(
+ name = "files_unittests",
+ srcs = glob(
+ [
+ "test/**/*.cpp",
+ "test/include/**/*.h",
+ ],
+ exclude = ["test/**/Win32"],
+ ),
+ linkopts = ["-undefined error"],
+ deps = [
+ ":files",
+ "//hardware/generic/goldfish/logging:backend",
+ "//hardware/generic/goldfish/system:test-headers",
+ "@com_google_googletest//:gtest_main",
+ ],
+)
diff --git a/files/include/android/goldfish/ConfigDirs.h b/files/include/android/goldfish/ConfigDirs.h
new file mode 100644
index 0000000..071d59c
--- /dev/null
+++ b/files/include/android/goldfish/ConfigDirs.h
@@ -0,0 +1,101 @@
+// Copyright 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#pragma once
+#include <filesystem>
+
+namespace android::goldfish {
+
+namespace fs = std::filesystem;
+// Helper struct to hold static methods related to misc. configuration
+// directories used by the emulator.
+struct ConfigDirs {
+ // Return the user-specific directory containing Android-related
+ // configuration files. The default location can be overridden:
+ //
+ // - If ANDROID_EMULATOR_HOME is defined in the environment, its
+ // value is returned by this function.
+ //
+ // - Otherwise, if ANDROID_SDK_HOME is defined in the environment,
+ // this function returns one of its sub-directories.
+ //
+ // - Otherwise, this returns a subdirectory of the user's home dir.
+ static fs::path getUserDirectory();
+
+ // Return the root path containing all AVD sub-directories.
+ // More specifically:
+ //
+ // - If $ANDROID_AVD_HOME points to a directory, it is returned.
+ //
+ // - Else if $ANDROID_SDK_HOME/.android/avd is a valid root, it is returned.
+ //
+ // - Else if $ANDROID_SDK_HOME is defined but does not lead to a valid root
+ // - $TEST_TMPDIR/.android/avd is returned if it is valid, or
+ // - $USER_HOME/.android/avd is returned if it is valid, or
+ // - $HOME/.android/avd is returned if it is valid
+ //
+ // - Otherwise, a sub-directory named 'avd' of getUserDirectory()'s
+ // output is returned.
+ static fs::path getAvdRootDirectory();
+
+ // Returns the path to the root of the android sdk by checking
+ // - ANDROID_HOME, then
+ // - ANDROID_SDK_ROOT
+ static fs::path getSdkRootDirectoryByEnv(bool verbose = false);
+
+ // Returns the path to the root of the android sdk by inferring it from the
+ // path of the running emulator binary.
+ static fs::path getSdkRootDirectoryByPath(bool verbose = false);
+
+ // Returns the path to the root of the android sdk.
+ // - If ANDROID_SDK_ROOT is defined in the environment and the path exists,
+ // its value is returned.
+ //
+ // - Otherwise, Sdk root is inferred from the path of the running emulator
+ // binary.
+ static fs::path getSdkRootDirectory(bool verbose = false);
+
+ // Returns the <user-specific_tmp_directory>/avd/running directory
+ // used by android studio to detect running emulator. This directory
+ // will be created if it does not exist, with 0700 permissions.
+ //
+ // The user-specific_tmp_directory is based on the first directory that
+ // exists in the following preference order:
+ //
+ // Linux:
+ // - $XDG_RUNTIME_DIR or
+ // - /run/user/$UID or
+ // - $HOME/.android
+ // MacOs:
+ // - ~/Library/Caches/TemporaryItems
+ // - $HOME/.android
+ // Windows:
+ // - %LOCALAPPDATA%/Temp
+ // - %USERPROFILE%/.android
+ static fs::path getDiscoveryDirectory();
+
+private:
+ // Check if the specified path is a valid AVD root path.
+ // It is considered valid if it has an 'avd' subdirectory
+ static bool isValidAvdRoot(const fs::path &avdPath);
+
+ // Check if the specified path is a valid SDK root path.
+ // It is considered valid if it has a 'platforms' subdirectory
+ // and a 'platform-tools' subdirectory.
+ static bool isValidSdkRoot(const fs::path &rootPath, bool verbose = false);
+
+ static fs::path getAvdRootDirectoryWithPrefsRoot(const fs::path &path);
+};
+
+} // namespace android::goldfish \ No newline at end of file
diff --git a/files/include/android/goldfish/IniFile.h b/files/include/android/goldfish/IniFile.h
new file mode 100644
index 0000000..d4fe1d3
--- /dev/null
+++ b/files/include/android/goldfish/IniFile.h
@@ -0,0 +1,173 @@
+// Copyright 2015 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.
+
+#pragma once
+
+#include <inttypes.h>
+
+#include <filesystem>
+#include <iosfwd>
+#include <string>
+#include <string_view>
+#include <unordered_map>
+#include <utility>
+#include <vector>
+
+#include "aemu/base/Compiler.h"
+
+namespace android::goldfish {
+
+class IniFile {
+ DISALLOW_COPY_AND_ASSIGN(IniFile);
+
+public:
+ using DiskSize = uint64_t;
+ using MapType = std::unordered_map<std::string, std::string>;
+ using ElementOrderList = std::vector<const MapType::value_type *>;
+
+ // A custom iterator to return the keys in original order.
+ class const_iterator : public ElementOrderList::const_iterator {
+ public:
+ using value_type = std::string;
+
+ explicit const_iterator(ElementOrderList::const_iterator keyIterator)
+ : ElementOrderList::const_iterator(keyIterator) {}
+
+ const value_type &operator*() const {
+ return ElementOrderList::const_iterator::operator*()->first;
+ }
+ const value_type *operator->() const { return &**this; }
+ };
+
+ // Note that the constructor _does not_ read data from the backing file.
+ // Call |Read| to read the data.
+ // When created without a backing file, all |read|/|write*| operations will
+ // fail unless |setBackingFile| is called to point to a valid file path.
+ explicit IniFile(std::filesystem::path backingFilePath = {})
+ : mBackingFilePath(backingFilePath) {}
+
+ // This constructor reads the data from memory at |data| of |size| bytes.
+ IniFile(const char *data, int size);
+
+ // Set a new backing file. This does not read data from the file. Call
+ // |read| to refresh data from the new backing file.
+ void setBackingFile(std::filesystem::path filePath);
+ const std::string &getBackingFile() const { return mBackingFilePath; }
+
+ // Reads data into IniFile from the backing file, overwriting any
+ // existing data.
+ bool read(bool keepComments = true);
+ // Same thing, but parses an already read file data.
+ // Note: write operations fail unless there's a backing file set
+ bool readFromMemory(std::string_view data);
+
+ // Write the current IniFile to the backing file.
+ bool write();
+ // Write the current IniFile to backing file. Discard any keys that have
+ // empty values.
+ bool writeDiscardingEmpty();
+ // An optimized write.
+ // - Advantage: We don't write if there have been no updates since last
+ // write.
+ // - Disadvantage: Not safe if something else might be changing the ini
+ // file -- your view of the file is no longer consistent. Actually, this
+ // "bug" can be considered a "feature", if the ini file changed unbeknown
+ // to you, you're probably doing wrong in overwriting the changes without
+ // any update on your side.
+ bool writeIfChanged();
+
+ // Gets the number of (key,value) pairs in the file.
+ int size() const;
+ // Check if a certain key exists in the file.
+ bool hasKey(std::string_view key) const;
+
+ // Make sure the string can be used as a valid key/value
+ static std::string makeValidKey(std::string_view str);
+ static std::string makeValidValue(std::string_view str);
+
+ // ///////////////////// Value Getters
+ // //////////////////////////////////////
+ // The IniFile has no knowledge about the type of the values.
+ // |defaultValue| is returned if the key doesn't exist or the value is badly
+ // formatted for the requested type.
+ //
+ // For some value types where the disk format is significantly more useful
+ // for human-parsing, overloads are provided that accept default values as
+ // strings to be parsed just like the backing ini file.
+ // - This has the benefit that default values can be stored in a separate
+ // file in human friendly form, and used directly.
+ // - The disadvantage is that behaviour is undefined if we fail to parse the
+ // default value.
+ std::string getString(const std::string &key,
+ std::string_view defaultValue) const;
+ int getInt(const std::string &key, int defaultValue) const;
+ int64_t getInt64(const std::string &key, int64_t defaultValue) const;
+ double getDouble(const std::string &key, double defaultValue) const;
+ // The serialized format for a bool acceepts the following values:
+ // True: "1", "yes", "YES".
+ // False: "0", "no", "NO".
+ bool getBool(const std::string &key, bool defaultValue) const;
+ bool getBool(const std::string &key, std::string_view defaultValueStr) const;
+ bool getBool(const std::string &key, const char *defaultValue) const {
+ return getBool(key, std::string_view(defaultValue));
+ }
+ // Parses a string as disk size. The serialized format is [0-9]+[kKmMgG].
+ // The
+ // suffixes correspond to KiB, MiB and GiB multipliers.
+ // Note: We consider 1K = 1024, not 1000.
+ DiskSize getDiskSize(const std::string &key, DiskSize defaultValue) const;
+ DiskSize getDiskSize(const std::string &key,
+ std::string_view defaultValue) const;
+
+ // ///////////////////// Value Setters
+ // //////////////////////////////////////
+ void setString(const std::string &key, std::string_view value);
+ void setInt(const std::string &key, int value);
+ void setInt64(const std::string &key, int64_t value);
+ void setDouble(const std::string &key, double value);
+ void setBool(const std::string &key, bool value);
+ void setDiskSize(const std::string &key, DiskSize value);
+
+ // //////////////////// Iterators
+ // ///////////////////////////////////////////
+ // You can iterate through (string) keys in this IniFile, and then use the
+ // correct |get*| function to obtain the corresponding value.
+ // The order of keys is guaranteed to be an extension of the order in the
+ // backing file:
+ // - For keys that exist in the backing file, order is maintained.
+ // - Rest of the keys are appended in the end, in the order they were
+ // first added.
+ // Only const_iterator is provided. Use |set*| functions to modify the
+ // IniFile.
+ const_iterator begin() const {
+ return const_iterator(std::begin(mOrderList));
+ }
+ const_iterator end() const { return const_iterator(std::end(mOrderList)); }
+
+protected:
+ void parseStream(std::istream *inFile, bool keepComments);
+ void updateData(const std::string &key, std::string &&value);
+ bool writeCommon(bool discardEmpty);
+
+private:
+ bool writeCommonImpl(bool discardEmpty, const std::string &filepath);
+
+ MapType mData;
+ ElementOrderList mOrderList;
+ std::vector<std::pair<int, std::string>> mComments;
+ std::string mBackingFilePath;
+ bool mDirty = true;
+};
+
+} // namespace android::goldfish \ No newline at end of file
diff --git a/files/include/android/goldfish/ParameterList.h b/files/include/android/goldfish/ParameterList.h
new file mode 100644
index 0000000..e6c35af
--- /dev/null
+++ b/files/include/android/goldfish/ParameterList.h
@@ -0,0 +1,92 @@
+// Copyright 2016 The Android Open Source Project
+//
+// This software is licensed under the terms of the GNU General Public
+// License version 2, as published by the Free Software Foundation, and
+// may be copied, distributed, and modified under those terms.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+#pragma once
+
+#include <stdarg.h>
+
+#include <string>
+#include <vector>
+
+#include "aemu/base/StringFormat.h"
+
+namespace android {
+
+// Convenience class used to build a space-based parameter list incrementally.
+// Usage example:
+// 1/ Create new instance.
+// 2/ Add individual parameters with add(), add2(), addIf() or add2If()
+// 3/ Retrieve final result as a single string with toString() or
+// toCString().
+// 4/ Alternatively, retrieve the list's size with size(), and individual
+// parameters as a char* array with array().
+class ParameterList {
+public:
+ // Default constructor.
+ ParameterList() = default;
+ ParameterList(int argc, char **argv);
+ ParameterList(std::initializer_list<std::string> lst);
+
+ // Return the number of items in the list.
+ size_t size() const;
+
+ // Return a char* array of size() items that points to the content of
+ // individual items.
+ char **array() const;
+
+ // Access |n-th| items in the list. WARNING: Does not check bounds.
+ const std::string &operator[](size_t n) const { return mParams[n]; }
+
+ // Convert list into a std::string. This joins all parameters with a
+ // single space.
+ // |quotes| add quotes to parameters when there is a space
+ std::string toString(bool quotes = true) const;
+
+ // Adds a set of parameters to the list.
+ void add(const ParameterList &other);
+
+ // Add new parameter |param| to the list.
+ void add(const std::string &param);
+
+ // Note: this will also handle const char* parameters automatically.
+ void add(std::string &&param);
+
+ // Add a new parameter from a printf-formatted string.
+ // |format| is a printf-like formatting string, followed by optional
+ // parameters. Users are recommended to use addFormat() instead.
+ void addFormatRaw(const char *format, ...);
+
+ // Variant of addFormatRaw() that uses a va_list to pass the formatting
+ // arguments.
+ void addFormatWithArgs(const char *format, va_list args);
+
+ // These templated versions of StringFormat*() allow one to pass all kinds of
+ // string objects into the argument list
+ template <class... Args> void addFormat(const char *format, Args &&...args) {
+ return addFormatRaw(
+ format, android::base::unpackFormatArg(std::forward<Args>(args))...);
+ }
+
+ // Add two new parameters |param1| and |param2| to the list.
+ void add2(const char *param1, const char *param2);
+
+ // Add parameter |param| to the list iff |flag| is true.
+ void addIf(const char *param, bool flag);
+
+ // Add parameters |param1| and |param2| iff |param2| is not nullptr.
+ void add2If(const char *param1, const char *param2);
+
+private:
+ std::vector<std::string> mParams;
+ mutable std::vector<char *> mArray;
+};
+
+} // namespace android
diff --git a/files/include/android/goldfish/avd/Avd.h b/files/include/android/goldfish/avd/Avd.h
new file mode 100644
index 0000000..e11c93b
--- /dev/null
+++ b/files/include/android/goldfish/avd/Avd.h
@@ -0,0 +1,131 @@
+// 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.
+#pragma once
+#include <cstdint>
+#include <filesystem>
+#include <memory>
+#include <optional>
+#include <vector>
+
+#include "absl/status/statusor.h"
+#include "android/goldfish/IniFile.h"
+
+namespace android::goldfish {
+namespace fs = std::filesystem;
+
+/* An Android Virtual Device (AVD for short) corresponds to a
+ * directory containing all kernel/disk images for a given virtual
+ * device, as well as information about its hardware capabilities,
+ * SDK version number, skin, etc...
+ *
+ * Each AVD has a human-readable name and is backed by a root
+ * configuration file and a content directory. For example, an
+ * AVD named 'foo' will correspond to the following:
+ *
+ * - a root configuration file named ~/.android/avd/foo.ini
+ * describing where the AVD's content can be found
+ *
+ * - a content directory like ~/.android/avd/foo/ containing all
+ * disk image and configuration files for the virtual device.
+ *
+ * the 'foo.ini' file should contain at least one line of the form:
+ *
+ * rootPath=<content-path>
+ *
+ * it may also contain other lines that cache stuff found in the
+ * content directory, like hardware properties or SDK version number.
+ *
+ * it is possible to move the content directory by updating the foo.ini
+ * file to point to the new location. This can be interesting when your
+ * $HOME directory is located on a network share or in a roaming profile
+ * (Windows), given that the content directory of a single virtual device
+ * can easily use more than 100MB of data.
+ *
+ */
+
+/* a macro used to define the list of disk images managed by the
+ * implementation. This macro will be expanded several times with
+ * varying definitions of _AVD_IMG
+ */
+#define AVD_IMAGE_LIST \
+ _AVD_IMG(KERNEL, "kernel-qemu", "kernel") \
+ _AVD_IMG(KERNELRANCHU, "kernel-ranchu", "kernel") \
+ _AVD_IMG(KERNELRANCHU64, "kernel-ranchu-64", "kernel") \
+ _AVD_IMG(RAMDISK, "ramdisk.img", "ramdisk") \
+ _AVD_IMG(USERRAMDISK, "ramdisk-qemu.img", "user ramdisk") \
+ _AVD_IMG(INITSYSTEM, "system.img", "init system") \
+ _AVD_IMG(INITVENDOR, "vendor.img", "init vendor") \
+ _AVD_IMG(INITDATA, "userdata.img", "init data") \
+ _AVD_IMG(INITZIP, "data", "init data zip") \
+ _AVD_IMG(USERSYSTEM, "system-qemu.img", "user system") \
+ _AVD_IMG(USERVENDOR, "vendor-qemu.img", "user vendor") \
+ _AVD_IMG(USERDATA, "userdata-qemu.img", "user data") \
+ _AVD_IMG(CACHE, "cache.img", "cache") \
+ _AVD_IMG(SDCARD, "sdcard.img", "SD Card") \
+ _AVD_IMG(ENCRYPTIONKEY, "encryptionkey.img", "Encryption Key") \
+ _AVD_IMG(SNAPSHOTS, "snapshots.img", "snapshots") \
+ _AVD_IMG(VERIFIEDBOOTPARAMS, "VerifiedBootParams.textproto", \
+ "Verified Boot Parameters") \
+ _AVD_IMG(BUILDPROP, "build.prop", "Build properties")
+
+/* define the enumared values corresponding to each AVD image type
+ * examples are: AVD_IMAGE_KERNEL, AVD_IMAGE_SYSTEM, etc..
+ */
+#define _AVD_IMG(x, y, z) x,
+enum class AvdImageType : uint8_t {
+ AVD_IMAGE_LIST AVD_IMAGE_MAX /* do not remove */
+};
+#undef _AVD_IMG
+
+class Avd {
+public:
+ enum class Flavor : uint8_t {
+ PHONE = 0,
+ TV = 1,
+ WEAR = 2,
+ ANDROID_AUTO = 3,
+ DESKTOP = 4,
+ OTHER = 255,
+ };
+
+ ~Avd() = default;
+ // Move Constructor
+ Avd(Avd &&other) noexcept
+ : mTarget(std::move(other.mTarget)), mConfig(std::move(other.mConfig)),
+ mName(std::move(other.mName)) {}
+
+ std::string details();
+ std::string name() { return mName; }
+ fs::path getContentPath() { return mContentPath; };
+ absl::StatusOr<fs::path> getImagePath(AvdImageType imgType);
+ absl::StatusOr<fs::path> getSystemImagePath(AvdImageType imgType);
+ Flavor getFlavor();
+
+ // List all the avd names that are available.
+ static std::vector<std::string> list();
+ static absl::StatusOr<Avd> fromName(std::string name);
+
+private:
+ static absl::StatusOr<Avd> parse(fs::path target, std::string name);
+ Avd(fs::path content_path, std::unique_ptr<IniFile> target,
+ std::unique_ptr<IniFile> config, std::string name);
+
+ std::unique_ptr<IniFile> mTarget;
+ std::unique_ptr<IniFile> mConfig;
+ std::string mName;
+ std::optional<Flavor> mFlavor;
+ fs::path mContentPath;
+};
+
+} // namespace android::goldfish \ No newline at end of file
diff --git a/files/include/android/goldfish/bootconfig.h b/files/include/android/goldfish/bootconfig.h
new file mode 100644
index 0000000..e679d47
--- /dev/null
+++ b/files/include/android/goldfish/bootconfig.h
@@ -0,0 +1,29 @@
+// Copyright 2021 The Android Open Source Project
+//
+// This software is licensed under the terms of the GNU General Public
+// License version 2, as published by the Free Software Foundation, and
+// may be copied, distributed, and modified under those terms.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+#pragma once
+
+#include <string>
+#include <vector>
+
+namespace android::goldfish {
+// [src ramdisk][bootconfig][padding][size(le32)][csum(le32)][#BOOTCONFIG\n]
+// ^ 4 byte aligned
+
+std::vector<char> buildBootconfigBlob(
+ const size_t srcSize,
+ const std::vector<std::pair<std::string, std::string>> &bootconfig);
+
+int createRamdiskWithBootconfig(
+ const char *srcRamdiskPath, const char *dstRamdiskPath,
+ const std::vector<std::pair<std::string, std::string>> &bootconfig);
+
+} // namespace android::goldfish \ No newline at end of file
diff --git a/files/src/android/goldfish/ConfigDirs.cpp b/files/src/android/goldfish/ConfigDirs.cpp
new file mode 100644
index 0000000..73ae22a
--- /dev/null
+++ b/files/src/android/goldfish/ConfigDirs.cpp
@@ -0,0 +1,316 @@
+// Copyright 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+#include "android/goldfish/ConfigDirs.h"
+
+#include <cassert>
+#include <filesystem>
+#include <string_view>
+
+#include "aemu/base/logging/Log.h"
+#include "aemu/base/system/System.h"
+#include "android/base/system/System.h"
+
+namespace android::goldfish {
+
+namespace fs = std::filesystem;
+
+using android::base::System;
+// Name of the Android configuration directory under $HOME.
+static const std::string_view kAndroidSubDir = ".android";
+// Subdirectory for AVD data files.
+static const std::string_view kAvdSubDir = "avd";
+
+// static
+auto ConfigDirs::getUserDirectory() -> fs::path {
+ fs::path home = System::get()->envGet("ANDROID_EMULATOR_HOME");
+ if (!home.empty()) {
+ return home;
+ }
+
+ // New key: ANDROID_PREFS_ROOT
+ home = System::get()->envGet("ANDROID_PREFS_ROOT");
+ if (!home.empty()) {
+ // In v1.9 emulator was changed to use $ANDROID_SDK_HOME/.android
+ // directory, but Android Studio has always been using $ANDROID_SDK_HOME
+ // directly. Put a workaround here to make sure it works both ways,
+ // preferring the one from AS.
+ auto homeNewWay = fs::path(home) / kAndroidSubDir;
+ return fs::is_directory(homeNewWay) ? homeNewWay : home;
+ } // Old key that is deprecated (ANDROID_SDK_HOME)
+ home = System::get()->envGet("ANDROID_SDK_HOME");
+ if (!home.empty()) {
+ auto homeOldWay = fs::path(home) / kAndroidSubDir;
+ return System::get()->pathExists(homeOldWay) ? homeOldWay : home;
+ }
+
+ home = android::base::System::get()->getHomeDirectory();
+ if (home.empty()) {
+ return fs::temp_directory_path();
+ }
+ return home / kAndroidSubDir;
+}
+
+// static
+auto ConfigDirs::getAvdRootDirectory() -> fs::path {
+ System *system = System::get();
+ // The search order here should match that in AndroidLocation.java
+ // in Android Studio. Otherwise, Studio and the Emulator may find
+ // different AVDs. Or one may find an AVD when the other doesn't.
+ std::string avdRoot = System::get()->envGet("ANDROID_AVD_HOME");
+ if (!avdRoot.empty() && system->pathIsDir(avdRoot)) {
+ return avdRoot;
+ }
+
+ // No luck with ANDROID_AVD_HOME, try ANDROID_PREFS_ROOT/ANDROID_SDK_HOME
+ avdRoot = getAvdRootDirectoryWithPrefsRoot(
+ System::get()->envGet("ANDROID_PREFS_ROOT"));
+ if (!avdRoot.empty()) {
+ return avdRoot;
+ }
+ avdRoot = getAvdRootDirectoryWithPrefsRoot(
+ System::get()->envGet("ANDROID_SDK_HOME"));
+ if (!avdRoot.empty()) {
+ return avdRoot;
+ } // ANDROID_PREFS_ROOT/ANDROID_SDK_HOME is defined but bad. In this case,
+ // Android Studio tries $TEST_TMPDIR, $USER_HOME, and
+ // $HOME. We'll do the same.
+ avdRoot = System::get()->envGet("TEST_TMPDIR");
+ if (!avdRoot.empty()) {
+ avdRoot = fs::path(avdRoot) / kAndroidSubDir;
+ if (isValidAvdRoot(avdRoot)) {
+ return fs::path(avdRoot) / kAvdSubDir;
+ }
+ }
+ avdRoot = System::get()->envGet("USER_HOME");
+ if (!avdRoot.empty()) {
+ avdRoot = fs::path(avdRoot) / kAndroidSubDir;
+ if (isValidAvdRoot(avdRoot)) {
+ return fs::path(avdRoot) / kAvdSubDir;
+ }
+ }
+ avdRoot = System::get()->envGet("HOME");
+ if (!avdRoot.empty()) {
+ avdRoot = fs::path(avdRoot) / kAndroidSubDir;
+ if (isValidAvdRoot(avdRoot)) {
+ return fs::path(avdRoot) / kAvdSubDir;
+ }
+ }
+
+ // No luck with ANDROID_AVD_HOME, ANDROID_SDK_HOME,
+ // TEST_TMPDIR, USER_HOME, or HOME. Try even more.
+ return getUserDirectory() / kAvdSubDir;
+}
+
+// static
+auto ConfigDirs::getSdkRootDirectoryByEnv(bool verbose) -> fs::path {
+ if (verbose) {
+ LOG(INFO) << "checking ANDROID_HOME for valid sdk root.";
+ }
+ std::string sdkRoot = System::get()->envGet("ANDROID_HOME");
+ if (verbose) {
+ dinfo("ANDROID_HOME: %s", sdkRoot);
+ }
+ if (!sdkRoot.empty() && isValidSdkRoot(sdkRoot, verbose)) {
+ return sdkRoot;
+ }
+
+ if (verbose) {
+ LOG(INFO) << "checking ANDROID_SDK_ROOT for valid sdk root.";
+ }
+ // ANDROID_HOME is not good. Try ANDROID_SDK_ROOT.
+ sdkRoot = System::get()->envGet("ANDROID_SDK_ROOT");
+ if (static_cast<unsigned int>(!sdkRoot.empty()) != 0U) {
+ // Unquote a possibly "quoted" path.
+ if (sdkRoot[0] == '"') {
+ assert(sdkRoot.back() == '"');
+ sdkRoot.erase(0, 1);
+ sdkRoot.pop_back();
+ }
+ if (isValidSdkRoot(sdkRoot, verbose)) {
+ return sdkRoot;
+ }
+ } else if (verbose) {
+ dwarning("ANDROID_SDK_ROOT is missing.");
+ }
+
+ return {};
+}
+
+auto ConfigDirs::getSdkRootDirectoryByPath(bool verbose) -> fs::path {
+ auto parts = System::get()->getLauncherDirectory();
+
+ fs::path sdkRoot = fs::path(parts);
+ for (int i = 0; i < 3; ++i) {
+ sdkRoot = sdkRoot.parent_path();
+ if (verbose) {
+ dinfo("guessed sdk root: %s", sdkRoot);
+ }
+ if (isValidSdkRoot(sdkRoot, verbose)) {
+ return sdkRoot;
+ }
+ if (verbose) {
+ LOG(INFO) << "guessed sdk root " << sdkRoot
+ << " does not seem to be valid";
+ }
+ }
+ if (verbose) {
+ LOG(WARNING) << "invalid sdk root:" << sdkRoot;
+ }
+ return {};
+}
+
+// static
+auto ConfigDirs::getSdkRootDirectory(bool verbose) -> fs::path {
+ std::string sdkRoot = getSdkRootDirectoryByEnv(verbose);
+ if (!sdkRoot.empty()) {
+ return sdkRoot;
+ }
+
+ if (verbose) {
+ dwarning("Cannot find valid sdk root from environment "
+ "variable ANDROID_HOME nor ANDROID_SDK_ROOT,"
+ "Try to infer from emulator's path");
+ }
+ // Otherwise, infer from the path of the emulator's binary.
+ return getSdkRootDirectoryByPath(verbose);
+}
+
+// static
+auto ConfigDirs::isValidSdkRoot(const fs::path &rootPath,
+ bool verbose) -> bool {
+ if (rootPath.empty()) {
+ if (verbose) {
+ dwarning("empty sdk root");
+ }
+ return false;
+ }
+
+ System *system = System::get();
+ if (!system->pathIsDir(rootPath) || !system->pathCanRead(rootPath)) {
+ if (verbose) {
+ if (!system->pathIsDir(rootPath)) {
+ dwarning("%s is not a directory, and cannot be sdk root", rootPath);
+ } else if (!system->pathCanRead(rootPath)) {
+ dwarning("%s is not readable, and cannot be sdk root", rootPath);
+ }
+ }
+ return false;
+ }
+ fs::path platformsPath = fs::path(rootPath) / "platforms";
+ if (!system->pathIsDir(rootPath) || !system->pathCanRead(rootPath)) {
+ if (verbose) {
+ LOG(WARNING) << "platforms subdirectory is missing under " << rootPath
+ << ", please install it";
+ }
+ return false;
+ }
+ fs::path platformToolsPath = fs::path(rootPath) / "platform-tools";
+ if (!system->pathIsDir(platformToolsPath)) {
+ if (verbose) {
+ LOG(WARNING) << "platform-tools subdirectory is missing under "
+ << rootPath << ", please install it";
+ }
+ return false;
+ }
+
+ return true;
+}
+
+// static
+auto ConfigDirs::isValidAvdRoot(const fs::path &avdPath) -> bool {
+ if (avdPath.empty()) {
+ return false;
+ }
+ System *system = System::get();
+ if (!system->pathIsDir(avdPath) || !system->pathCanRead(avdPath)) {
+ return false;
+ }
+ fs::path avdAvdPath = avdPath / "avd";
+ return (system->pathIsDir(avdAvdPath) && system->pathCanRead(avdAvdPath));
+}
+
+auto ConfigDirs::getAvdRootDirectoryWithPrefsRoot(const fs::path &path)
+ -> fs::path {
+ if (path.empty()) {
+ return {};
+ }
+
+ // ANDROID_PREFS_ROOT is defined
+ if (isValidAvdRoot(path)) {
+ // ANDROID_PREFS_ROOT is good
+ return path / kAvdSubDir;
+ }
+
+ fs::path avdRoot = path / kAndroidSubDir;
+ if (isValidAvdRoot(avdRoot)) {
+ // ANDROID_PREFS_ROOT/.android is good
+ return avdRoot / kAvdSubDir;
+ }
+
+ return {};
+}
+
+using discovery_dir = struct discovery_dir {
+ const char *root_env;
+ const char *subdir;
+};
+
+#if defined(_WIN32)
+discovery_dir discovery{"LOCALAPPDATA", "Temp"};
+#elif defined(__linux__)
+discovery_dir discovery{"XDG_RUNTIME_DIR", ""};
+#elif defined(__APPLE__)
+discovery_dir discovery{"HOME", "Library/Caches/TemporaryItems"};
+#else
+#error This platform is not supported.
+#endif
+
+static auto getAlternativeRoot() -> fs::path {
+#ifdef __linux__
+ auto uid = getuid();
+ auto discovery = pj("/run/user/", std::to_string(uid));
+ if (System::get()->pathExists(discovery)) {
+ return discovery;
+ }
+#endif
+
+ // Reverting to the standard emulator user directories
+ return ConfigDirs::getUserDirectory();
+}
+
+auto ConfigDirs::getDiscoveryDirectory() -> fs::path {
+ fs::path root = System::get()->envGet(discovery.root_env);
+ if (root.empty()) {
+ // Reverting to the alternative root if these environment variables do
+ // not exist.
+ LOG(WARNING)
+ << "Using fallback path for the emulator registration directory.";
+ root = getAlternativeRoot();
+ } else {
+ root = root / discovery.subdir;
+ }
+
+ auto desired_directory = root / "avd" / "running";
+ auto recomposed = fs::canonical(desired_directory);
+ if (!fs::exists(recomposed)) {
+ std::error_code ec;
+ if (!fs::create_directories(recomposed, ec)) {
+ LOG(WARNING) << "Unable to create directories: " << recomposed
+ << " due to " << ec.message();
+ }
+ fs::permissions(recomposed, fs::perms::owner_all, fs::perm_options::remove);
+ }
+ return recomposed;
+}
+} // namespace android::goldfish
diff --git a/files/src/android/goldfish/EncryptionKeyImg.cpp b/files/src/android/goldfish/EncryptionKeyImg.cpp
new file mode 100644
index 0000000..ef5ab2b
--- /dev/null
+++ b/files/src/android/goldfish/EncryptionKeyImg.cpp
@@ -0,0 +1,37 @@
+
+#include "android/goldfish/avd/Avd.h"
+
+namespace android::goldfish {
+
+static bool createInitalEncryptionKeyPartition(const Avd *avd) {
+ // avd->getSystemImagePath(AvdImageType::ENCRYPTIONKEY);
+ // avd->
+ // auto partition = "disk.dataPartition.path";
+ // if (!userdata_dir) {
+ // derror("no userdata_dir");
+ // return false;
+ // }
+ // hw->disk_encryptionKeyPartition_path = path_join(userdata_dir.get(),
+ // "encryptionkey.img"); if (path_exists(hw->disk_systemPartition_initPath)) {
+ // ScopedCPtr<char>
+ // sysimg_dir(path_dirname(hw->disk_systemPartition_initPath)); if
+ // (!sysimg_dir.get()) {
+ // derror("no sysimg_dir %s", hw->disk_systemPartition_initPath);
+ // return false;
+ // }
+ // ScopedCPtr<char> init_encryptionkey_img_path(
+ // path_join(sysimg_dir.get(), "encryptionkey.img"));
+ // if (path_exists(init_encryptionkey_img_path.get())) {
+ // if (path_copy_file(hw->disk_encryptionKeyPartition_path,
+ // init_encryptionkey_img_path.get()) >= 0) {
+ // return true;
+ // }
+ // } else {
+ // derror("no init encryptionkey.img");
+ // }
+ // } else {
+ // derror("no system partition %s", hw->disk_systemPartition_initPath);
+ // }
+ // return false;
+}
+} // namespace android::goldfish \ No newline at end of file
diff --git a/files/src/android/goldfish/Ext4.cpp b/files/src/android/goldfish/Ext4.cpp
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/files/src/android/goldfish/Ext4.cpp
diff --git a/files/src/android/goldfish/FileShareOpen.cpp b/files/src/android/goldfish/FileShareOpen.cpp
new file mode 100644
index 0000000..4efb155
--- /dev/null
+++ b/files/src/android/goldfish/FileShareOpen.cpp
@@ -0,0 +1,156 @@
+// Copyright 2018 The Android Open Source Project
+//
+// This software is licensed under the terms of the GNU General Public
+// License version 2, as published by the Free Software Foundation, and
+// may be copied, distributed, and modified under those terms.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+#include "aemu/base/files/FileShareOpen.h"
+
+#include <errno.h>
+
+#include <algorithm>
+#include <chrono>
+#include <cstdio>
+#include <thread>
+
+#include "absl//strings/str_format.h"
+#include "aemu/base/files/FileShareOpenImpl.h"
+#include "android/base/file/file_io.h"
+
+void android::base::createFileForShare(const char *filename) {
+ void *handle = internal::openFileForShare(filename);
+ if (handle) {
+ internal::closeFileForShare(handle);
+ }
+}
+
+FILE *android::base::fsopenWithTimeout(const char *filename, const char *mode,
+ android::base::FileShare fileshare,
+ int timeoutMs) {
+ if (timeoutMs <= 0) {
+ return fsopen(filename, mode, fileshare);
+ }
+ FILE *ret = nullptr;
+ int waited = 0;
+ while (!ret && waited < timeoutMs) {
+ ret = fsopen(filename, mode, fileshare);
+ if (ret) {
+ return ret;
+ }
+ int wait = std::min(timeoutMs - waited, 200);
+ std::this_thread::sleep_for(std::chrono::milliseconds(wait));
+ waited += wait;
+ }
+ return nullptr;
+}
+
+#ifdef _WIN32
+
+#include <share.h>
+#include <windows.h>
+
+#include "aemu/base/system/Win32UnicodeString.h"
+
+FILE *android::base::fsopen(const char *filename, const char *mode,
+ android::base::FileShare fileshare) {
+ int shflag = _SH_DENYWR;
+ switch (fileshare) {
+ case FileShare::Read:
+ // Others cannot write
+ shflag = _SH_DENYWR;
+ break;
+ case FileShare::Write:
+ // Others cannot read nor write
+ shflag = _SH_DENYRW;
+ break;
+ }
+ const Win32UnicodeString filenameW(filename);
+ const Win32UnicodeString modeW(mode);
+ FILE *file = _wfsopen(filenameW.c_str(), modeW.c_str(), shflag);
+ return file;
+}
+
+bool android::base::updateFileShare(FILE *file, FileShare fileshare) {
+ // TODO: have windows support
+ // BUG: 112265408
+ derror("Error: updateFileShare not supported on windows");
+ return false;
+}
+
+void *android::base::internal::openFileForShare(const char *filename) {
+ const Win32UnicodeString filenameW(filename);
+ void *hndl =
+ CreateFileW(filenameW.c_str(), 0,
+ FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
+ CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
+ if (hndl != INVALID_HANDLE_VALUE) {
+ return hndl;
+ } else {
+ return nullptr;
+ }
+}
+
+void android::base::internal::closeFileForShare(void *fileHandle) {
+ CloseHandle(fileHandle);
+}
+
+#else
+#include <sys/file.h>
+#ifndef _MSC_VER
+#include <unistd.h>
+#endif
+
+static int getFlockOperation(android::base::FileShare fileshare) {
+ switch (fileshare) {
+ case android::base::FileShare::Read:
+ return LOCK_SH;
+ case android::base::FileShare::Write:
+ return LOCK_EX;
+ default:
+ return LOCK_SH;
+ }
+}
+
+FILE *android::base::fsopen(const char *filename, const char *mode,
+ android::base::FileShare fileshare) {
+ std::string tmp;
+ // "e" is a glibc extension from glibc 2.7, it open the file with
+ // O_CLOEXEC flag. It doesn't work for macOS/Windows actually but it
+ // also doesn't hurt.
+ if (!strchr(mode, 'e')) {
+ tmp = absl::StrFormat("%se", mode);
+ mode = tmp.c_str();
+ }
+ FILE *file = android_fopen(filename, mode);
+ if (!file) {
+ return nullptr;
+ }
+ int operation = getFlockOperation(fileshare);
+ int fd = fileno(file);
+ if (flock(fd, operation | LOCK_NB) == -1) {
+ fclose(file);
+ return nullptr;
+ }
+ return file;
+}
+
+bool android::base::updateFileShare(FILE *file, FileShare fileshare) {
+ int operation = getFlockOperation(fileshare);
+ int fd = fileno(file);
+ return -1 != flock(fd, operation | LOCK_NB);
+}
+
+void *android::base::internal::openFileForShare(const char *filename) {
+ return android_fopen(filename, "a");
+}
+
+void android::base::internal::closeFileForShare(void *fileHandle) {
+ fclose((FILE *)fileHandle);
+}
+
+#endif
diff --git a/files/src/android/goldfish/IniFile.cpp b/files/src/android/goldfish/IniFile.cpp
new file mode 100644
index 0000000..a9c53be
--- /dev/null
+++ b/files/src/android/goldfish/IniFile.cpp
@@ -0,0 +1,652 @@
+// Copyright 2015 The Android Open Source Project
+//
+// This software is licensed under the terms of the GNU General Public
+// License version 2, as published by the Free Software Foundation, and
+// may be copied, distributed, and modified under those terms.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+#include "android/goldfish/IniFile.h"
+
+#include <filesystem>
+
+#include "aemu/base/logging/Log.h"
+#include "android/base/system/System.h"
+
+#ifdef _MSC_VER
+#include "aemu/base/system/Win32UnicodeString.h"
+#endif
+
+#ifdef _MSC_VER
+#include "msvc-posix.h"
+#endif
+
+#include <assert.h>
+#include <string.h>
+
+#include <fstream>
+#include <iomanip>
+#include <istream>
+#include <sstream>
+#include <streambuf>
+#include <string>
+#include <string_view>
+#include <utility>
+
+namespace android {
+namespace goldfish {
+
+using std::ifstream;
+using std::ios_base;
+using std::string;
+using std::to_string;
+
+static bool move(const std::string &from, const std::string &to) {
+ // std::rename returns 0 on success.
+ if (std::rename(from.c_str(), to.c_str())) {
+ if (errno == ENOENT) {
+ return false;
+ }
+
+#ifdef _SUPPORT_FILESYSTEM
+ // Rename can fail if files are on different disks
+ if (std::filesystem::copy_file(from.c_str(), to.c_str())) {
+ std::filesystem::remove(from.c_str());
+ return true;
+ } else {
+ return false;
+ }
+#else // _SUPPORT_FILESYSTEM
+ return false;
+#endif // _SUPPORT_FILESYSTEM
+ }
+ return true;
+}
+
+IniFile::IniFile(const char *data, int size) {
+ readFromMemory(std::string_view(data, size));
+}
+
+void IniFile::setBackingFile(std::filesystem::path filePath) {
+ // We have no idea what the new backing file contains.
+ mDirty = true;
+ mBackingFilePath = filePath;
+}
+
+static bool isSpaceChar(unsigned uc) {
+ return uc == ' ' || uc == '\r' || uc == '\t';
+}
+
+static bool isValueChar(unsigned uc) { return uc != '\r' && uc != '\n'; }
+
+static bool isKeyStartChar(unsigned uc) {
+ static const unsigned smallRange = 'z' - 'a' + 1;
+ static const unsigned capitalRange = 'Z' - 'A' + 1;
+ return (uc - 'a' < smallRange) || (uc - 'A' < capitalRange) || (uc == '_');
+}
+
+static bool isKeyChar(unsigned uc) {
+ static const unsigned numRange = '9' - '0' + 1;
+ return isKeyStartChar(uc) || (uc - '0' < numRange) || (uc == '.') ||
+ (uc == '-');
+}
+
+template <typename CIterator, typename Pred>
+static CIterator eat(CIterator citer, CIterator cend, Pred pred) {
+ while (citer != cend && pred(*citer)) {
+ ++citer;
+ }
+ return citer;
+}
+
+void IniFile::parseStream(std::istream *in, bool keepComments) {
+ string line;
+ int lineno = 0;
+ // This is the line number we'd print at if the IniFile were immediately
+ // written back. Unlike |line|, this will not be incremented for invalid
+ // lines, since they're completely dropped.
+ int outputLineno = 0;
+ while (std::getline(*in, line)) {
+ ++lineno;
+ ++outputLineno;
+
+ const string &cline = line;
+ auto citer = std::begin(cline);
+ auto cend = std::end(cline);
+ citer = eat(citer, cend, isSpaceChar);
+
+ // Handle empty lines, comments.
+ if (citer == cend) {
+ LOG(DEBUG) << "Line " << lineno << ": Skipped empty line.";
+ if (keepComments) {
+ mComments.emplace_back(outputLineno, std::move(line));
+ }
+ continue;
+ }
+ if (*citer == '#' || *citer == ';') {
+ LOG(DEBUG) << "Line " << lineno << ": Skipped comment line.";
+ if (keepComments) {
+ mComments.emplace_back(outputLineno, std::move(line));
+ }
+ continue;
+ }
+
+ // Extract and validate key.
+ const auto keyStartIter = citer;
+ if (!isKeyStartChar(*citer)) {
+ LOG(DEBUG) << "Line " << lineno
+ << ": Key does not start with a valid character."
+ << " Skipped line.";
+ --outputLineno;
+ continue;
+ }
+ ++citer;
+ citer = eat(citer, cend, isKeyChar);
+ auto key = string(keyStartIter, citer);
+
+ // Gobble the = sign.
+ citer = eat(citer, cend, isSpaceChar);
+ if (citer == cend || *citer != '=') {
+ LOG(DEBUG) << "Line " << lineno
+ << ": Missing expected assignment operator (=)."
+ << " Skipped line.";
+ --outputLineno;
+ continue;
+ }
+ ++citer;
+
+ // Extract the value.
+ citer = eat(citer, cend, isSpaceChar);
+ const auto valueStartIter = citer;
+ citer = eat(citer, cend, isValueChar);
+ auto value = string(valueStartIter, citer);
+ // Remove trailing space.
+ auto trailingSpaceIter = eat(value.rbegin(), value.rend(), isSpaceChar);
+ value.erase(trailingSpaceIter.base(), value.end());
+
+ // Ensure there's no invalid remainder.
+ citer = eat(citer, cend, isSpaceChar);
+ if (citer != cend) {
+ LOG(DEBUG) << "Line " << lineno
+ << ": Contains invalid character in the value."
+ << " Skipped line.";
+ --outputLineno;
+ continue;
+ }
+
+ // Everything parsed.
+ auto insertRes = mData.emplace(std::move(key), std::string());
+ insertRes.first->second = std::move(value);
+ if (insertRes.second) {
+ mOrderList.push_back(&*insertRes.first);
+ }
+ }
+}
+
+bool IniFile::read(bool keepComments) {
+ mDirty = false;
+ mData.clear();
+ mOrderList.clear();
+ mComments.clear();
+
+ if (mBackingFilePath.empty()) {
+ LOG(WARNING) << "Read called without a backing file!";
+ return false;
+ }
+
+#ifdef _MSC_VER
+ Win32UnicodeString wBackingFilePath(mBackingFilePath);
+ ifstream inFile(wBackingFilePath.c_str(), ios_base::in | ios_base::ate);
+#else
+ ifstream inFile(mBackingFilePath, ios_base::in | ios_base::ate);
+#endif
+
+ if (!inFile) {
+ LOG(WARNING) << "Failed to process .ini file " << mBackingFilePath
+ << " for reading.";
+ return false;
+ }
+
+ // avoid reading a very large file that was passed by mistake
+ // this threshold is quite liberal.
+ static const auto kMaxIniFileSize = ifstream::pos_type(655360);
+ static const auto kInvalidPos = ifstream::pos_type(-1);
+ const ifstream::pos_type endPos = inFile.tellg();
+ inFile.seekg(0, ios_base::beg);
+ const ifstream::pos_type begPos = inFile.tellg();
+ if (begPos == kInvalidPos || endPos == kInvalidPos ||
+ endPos - begPos > kMaxIniFileSize) {
+ LOG(WARNING) << ".ini File " << mBackingFilePath << " too large ("
+ << (endPos - begPos) << " bytes)";
+ return false;
+ }
+
+ parseStream(&inFile, keepComments);
+ return true;
+}
+
+bool IniFile::readFromMemory(std::string_view data) {
+ mDirty = false;
+ mData.clear();
+ mOrderList.clear();
+ mComments.clear();
+
+ // Create a streambuf that's able to do a single pass over an array only.
+ class OnePassIBuf : public std::streambuf {
+ public:
+ explicit OnePassIBuf(std::string_view data) {
+ setg(const_cast<char *>(data.data()), const_cast<char *>(data.data()),
+ const_cast<char *>(data.data()) + data.size());
+ }
+ };
+ OnePassIBuf ibuf(data);
+ std::istream in(&ibuf);
+ if (!in) {
+ LOG(WARNING) << "Failed to process input data for reading.";
+ return false;
+ }
+
+ parseStream(&in, true);
+ return true;
+}
+
+bool IniFile::writeCommonImpl(bool discardEmpty, const std::string &filePath) {
+#ifdef _MSC_VER
+ Win32UnicodeString wFilePath(filePath);
+ std::ofstream outFile(wFilePath.c_str(), ios_base::out | ios_base::trunc);
+#else
+ std::ofstream outFile(filePath, std::ios_base::out | std::ios_base::trunc);
+#endif
+
+ if (!outFile) {
+ LOG(WARNING) << "Failed to open '" << filePath << "' for writing.";
+ return false;
+ }
+
+ int lineno = 0;
+ auto commentIter = std::begin(mComments);
+ for (const auto &pair : mOrderList) {
+ ++lineno;
+
+ // Write comments
+ for (; commentIter != std::end(mComments) && lineno >= commentIter->first;
+ ++commentIter, ++lineno) {
+ outFile << commentIter->second << "\n";
+ }
+
+ const string &value = pair->second;
+ if (discardEmpty && value.empty()) {
+ continue;
+ }
+ const string &key = pair->first;
+ outFile << key << " = " << value << '\n';
+ }
+
+ mDirty = false;
+ return true;
+}
+
+// 1. write config to mBackingFilePath.new
+// 2. rename mBackingFilePath to mBackingFilePath.old
+// 3. rename mBackingFilePath.new to mBackingFilePath
+// 4. delete mBackingFilePath.old
+bool IniFile::writeCommon(const bool discardEmpty) {
+ if (mBackingFilePath.empty()) {
+ LOG(WARNING) << "Write called without a backing file!";
+ return false;
+ }
+
+ const std::string iniFileNew = mBackingFilePath + ".new";
+ if (!writeCommonImpl(discardEmpty, iniFileNew)) {
+ return false;
+ }
+
+ const std::string iniFileOld = mBackingFilePath + ".old";
+ std::filesystem::remove(
+ iniFileOld.c_str()); // just in case `myRemove` below failed
+
+ const bool deleteOldConfig =
+ move(mBackingFilePath.c_str(), iniFileOld.c_str());
+
+ if (!move(iniFileNew.c_str(), mBackingFilePath.c_str())) {
+ if (deleteOldConfig) {
+ // try to revert the first `rename`
+ if (!move(iniFileOld.c_str(), mBackingFilePath.c_str())) {
+ // mBackingFilePath is missing here
+ LOG(ERROR) << "Failed to update '" << mBackingFilePath
+ << "', the file no longer exists";
+ } else {
+ // mBackingFilePath is reverted back
+ LOG(WARNING) << "Failed to update '" << mBackingFilePath << "'";
+ }
+ } else {
+ // mBackingFilePath could be read-only
+ LOG(WARNING) << "Failed to save '" << mBackingFilePath << "'";
+ }
+
+ std::filesystem::remove(iniFileNew.c_str());
+ return false;
+ }
+
+ if (deleteOldConfig) {
+ std::filesystem::remove(iniFileOld.c_str());
+ }
+
+ return true;
+}
+
+bool IniFile::write() { return writeCommon(false); }
+
+bool IniFile::writeDiscardingEmpty() { return writeCommon(true); }
+
+bool IniFile::writeIfChanged() { return !mDirty || writeCommon(false); }
+
+int IniFile::size() const { return static_cast<int>(mData.size()); }
+
+bool IniFile::hasKey(std::string_view key) const {
+ return mData.find(key.data()) != std::end(mData);
+}
+
+std::string IniFile::makeValidKey(std::string_view str) {
+ std::ostringstream res;
+ res << std::hex << std::uppercase;
+ res << '_'; // mark all keys passed through this function with a leading
+ // underscore
+ for (char c : str) {
+ if (isKeyChar(c)) {
+ res << c;
+ } else {
+ res << '.' << std::setw(2) << std::setfill('0') << static_cast<int>(c);
+ }
+ }
+ return res.str();
+}
+
+string IniFile::makeValidValue(std::string_view str) {
+ std::ostringstream res;
+ for (auto ch = str.begin(); ch != str.end() && *ch != '\0'; ch++) {
+ if (*ch == '%')
+ res << *ch;
+ res << *ch;
+ }
+ return res.str();
+}
+
+// Substitute environment variables inside a string.
+// Substitution is based on %ENVIRONMENT_VARIABLE%
+// Double %% is treated as an escape.
+// If a string is not terminated (i.e a starting %, but
+// no ending %) the whole string will be returned.
+//
+// For example:
+// "%PAGER%" => "less"
+// "%PAG%%ER%" => "" ("PAG%ER" is not a valid name)
+// "%FOO" => "%FOO" (Not terminated)
+// "%%HI%%" => "%HI%" (Escaped)
+// Note that: %%%USER%%% is parsed as %(USER)% and not %(USER%)%
+static string envSubst(const std::string_view fix) {
+ size_t len = fix.size();
+
+ string res;
+ string var;
+ string *curr = &res;
+ for (unsigned int i = 0; i < len; i++) {
+ char ch = fix[i];
+
+ // A normal character will be added to the current string
+ if (ch != '%') {
+ curr->push_back(ch);
+ continue;
+ }
+
+ // Let's see if we are closing
+ if (curr == &var) {
+ string env = base::System::get()->envGet(var);
+ if (env.empty()) {
+ LOG(WARNING) << "Environment variable " << var << " is not set";
+ }
+ res.append(env);
+ var.clear();
+ curr = &res;
+ continue;
+ }
+
+ // Check if we have a case of escapism..
+ // Note that len-1 >= 0
+ char next = (i < len - 1) ? fix[i + 1] : '\0';
+
+ if (next == '%') {
+ // Escaped, let's skip it.
+ curr->push_back(ch);
+ i++;
+ } else {
+ // We open and start a env variable name.
+ curr = &var;
+ }
+ }
+
+ // Return full string for unterminated piece.
+ if (curr == &var) {
+ res.push_back('%');
+ res.append(var);
+ }
+
+ return res;
+}
+
+string IniFile::getString(const string &key,
+ std::string_view defaultValue) const {
+ auto citer = mData.find(key);
+ return envSubst((citer == std::end(mData)) ? defaultValue
+ : std::string_view(citer->second));
+}
+
+int IniFile::getInt(const string &key, int defaultValue) const {
+ if (mData.find(key) == std::end(mData)) {
+ return defaultValue;
+ }
+
+ auto value = getString(key, "");
+ char *end;
+ errno = 0;
+ const int result = strtol(value.c_str(), &end, 10);
+ if (errno || *end != 0) {
+ LOG(DEBUG) << "Malformed int value " << value << " for key " << key;
+ return defaultValue;
+ }
+ return result;
+}
+
+int64_t IniFile::getInt64(const string &key, int64_t defaultValue) const {
+ if (mData.find(key) == std::end(mData)) {
+ return defaultValue;
+ }
+
+ auto value = getString(key, "");
+ char *end;
+ errno = 0;
+ const int64_t result = strtoll(value.c_str(), &end, 10);
+ if (errno || *end != 0) {
+ LOG(DEBUG) << "Malformed int64 value " << value << " for key " << key;
+ return defaultValue;
+ }
+ return result;
+}
+
+double IniFile::getDouble(const string &key, double defaultValue) const {
+ if (mData.find(key) == std::end(mData)) {
+ return defaultValue;
+ }
+
+ auto value = getString(key, "");
+ char *end;
+ errno = 0;
+ const double result = strtod(value.c_str(), &end);
+ if (errno || *end != 0) {
+ LOG(DEBUG) << "Malformed double value " << value << " for key " << key;
+ return defaultValue;
+ }
+ return result;
+}
+
+static bool isBoolTrue(std::string_view value) {
+ const char *cstr = value.data();
+ const size_t size = value.size();
+
+ return strncasecmp("yes", cstr, size) == 0 ||
+ strncasecmp("true", cstr, size) == 0 ||
+ strncasecmp("1", cstr, size) == 0;
+}
+
+static bool isBoolFalse(std::string_view value) {
+ const char *cstr = value.data();
+ const size_t size = value.size();
+ return strncasecmp("no", cstr, size) == 0 ||
+ strncasecmp("false", cstr, size) == 0 ||
+ strncasecmp("0", cstr, size) == 0;
+}
+
+bool IniFile::getBool(const string &key, bool defaultValue) const {
+ if (mData.find(key) == std::end(mData)) {
+ return defaultValue;
+ }
+
+ const string &value = getString(key, "");
+ if (isBoolTrue(value)) {
+ return true;
+ } else if (isBoolFalse(value)) {
+ return false;
+ } else {
+ LOG(DEBUG) << "Malformed bool value " << value << " for key " << key;
+ return defaultValue;
+ }
+}
+
+bool IniFile::getBool(const string &key, std::string_view defaultValue) const {
+ return getBool(key, isBoolTrue(defaultValue));
+}
+
+// If not nullptr, |*outMalformed| is set to true if |valueStr| is malformed.
+static IniFile::DiskSize parseDiskSize(std::string_view valueStr,
+ IniFile::DiskSize defaultValue,
+ bool *outMalformed) {
+ if (outMalformed) {
+ *outMalformed = false;
+ }
+
+ char *end;
+ errno = 0;
+ std::string safe_str = std::string(valueStr);
+ IniFile::DiskSize result = strtoll(safe_str.c_str(), &end, 10);
+ bool malformed = (errno != 0);
+ if (!malformed) {
+ switch (*end) {
+ case 0:
+ break;
+ case 'k':
+ case 'K':
+ result *= 1024ULL;
+ break;
+ case 'm':
+ case 'M':
+ result *= 1024 * 1024ULL;
+ break;
+ case 'g':
+ case 'G':
+ result *= 1024 * 1024 * 1024ULL;
+ break;
+ default:
+ malformed = true;
+ }
+ }
+
+ if (malformed) {
+ if (outMalformed) {
+ *outMalformed = true;
+ }
+ return defaultValue;
+ }
+ return result;
+}
+
+IniFile::DiskSize IniFile::getDiskSize(const string &key,
+ IniFile::DiskSize defaultValue) const {
+ if (!hasKey(key)) {
+ return defaultValue;
+ }
+ bool malformed = false;
+ auto value = getString(key, "");
+ IniFile::DiskSize result = parseDiskSize(value, defaultValue, &malformed);
+
+ LOG_IF(VERBOSE, malformed)
+ << "Malformed DiskSize value " << value << " for key " << key;
+ return result;
+}
+
+IniFile::DiskSize IniFile::getDiskSize(const string &key,
+ std::string_view defaultValue) const {
+ return getDiskSize(key, parseDiskSize(defaultValue, 0, nullptr));
+}
+
+void IniFile::updateData(const string &key, string &&value) {
+ mDirty = true;
+ // note: may not move here, as it's currently unspecified if failed
+ // insertion moves from |value| or not. Move it separately instead.
+ auto result = mData.emplace(key, std::string());
+ result.first->second = std::move(value);
+ if (result.second) {
+ // New element was created.
+ mOrderList.push_back(&*result.first);
+ }
+}
+
+void IniFile::setString(const string &key, std::string_view value) {
+ updateData(key, value.data());
+}
+
+void IniFile::setInt(const string &key, int value) {
+ updateData(key, to_string(value));
+}
+
+void IniFile::setInt64(const string &key, int64_t value) {
+ // long long is at least 64 bit in C++0x.
+ updateData(key, to_string(static_cast<long long>(value)));
+}
+
+void IniFile::setDouble(const string &key, double value) {
+ updateData(key, to_string(value));
+}
+
+void IniFile::setBool(const string &key, bool value) {
+ updateData(key, value ? "true" : "false");
+}
+
+void IniFile::setDiskSize(const string &key, DiskSize value) {
+ static const DiskSize kKilo = 1024;
+ static const DiskSize kMega = 1024 * kKilo;
+ static const DiskSize kGiga = 1024 * kMega;
+
+ char suffix = 0;
+ if (value >= kGiga && !(value % kGiga)) {
+ value /= kGiga;
+ suffix = 'g';
+ } else if (value >= kMega && !(value % kMega)) {
+ value /= kMega;
+ suffix = 'm';
+ } else if (value >= kKilo && !(value % kKilo)) {
+ value /= kKilo;
+ suffix = 'k';
+ }
+
+ auto valueStr = to_string(value);
+ if (suffix) {
+ valueStr += suffix;
+ }
+ updateData(key, std::move(valueStr));
+}
+
+} // namespace goldfish
+} // namespace android
diff --git a/files/src/android/goldfish/ParameterList.cpp b/files/src/android/goldfish/ParameterList.cpp
new file mode 100644
index 0000000..dfeae84
--- /dev/null
+++ b/files/src/android/goldfish/ParameterList.cpp
@@ -0,0 +1,96 @@
+// Copyright 2016 The Android Open Source Project
+//
+// This software is licensed under the terms of the GNU General Public
+// License version 2, as published by the Free Software Foundation, and
+// may be copied, distributed, and modified under those terms.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+#include "android/goldfish/ParameterList.h"
+
+#include "absl/strings/match.h"
+#include "aemu/base/misc/StringUtils.h"
+#include "aemu/base/system/Win32Utils.h"
+
+namespace android {
+
+ParameterList::ParameterList(int argc, char **argv)
+ : mParams(argv, argv + argc) {}
+
+ParameterList::ParameterList(std::initializer_list<std::string> lst)
+ : mParams(lst) {}
+
+size_t ParameterList::size() const { return mParams.size(); }
+
+char **ParameterList::array() const {
+ if (mArray.empty() && !mParams.empty()) {
+ mArray.resize(mParams.size());
+ for (size_t n = 0; n < mParams.size(); ++n) {
+ mArray[n] = const_cast<char *>(&(mParams[n][0]));
+ }
+ }
+ return &mArray[0];
+}
+
+// Properly escapes individual parameters depending on the platform
+inline static std::string quoteCommandLine(const std::string &line) {
+#ifdef _WIN32
+ return base::Win32Utils::quoteCommandLine(line);
+#else
+ return !absl::StrContains(line, ' ') ? line : '\'' + line + '\'';
+#endif
+}
+
+std::string ParameterList::toString(bool quotes) const {
+ std::string result;
+ for (size_t n = 0; n < mParams.size(); ++n) {
+ if (n > 0) {
+ result += ' ';
+ }
+ result += quotes ? quoteCommandLine(mParams[n]) : mParams[n];
+ }
+ return result;
+}
+
+void ParameterList::add(const std::string &param) { mParams.push_back(param); }
+
+void ParameterList::add(const ParameterList &other) {
+ mParams.insert(mParams.end(), other.mParams.begin(), other.mParams.end());
+}
+
+void ParameterList::add(std::string &&param) {
+ mParams.emplace_back(std::move(param));
+}
+
+void ParameterList::addFormatRaw(const char *format, ...) {
+ va_list args;
+ va_start(args, format);
+ addFormatWithArgs(format, args);
+ va_end(args);
+}
+
+void ParameterList::addFormatWithArgs(const char *format, va_list args) {
+ add(android::base::StringFormatWithArgs(format, args));
+}
+
+void ParameterList::add2(const char *param1, const char *param2) {
+ mParams.push_back(param1);
+ mParams.push_back(param2);
+}
+
+void ParameterList::addIf(const char *param, bool flag) {
+ if (flag) {
+ add(param);
+ }
+}
+
+void ParameterList::add2If(const char *param1, const char *param2) {
+ if (param2) {
+ add2(param1, param2);
+ }
+}
+
+} // namespace android
diff --git a/files/src/android/goldfish/RingStreambuf.cpp b/files/src/android/goldfish/RingStreambuf.cpp
new file mode 100644
index 0000000..a90c307
--- /dev/null
+++ b/files/src/android/goldfish/RingStreambuf.cpp
@@ -0,0 +1,219 @@
+// Copyright (C) 2019 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.
+#include "aemu/base/streams/RingStreambuf.h"
+
+#include <string.h> // for memcpy
+
+#include <algorithm> // for max, min
+
+namespace android {
+namespace base {
+namespace streams {
+
+// See https://jameshfisher.com/2018/03/30/round-up-power-2 for details
+static uint64_t next_pow2(uint64_t x) {
+ return x == 1 ? 1 : 1 << (64 - __builtin_clzl(x - 1));
+}
+RingStreambuf::RingStreambuf(uint32_t capacity, milliseconds timeout)
+ : mTimeout(timeout) {
+ uint64_t cap = next_pow2(capacity + 1);
+ mRingbuffer.resize(cap);
+}
+
+void RingStreambuf::close() {
+ {
+ std::unique_lock<std::mutex> lock(mLock);
+ mTimeout = std::chrono::milliseconds(0);
+ mClosed = true;
+ }
+ mCanRead.notify_all();
+}
+
+std::streamsize RingStreambuf::xsputn(const char *s, std::streamsize n) {
+ // Usually n >> 1..
+ mLock.lock();
+ std::streamsize capacity = mRingbuffer.capacity();
+ std::streamsize maxWrite = std::max(n, capacity);
+
+ if (mClosed) {
+ mLock.unlock();
+ return 0;
+ }
+
+ // Case 1: It doesn't fit in the ringbuffer
+ if (n >= capacity) {
+ // We are overwriting everything, so let's just reset it all.
+ memcpy(mRingbuffer.data(), s + n - capacity, capacity);
+ mHead = capacity;
+ mTail = 0;
+ mHeadOffset += n;
+ mLock.unlock();
+ mCanRead.notify_all();
+ return n;
+ }
+
+ // Case 2, it fits in the ringbuffer.
+ // Case 2a: We are going over the edge of the buffer.
+
+ // Check to see if we have to update the tail, we are checking
+ // the case where the head is moving over the tail.
+ bool updateTail = (mHead < mTail && mTail <= mHead + n);
+
+ // We are getting overwritten from the end..
+ if (mHead + n > capacity) {
+ // Write up until the end of the buffer.
+ std::streamsize bytesUntilTheEnd = capacity - mHead;
+ memcpy(mRingbuffer.data() + mHead, s, bytesUntilTheEnd);
+
+ // Write he remaining bytes from the start of the buffer.
+ memcpy(mRingbuffer.data(), s + bytesUntilTheEnd, n - bytesUntilTheEnd);
+ mHead = n - bytesUntilTheEnd;
+
+ // We are checking the case where the tail got overwritten from the
+ // front.
+ updateTail |= mTail <= mHead;
+ } else {
+ // Case 2b: We are not falling off the edge of the world.
+ memcpy(mRingbuffer.data() + mHead, s, n);
+ mHead = (mHead + n) & (capacity - 1);
+
+ // Check the corner case where we flipped to pos 0.
+ updateTail |= mHead == mTail;
+ }
+ if (updateTail)
+ mTail = (mHead + 1) & (capacity - 1);
+ mHeadOffset += n;
+ mLock.unlock();
+ mCanRead.notify_all();
+ return n;
+}
+
+int RingStreambuf::overflow(int c) { return EOF; }
+
+std::streamsize RingStreambuf::waitForAvailableSpace(std::streamsize n) {
+ std::unique_lock<std::mutex> lock(mLock);
+ mCanRead.wait_for(lock, mTimeout,
+ [this, n]() { return showmanyw() >= n || mClosed; });
+ return showmanyw();
+}
+
+std::streamsize RingStreambuf::showmanyw() {
+ return mRingbuffer.capacity() - 1 - showmanyc();
+}
+
+std::streamsize RingStreambuf::showmanyc() {
+ // Note that:
+ // Full state is mHead + 1 == mTail
+ // Empty state is mHead == mTail
+ if (mHead < mTail) {
+ return mHead + mRingbuffer.capacity() - mTail;
+ }
+ return mHead - mTail;
+}
+
+std::streamsize RingStreambuf::xsgetn(char *s, std::streamsize n) {
+ std::unique_lock<std::mutex> lock(mLock);
+ if (!mCanRead.wait_for(lock, mTimeout, [this]() { return mTail != mHead; })) {
+ return 0;
+ }
+ std::streamsize toRead = std::min(showmanyc(), n);
+ std::streamsize capacity = mRingbuffer.capacity();
+ // 2 Cases:
+ // We are falling over the edge, or not:
+ if (mTail + toRead > capacity) {
+ // We wrap around
+ std::streamsize bytesUntilTheEnd = capacity - mTail;
+ memcpy(s, mRingbuffer.data() + mTail, bytesUntilTheEnd);
+ memcpy(s + bytesUntilTheEnd, mRingbuffer.data(), toRead - bytesUntilTheEnd);
+ } else {
+ // We don't
+ memcpy(s, mRingbuffer.data() + mTail, toRead);
+ }
+ mTail = (mTail + toRead) & (capacity - 1);
+ return toRead;
+}
+
+int RingStreambuf::underflow() {
+ std::unique_lock<std::mutex> lock(mLock);
+ if (!mCanRead.wait_for(lock, mTimeout,
+ [this]() { return mTail != mHead || mClosed; })) {
+ return traits_type::eof();
+ }
+ if (mClosed && mTail == mHead) {
+ return traits_type::eof();
+ }
+ return mRingbuffer[mTail];
+};
+
+int RingStreambuf::uflow() {
+ std::unique_lock<std::mutex> lock(mLock);
+ if (!mCanRead.wait_for(lock, mTimeout,
+ [this]() { return mTail != mHead || mClosed; })) {
+ return traits_type::eof();
+ }
+ if (mClosed && mTail == mHead) {
+ // [[unlikely]]
+ return traits_type::eof();
+ }
+
+ int val = mRingbuffer[mTail];
+ mTail = (mTail + 1) & (mRingbuffer.capacity() - 1);
+ return val;
+}
+
+std::pair<int, std::string>
+RingStreambuf::bufferAtOffset(std::streamsize offset, milliseconds timeoutMs) {
+ std::unique_lock<std::mutex> lock(mLock);
+ std::string res;
+ if (!mCanRead.wait_for(lock, timeoutMs,
+ [offset, this]() { return offset < mHeadOffset; })) {
+ return std::make_pair(mHeadOffset, res);
+ }
+
+ // Prepare the outgoing buffer.
+ std::streamsize capacity = mRingbuffer.capacity();
+ std::streamsize toRead = showmanyc();
+ std::streamsize startOffset = mHeadOffset - toRead;
+ std::streamsize skip = std::max(startOffset, offset) - startOffset;
+
+ // Let's find the starting point where we should be reading.
+ uint16_t read = (mTail + skip) & (capacity - 1);
+
+ // We are looking for an offset that is in the future...
+ // Return the current start offset, without anything
+ if (skip > toRead) {
+ return std::make_pair(mHeadOffset, res);
+ }
+
+ // Actual size of bytes we are going to read.
+ toRead -= skip;
+
+ // We are falling over the edge, or not:
+ res.reserve(toRead);
+ if (read + toRead > capacity) {
+ // We wrap around
+ std::streamsize bytesUntilTheEnd = capacity - read;
+ res.assign(mRingbuffer.data() + read, bytesUntilTheEnd);
+ res.append(mRingbuffer.data(), toRead - bytesUntilTheEnd);
+ } else {
+ // We don't fall of the cliff..
+ res.assign(mRingbuffer.data() + read, toRead);
+ }
+
+ return std::make_pair(startOffset + skip, res);
+}
+
+} // namespace streams
+} // namespace base
+} // namespace android
diff --git a/files/src/android/goldfish/avd/Avd.cpp b/files/src/android/goldfish/avd/Avd.cpp
new file mode 100644
index 0000000..9f61f7e
--- /dev/null
+++ b/files/src/android/goldfish/avd/Avd.cpp
@@ -0,0 +1,234 @@
+// 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.
+#include "android/goldfish/avd/Avd.h"
+
+#include <filesystem>
+#include <memory>
+#include <regex>
+#include <unordered_map>
+#include <vector>
+
+#include "absl/status/status.h"
+#include "absl/strings/str_format.h"
+#include "android/base/system/System.h"
+#include "android/goldfish/ConfigDirs.h"
+#include "android/goldfish/IniFile.h"
+#include "android/goldfish/avd/keys.h"
+
+/* technical note on how all of this is supposed to work:
+ *
+ * Each AVD corresponds to a "content directory" that is used to
+ * store persistent disk images and configuration files. Most remarkable
+ * are:
+ *
+ * - a "config.ini" file used to hold configuration information for the
+ * AVD
+ *
+ * - mandatory user data image ("userdata-qemu.img") and cache image
+ * ("cache.img")
+ *
+ * - optional mutable system image ("system-qemu.img"), kernel image
+ * ("kernel-qemu") and read-only ramdisk ("ramdisk.img")
+ *
+ * When starting up an AVD, the emulator looks for relevant disk images
+ * in the content directory. If it doesn't find a given image there, it
+ * will try to search in the list of system directories listed in the
+ * 'config.ini' file through one of the following (key,value) pairs:
+ *
+ * images.sysdir.1 = <first search path>
+ * images.sysdir.2 = <second search path>
+ *
+ * The search paths can be absolute, or relative to the root SDK installation
+ * path (which is determined from the emulator program's location, or from the
+ * ANDROID_SDK_ROOT environment variable).
+ *
+ * Individual image disk search patch can be over-riden on the command-line
+ * with one of the usual options.
+ */
+namespace android::goldfish {
+
+using android::base::System;
+using PropertyList = const std::array<std::string, 3>;
+using AvdFlavor = android::goldfish::Avd::Flavor;
+
+static const std::string_view
+ _imageFileNames[static_cast<int>(AvdImageType::AVD_IMAGE_MAX)] = {
+#define _AVD_IMG(x, y, z) y,
+ AVD_IMAGE_LIST
+#undef _AVD_IMG
+};
+
+static std::string getIconForFlavor(AvdFlavor flavor) {
+ switch (flavor) {
+ case AvdFlavor::PHONE:
+ return "📱"; // 📱 (Smartphone)
+ case AvdFlavor::TV:
+ return "📺"; // 📺 (Television)
+ case AvdFlavor::WEAR:
+ return "⌚️"; // ⌚️ (Smartwatch)
+ case AvdFlavor::ANDROID_AUTO:
+ return "🚗"; // 🚗 (Car)
+ case AvdFlavor::DESKTOP:
+ return "🖥️"; // 🖥️ (Desktop computer)
+ default:
+ return "🤷"; // 🤷 (Unknown)
+ }
+}
+
+absl::StatusOr<Avd> Avd::fromName(std::string name) {
+ auto directory_path = ConfigDirs::getAvdRootDirectory();
+ return Avd::parse(directory_path / (name + ".ini"), name);
+}
+
+AvdFlavor Avd::getFlavor() {
+ if (mFlavor.has_value()) {
+ return mFlavor.value();
+ }
+
+ AvdFlavor res = AvdFlavor::OTHER;
+
+ const std::unordered_map<std::string, Flavor> labelMap{
+ {"phone", Flavor::PHONE}, {"atv", Flavor::TV},
+ {"wear", Flavor::WEAR}, {"aw", Flavor::WEAR},
+ {"car", Flavor::ANDROID_AUTO}, {"pc", Flavor::DESKTOP}};
+
+ const PropertyList props = {"ro.product.name", "ro.product.system.name",
+ "ro.build.flavor"};
+
+ auto sysimg = getSystemImagePath(AvdImageType::BUILDPROP);
+ if (!sysimg.ok()) {
+ dwarning("Unable to retrieve image path: %s, using unknown avd flavor",
+ sysimg.status().message());
+ return AvdFlavor::OTHER;
+ }
+
+ auto buildprop = sysimg.value() / "build.prop";
+ if (!System::get()->pathExists(buildprop) ||
+ !System::get()->pathCanRead(buildprop)) {
+ dwarning("Unable to read build properties: %s, using unknown avd flavor",
+ buildprop);
+ return AvdFlavor::OTHER;
+ }
+ IniFile buildIni(buildprop);
+ buildIni.read();
+
+ for (const auto &prop : props) {
+ if (!buildIni.hasKey(prop)) {
+ continue;
+ }
+
+ auto build = buildIni.getString(prop, "_unused");
+ for (const auto &[key, val] : labelMap) {
+ if (build.find(key) != std::string::npos) {
+ // We found the flavor;
+ dinfo("Found %s in %s", key, build);
+ mFlavor = val;
+ return mFlavor.value();
+ }
+ }
+ }
+
+ // Likely unknown.
+ return res;
+}
+
+absl::StatusOr<fs::path> Avd::getImagePath(AvdImageType imgType) {
+ auto possible = mContentPath / _imageFileNames[static_cast<uint8_t>(imgType)];
+ if (System::get()->pathIsFile(possible) &&
+ System::get()->pathCanRead(possible)) {
+ return possible;
+ }
+
+ return getSystemImagePath(imgType);
+}
+
+absl::StatusOr<fs::path> Avd::getSystemImagePath(AvdImageType imgType) {
+ auto sdk = ConfigDirs::getSdkRootDirectory();
+ for (int n = 0; n < MAX_SEARCH_PATHS; n++) {
+ std::string key = absl::StrFormat("%s%d", SEARCH_PREFIX, n);
+ if (!mConfig->hasKey(key)) {
+ continue;
+ }
+ auto path = sdk / mConfig->getString(key, "unused") /
+ _imageFileNames[static_cast<uint8_t>(imgType)];
+
+ if (System::get()->pathExists(path) && System::get()->pathCanRead(path)) {
+ return path;
+ }
+ }
+ return absl::NotFoundError(absl::StrFormat("No system image path (%s) in %s",
+ SEARCH_PREFIX,
+ mConfig->getBackingFile()));
+}
+
+std::string Avd::details() {
+ auto display = mConfig->getString("tag.display", "");
+ auto width = mConfig->getString("hw.lcd.width", "?");
+ auto height = mConfig->getString("hw.lcd.height", "?");
+ auto icon = getIconForFlavor(getFlavor());
+ return absl::StrFormat("%s - (%sx%s) %s %s", mName, width, height, display,
+ icon);
+}
+
+Avd::Avd(fs::path content_path, std::unique_ptr<IniFile> target,
+ std::unique_ptr<IniFile> config, std::string name)
+ : mContentPath(content_path), mTarget(std::move(target)),
+ mConfig(std::move(config)), mName(name) {}
+
+absl::StatusOr<Avd> Avd::parse(fs::path target, std::string name) {
+ auto sys = System::get();
+ if (!sys->pathExists(target) || !sys->pathCanRead(target)) {
+ return absl::NotFoundError("Unable to parse " + name +
+ ", no access to: " + target.string());
+ }
+
+ auto avd_ini = target;
+ auto ini = std::make_unique<IniFile>(avd_ini);
+ if (!ini->read()) {
+ return absl::InternalError("Unable to parse ini file: " + avd_ini.string());
+ }
+
+ auto rel_path = ini->getString("path.rel", "avd/" + name + ".avd");
+ auto content_path = ConfigDirs::getUserDirectory() / rel_path;
+ auto cfg_ini = content_path / "config.ini";
+ if (!sys->pathExists(cfg_ini) || !sys->pathCanRead(cfg_ini)) {
+ return absl::NotFoundError("Unable to parse " + name +
+ ", no access to config: " + cfg_ini.string());
+ }
+
+ auto config = std::make_unique<IniFile>(cfg_ini);
+ if (!config->read()) {
+ return absl::InternalError("Unable to parse ini file: " + cfg_ini.string());
+ }
+ return Avd(content_path, std::move(ini), std::move(config), name);
+}
+
+std::vector<std::string> Avd::list() {
+ std::vector<std::string> avds;
+ auto pattern = std::regex(".*.ini");
+ auto directory_path = ConfigDirs::getAvdRootDirectory();
+
+ for (const auto &entry : fs::directory_iterator(directory_path)) {
+ const auto &filename = entry.path().filename().string();
+
+ // Simple pattern matching
+ if (std::regex_match(filename, pattern)) {
+ std::string name = filename;
+ name.erase(name.size() - 4);
+ avds.push_back(name);
+ }
+ }
+ return avds;
+}
+} // namespace android::goldfish \ No newline at end of file
diff --git a/files/src/android/goldfish/avd/keys.h b/files/src/android/goldfish/avd/keys.h
new file mode 100644
index 0000000..c922850
--- /dev/null
+++ b/files/src/android/goldfish/avd/keys.h
@@ -0,0 +1,100 @@
+/* Copyright (C) 2012 The Android Open Source Project
+**
+** This software is licensed under the terms of the GNU General Public
+** License version 2, as published by the Free Software Foundation, and
+** may be copied, distributed, and modified under those terms.
+**
+** This program is distributed in the hope that it will be useful,
+** but WITHOUT ANY WARRANTY; without even the implied warranty of
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+** GNU General Public License for more details.
+*/
+
+#pragma once
+
+/* Keys of the properties found in avd/name.ini and config.ini files.
+ *
+ * These keys must match their counterpart defined in
+ * sdk/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/avd/AvdManager.java
+ */
+
+/* -- Keys used in avd/name.ini -- */
+
+/* Absolute path of the AVD content directory.
+ */
+#define ROOT_ABS_PATH_KEY "path"
+
+/* Relative path of the AVD content directory.
+ * Path is relative to the bufprint_config_path().
+ */
+#define ROOT_REL_PATH_KEY "path.rel"
+
+/* -- Keys used in config.ini -- */
+
+/* AVD/config.ini key name representing the abi type of the specific avd
+ */
+#define ABI_TYPE "abi.type"
+
+/* AVD/config.ini key name representing the CPU architecture of the specific avd
+ */
+#define CPU_ARCH "hw.cpu.arch"
+/* the prefix of config.ini keys that will be used for search directories
+ * of system images.
+ */
+#define SEARCH_PREFIX "image.sysdir."
+
+/* the maximum number of search path keys we're going to read from the
+ * config.ini file
+ */
+#define MAX_SEARCH_PATHS 2
+
+/* the config.ini key that will be used to indicate the full relative
+ * path to the skin directory (including the skin name).
+ */
+#define SKIN_PATH "skin.path"
+
+/* the config.ini key that will be used to indicate the default skin's name.
+ * this is ignored if there is a valid SKIN_PATH entry in the file.
+ */
+#define SKIN_NAME "skin.name"
+
+/*
+ * The pixel_fold default skin and closed skin name, they are inside
+ * skins/pixel_fold/
+ */
+#define PIXEL_FOLD_DEFAULT_SKIN_NAME "default"
+#define PIXEL_FOLD_CLOSED_SKIN_NAME "closed"
+
+/* default skin name */
+#define SKIN_DEFAULT "HVGA"
+
+/* the config.ini key that is used to indicate the absolute path
+ * to the SD Card image file, if you don't want to place it in
+ * the content directory.
+ */
+#define SDCARD_PATH "sdcard.path"
+
+/* The config.ini key name representing the second path where the emulator looks
+ * for system images. Typically this is the path to the platform system image.
+ */
+#define IMAGES_2 "image.sysdir.2"
+
+/* AVD/config.ini key name representing the presence of the snapshots file.
+ */
+#define SNAPSHOT_PRESENT "snapshot.present"
+
+/* AVD/config.ini key name representing the size of the SD card.
+ */
+#define SDCARD_SIZE "sdcard.size"
+
+/* AVD/config.ini key name representing the tag id of the specific avd
+ */
+#define TAG_ID "tag.id"
+
+/* AVD/config.ini value for tag id of Chrome OS.
+ */
+#define TAG_ID_CHROMEOS "chromeos"
+
+/* AVD/config.ini key name representing the tag display of the specific avd
+ */
+#define TAG_DISPLAY "tag.display"
diff --git a/files/src/android/goldfish/bootconfig.cpp b/files/src/android/goldfish/bootconfig.cpp
new file mode 100644
index 0000000..1efdf6b
--- /dev/null
+++ b/files/src/android/goldfish/bootconfig.cpp
@@ -0,0 +1,131 @@
+#include "android/goldfish/bootconfig.h"
+
+#include <stdio.h>
+
+#include <memory>
+#include <numeric>
+#include <string_view>
+
+#include "aemu/base/logging/Log.h"
+#include "android/base/file/file_io.h"
+
+namespace android::goldfish {
+using namespace std::literals;
+
+constexpr std::string_view kBootconfigMagic = "#BOOTCONFIG\n"sv;
+constexpr uint32_t kBootconfigAlign = 4;
+
+std::pair<int, size_t> copyFile(FILE *src, FILE *dst) {
+ size_t sz = 0;
+ std::vector<char> buf(64 * 1024);
+
+ while (true) {
+ const size_t szR = ::fread(buf.data(), 1, buf.size(), src);
+ if (!szR) {
+ return {::ferror(src), sz};
+ }
+
+ const size_t szW = ::fwrite(buf.data(), 1, szR, dst);
+ if (szR != szW) {
+ return {::ferror(dst), sz};
+ }
+
+ sz += szR;
+ }
+}
+
+void host2le32(const uint32_t v32, void *dst) {
+ auto m8 = static_cast<uint8_t *>(dst);
+ m8[0] = v32;
+ m8[1] = v32 >> 8;
+ m8[2] = v32 >> 16;
+ m8[3] = v32 >> 24;
+}
+
+std::vector<char> flattenBootconfig(
+ const std::vector<std::pair<std::string, std::string>> &bootconfig) {
+ std::vector<char> bits;
+
+ for (const auto &kv : bootconfig) {
+ bits.insert(bits.end(), kv.first.begin(), kv.first.end());
+ bits.push_back('=');
+ bits.push_back('\"');
+ bits.insert(bits.end(), kv.second.begin(), kv.second.end());
+ bits.push_back('\"');
+ bits.push_back('\n');
+ }
+ bits.push_back(0); // it is ASCIIZ
+
+ return bits;
+}
+
+int appendBootconfig(
+ const size_t srcSize,
+ const std::vector<std::pair<std::string, std::string>> &bootconfig,
+ FILE *dst) {
+ const std::vector<char> blob = buildBootconfigBlob(srcSize, bootconfig);
+
+ if (blob.size() != ::fwrite(blob.data(), 1, blob.size(), dst)) {
+ return ::ferror(dst);
+ }
+
+ return 0;
+}
+
+std::vector<char> buildBootconfigBlob(
+ const size_t srcSize,
+ const std::vector<std::pair<std::string, std::string>> &bootconfig) {
+ std::vector<char> blob = flattenBootconfig(bootconfig);
+
+ const size_t unaligend = (srcSize + blob.size()) % kBootconfigAlign;
+ if (unaligend) {
+ blob.insert(blob.end(), kBootconfigAlign - unaligend, '+');
+ }
+
+ const uint32_t csum = std::accumulate(blob.begin(), blob.end(), 0,
+ [](const uint32_t z, const char c) {
+ return z + static_cast<uint8_t>(c);
+ });
+
+ const size_t size = blob.size();
+
+ blob.insert(blob.end(), 8, '+'); // size(u32, LE), csum(u32, LE)
+ host2le32(size, &blob[blob.size() - 8]);
+ host2le32(csum, &blob[blob.size() - 4]);
+
+ blob.insert(blob.end(), kBootconfigMagic.begin(), kBootconfigMagic.end());
+
+ return blob;
+}
+
+int createRamdiskWithBootconfig(
+ const char *srcRamdiskPath, const char *dstRamdiskPath,
+ const std::vector<std::pair<std::string, std::string>> &bootconfig) {
+ struct FILE_deleter {
+ void operator()(FILE *fp) const { ::fclose(fp); }
+ };
+
+ std::unique_ptr<FILE, FILE_deleter> srcRamdisk(
+ android_fopen(srcRamdiskPath, "rb"));
+ if (!srcRamdisk) {
+ derror("%s Can't open '%s' for reading\n", __func__, srcRamdiskPath);
+ return 1;
+ }
+
+ std::unique_ptr<FILE, FILE_deleter> dstRamdisk(
+ android_fopen(dstRamdiskPath, "wb"));
+ if (!dstRamdisk) {
+ derror("%s: Can't open '%s' for writing", __func__, dstRamdiskPath);
+ return 1;
+ }
+
+ const auto r = copyFile(srcRamdisk.get(), dstRamdisk.get());
+ if (r.first) {
+ derror("%s Error copying '%s' into '%s'\n", __func__, srcRamdiskPath,
+ dstRamdiskPath);
+ return r.first;
+ }
+
+ return appendBootconfig(r.second, bootconfig, dstRamdisk.get());
+}
+} // namespace android::goldfish \ No newline at end of file
diff --git a/files/test/android/goldfish/Avd_unittest.cpp b/files/test/android/goldfish/Avd_unittest.cpp
new file mode 100644
index 0000000..1d1020b
--- /dev/null
+++ b/files/test/android/goldfish/Avd_unittest.cpp
@@ -0,0 +1,63 @@
+// Copyright 2014 The Android Open Source Project
+//
+// This software is licensed under the terms of the GNU General Public
+// License version 2, as published by the Free Software Foundation, and
+// may be copied, distributed, and modified under those terms.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+#include <gtest/gtest.h>
+
+#include "android/goldfish/avd/Avd.h"
+
+#include <fstream>
+#include <iostream>
+#include <memory>
+
+#include "aemu/base/ArraySize.h"
+#include "aemu/base/files/PathUtils.h"
+#include "aemu/base/memory/ScopedPtr.h"
+#include "android/base/testing/TestSystem.h"
+#include "android/base/testing/TestTempDir.h"
+#include "android/goldfish/ConfigDirs.h"
+
+using android::base::ScopedCPtr;
+using android::base::TestSystem;
+using android::base::TestTempDir;
+
+namespace android::goldfish {
+static fs::path pj(fs::path a, fs::path b) { return a / b; }
+
+void writeToFile(fs::path path, std::string text) {
+ std::ofstream iniFile(path, std::ios::trunc);
+ iniFile << text;
+ iniFile.close();
+}
+
+TEST(AvdUtil, path_getAvdSystemPath) {
+ TestSystem sys("/home", "/");
+ TestTempDir *tmp = sys.getTempRoot();
+ tmp->makeSubDir("android_home");
+ tmp->makeSubDir(pj("android_home", "sysimg"));
+ tmp->makeSubDir(pj("android_home", "avd"));
+ tmp->makeSubDir("nothome");
+
+ std::string sdkRoot = pj(tmp->pathString(), "android_home");
+ std::string avdConfig = pj(pj(sdkRoot, "avd"), "config.ini");
+ sys.envSet("ANDROID_AVD_HOME", sdkRoot);
+ EXPECT_EQ(ConfigDirs::getAvdRootDirectory().string(),
+ tmp->path() / "android_home");
+
+ // Create an in file for the @q avd.
+ writeToFile(pj(sdkRoot, "q.ini"),
+ std::string("path=") + pj(sdkRoot, "avd").string());
+
+ // A relative path should be resolved from ANRDOID_AVD_HOME
+ writeToFile(avdConfig, "image.sysdir.1=sysimg");
+
+ auto inis = Avd::list();
+ EXPECT_EQ(1, inis.size());
+}
+} // namespace android::goldfish \ No newline at end of file
diff --git a/files/test/android/goldfish/ConfigDirs_unittest.cpp b/files/test/android/goldfish/ConfigDirs_unittest.cpp
new file mode 100644
index 0000000..b6eebcb
--- /dev/null
+++ b/files/test/android/goldfish/ConfigDirs_unittest.cpp
@@ -0,0 +1,169 @@
+// Copyright 2015 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.
+
+#include <gtest/gtest.h>
+
+#include "android/goldfish/ConfigDirs.h"
+
+#include "android/base/testing/TestSystem.h"
+
+using android::base::TestSystem;
+using android::goldfish::ConfigDirs;
+
+namespace fs = std::filesystem;
+
+TEST(ConfigDirs, getUserDirectoryDefault) {
+ TestSystem sys("bin", "myhome");
+ fs::path kExpected = fs::path("myhome") / ".android";
+ EXPECT_EQ(kExpected, ConfigDirs::getUserDirectory());
+}
+
+TEST(ConfigDirs, getUserDirectoryWithAndroidSdkHome) {
+ TestSystem sys("bin", "myhome");
+ sys.envSet("ANDROID_SDK_HOME", "android-sdk");
+ EXPECT_STREQ("android-sdk", ConfigDirs::getUserDirectory().c_str());
+
+ sys.getTempRoot()->makeSubDir(fs::path("android-sdk"));
+ sys.getTempRoot()->makeSubDir(fs::path("android-sdk") / ".android");
+ EXPECT_STREQ((fs::path("android-sdk") / ".android").c_str(),
+ ConfigDirs::getUserDirectory().c_str());
+}
+
+TEST(ConfigDirs, getUserDirectoryWithAndroidSdkHomeAndPrefsRoot) {
+ TestSystem sys("bin", "myhome");
+ sys.envSet("ANDROID_SDK_HOME", "android-sdk");
+ sys.envSet("ANDROID_SDK_HOME", "android-sdk-new");
+ EXPECT_STREQ("android-sdk-new", ConfigDirs::getUserDirectory().c_str());
+
+ sys.getTempRoot()->makeSubDir(fs::path("android-sdk-new"));
+ sys.getTempRoot()->makeSubDir(fs::path("android-sdk-new") / ".android");
+ EXPECT_STREQ((fs::path("android-sdk-new") / ".android").c_str(),
+ ConfigDirs::getUserDirectory().c_str());
+}
+
+TEST(ConfigDirs, getUserDirectoryWithAndroidEmulatorHome) {
+ TestSystem sys("bin", "myhome");
+ sys.envSet("ANDROID_EMULATOR_HOME", "android"
+ "home");
+ EXPECT_STREQ("android"
+ "home",
+ ConfigDirs::getUserDirectory().c_str());
+}
+
+TEST(ConfigDirs, getSdkRootDirectory) {
+ TestSystem sys("", "myhome");
+ ASSERT_TRUE(sys.getTempRoot()->makeSubDir(fs::path("Sdk")));
+ ASSERT_TRUE(
+ sys.getTempRoot()->makeSubDir(fs::path("Sdk") / "platform-tools"));
+ ASSERT_TRUE(sys.getTempRoot()->makeSubDir(fs::path("Sdk") / "platforms"));
+ ASSERT_TRUE(sys.pathIsDir("Sdk"));
+
+ sys.envSet("ANDROID_SDK_ROOT", "Sdk");
+ EXPECT_STREQ("Sdk", ConfigDirs::getSdkRootDirectory(true).c_str());
+
+ sys.envSet("ANDROID_SDK_ROOT", "\""
+ "Sdk\"");
+ EXPECT_STREQ("Sdk", ConfigDirs::getSdkRootDirectory(true).c_str());
+
+ sys.envSet("ANDROID_SDK_ROOT", "");
+ EXPECT_STRNE("Sdk", ConfigDirs::getSdkRootDirectory(true).c_str());
+
+ ASSERT_TRUE(sys.getTempRoot()->makeSubDir(fs::path("Sdk2")));
+ ASSERT_TRUE(
+ sys.getTempRoot()->makeSubDir(fs::path("Sdk2") / "platform-tools"));
+ ASSERT_TRUE(sys.getTempRoot()->makeSubDir(fs::path("Sdk2") / "platforms"));
+ ASSERT_TRUE(sys.pathIsDir("Sdk2"));
+
+ // ANDROID_HOME should take precedence over ANDROID_SDK_ROOT
+ sys.envSet("ANDROID_HOME", "Sdk2");
+ EXPECT_STREQ("Sdk2", ConfigDirs::getSdkRootDirectory(true).c_str());
+
+ // Bad ANDROID_HOME falls back to ANDROID_SDK_ROOT
+ sys.envSet("ANDROID_HOME", "bogus");
+ sys.envSet("ANDROID_SDK_ROOT", "Sdk");
+ EXPECT_STREQ("Sdk", ConfigDirs::getSdkRootDirectory(true).c_str());
+}
+
+TEST(ConfigDirs, getAvdRootDirectory) {
+ TestSystem sys("", "myhome");
+ ASSERT_TRUE(sys.getTempRoot()->makeSubDir(fs::path("Area_1")));
+ ASSERT_TRUE(sys.getTempRoot()->makeSubDir(fs::path("Area_1") / ".android"));
+ ASSERT_TRUE(
+ sys.getTempRoot()->makeSubDir(fs::path("Area_1") / ".android" / "avd"));
+ ASSERT_TRUE(sys.getTempRoot()->makeSubDir(fs::path("Area_2")));
+ ASSERT_TRUE(sys.getTempRoot()->makeSubDir(fs::path("Area_2") / ".android"));
+ ASSERT_TRUE(
+ sys.getTempRoot()->makeSubDir(fs::path("Area_2") / ".android" / "avd"));
+ ASSERT_TRUE(sys.getTempRoot()->makeSubDir(fs::path("Area_3")));
+ ASSERT_TRUE(sys.getTempRoot()->makeSubDir(fs::path("Area_3") / ".android"));
+ ASSERT_TRUE(
+ sys.getTempRoot()->makeSubDir(fs::path("Area_3") / ".android" / "avd"));
+ ASSERT_TRUE(sys.getTempRoot()->makeSubDir(fs::path("Area_4")));
+ ASSERT_TRUE(sys.getTempRoot()->makeSubDir(fs::path("Area_4") / ".android"));
+ ASSERT_TRUE(
+ sys.getTempRoot()->makeSubDir(fs::path("Area_4") / ".android" / "avd"));
+ ASSERT_TRUE(sys.getTempRoot()->makeSubDir(fs::path("Area_5")));
+ ASSERT_TRUE(sys.getTempRoot()->makeSubDir(fs::path("Area_5") / ".android"));
+ ASSERT_TRUE(
+ sys.getTempRoot()->makeSubDir(fs::path("Area_5") / ".android" / "avd"));
+ ASSERT_TRUE(sys.getTempRoot()->makeSubDir(fs::path("Area_6")));
+ ASSERT_TRUE(sys.getTempRoot()->makeSubDir(fs::path("Area_6") / ".android"));
+ ASSERT_TRUE(
+ sys.getTempRoot()->makeSubDir(fs::path("Area_6") / ".android" / "avd"));
+ ASSERT_TRUE(sys.pathIsDir(fs::path("Area_1") / ".android" / "avd"));
+ ASSERT_TRUE(sys.pathIsDir(fs::path("Area_2") / ".android" / "avd"));
+ ASSERT_TRUE(sys.pathIsDir(fs::path("Area_3") / ".android" / "avd"));
+ ASSERT_TRUE(sys.pathIsDir(fs::path("Area_4") / ".android" / "avd"));
+ ASSERT_TRUE(sys.pathIsDir(fs::path("Area_5") / ".android" / "avd"));
+ ASSERT_TRUE(sys.pathIsDir(fs::path("Area_6") / ".android" / "avd"));
+
+ // Order of precedence is
+ // ANDROID_AVD_HOME
+ // ANDROID_PREFS_ROOT
+ // ANDROID_SDK_HOME
+ // TEMP_TSTDIR
+ // USER_HOME or HOME
+ // ANDROID_EMULATOR_HOME
+
+ sys.envSet("ANDROID_AVD_HOME",
+ (std::string)(fs::path("Area_1") / ".android" / "avd"));
+ sys.envSet("ANDROID_PREFS_ROOT", (std::string)fs::path("Area_2"));
+ sys.envSet("ANDROID_SDK_HOME", (std::string)fs::path("Area_3"));
+ sys.envSet("TEST_TMPDIR", (std::string)fs::path("Area_4"));
+ sys.envSet("USER_HOME", (std::string)fs::path("Area_5"));
+ sys.envSet("ANDROID_EMULATOR_HOME",
+ (std::string)(fs::path("Area_6") / ".android"));
+ EXPECT_EQ((std::string)(fs::path("Area_1") / ".android" / "avd"),
+ ConfigDirs::getAvdRootDirectory().c_str());
+
+ sys.envSet("ANDROID_AVD_HOME", "bogus");
+ EXPECT_EQ((fs::path("Area_2") / ".android" / "avd").string(),
+ ConfigDirs::getAvdRootDirectory().string());
+
+ sys.envSet("ANDROID_PREFS_ROOT", "bogus");
+ EXPECT_EQ((std::string)(fs::path("Area_3") / ".android" / "avd"),
+ ConfigDirs::getAvdRootDirectory());
+
+ sys.envSet("ANDROID_SDK_HOME", "bogus");
+ EXPECT_EQ((std::string)(fs::path("Area_4") / ".android" / "avd"),
+ ConfigDirs::getAvdRootDirectory().c_str());
+
+ sys.envSet("TEST_TMPDIR", "bogus");
+ EXPECT_EQ((std::string)(fs::path("Area_5") / ".android" / "avd"),
+ ConfigDirs::getAvdRootDirectory().c_str());
+
+ sys.envSet("USER_HOME", "bogus");
+ EXPECT_EQ((std::string)(fs::path("Area_6") / ".android" / "avd"),
+ ConfigDirs::getAvdRootDirectory().c_str());
+}
diff --git a/files/test/android/goldfish/FileShareOpen_unittest.cpp b/files/test/android/goldfish/FileShareOpen_unittest.cpp
new file mode 100644
index 0000000..ddd9d8e
--- /dev/null
+++ b/files/test/android/goldfish/FileShareOpen_unittest.cpp
@@ -0,0 +1,149 @@
+// Copyright 2018 The Android Open Source Project
+//
+// This software is licensed under the terms of the GNU General Public
+// License version 2, as published by the Free Software Foundation, and
+// may be copied, distributed, and modified under those terms.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+#include <gtest/gtest.h>
+
+#include "absl/memory/memory.h"
+#include "aemu/base/files/FileShareOpen.h"
+
+#include <memory>
+
+#include "aemu/base/files/FileShareOpenImpl.h"
+#include "android/base/testing/TestSystem.h"
+#include "android/base/testing/TestTempDir.h"
+
+namespace {
+
+class FileShareTest : public ::testing::Test {
+public:
+ void SetUp() override {
+ // Test non-ascii path
+ mTempDir =
+ absl::make_unique<android::base::TestTempDir>("fileShareTest中文");
+ mFilePath = mTempDir->makeSubPath(kFileName);
+ EXPECT_TRUE(mTempDir->makeSubFile(kFileName));
+ }
+ void TearDown() override { mTempDir.reset(); }
+
+protected:
+ std::unique_ptr<android::base::TestTempDir> mTempDir;
+ std::string mFilePath;
+ const char *kFileName = "test.txt";
+};
+
+} // namespace
+
+using android::base::FileShare;
+using android::base::fsopen;
+
+TEST_F(FileShareTest, shareRead) {
+ FILE *f1 = fsopen(mFilePath.c_str(), "r", FileShare::Read);
+ EXPECT_TRUE(f1) << "File: " << mFilePath
+ << " was not opened: errno: " << errno;
+ FILE *f2 = fsopen(mFilePath.c_str(), "r", FileShare::Read);
+ EXPECT_TRUE(f2);
+ if (f1) {
+ fclose(f1);
+ }
+ if (f2) {
+ fclose(f2);
+ }
+}
+
+TEST_F(FileShareTest, readWriteRefuse) {
+ FILE *f1 = fsopen(mFilePath.c_str(), "r", FileShare::Read);
+ EXPECT_TRUE(f1) << "File: " << mFilePath
+ << " was not opened: errno: " << errno;
+ FILE *f2 = fsopen(mFilePath.c_str(), "w", FileShare::Write);
+ EXPECT_FALSE(f2) << "File: " << mFilePath
+ << " was not opened: errno: " << errno;
+ if (f1) {
+ fclose(f1);
+ }
+ if (f2) {
+ fclose(f2);
+ }
+}
+
+TEST_F(FileShareTest, writeWriteRefuse) {
+ FILE *f1 = fsopen(mFilePath.c_str(), "w", FileShare::Write);
+ EXPECT_TRUE(f1);
+ FILE *f2 = fsopen(mFilePath.c_str(), "w", FileShare::Write);
+ EXPECT_FALSE(f2);
+ if (f1) {
+ fclose(f1);
+ }
+ if (f2) {
+ fclose(f2);
+ }
+}
+
+TEST_F(FileShareTest, writeReadRefuse) {
+ FILE *f1 = fsopen(mFilePath.c_str(), "w", FileShare::Write);
+ EXPECT_TRUE(f1) << "File: " << mFilePath
+ << " was not opened: errno: " << errno;
+ FILE *f2 = fsopen(mFilePath.c_str(), "r", FileShare::Read);
+ EXPECT_FALSE(f2);
+ if (f1) {
+ fclose(f1);
+ }
+ if (f2) {
+ fclose(f2);
+ }
+}
+
+TEST_F(FileShareTest, createShareRead) {
+ void *f1 = android::base::internal::openFileForShare(mFilePath.c_str());
+ EXPECT_TRUE(f1);
+ FILE *f2 = fsopen(mFilePath.c_str(), "r", FileShare::Read);
+ EXPECT_TRUE(f2);
+ if (f1) {
+ android::base::internal::closeFileForShare(f1);
+ }
+ if (f2) {
+ fclose(f2);
+ }
+}
+
+TEST_F(FileShareTest, createShareWrite) {
+ void *f1 = android::base::internal::openFileForShare(mFilePath.c_str());
+ EXPECT_TRUE(f1);
+ FILE *f2 = fsopen(mFilePath.c_str(), "w", FileShare::Write);
+ EXPECT_TRUE(f2);
+ if (f1) {
+ android::base::internal::closeFileForShare(f1);
+ }
+ if (f2) {
+ fclose(f2);
+ }
+}
+
+#ifndef _WIN32
+TEST_F(FileShareTest, updateShareReadToWrite) {
+ FILE *f1 = fsopen(mFilePath.c_str(), "r", FileShare::Read);
+ EXPECT_TRUE(f1);
+ EXPECT_TRUE(updateFileShare(f1, FileShare::Write));
+ FILE *f2 = fsopen(mFilePath.c_str(), "r", FileShare::Read);
+ EXPECT_FALSE(f2);
+ fclose(f1);
+}
+
+TEST_F(FileShareTest, updateShareWriteToRead) {
+ FILE *f1 = fsopen(mFilePath.c_str(), "w", FileShare::Write);
+ EXPECT_TRUE(f1);
+ EXPECT_TRUE(updateFileShare(f1, FileShare::Read));
+ FILE *f2 = fsopen(mFilePath.c_str(), "r", FileShare::Read);
+ EXPECT_TRUE(f2);
+ fclose(f1);
+ fclose(f2);
+}
+
+#endif // ifndef _WIN32
diff --git a/files/test/android/goldfish/IniFile_unittest.cpp b/files/test/android/goldfish/IniFile_unittest.cpp
new file mode 100644
index 0000000..6fb8c7f
--- /dev/null
+++ b/files/test/android/goldfish/IniFile_unittest.cpp
@@ -0,0 +1,581 @@
+// Copyright 2015 The Android Open Source Project
+//
+// This software is licensed under the terms of the GNU General Public
+// License version 2, as published by the Free Software Foundation, and
+// may be copied, distributed, and modified under those terms.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+#include <gtest/gtest.h>
+
+#include "absl/memory/memory.h"
+#include "android/goldfish/IniFile.h"
+
+#include <algorithm>
+#include <fstream>
+#include <limits>
+#include <memory>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+#include "aemu/base/ArraySize.h"
+#include "android/base/testing/TestSystem.h"
+#include "android/base/testing/TestTempDir.h"
+
+namespace android {
+namespace base {
+
+using android::goldfish::IniFile;
+
+using std::endl;
+using std::numeric_limits;
+using std::string;
+using std::unique_ptr;
+using std::unordered_map;
+using std::vector;
+
+namespace {
+
+class IniFileTest : public ::testing::Test {
+public:
+ void SetUp() override {
+ mTempDir = absl::make_unique<TestTempDir>("inifiletest");
+ mIniFilePath = mTempDir->makeSubPath("test.ini").c_str();
+ mIni = absl::make_unique<IniFile>(mIniFilePath);
+ }
+
+ void TearDown() override {
+ mIni.reset();
+ mTempDir.reset();
+ }
+
+protected:
+ void writeIniFileData(const vector<string> &lines) {
+ std::ofstream outFile(mIniFilePath,
+ std::ios_base::out | std::ios_base::trunc);
+ ASSERT_FALSE(outFile.fail());
+ for (const auto &line : lines) {
+ outFile << line << '\n';
+ }
+ }
+
+ void verifyFileContents(const vector<string> &lines) {
+ std::ifstream inFile(mIniFilePath);
+ ASSERT_FALSE(inFile.fail());
+ vector<string> fileLines;
+ string line;
+ while (std::getline(inFile, line)) {
+ fileLines.push_back(line);
+ }
+
+ ASSERT_EQ(lines.size(), fileLines.size());
+ for (size_t i = 0; i < lines.size(); ++i) {
+ EXPECT_EQ(lines[i], fileLines[i]);
+ }
+ }
+
+ // Verifies that the underlying file for |ini| is |updated| when
+ // |writeIfChanged| is called.
+ // Note that this changes |ini|. After this function is called, |ini| is
+ // always
+ // clean, and the data has been changed arbitrarily.
+ void verifyFileUpdated(bool updated) {
+ static int counter = 0;
+ string key = "key" + std::to_string(counter++);
+ vector<string> lines = {key + " = somevalue"};
+
+ writeIniFileData(lines);
+ EXPECT_TRUE(mIni->writeIfChanged());
+ EXPECT_TRUE(mIni->read());
+
+ // If the file was updated, then the new uniquely written data must have
+ // disappeared, not otherwise.
+ if (updated) {
+ EXPECT_FALSE(mIni->hasKey(key));
+ } else {
+ EXPECT_TRUE(mIni->hasKey(key));
+ }
+ }
+
+ unique_ptr<TestTempDir> mTempDir;
+ string mIniFilePath;
+ unique_ptr<IniFile> mIni;
+};
+
+} // namespace
+
+TEST_F(IniFileTest, readWrite) {
+ static const unordered_map<string, int> intData = {
+ {"zeroInt", 0},
+ {"positiveInt", 1},
+ {"negativeInt", -1},
+ {"maxInt", numeric_limits<int>::max()},
+ {"minInt", numeric_limits<int>::min()},
+ {"lowestInt", numeric_limits<int>::lowest()}};
+ static const unordered_map<string, int64_t> int64Data = {
+ {"zeroInt64", 0ULL},
+ {"positiveInt64", 1ULL},
+ {"negativeInt64", -1ULL},
+ {"maxInt64", numeric_limits<int64_t>::max()},
+ {"minInt64", numeric_limits<int64_t>::min()},
+ {"lowestInt64", numeric_limits<int64_t>::lowest()}};
+ static const unordered_map<string, double> doubleData = {
+ {"zeroDouble", 0.0},
+ {"positiveDouble", 1.5},
+ {"negativeDouble", -1.5},
+ {"maxDouble", numeric_limits<double>::max()},
+ // minDouble fails because of rounding errors.
+ // {"minDouble", numeric_limits<double>::min()},
+ {"lowestDouble", numeric_limits<double>::lowest()}};
+
+ // This doesn't actually test the format in which values are persisted.
+ // But it does test that serialize-deserialize are consistent.
+ static const unordered_map<string, bool> boolData = {{"trueKey", true},
+ {"falseKey", false}};
+ static const unordered_map<string, IniFile::DiskSize> diskSizeData = {
+ {"ds0", 0ULL},
+ {"ds1000B", 1000ULL},
+ {"ds1K", 1024ULL},
+ {"ds5k", 5 * 1024ULL},
+ {"ds1M", 1024 * 1024ULL},
+ {"ds3G", 3 * 1024 * 1024 * 1024ULL}};
+
+ for (const auto &keyval : intData) {
+ mIni->setInt(keyval.first, keyval.second);
+ }
+ for (const auto &keyval : int64Data) {
+ mIni->setInt64(keyval.first, keyval.second);
+ }
+ for (const auto &keyval : doubleData) {
+ mIni->setDouble(keyval.first, keyval.second);
+ }
+ for (const auto &keyval : boolData) {
+ mIni->setBool(keyval.first, keyval.second);
+ }
+ for (const auto &keyval : diskSizeData) {
+ mIni->setDiskSize(keyval.first, keyval.second);
+ }
+
+ ASSERT_TRUE(mIni->write());
+
+ mIni = absl::make_unique<IniFile>(mIniFilePath);
+ ASSERT_EQ(0, mIni->size());
+ ASSERT_TRUE(mIni->read());
+ EXPECT_EQ(static_cast<int>(intData.size() + int64Data.size() +
+ doubleData.size() + boolData.size() +
+ diskSizeData.size()),
+ mIni->size());
+
+ // Mix-up the order a bit.
+ for (const auto &keyval : boolData) {
+ EXPECT_TRUE(mIni->hasKey(keyval.first));
+ EXPECT_EQ(keyval.second, mIni->getBool(keyval.first, 99));
+ }
+ for (const auto &keyval : diskSizeData) {
+ EXPECT_TRUE(mIni->hasKey(keyval.first));
+ EXPECT_EQ(keyval.second, mIni->getDiskSize(keyval.first, 99));
+ }
+ for (const auto &keyval : int64Data) {
+ EXPECT_TRUE(mIni->hasKey(keyval.first));
+ EXPECT_EQ(keyval.second, mIni->getInt64(keyval.first, 99));
+ }
+ for (const auto &keyval : intData) {
+ EXPECT_TRUE(mIni->hasKey(keyval.first));
+ EXPECT_EQ(keyval.second, mIni->getInt(keyval.first, 99));
+ }
+ for (const auto &keyval : doubleData) {
+ EXPECT_TRUE(mIni->hasKey(keyval.first));
+ EXPECT_EQ(keyval.second, mIni->getDouble(keyval.first, 99));
+ }
+}
+
+TEST_F(IniFileTest, duplicateAndMissingKeys) {
+ mIni->setInt("int", 0);
+ mIni->setInt("int", 1);
+ mIni->setInt64("int64", 0ULL);
+ mIni->setInt64("int64", 1ULL);
+ mIni->setDouble("double", 0.0);
+ mIni->setDouble("double", 1.1);
+ mIni->setBool("bool", false);
+ mIni->setBool("bool", true);
+ mIni->setDiskSize("ds", 0ULL);
+ mIni->setDiskSize("ds", 1ULL);
+
+ ASSERT_TRUE(mIni->write());
+ mIni = absl::make_unique<IniFile>(mIniFilePath);
+ ASSERT_EQ(0, mIni->size());
+ ASSERT_TRUE(mIni->read());
+ EXPECT_NE(0, mIni->size());
+
+ EXPECT_EQ(1, mIni->getInt("int", 99));
+ EXPECT_EQ(1LL, mIni->getInt64("int64", 99));
+ EXPECT_EQ(1.1, mIni->getDouble("double", 99));
+ EXPECT_EQ(true, mIni->getBool("bool", false));
+ EXPECT_EQ(1ULL, mIni->getDiskSize("ds", 99ULL));
+
+ EXPECT_EQ(-11, mIni->getInt("missing", -11));
+ EXPECT_EQ(22LL, mIni->getInt64("missing", 22LL));
+ EXPECT_EQ(3.3, mIni->getDouble("missing", 3.3));
+ EXPECT_EQ(true, mIni->getBool("missing", true));
+ EXPECT_EQ(44ULL, mIni->getDiskSize("missing", 44ULL));
+}
+
+TEST_F(IniFileTest, valueFormat) {
+ static const vector<string> fileData = {
+ "key1 = value with spaces", "key2 = value with trailing spaces ",
+ "key3 = \"value with redundant quotes\"", "keyAllSpaces = "};
+ writeIniFileData(fileData);
+
+ ASSERT_TRUE(mIni->read());
+ EXPECT_EQ(fileData.size(), static_cast<size_t>(mIni->size()));
+ EXPECT_EQ("value with spaces", mIni->getString("key1", ""));
+ EXPECT_EQ("value with trailing spaces", mIni->getString("key2", ""));
+ EXPECT_EQ("\"value with redundant quotes\"", mIni->getString("key3", ""));
+ EXPECT_EQ("", mIni->getString("keyAllSpaces", "nonemptydefault"));
+}
+
+TEST_F(IniFileTest, makeValidValue) {
+ EXPECT_EQ("%%", IniFile::makeValidValue("%"));
+ EXPECT_EQ("", IniFile::makeValidValue(""));
+ EXPECT_EQ("%%Hello%%", IniFile::makeValidValue("%Hello%"));
+ EXPECT_EQ("%%%%%%", IniFile::makeValidValue("%%%"));
+}
+
+TEST_F(IniFileTest, environmentSubstitution) {
+ TestSystem ts("/");
+ ts.envSet("Hello", "World!");
+ ts.envSet("Hallo", "Wereld!");
+ ts.envSet("Gutentag", "Welt!");
+ std::string UNKNOWN = "X_UNKNOWN_X";
+
+ EXPECT_EQ("", System::get()->envGet(UNKNOWN));
+ EXPECT_EQ("World!", System::get()->envGet("Hello"));
+
+ static const vector<string> fileData = {
+ "TEST = %%TEST%%", string("FOO = %").append(UNKNOWN).append("%")};
+
+ writeIniFileData(fileData);
+
+ const char *NON = "NOT_IN_THE_MAP";
+ ASSERT_TRUE(mIni->read());
+
+ // Make sure substitution happens for something in the file.
+ EXPECT_EQ("%TEST%", mIni->getString("TEST", ""));
+
+ // And for default values
+ EXPECT_EQ("%HI%", mIni->getString(NON, "%%HI%%"));
+ EXPECT_EQ("%HI", mIni->getString(NON, "%HI"));
+ EXPECT_EQ("%%HI%%", mIni->getString(NON, "%%%%HI%%%%"));
+ EXPECT_EQ("", mIni->getString(NON, ""));
+ EXPECT_EQ("", mIni->getString(NON, "%INVA%%LID_ENV_NAME%"));
+
+ // Check that we can substitute all the environment variables.
+ for (const auto &env : System::get()->envGetAll()) {
+ string name = env.substr(0, env.find_first_of('='));
+
+ // Empty environment names??!
+ if (name.empty())
+ continue;
+
+ string escaped = string("%").append(name).append("%");
+ string value = System::get()->envGet(name);
+ EXPECT_EQ(value, mIni->getString(NON, escaped));
+
+ escaped = string("%%HELLO%% %").append(name).append("%");
+ string expect = string("%HELLO% ").append(value);
+ EXPECT_EQ(expect, mIni->getString(NON, escaped));
+
+ escaped = string("%%%").append(name).append("%%%");
+ expect = string("%").append(value).append("%");
+ EXPECT_EQ(expect, mIni->getString(NON, escaped));
+ }
+
+ // It should work with numbers too..
+ System::get()->envSet(UNKNOWN, "15.5");
+ EXPECT_EQ(15.5, mIni->getDouble("FOO", -1.2));
+ System::get()->envSet(UNKNOWN, "");
+
+ System::get()->envSet(UNKNOWN, "42");
+ EXPECT_EQ(42, mIni->getInt("FOO", 0));
+
+ System::get()->envSet(UNKNOWN, "true");
+ EXPECT_EQ(true, mIni->getBool("FOO", false));
+}
+
+TEST_F(IniFileTest, readMalformedFile) {
+ static const int defaultInt = -99;
+ static const vector<string> fileData = {
+ "a = 5",
+ "; This comment will be skipped",
+ " b = 4",
+ " # So will this: irrelevant ; and #",
+ "c=43",
+ "d= 37malformedint,otherwiseOK",
+ "This is actually malformed, and will be skipped with warning",
+ " d = 45.6 now this becomes malformed here",
+ "d = 43 ; hanging comments are not supported.",
+ " ee = 546",
+ "f=\"56\"",
+ "f32ASDF_-.dfae3=1",
+ "90=KeyMustStartWithAlpha",
+ "a9%0=KeyCanNotContainPercent",
+ ""};
+ static const unordered_map<string, int> validEntries = {
+ {"a", 5},
+ {"b", 4},
+ {"c", 43},
+ {"d", defaultInt},
+ {"ee", 546},
+ {"f", defaultInt},
+ {"f32ASDF_-.dfae3", 1}};
+
+ writeIniFileData(fileData);
+
+ ASSERT_TRUE(mIni->read());
+ EXPECT_EQ(validEntries.size(), static_cast<size_t>(mIni->size()));
+ for (const auto &keyval : validEntries) {
+ EXPECT_TRUE(mIni->hasKey(keyval.first));
+ EXPECT_EQ(keyval.second, mIni->getInt(keyval.first, defaultInt));
+ }
+}
+
+static void formatToLines(vector<string> *lines,
+ const unordered_map<string, string> &dataMap) {
+ for (const auto &keyval : dataMap) {
+ lines->push_back(keyval.first + " = " + keyval.second);
+ }
+}
+
+TEST_F(IniFileTest, boolFormat) {
+ static const unordered_map<string, string> validTrues = {{"true1", "yes"},
+ {"true2", "YES"},
+ {"true3", "true"},
+ {"true4", "TRUE"},
+ {"true5", "1"}};
+ static const unordered_map<string, string> validFalses = {{"false1", "no"},
+ {"false2", "NO"},
+ {"false3", "false"},
+ {"flase4", "FALSE"},
+ {"false5", "0"}};
+ static const unordered_map<string, string> invalidTrues = {
+ {"true12", "blah"}, {"true13", "\"1\""}};
+ static const unordered_map<string, string> invalidFalses = {
+ {"false12", "blah"}, {"false13", "\"0\""}};
+
+ vector<string> lines;
+ formatToLines(&lines, validTrues);
+ formatToLines(&lines, invalidTrues);
+ formatToLines(&lines, validFalses);
+ formatToLines(&lines, invalidFalses);
+ writeIniFileData(lines);
+
+ ASSERT_TRUE(mIni->read());
+ EXPECT_EQ(lines.size(), static_cast<size_t>(mIni->size()));
+ for (const auto &keyval : validTrues) {
+ EXPECT_TRUE(mIni->hasKey(keyval.first));
+ EXPECT_TRUE(mIni->getBool(keyval.first, false));
+ }
+ for (const auto &keyval : validFalses) {
+ EXPECT_TRUE(mIni->hasKey(keyval.first));
+ EXPECT_FALSE(mIni->getBool(keyval.first, true));
+ }
+ for (const auto &keyval : invalidTrues) {
+ // The keyval exists, it's just not a valid bool value.
+ EXPECT_TRUE(mIni->hasKey(keyval.first));
+ EXPECT_FALSE(mIni->getBool(keyval.first, false));
+ }
+ for (const auto &keyval : invalidFalses) {
+ // The keyval exists, it's just not a valid bool value.
+ EXPECT_TRUE(mIni->hasKey(keyval.first));
+ EXPECT_TRUE(mIni->getBool(keyval.first, true));
+ }
+}
+
+TEST_F(IniFileTest, diskSizeFormat) {
+ static const unordered_map<string, string> validDiskSizes = {
+ {"ThirtyB", "30"}, {"OneKilo", "1k"}, {"FiveKilo", "5K"},
+ {"OneMega", "1m"}, {"FiveMega", "5M"}, {"OneGiga", "1g"},
+ {"FiveGiga", "5G"}};
+ static const unordered_map<string, string> invalidDiskSizes = {
+ {"WrongUnit", "30hertz"},
+ {"FractionalNumber", "2.14142135423"},
+ {"FractionalKilo", "3.14K"},
+ {"smiley_really", ";-)"}};
+
+ vector<string> lines;
+ formatToLines(&lines, validDiskSizes);
+ formatToLines(&lines, invalidDiskSizes);
+ writeIniFileData(lines);
+
+ ASSERT_TRUE(mIni->read());
+ EXPECT_EQ(lines.size(), static_cast<size_t>(mIni->size()));
+ EXPECT_EQ(30ULL, mIni->getDiskSize("ThirtyB", 99));
+ EXPECT_EQ(1024ULL, mIni->getDiskSize("OneKilo", 99));
+ EXPECT_EQ(1024 * 1024ULL, mIni->getDiskSize("OneMega", 99));
+ EXPECT_EQ(1024 * 1024 * 1024ULL, mIni->getDiskSize("OneGiga", 99));
+ EXPECT_EQ(5 * 1024ULL, mIni->getDiskSize("FiveKilo", 99));
+ EXPECT_EQ(5 * 1024 * 1024ULL, mIni->getDiskSize("FiveMega", 99));
+ EXPECT_EQ(5 * 1024 * 1024 * 1024ULL, mIni->getDiskSize("FiveGiga", 99));
+
+ EXPECT_EQ(99ULL, mIni->getDiskSize("WrongUnit", 99));
+ EXPECT_EQ(99ULL, mIni->getDiskSize("FractionalNumber", 99));
+ EXPECT_EQ(99ULL, mIni->getDiskSize("FractionalKilo", 99));
+ EXPECT_EQ(99ULL, mIni->getDiskSize("smiley_really", 99));
+}
+
+TEST_F(IniFileTest, discardEmpty) {
+ mIni->setString("nonEmpty", "someValue");
+ mIni->setString("empty", "");
+
+ ASSERT_TRUE(mIni->write());
+ mIni = absl::make_unique<IniFile>(mIniFilePath);
+ ASSERT_EQ(0, mIni->size());
+ ASSERT_TRUE(mIni->read());
+ EXPECT_EQ(2, mIni->size());
+ EXPECT_EQ("someValue", mIni->getString("nonEmpty", "defaultString"));
+ EXPECT_EQ("", mIni->getString("empty", "defaultString"));
+
+ EXPECT_TRUE(mIni->writeDiscardingEmpty());
+ mIni = absl::make_unique<IniFile>(mIniFilePath);
+ ASSERT_EQ(0, mIni->size());
+ ASSERT_TRUE(mIni->read());
+ EXPECT_EQ(1, mIni->size());
+ EXPECT_EQ("someValue", mIni->getString("nonEmpty", "defaultString"));
+ EXPECT_EQ("defaultString", mIni->getString("empty", "defaultString"));
+}
+
+TEST_F(IniFileTest, writeIfChanged) {
+ // Initially, we must treat the object as dirty.
+ // Hence, we'll clear the lines we'd written previously.
+ mIni = absl::make_unique<IniFile>(mIniFilePath);
+ verifyFileUpdated(true);
+
+ mIni->write();
+ // Now we consider the object as clean, so write shouldn't modify the
+ // underlying file.
+ verifyFileUpdated(false);
+
+ // Now let's change the data.
+ mIni->setString("random", "yippeeee");
+ verifyFileUpdated(true);
+
+ mIni->read();
+ // No changes, so write shouldn't do anything.
+ verifyFileUpdated(false);
+ // But resetting the file path means we should flush.
+ mIni->setBackingFile(mIniFilePath);
+ verifyFileUpdated(true);
+}
+
+TEST_F(IniFileTest, noBackingFile) {
+ mIni = absl::make_unique<IniFile>();
+ ASSERT_FALSE(mIni->read());
+
+ mIni->setBackingFile(mIniFilePath);
+ ASSERT_FALSE(mIni->read());
+ ASSERT_TRUE(mIni->write());
+ ASSERT_TRUE(mIni->read());
+}
+
+TEST_F(IniFileTest, iterator) {
+ mIni->setString("firstKey", "firstValue");
+ mIni->setString("secondKey", "secondValue");
+
+ // Const iterators, also verify order.
+ const IniFile &cIni = *mIni;
+ vector<string> keys = {"firstKey", "secondKey"};
+ ASSERT_EQ(keys.size(), static_cast<size_t>(mIni->size()));
+ size_t i = 0;
+ for (const auto &key : cIni) {
+ EXPECT_EQ(keys[i], key);
+ ++i;
+ }
+}
+
+TEST_F(IniFileTest, diskFileOrder) {
+ vector<string> lines = {
+ "first = some valid value", "second line is invalid",
+ "; Third line is a comment", "fourth = is valid",
+ "# Fifth is also a comment", "",
+ ";Sixth was a comment", "3p0 = is an invalid key",
+ "lets = finish with valid"};
+ vector<string> rewritten_lines = {"first = some valid value",
+ "; Third line is a comment",
+ "fourth = is valid",
+ "# Fifth is also a comment",
+ "",
+ ";Sixth was a comment",
+ "lets = finish with valid"};
+
+ writeIniFileData(lines);
+ ASSERT_TRUE(mIni->read());
+ ASSERT_TRUE(mIni->write());
+ verifyFileContents(rewritten_lines);
+
+ // This is order independent. Let's try the reverse
+ std::reverse(std::begin(lines), std::end(lines));
+ std::reverse(std::begin(rewritten_lines), std::end(rewritten_lines));
+ writeIniFileData(lines);
+ ASSERT_TRUE(mIni->read());
+ ASSERT_TRUE(mIni->write());
+ verifyFileContents(rewritten_lines);
+
+ // Extra keys are appended to the file.
+ mIni->setString("extraKey", "extraValue");
+ ASSERT_TRUE(mIni->write());
+ rewritten_lines.push_back("extraKey = extraValue");
+ verifyFileContents(rewritten_lines);
+
+ // Test order of iteration in this complicated case.
+ // Remember we reversed the lines.
+ vector<string> keys = {"lets", "fourth", "first", "extraKey"};
+ ASSERT_EQ(keys.size(), static_cast<size_t>(mIni->size()));
+ size_t i = 0;
+ for (const auto &key : *mIni) {
+ EXPECT_EQ(keys[i], key);
+ ++i;
+ }
+}
+
+TEST_F(IniFileTest, strDefaultValues) {
+ ASSERT_EQ(0, mIni->size());
+ EXPECT_TRUE(mIni->getBool("missingKey", "yes"));
+ EXPECT_FALSE(mIni->getBool("missingKey", "no"));
+ EXPECT_EQ(1024ULL, mIni->getDiskSize("missingKey", "1k"));
+}
+
+TEST_F(IniFileTest, makeValidKey) {
+ EXPECT_STREQ("_key", IniFile::makeValidKey("key").c_str());
+ EXPECT_STREQ("_", IniFile::makeValidKey("").c_str());
+ EXPECT_STREQ("_.3D", IniFile::makeValidKey("=").c_str());
+ EXPECT_STREQ("_a.20sign.20.23", IniFile::makeValidKey("a sign #").c_str());
+ EXPECT_STREQ("_some.20number.2010.20within",
+ IniFile::makeValidKey("some number 10 within").c_str());
+}
+
+TEST(IniFileTest2, parseInMemory) {
+ static const char data[] =
+ R"(key1=val1
+key2=1011
+key3=false
+)";
+
+ IniFile ini;
+ ASSERT_TRUE(ini.readFromMemory(data));
+ EXPECT_STREQ("", ini.getBackingFile().c_str());
+
+ ASSERT_EQ(3, ini.size());
+ EXPECT_STREQ("val1", ini.getString("key1", "").c_str());
+ EXPECT_EQ(1011, ini.getInt64("key2", 1));
+ EXPECT_FALSE(ini.getBool("key3", true));
+
+ IniFile ini2(data, stringLiteralLength(data));
+ ASSERT_EQ(3, ini2.size());
+}
+
+} // namespace base
+} // namespace android
diff --git a/files/test/android/goldfish/ParameterList_unittest.cpp b/files/test/android/goldfish/ParameterList_unittest.cpp
new file mode 100644
index 0000000..443e424
--- /dev/null
+++ b/files/test/android/goldfish/ParameterList_unittest.cpp
@@ -0,0 +1,122 @@
+// Copyright 2016 The Android Open Source Project
+//
+// This software is licensed under the terms of the GNU General Public
+// License version 2, as published by the Free Software Foundation, and
+// may be copied, distributed, and modified under those terms.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+#include <gtest/gtest.h>
+
+#include "android/goldfish/ParameterList.h"
+
+#include <stdlib.h>
+
+#include <memory>
+#include <string>
+
+namespace android {
+
+TEST(ParameterList, Construction) {
+ ParameterList list;
+ EXPECT_EQ(0U, list.size());
+
+ EXPECT_EQ(std::string(), list.toString());
+
+ EXPECT_EQ(std::string(), list.toString(false));
+}
+
+TEST(ParameterList, Add) {
+ ParameterList list;
+ list.add("foo");
+ list.add(std::string("bar"));
+ list.add(std::move(std::string("zoo")));
+
+ EXPECT_EQ(3U, list.size());
+ EXPECT_STREQ("foo", list[0].c_str());
+ EXPECT_STREQ("bar", list[1].c_str());
+ EXPECT_STREQ("zoo", list[2].c_str());
+}
+
+TEST(ParameterList, AddFormat) {
+ ParameterList list;
+ list.addFormat("Hello World");
+ list.addFormat("%s=%s", "foo", "bar");
+ list.addFormat("%s-%s", std::string("zoo"), std::string("crab"));
+
+ EXPECT_EQ(3U, list.size());
+ EXPECT_STREQ("Hello World", list[0].c_str());
+ EXPECT_STREQ("foo=bar", list[1].c_str());
+ EXPECT_STREQ("zoo-crab", list[2].c_str());
+}
+
+TEST(ParameterList, Add2) {
+ ParameterList list;
+ list.add2("foo", "bar");
+ EXPECT_EQ(2U, list.size());
+ EXPECT_STREQ("foo", list[0].c_str());
+ EXPECT_STREQ("bar", list[1].c_str());
+}
+
+TEST(ParameterList, AddIf) {
+ ParameterList list;
+ list.addIf("foo", true);
+ list.addIf("bar", false);
+ list.addIf("zoo", true);
+
+ EXPECT_EQ(2U, list.size());
+ EXPECT_STREQ("foo", list[0].c_str());
+ EXPECT_STREQ("zoo", list[1].c_str());
+}
+
+TEST(ParameterList, Add2If) {
+ ParameterList list;
+ list.add2If("foo", "bar");
+ list.add2If("zoo", nullptr);
+ list.add2If("under", "over");
+
+ EXPECT_EQ(4U, list.size());
+ EXPECT_STREQ("foo", list[0].c_str());
+ EXPECT_STREQ("bar", list[1].c_str());
+ EXPECT_STREQ("under", list[2].c_str());
+ EXPECT_STREQ("over", list[3].c_str());
+}
+
+TEST(ParameterList, toString) {
+ ParameterList list;
+ list.add2("foo", "bar");
+ list.add(" zoo ");
+ EXPECT_EQ(3U, list.size());
+#ifdef _WIN32
+ const char *expect_str = "foo bar \" zoo \"";
+#else
+ const char *expect_str = "foo bar ' zoo '";
+#endif
+ EXPECT_STREQ(expect_str, list.toString().c_str());
+}
+
+TEST(ParameterList, toStringWithoutQuotes) {
+ ParameterList list;
+ list.add2("foo", "bar");
+ list.add(" zoo ");
+ EXPECT_EQ(3U, list.size());
+ const char *expect_str = "foo bar zoo ";
+ EXPECT_STREQ(expect_str, list.toString(false).c_str());
+}
+
+TEST(ParameterList, array) {
+ ParameterList list;
+ list.add2("foo", "bar");
+ list.add("zoo");
+ EXPECT_EQ(3U, list.size());
+ char **array = list.array();
+ EXPECT_TRUE(array);
+ EXPECT_STREQ("foo", array[0]);
+ EXPECT_STREQ("bar", array[1]);
+ EXPECT_STREQ("zoo", array[2]);
+}
+
+} // namespace android
diff --git a/files/test/android/goldfish/bootconfig_unittest.cpp b/files/test/android/goldfish/bootconfig_unittest.cpp
new file mode 100644
index 0000000..02c26ae
--- /dev/null
+++ b/files/test/android/goldfish/bootconfig_unittest.cpp
@@ -0,0 +1,92 @@
+// Copyright 2021 The Android Open Source Project
+//
+// This software is licensed under the terms of the GNU General Public
+// License version 2, as published by the Free Software Foundation, and
+// may be copied, distributed, and modified under those terms.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+#include <gtest/gtest.h>
+
+#include "android/goldfish/bootconfig.h"
+
+#include <string.h>
+
+#include <string_view>
+
+namespace android {
+
+using namespace std::literals;
+
+namespace goldfish {
+constexpr std::string_view kBootconfigMagic = "#BOOTCONFIG\n"sv;
+
+uint32_t loadLE32(const void *m) {
+ const uint8_t *m8 = static_cast<const uint8_t *>(m);
+ return uint32_t(m8[0]) | (uint32_t(m8[1]) << 8) | (uint32_t(m8[2]) << 16) |
+ (uint32_t(m8[3]) << 24);
+}
+
+TEST(buildBootconfigBlob, OptsMagic) {
+ const std::vector<std::pair<std::string, std::string>> bootconfig = {
+ {"a", "b"},
+ {"c", "2"},
+ };
+
+ constexpr auto propsBlob = "a=\"b\"\nc=\"2\"\n\0"sv;
+
+ const auto blob = buildBootconfigBlob(0, bootconfig);
+
+ EXPECT_GT(blob.size(), propsBlob.size() + kBootconfigMagic.size());
+ EXPECT_TRUE(!memcmp(blob.data(), propsBlob.data(), propsBlob.size()));
+ EXPECT_TRUE(!memcmp(&blob[blob.size() - kBootconfigMagic.size()],
+ kBootconfigMagic.data(), kBootconfigMagic.size()));
+}
+
+TEST(buildBootconfigBlob, SizeAlignmentCsum) {
+ const std::vector<std::pair<std::string, std::string>> bootconfig = {
+ {"a", "b"},
+ };
+
+ constexpr auto propsBlob = "a=\"b\"\n\0"sv; // 7 byte long
+ constexpr uint32_t propsCsum = 'a' + '=' + '\"' + 'b' + '\"' + '\n';
+
+ {
+ const auto blob = buildBootconfigBlob(0, bootconfig);
+ EXPECT_EQ(blob.size() - kBootconfigMagic.size() - 8 - propsBlob.size(), 1);
+
+ const char *lencsum = &blob[blob.size() - kBootconfigMagic.size() - 8];
+ EXPECT_EQ(loadLE32(lencsum), propsBlob.size() + 1);
+ EXPECT_EQ(loadLE32(lencsum + 4), propsCsum + '+');
+ }
+ {
+ const auto blob = buildBootconfigBlob(1, bootconfig);
+ EXPECT_EQ(blob.size() - kBootconfigMagic.size() - 8 - propsBlob.size(), 0);
+
+ const char *lencsum = &blob[blob.size() - kBootconfigMagic.size() - 8];
+ EXPECT_EQ(loadLE32(lencsum), propsBlob.size());
+ EXPECT_EQ(loadLE32(lencsum + 4), propsCsum);
+ }
+ {
+ const auto blob = buildBootconfigBlob(2, bootconfig);
+ EXPECT_EQ(blob.size() - kBootconfigMagic.size() - 8 - propsBlob.size(), 3);
+
+ const char *lencsum = &blob[blob.size() - kBootconfigMagic.size() - 8];
+ EXPECT_EQ(loadLE32(lencsum), propsBlob.size() + 3);
+ EXPECT_EQ(loadLE32(lencsum + 4), propsCsum + '+' + '+' + '+');
+ }
+ {
+ const auto blob = buildBootconfigBlob(3, bootconfig);
+ EXPECT_EQ(blob.size() - kBootconfigMagic.size() - 8 - propsBlob.size(), 2);
+
+ const char *lencsum = &blob[blob.size() - kBootconfigMagic.size() - 8];
+ EXPECT_EQ(loadLE32(lencsum), propsBlob.size() + 2);
+ EXPECT_EQ(loadLE32(lencsum + 4), propsCsum + '+' + '+');
+ }
+}
+
+} // namespace goldfish
+} // namespace android \ No newline at end of file