diff options
author | Erwin Jansen <jansene@google.com> | 2024-03-26 11:21:31 -0700 |
---|---|---|
committer | Erwin Jansen <jansene@google.com> | 2024-03-26 12:01:37 -0700 |
commit | 2df9a028719c965be4ad1754cff446d2124fdd1d (patch) | |
tree | 32e150b401ba68f551a6f81bc840fff1f452bfca | |
parent | 1d2472876b64b71f713c035af10a3bc9b35aa47f (diff) | |
download | goldfish-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
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 ¶m); + + // Note: this will also handle const char* parameters automatically. + void add(std::string &¶m); + + // 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 ¶m) { 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 &¶m) { + 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 |