blob: 8e2dde3396f684ba9a934fddb2e62501fdba1082 [file] [log] [blame]
// Copyright 2017 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/snapshot/Snapshot.h"
#include "android/base/ArraySize.h"
#include "android/base/Optional.h"
#include "android/base/StringFormat.h"
#include "android/base/files/IniFile.h"
#include "android/base/files/PathUtils.h"
#include "android/base/files/ScopedFd.h"
#include "android/base/memory/ScopedPtr.h"
#include "android/base/misc/StringUtils.h"
#include "android/base/system/System.h"
#include "android/featurecontrol/Features.h"
#include "android/globals.h"
#include "android/opengl/emugl_config.h"
#include "android/opengl/gpuinfo.h"
#include "android/protobuf/LoadSave.h"
#include "android/snapshot/PathUtils.h"
#include "android/snapshot/Quickboot.h"
#include "android/snapshot/Snapshotter.h"
#include "android/utils/fd.h"
#include "android/utils/file_io.h"
#include "android/utils/path.h"
#include <fcntl.h>
#include <sys/mman.h>
#include <algorithm>
#define ALLOW_CHANGE_RENDERER
using android::base::c_str;
using android::base::endsWith;
using android::base::IniFile;
using android::base::makeCustomScopedPtr;
using android::base::Optional;
using android::base::PathUtils;
using android::base::ScopedCPtr;
using android::base::ScopedFd;
using android::base::StringFormat;
using android::base::StringView;
using android::base::System;
using android::featurecontrol::Feature;
using android::protobuf::loadProtobuf;
using android::protobuf::ProtobufLoadResult;
using android::protobuf::saveProtobuf;
using android::protobuf::ProtobufSaveResult;
namespace fc = android::featurecontrol;
namespace pb = emulator_snapshot;
namespace android {
namespace snapshot {
static void fillImageInfo(pb::Image::Type type,
StringView path,
pb::Image* image) {
image->set_type(type);
*image->mutable_path() = path;
if (path.empty() || !path_is_regular(c_str(path))) {
image->set_present(false);
return;
}
image->set_present(true);
struct stat st;
if (!android_stat(c_str(path), &st)) {
image->set_size(st.st_size);
image->set_modification_time(st.st_mtime);
}
}
static bool verifyImageInfo(pb::Image::Type type,
StringView path,
const pb::Image& in) {
auto pathStr = path.str();
if (in.type() != type) {
return false;
}
const bool savedPresent = in.has_present() && in.present();
const bool realPresent = !path.empty() && path_is_regular(c_str(path));
if (savedPresent != realPresent) {
return false;
}
struct stat st;
static_assert(sizeof(st.st_size >= sizeof(uint64_t)),
"Bad size member in struct stat, fix build options");
if (android_stat(c_str(path), &st) != 0) {
if (in.has_size() || in.has_modification_time()) {
return false;
}
} else {
if (!in.has_size() || in.size() != st.st_size) {
return false;
}
}
return true;
}
bool areHwConfigsEqual(const IniFile& expected, const IniFile& actual) {
// We need to explicitly check the list because when booted with -wipe-data
// the emulator will add a line (disk.dataPartition.initPath) which should
// not affect snapshot.
int numPathExpected = 0;
int numPathActual = 0;
for (auto&& key : expected) {
if (endsWith(key, ".path") || endsWith(key, ".initPath")) {
if (key != "disk.dataPartition.initPath") {
numPathExpected++;
}
continue; // these contain absolute paths and will be checked
// separately.
}
#ifdef ALLOW_CHANGE_RENDERER
if (!actual.hasKey(key) || actual.getString(key, "$$$")
!= expected.getString(key, "$$$")) {
if (key != "hw.gpu.mode") {
return false;
}
}
#else // ALLOW_CHANGE_RENDERER
if (!actual.hasKey(key)) {
return false;
}
if (actual.getString(key, "$$$") != expected.getString(key, "$$$")) {
return false;
}
#endif // ALLOW_CHANGE_RENDERER
}
for (auto&& key : actual) {
if (endsWith(key, ".path") || endsWith(key, ".initPath")) {
if (key != "disk.dataPartition.initPath") {
numPathActual++;
}
continue; // these contain absolute paths and will be checked
// separately.
}
#ifdef ALLOW_CHANGE_RENDERER
if (!expected.hasKey(key) || actual.getString(key, "$$$")
!= expected.getString(key, "$$$")) {
if (key != "hw.gpu.mode") {
return false;
}
}
#else // ALLOW_CHANGE_RENDERER
if (!expected.hasKey(key)) {
return false;
}
if (actual.getString(key, "$$$") != expected.getString(key, "$$$")) {
return false;
}
#endif // ALLOW_CHANGE_RENDERER
}
return numPathActual == numPathExpected;
}
static std::string gpuDriverString(const GpuInfo& info) {
return StringFormat("%s-%s-%s", info.make, info.model, info.version);
}
static Optional<std::string> currentGpuDriverString() {
const auto& gpuList = globalGpuInfoList().infos;
auto currentIt =
std::find_if(gpuList.begin(), gpuList.end(),
[](const GpuInfo& i) { return i.current_gpu; });
if (currentIt == gpuList.end() && !gpuList.empty()) {
currentIt = gpuList.begin();
}
if (currentIt == gpuList.end()) {
return {};
}
return gpuDriverString(*currentIt);
}
bool Snapshot::verifyHost(const pb::Host& host, bool writeFailure) {
VmConfiguration vmConfig;
Snapshotter::get().vmOperations().getVmConfiguration(&vmConfig);
if (host.has_hypervisor() && host.hypervisor() != vmConfig.hypervisorType) {
if (writeFailure) saveFailure(FailureReason::ConfigMismatchHostHypervisor);
return false;
}
// Do not worry about backend if in swiftshader_indirect
if (emuglConfig_get_current_renderer() == SELECTED_RENDERER_SWIFTSHADER_INDIRECT) {
return true;
}
if (auto gpuString = currentGpuDriverString()) {
if (!host.has_gpu_driver() || host.gpu_driver() != *gpuString) {
if (writeFailure) saveFailure(FailureReason::ConfigMismatchHostGpu);
return false;
}
} else if (host.has_gpu_driver()) {
if (writeFailure) saveFailure(FailureReason::ConfigMismatchHostGpu);
return false;
}
return true;
}
static constexpr Feature sInsensitiveFeatures[] = {
#undef FEATURE_CONTROL_ITEM
#define FEATURE_CONTROL_ITEM(feature) \
fc::feature,
#include "android/featurecontrol/FeatureControlDefSnapshotInsensitive.h"
#undef FEATURE_CONTROL_ITEM
};
const std::vector<Feature> Snapshot::getSnapshotInsensitiveFeatures() {
std::vector<Feature> res;
for (int i = 0; i < sizeof(sInsensitiveFeatures) / sizeof(Feature); i++) {
res.push_back(sInsensitiveFeatures[i]);
}
return res;
}
// static
bool Snapshot::isFeatureSnapshotInsensitive(Feature input) {
for (int i = 0; i < sizeof(sInsensitiveFeatures) / sizeof(Feature); i++) {
if (input == sInsensitiveFeatures[i]) return true;
}
return false;
}
bool Snapshot::isCompatibleWithCurrentFeatures() {
return verifyFeatureFlags(mSnapshotPb.config());
}
bool Snapshot::verifyFeatureFlags(const pb::Config& config) {
auto enabledFeatures = fc::getEnabled();
enabledFeatures.erase(
std::remove_if(
enabledFeatures.begin(),
enabledFeatures.end(),
isFeatureSnapshotInsensitive),
enabledFeatures.end());
// need a conversion from int to Feature here
std::vector<Feature> configFeatures;
for (auto it = config.enabled_features().begin();
it != config.enabled_features().end();
++it) {
configFeatures.push_back((Feature)(*it));
}
configFeatures.erase(
std::remove_if(
configFeatures.begin(),
configFeatures.end(),
isFeatureSnapshotInsensitive),
configFeatures.end());
if (int(enabledFeatures.size()) != configFeatures.size() ||
!std::equal(configFeatures.begin(),
configFeatures.end(), enabledFeatures.begin(),
[](int l, Feature r) {
return int(l) == r;
})) {
return false;
}
return true;
}
bool Snapshot::verifyConfig(const pb::Config& config, bool writeFailure) {
VmConfiguration vmConfig;
Snapshotter::get().vmOperations().getVmConfiguration(&vmConfig);
if (config.has_cpu_core_count() &&
config.cpu_core_count() != vmConfig.numberOfCpuCores) {
if (writeFailure) saveFailure(FailureReason::ConfigMismatchAvd);
return false;
}
if (config.has_ram_size_bytes() &&
config.ram_size_bytes() != vmConfig.ramSizeBytes) {
if (writeFailure) saveFailure(FailureReason::ConfigMismatchAvd);
return false;
}
if (config.has_selected_renderer() &&
config.selected_renderer() != int(emuglConfig_get_current_renderer())) {
#ifdef ALLOW_CHANGE_RENDERER
fprintf(stderr, "WARNING: change of renderer detected.\n");
#else // ALLOW_CHANGE_RENDERER
if (writeFailure) saveFailure(FailureReason::ConfigMismatchRenderer);
return false;
#endif // ALLOW_CHANGE_RENDERER
}
if (!verifyFeatureFlags(config)) {
if (writeFailure) saveFailure(FailureReason::ConfigMismatchFeatures);
return false;
}
return true;
}
bool Snapshot::writeSnapshotToDisk() {
auto res =
saveProtobuf(
PathUtils::join(mDataDir, "snapshot.pb"),
mSnapshotPb,
&mSize);
return res == ProtobufSaveResult::Success;
}
struct {
pb::Image::Type type;
char* (*pathGetter)(const AvdInfo* info);
} static constexpr kImages[] = {
{pb::Image::IMAGE_TYPE_KERNEL, avdInfo_getKernelPath},
{pb::Image::IMAGE_TYPE_KERNEL_RANCHU, avdInfo_getRanchuKernelPath},
{pb::Image::IMAGE_TYPE_SYSTEM, avdInfo_getSystemInitImagePath},
{pb::Image::IMAGE_TYPE_SYSTEM_COPY, avdInfo_getSystemImagePath},
{pb::Image::IMAGE_TYPE_DATA, avdInfo_getDataInitImagePath},
{pb::Image::IMAGE_TYPE_DATA_COPY, avdInfo_getDataImagePath},
{pb::Image::IMAGE_TYPE_RAMDISK, avdInfo_getRamdiskPath},
{pb::Image::IMAGE_TYPE_SDCARD, avdInfo_getSdCardPath},
{pb::Image::IMAGE_TYPE_CACHE, avdInfo_getCachePath},
{pb::Image::IMAGE_TYPE_VENDOR, avdInfo_getVendorImagePath},
{pb::Image::IMAGE_TYPE_ENCRYPTION_KEY,
avdInfo_getEncryptionKeyImagePath},
};
static constexpr int kVersion = 33;
static constexpr int kMaxSaveStatsHistory = 10;
base::StringView Snapshot::dataDir(const char* name) {
return getSnapshotDir(name);
}
base::Optional<std::string> Snapshot::parent() {
auto info = getGeneralInfo();
if (!info || !info->has_parent()) return base::kNullopt;
auto parentName = info->parent();
if (parentName == "") return base::kNullopt;
return parentName;
}
Snapshot::Snapshot(const char* name)
: Snapshot(name, getSnapshotDir(name).c_str()) { }
Snapshot::Snapshot(const char* name, const char* dataDir)
: mName(name), mDataDir(dataDir), mSaveStats(kMaxSaveStatsHistory) {
// All persisted snapshot protobuf fields across
// saves / loads go here.
if (preload()) {
for (int i = 0; i < mSnapshotPb.save_stats_size(); i++) {
mSaveStats.push_back(mSnapshotPb.save_stats(i));
}
}
}
// static
std::vector<Snapshot> Snapshot::getExistingSnapshots() {
std::vector<Snapshot> res = {};
auto snapshotFiles = getSnapshotDirEntries();
for (const auto& filename : snapshotFiles) {
Snapshot s(filename.c_str());
if (s.preload()) {
res.push_back(s);
}
}
return res;
}
void Snapshot::initProto() {
mSnapshotPb.Clear();
mSnapshotPb.set_version(kVersion);
mSnapshotPb.set_creation_time(System::get()->getUnixTime());
}
void Snapshot::saveEnabledFeatures() {
for (auto f : fc::getEnabled()) {
if (isFeatureSnapshotInsensitive(f)) continue;
mSnapshotPb.mutable_config()->add_enabled_features(int(f));
}
}
bool Snapshot::save() {
// In saving, we assume the state is different,
// so we reset the invalid/successful counters.
auto targetHwIni = PathUtils::join(mDataDir, "hardware.ini");
if (path_copy_file(targetHwIni.c_str(),
avdInfo_getCoreHwIniPath(android_avdInfo)) != 0) {
return false;
}
initProto();
VmConfiguration vmConfig;
Snapshotter::get().vmOperations().getVmConfiguration(&vmConfig);
mSnapshotPb.mutable_config()->set_cpu_core_count(vmConfig.numberOfCpuCores);
mSnapshotPb.mutable_config()->set_ram_size_bytes(vmConfig.ramSizeBytes);
mSnapshotPb.mutable_config()->set_selected_renderer(
int(emuglConfig_get_current_renderer()));
mSnapshotPb.mutable_host()->set_hypervisor(vmConfig.hypervisorType);
if (auto gpuString = currentGpuDriverString()) {
mSnapshotPb.mutable_host()->set_gpu_driver(*gpuString);
}
saveEnabledFeatures();
for (const auto& image : kImages) {
ScopedCPtr<char> path(image.pathGetter(android_avdInfo));
fillImageInfo(image.type, path.get(), mSnapshotPb.add_images());
}
mSnapshotPb.set_guest_data_partition_mounted(guest_data_partition_mounted);
mSnapshotPb.set_rotation(
int(Snapshotter::get().windowAgent().getRotation()));
mSnapshotPb.set_invalid_loads(mInvalidLoads);
mSnapshotPb.set_successful_loads(mSuccessfulLoads);
for (size_t i = 0; i < mSaveStats.size(); i++) {
auto toSave = mSnapshotPb.add_save_stats();
*toSave = mSaveStats[i];
}
auto parentSnapshot = Snapshotter::get().loadedSnapshotFile();
// We want to maintain the default_boot snapshot as outside
// the hierarchy. For that reason, don't set parent if default
// boot is involved either as the current snapshot or the parent snapshot.
if (mName != kDefaultBootSnapshot &&
parentSnapshot != "" &&
parentSnapshot != kDefaultBootSnapshot) {
if (mName != parentSnapshot) {
mSnapshotPb.set_parent(parentSnapshot);
}
}
return writeSnapshotToDisk();
}
bool Snapshot::saveFailure(FailureReason reason) {
if (reason == FailureReason::Empty) {
mLatestFailureReason = reason;
// Don't write this to disk
return true;
}
if (reason == mLatestFailureReason) {
// Nothing to do
return true;
}
mSnapshotPb.set_failed_to_load_reason_code(int64_t(reason));
if (!mSnapshotPb.has_version()) {
mSnapshotPb.set_version(kVersion);
}
mSnapshotPb.set_invalid_loads(mInvalidLoads);
mSnapshotPb.set_successful_loads(mSuccessfulLoads);
if (writeSnapshotToDisk()) {
// Success
mLatestFailureReason = reason;
return true;
}
return false;
}
static bool isUnrecoverableReason(FailureReason reason) {
return reason != FailureReason::Empty &&
reason < FailureReason::UnrecoverableErrorLimit;
}
void Snapshot::loadProtobufOnce() {
if (mSnapshotPb.has_version()) {
return;
}
const auto file =
ScopedFd(::open(PathUtils::join(mDataDir, "snapshot.pb").c_str(),
O_RDONLY | O_BINARY | O_CLOEXEC, 0755));
System::FileSize size;
if (!System::get()->fileSize(file.get(), &size)) {
saveFailure(FailureReason::NoSnapshotPb);
return;
}
mSize = size;
const auto fileMap = makeCustomScopedPtr(
mmap(nullptr, size, PROT_READ, MAP_PRIVATE, file.get(), 0),
[size](void* ptr) {
if (ptr != MAP_FAILED) {
munmap(ptr, size);
}
});
if (!fileMap || fileMap.get() == MAP_FAILED) {
saveFailure(FailureReason::BadSnapshotPb);
return;
}
if (!mSnapshotPb.ParseFromArray(fileMap.get(), size)) {
saveFailure(FailureReason::BadSnapshotPb);
return;
}
if (mSnapshotPb.has_failed_to_load_reason_code() &&
isUnrecoverableReason(
FailureReason(mSnapshotPb.failed_to_load_reason_code()))) {
return;
}
if (!mSnapshotPb.has_version() || mSnapshotPb.version() != kVersion) {
saveFailure(FailureReason::IncompatibleVersion);
return;
}
// Success
}
// preload(): Obtain the protobuf metadata. Logic for deciding
// whether or not to load based on it, plus initialization
// of properties like skin rotation, is done afterward in load().
// Checks whether or not the version of the protobuf is compatible.
bool Snapshot::preload() {
loadProtobufOnce();
return (mSnapshotPb.has_version() && mSnapshotPb.version() == kVersion);
}
const emulator_snapshot::Snapshot* Snapshot::getGeneralInfo() {
loadProtobufOnce();
if (isUnrecoverableReason(
FailureReason(mSnapshotPb.failed_to_load_reason_code()))) {
saveFailure(FailureReason(mSnapshotPb.failed_to_load_reason_code()));
}
return &mSnapshotPb;
}
const bool Snapshot::checkValid(bool writeFailure) {
if (!preload()) {
return false;
}
if (fc::isEnabled(fc::AllowSnapshotMigration)) {
return true;
}
if (mSnapshotPb.has_host() && !verifyHost(mSnapshotPb.host(), writeFailure)) {
return false;
}
if (mSnapshotPb.has_config() && !verifyConfig(mSnapshotPb.config(), writeFailure)) {
return false;
}
if (mSnapshotPb.images_size() > int(ARRAY_SIZE(kImages))) {
if (writeFailure) saveFailure(FailureReason::ConfigMismatchAvd);
return false;
}
int matchedImages = 0;
for (const auto& image : kImages) {
ScopedCPtr<char> path(image.pathGetter(android_avdInfo));
const auto type = image.type;
const auto it = std::find_if(
mSnapshotPb.images().begin(), mSnapshotPb.images().end(),
[type](const pb::Image& im) {
return im.has_type() && im.type() == type;
});
if (it != mSnapshotPb.images().end()) {
if (!verifyImageInfo(image.type, path.get(), *it)) {
// If in build env, nuke the qcow2 as well.
if (avdInfo_inAndroidBuild(android_avdInfo)) {
std::string qcow2Path(path.get());
qcow2Path += ".qcow2";
path_delete_file(qcow2Path.c_str());
}
if (writeFailure) saveFailure(FailureReason::SystemImageChanged);
return false;
}
++matchedImages;
} else {
// Should match an empty image info
if (!verifyImageInfo(image.type, path.get(), pb::Image())) {
// If in build env, nuke the qcow2 as well.
if (avdInfo_inAndroidBuild(android_avdInfo)) {
std::string qcow2Path(path.get());
qcow2Path += ".qcow2";
path_delete_file(qcow2Path.c_str());
}
if (writeFailure) saveFailure(FailureReason::NoSnapshotInImage);
return false;
}
}
}
if (matchedImages != mSnapshotPb.images_size()) {
if (writeFailure) saveFailure(FailureReason::ConfigMismatchAvd);
return false;
}
IniFile expectedConfig(PathUtils::join(mDataDir, "hardware.ini"));
if (!expectedConfig.read(false)) {
if (writeFailure) saveFailure(FailureReason::CorruptedData);
return false;
}
IniFile actualConfig(avdInfo_getCoreHwIniPath(android_avdInfo));
if (!actualConfig.read(false)) {
if (writeFailure) saveFailure(FailureReason::InternalError);
return false;
}
IniFile expectedStripped;
androidHwConfig_stripDefaults(
reinterpret_cast<CIniFile*>(&expectedConfig),
reinterpret_cast<CIniFile*>(&expectedStripped));
IniFile actualStripped;
androidHwConfig_stripDefaults(reinterpret_cast<CIniFile*>(&actualConfig),
reinterpret_cast<CIniFile*>(&actualStripped));
if (!areHwConfigsEqual(expectedStripped, actualStripped)) {
if (writeFailure) saveFailure(FailureReason::ConfigMismatchAvd);
return false;
}
if (mSnapshotPb.has_invalid_loads()) {
mInvalidLoads = mSnapshotPb.invalid_loads();
} else {
mInvalidLoads = 0;
}
if (mSnapshotPb.has_successful_loads()) {
mSuccessfulLoads = mSnapshotPb.successful_loads();
} else {
mSuccessfulLoads = 0;
}
if (isUnrecoverableReason(
FailureReason(mSnapshotPb.failed_to_load_reason_code()))) {
if (writeFailure) {
saveFailure(FailureReason(mSnapshotPb.failed_to_load_reason_code()));
}
return false;
}
return true;
}
// load(): Loads all snapshot metadata and checks it against other files
// for integrity.
bool Snapshot::load() {
if (!checkValid(true /* save the failure reason */)) {
return false;
}
if (mSnapshotPb.has_guest_data_partition_mounted()) {
guest_data_partition_mounted =
mSnapshotPb.guest_data_partition_mounted();
}
if (mSnapshotPb.has_rotation() &&
Snapshotter::get().windowAgent().getRotation() !=
SkinRotation(mSnapshotPb.rotation())) {
Snapshotter::get().windowAgent().rotate(
SkinRotation(mSnapshotPb.rotation()));
}
return true;
}
void Snapshot::incrementInvalidLoads() {
++mInvalidLoads;
mSnapshotPb.set_invalid_loads(mInvalidLoads);
if (!mSnapshotPb.has_version()) {
mSnapshotPb.set_version(kVersion);
}
writeSnapshotToDisk();
}
void Snapshot::incrementSuccessfulLoads() {
++mSuccessfulLoads;
mSnapshotPb.set_successful_loads(mSuccessfulLoads);
if (!mSnapshotPb.has_version()) {
mSnapshotPb.set_version(kVersion);
}
writeSnapshotToDisk();
}
void Snapshot::addSaveStats(bool incremental,
const base::System::Duration duration,
uint64_t ramChangedBytes) {
emulator_snapshot::SaveStats stats;
stats.set_incremental(incremental ? 1 : 0);
stats.set_duration((uint64_t)duration);
stats.set_ram_changed_bytes((uint64_t)ramChangedBytes);
mSaveStats.push_back(stats);
}
bool Snapshot::areSavesSlow() const {
if (mSaveStats.empty()) return false;
static constexpr float kSlowSaveThresholdMs = 20000.0;
float maxSaveTimeMs = 0.0f;
for (size_t i = 0; i < mSaveStats.size(); ++i) {
float durationMs = mSaveStats[i].duration() / 1000.0f;
maxSaveTimeMs = std::max(durationMs, maxSaveTimeMs);
}
return maxSaveTimeMs >= kSlowSaveThresholdMs;
}
bool Snapshot::shouldInvalidate() const {
// Either there wasn't any successful loads,
// or there was more than one invalid load
// of this snapshot.
return !mSuccessfulLoads ||
mInvalidLoads > 1;
}
base::Optional<FailureReason> Snapshot::failureReason() const {
return mSnapshotPb.has_failed_to_load_reason_code()
? base::makeOptional(FailureReason(
mSnapshotPb.failed_to_load_reason_code()))
: base::kNullopt;
}
} // namespace snapshot
} // namespace android