| // 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 "android-qemu2-glue/qemu-control-impl.h" |
| |
| #include "android/base/StringFormat.h" |
| #include "android/base/StringView.h" |
| #include "android/base/files/PathUtils.h" |
| #include "android/base/misc/StringUtils.h" |
| #include "android/emulation/CpuAccelerator.h" |
| #include "android/emulation/VmLock.h" |
| #include "android/emulation/control/callbacks.h" |
| #include "android/emulation/control/vm_operations.h" |
| #include "android/snapshot/MemoryWatch.h" |
| #include "android/snapshot/PathUtils.h" |
| #include "android/snapshot/Snapshotter.h" |
| #include "android/snapshot/common.h" |
| #include "android/snapshot/interface.h" |
| #include "android/utils/path.h" |
| |
| #include <nlohmann/json.hpp> |
| |
| extern "C" { |
| #include "qemu/osdep.h" |
| |
| #include "block/block.h" |
| #include "exec/cpu-common.h" |
| #include "migration/qemu-file.h" |
| #include "qapi/error.h" |
| #include "qapi/qapi-commands-misc.h" |
| #include "qapi/qmp/qdict.h" |
| #include "qapi/qmp/qobject.h" |
| #include "qapi/qmp/qstring.h" |
| |
| #include "exec/cpu-common.h" |
| #include "exec/memory-remap.h" |
| #include "sysemu/block-backend.h" |
| #include "sysemu/cpus.h" |
| #include "sysemu/gvm.h" |
| #include "sysemu/hax.h" |
| #include "sysemu/hvf.h" |
| #include "sysemu/kvm.h" |
| #include "sysemu/sysemu.h" |
| #include "sysemu/whpx.h" |
| |
| #include "qapi/qapi-commands-block-core.h" |
| #include "qapi/qapi-types-block-core.h" |
| } |
| |
| // Qemu includes can redefine some windows behavior.. |
| // clang-format off |
| #include "android/snapshot/Snapshot.h" |
| // clang-format on |
| |
| // As well as introduce some workarounds we don't want.. |
| #ifdef accept |
| #undef accept |
| #endif |
| #include <string> |
| |
| #include <stdarg.h> |
| #include <stdlib.h> |
| #include <string.h> |
| |
| /* set to 1 for very verbose debugging */ |
| #define DEBUG 0 |
| |
| #if DEBUG >= 1 |
| #define DD(fmt, ...) \ |
| printf("%s:%s:%d| " fmt "\n", __FILE__, __func__, __LINE__, ##__VA_ARGS__) |
| #else |
| #define DD(...) (void)0 |
| #endif |
| |
| using android::base::PathUtils; |
| using android::base::pj; |
| using android::base::StringAppendFormatWithArgs; |
| using android::base::StringFormatWithArgs; |
| using android::base::StringView; |
| using android::base::System; |
| using android::snapshot::Snapshot; |
| using json = nlohmann::json; |
| |
| static bool qemu_vm_stop() { |
| vm_stop(RUN_STATE_PAUSED); |
| return true; |
| } |
| |
| static bool qemu_vm_start() { |
| vm_start(); |
| return true; |
| } |
| |
| static bool qemu_vm_is_running() { |
| return runstate_is_running() != 0; |
| } |
| |
| namespace { |
| |
| // A custom callback class to correctly format and forward messages |
| // into the user-supplied line callbacks. |
| struct MessageCallback : QEMUMessageCallback { |
| MessageCallback(void* opaque, |
| LineConsumerCallback out, |
| LineConsumerCallback err) |
| : userOpaque(opaque), userOut(out), userErr(err) { |
| this->opaque = this; |
| this->out = outCb; |
| this->err = errCb; |
| } |
| |
| MessageCallback(const MessageCallback& other) |
| : QEMUMessageCallback(other), |
| userOpaque(other.userOpaque), |
| userOut(other.userOut), |
| userErr(other.userErr) { |
| this->opaque = this; |
| } |
| |
| void operator=(const MessageCallback&) = delete; |
| |
| operator const QEMUMessageCallback*() const { |
| return (const QEMUMessageCallback*)this; |
| } |
| |
| private: |
| // GCC doesn't support converting lambdas with '...' to function pointers, |
| // so these have to be real functions. |
| static void outCb(void* opaque, const char* fmt, ...) { |
| auto self = (MessageCallback*)opaque; |
| if (self->userOut) { |
| va_list ap; |
| va_start(ap, fmt); |
| auto msg = StringFormatWithArgs(fmt, ap); |
| self->userOut(self->userOpaque, msg.c_str(), msg.size()); |
| va_end(ap); |
| } |
| } |
| |
| static void errCb(void* opaque, Error* err, const char* fmt, ...) { |
| auto self = (MessageCallback*)opaque; |
| if (self->userErr) { |
| std::string msg; |
| if (fmt) { |
| va_list ap; |
| va_start(ap, fmt); |
| msg = StringFormatWithArgs(fmt, ap); |
| va_end(ap); |
| } |
| if (err) { |
| msg += StringView(error_get_pretty(err)); |
| error_free(err); |
| } |
| msg += '\n'; // QEMU's error printing always appends this. |
| self->userErr(self->userOpaque, msg.c_str(), msg.size()); |
| } |
| } |
| |
| void* userOpaque; |
| LineConsumerCallback userOut; |
| LineConsumerCallback userErr; |
| }; |
| |
| } // namespace |
| |
| static android::snapshot::FailureReason sFailureReason = |
| android::snapshot::FailureReason::Empty; |
| static bool sExiting = false; |
| |
| static bool qemu_snapshot_list(void* opaque, |
| LineConsumerCallback outConsumer, |
| LineConsumerCallback errConsumer) { |
| android::RecursiveScopedVmLock vmlock; |
| return qemu_listvms(nullptr, nullptr, |
| MessageCallback(opaque, outConsumer, errConsumer)) == 0; |
| } |
| |
| static bool qemu_snapshot_save(const char* name, |
| void* opaque, |
| LineConsumerCallback errConsumer) { |
| android::RecursiveScopedVmLock vmlock; |
| bool wasVmRunning = runstate_is_running() != 0; |
| vm_stop(RUN_STATE_SAVE_VM); |
| |
| int res = qemu_savevm(name, MessageCallback(opaque, nullptr, errConsumer)); |
| |
| if (wasVmRunning && !sExiting) { |
| vm_start(); |
| } |
| |
| return res == 0; |
| } |
| |
| // TODO(jansene): Remove qemu-img dependency and use direct calls instead. |
| // See static int img_rebase(int argc, char **argv) in qemu-img.c |
| // |src| should live in the avd dir and must not be an absolute path |
| // |baseimg| should live in the avd dir and must not be an absolute path |
| static bool rebase_on_top_of(std::string src, |
| std::string baseimg, |
| void* opaque, |
| LineConsumerCallback errConsumer) { |
| assert(!qemu_vm_is_running()); |
| DD("Rebase %s -> %s", src.c_str(), baseimg.c_str()); |
| auto qemu_img = System::get()->findBundledExecutable("qemu-img"); |
| auto dst_img = pj(android::snapshot::getAvdDir(), baseimg); |
| if (!System::get()->pathExists(dst_img)) { |
| // TODO(jansene): replace with internal functions, vs calling qemu-img. |
| // See qemu-img.c: static int img_create(int argc, char **argv) |
| auto res = android::base::System::get()->runCommandWithResult( |
| {qemu_img, "create", "-f", "qcow2", dst_img, "0"}); |
| if (!res) { |
| std::string msg = "Could not create empty qcow2: " + dst_img; |
| errConsumer(opaque, msg.c_str(), msg.size()); |
| return false; |
| } |
| } |
| |
| // TODO(jansene): replace with internal functions, vs calling qemu-img. |
| // See qemu-img.c: static int img_rebase(int argc, char **argv) |
| auto res = System::get()->runCommandWithResult( |
| {qemu_img, "rebase", "-f", "qcow2", "-b", dst_img, src}); |
| |
| return res; |
| } |
| |
| // Swaps out the given blockdriver with a new blockdriver |
| // backed by the provided qcow2 file. |
| // |drive| Device to be swapped out |
| // |qcow2| The new image we are going to insert. |
| static bool qemu_swap_blockdriver(const json drive, |
| const char* qcow2, |
| void* opaque, |
| LineConsumerCallback errConsumer) { |
| assert(!qemu_vm_is_running()); |
| bool res = false; |
| auto device = drive["device"].get<std::string>(); |
| BlockBackend* blk = blk_by_name(device.c_str()); |
| if (blk == nullptr) |
| return res; |
| |
| Error* errp = nullptr; |
| BlockDriverState* bs = blk_bs(blk); |
| auto oldflags = bdrv_get_flags(bs); |
| |
| // Eject |
| DD("Ejecting %s", device.c_str()); |
| AioContext* aioCtx = bdrv_get_aio_context(bs); |
| aio_context_acquire(aioCtx); |
| blk_flush(blk); |
| blk_remove_bs(blk); |
| aio_context_release(aioCtx); |
| |
| // Inject. |
| DD("Injecting %s", qcow2); |
| auto dict = qdict_new(); |
| qdict_put_str(dict, "overlap-check", |
| drive["overlap-check"].get<std::string>().c_str()); |
| qdict_put_str(dict, "l2-cache-size", |
| drive["l2-cache-size"].get<std::string>().c_str()); |
| qdict_set_default_str(dict, BDRV_OPT_CACHE_DIRECT, "off"); |
| qdict_set_default_str(dict, BDRV_OPT_CACHE_NO_FLUSH, "off"); |
| qdict_set_default_str(dict, BDRV_OPT_READ_ONLY, "off"); |
| |
| auto replacement = bdrv_open(qcow2, nullptr, dict, oldflags, &errp); |
| if (replacement) { |
| blk_insert_bs(blk, replacement, &errp); |
| bdrv_unref(replacement); |
| } |
| |
| if (errp) { |
| const char* msg = error_get_pretty(errp); |
| errConsumer(opaque, msg, strlen(msg)); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| // Returns a json description of all the mounted qcow2 drives. This is partial |
| // representation that you would retrieve from using the query_block command |
| // on the qemuy qmp-shell. |
| // Notably you will find: {'device' : 'name-of-device', 'backing' : |
| // 'backing-file' } |
| static std::vector<json> qcow2_drives() { |
| Error* errp = nullptr; |
| const char json_str[6] = "json:"; |
| std::vector<json> drives; |
| BlockInfoList* info = qmp_query_block(&errp); |
| while (info) { |
| if (info->value->has_inserted) { |
| char* js = strstr(info->value->inserted->file, json_str); |
| if (js && json::accept(js + sizeof(json_str) - 1)) { |
| json drive = json::parse(js + sizeof(json_str) - 1); |
| if (drive.count("driver") > 0 && drive["driver"] == "qcow2" && |
| drive.count("file") > 0 && |
| drive["file"].count("filename") > 0) { |
| drive["device"] = info->value->device; |
| drive["backing"] = info->value->inserted->backing_file; |
| DD("Found %s", drive.dump().c_str()); |
| drives.push_back(drive); |
| } |
| } |
| } |
| info = info->next; |
| } |
| DD("Found %d drives", drives.size()); |
| return drives; |
| } |
| |
| static bool import_snapshot(const char* name, |
| void* opaque, |
| LineConsumerCallback errConsumer) { |
| assert(!qemu_vm_is_running()); |
| auto snapshot = Snapshot::getSnapshotById(name); |
| if (!snapshot || !snapshot->isImported()) { |
| // Ready to roll, this is not an imported snapshot.. |
| return true; |
| } |
| |
| bool success = true; |
| auto drives = qcow2_drives(); |
| |
| // Copy and replace the avds from the snapshot directory.. |
| auto avd = android::snapshot::getAvdDir(); |
| auto datadir = snapshot->dataDir(); |
| for (auto qcow2 : android::snapshot::getQcow2Files(datadir)) { |
| // Check if we have a device mapping this qcow. |
| auto it = std::find_if( |
| drives.begin(), drives.end(), [qcow2](const json object) { |
| return object["file"]["filename"].get<std::string>().find( |
| qcow2) != std::string::npos; |
| }); |
| |
| if (it == drives.end()) { |
| DD("%s is not mounted, ignoring", qcow2.c_str()); |
| continue; |
| } |
| auto drive = *it; |
| |
| DD("Copying %s", qcow2.c_str()); |
| auto dest = pj(avd, qcow2) + "_import.qcow2"; |
| auto src = pj(datadir, qcow2); |
| |
| // We do not symlink as we do not want to modify the existing snapshot. |
| path_delete_file(dest.c_str()); |
| if (path_copy_file(dest.c_str(), src.c_str()) != 0) { |
| std::string msg = "Failed to copy " + qcow2 + " to: " + dest + |
| " due to " + std::to_string(errno); |
| errConsumer(opaque, msg.c_str(), msg.size()); |
| success = false; |
| }; |
| |
| auto baseimg = drive["backing"]; |
| rebase_on_top_of(dest, baseimg, opaque, errConsumer); |
| |
| // Now we are going to replace the block driver associated with it. |
| success = qemu_swap_blockdriver(drive, dest.c_str(), opaque, |
| errConsumer) && |
| success; |
| } |
| |
| return success; |
| } |
| |
| static bool qemu_snapshot_load(const char* name, |
| void* opaque, |
| LineConsumerCallback errConsumer) { |
| android::RecursiveScopedVmLock vmlock; |
| |
| bool wasVmRunning = runstate_is_running() != 0; |
| |
| vm_stop(RUN_STATE_RESTORE_VM); |
| |
| Error* errp = nullptr; |
| |
| // Okay, so it could be that this is an imported snapshot.. |
| // An imported snapshot has the qcow2s stored in its own directory. |
| // in that case we need to swap out our current qcow2s, and use them |
| // for a reload. |
| |
| // TODO(jansen): We should make it possible to swap back, as |
| // swapping in an imported snapshots invalidates all the existing |
| // snapshots, and we cannot load local snapshots anymore as they |
| // live in the swapped out snapshot. |
| import_snapshot(name, opaque, errConsumer); |
| |
| int loadVmRes = |
| qemu_loadvm(name, MessageCallback(opaque, nullptr, errConsumer)); |
| |
| bool failed = loadVmRes != 0; |
| |
| // loadvm may have failed, but try to restart the current vm anyway, to |
| // prevent hanging on generic snapshot load errors such as snapshots not |
| // existing. |
| |
| if (wasVmRunning) { |
| if (failed) { |
| std::string failureStr("Snapshot load failure: "); |
| |
| failureStr += android::snapshot::failureReasonToString( |
| sFailureReason, SNAPSHOT_LOAD); |
| |
| failureStr += "\n"; |
| |
| if (errConsumer) { |
| errConsumer(opaque, failureStr.data(), failureStr.length()); |
| } |
| |
| if (sFailureReason < |
| android::snapshot::FailureReason::ValidationErrorLimit) { |
| // load failed, but it is OK to resume VM |
| vm_start(); |
| } else { |
| // load failed weirdly, don't resume |
| if (errConsumer) { |
| const char* fatalErrStr = "fatal error, VM stopped.\n"; |
| errConsumer(opaque, fatalErrStr, strlen(fatalErrStr)); |
| } |
| } |
| } else { |
| // Normal load, resume |
| vm_start(); |
| } |
| } |
| |
| return !failed; |
| } |
| |
| static bool qemu_snapshot_delete(const char* name, |
| void* opaque, |
| LineConsumerCallback errConsumer) { |
| android::RecursiveScopedVmLock vmlock; |
| return qemu_delvm(name, MessageCallback(opaque, nullptr, errConsumer)) == 0; |
| } |
| |
| static bool qemu_snapshot_remap(bool shared, |
| void* opaque, |
| LineConsumerCallback errConsumer) { |
| android::RecursiveScopedVmLock vmlock; |
| |
| // We currently only support remap of the Quickboot snapshot, |
| // and only if file-backed. |
| // |
| // It's good to skip doing anything if the use case |
| // is currently out of scope, or it would have no semantic effect |
| // (going from auto saving to continuing to auto-save). |
| auto currentRamFileStatus = androidSnapshot_getRamFileInfo(); |
| |
| // If the backing file is not "default_boot" then we cannot use file-backed |
| // ram. |
| // If it doesn't have a backing file then we will create a default_boot |
| // snapshot. |
| if (android::snapshot::Snapshotter::get().loadedSnapshotFile() != "" && |
| android::snapshot::Snapshotter::get().loadedSnapshotFile() != |
| "default_boot") { |
| return true; |
| } |
| |
| if (currentRamFileStatus == SNAPSHOT_RAM_FILE_SHARED && shared) { |
| return true; |
| } |
| |
| bool wasVmRunning = runstate_is_running() != 0; |
| |
| if (currentRamFileStatus != SNAPSHOT_RAM_FILE_PRIVATE && !shared) { |
| vm_stop(RUN_STATE_SAVE_VM); |
| android::snapshot::Snapshotter::get().setRemapping(true); |
| qemu_savevm("default_boot", |
| MessageCallback(opaque, nullptr, errConsumer)); |
| android::snapshot::Snapshotter::get().setRemapping(false); |
| ram_blocks_remap_shared(shared); |
| } else { |
| vm_stop(RUN_STATE_RESTORE_VM); |
| ram_blocks_remap_shared(shared); |
| qemu_loadvm("default_boot", |
| MessageCallback(opaque, nullptr, errConsumer)); |
| } |
| |
| android::snapshot::Snapshotter::get().setRamFileShared(shared); |
| androidSnapshot_setRamFileDirty("default_boot", false); |
| |
| if (wasVmRunning) { |
| vm_start(); |
| } |
| |
| return true; |
| } |
| |
| static SnapshotCallbacks sSnapshotCallbacks = {}; |
| static void* sSnapshotCallbacksOpaque = nullptr; |
| |
| static int onSaveVmStart(const char* name) { |
| return sSnapshotCallbacks.ops[SNAPSHOT_SAVE].onStart( |
| sSnapshotCallbacksOpaque, name); |
| } |
| |
| static void onSaveVmEnd(const char* name, int res) { |
| sSnapshotCallbacks.ops[SNAPSHOT_SAVE].onEnd(sSnapshotCallbacksOpaque, name, |
| res); |
| } |
| |
| static void onSaveVmQuickFail(const char* name, int res) { |
| sSnapshotCallbacks.ops[SNAPSHOT_SAVE].onQuickFail(sSnapshotCallbacksOpaque, |
| name, res); |
| } |
| |
| static bool saveVmQueryCanceled(const char* name) { |
| return sSnapshotCallbacks.ops[SNAPSHOT_SAVE].isCanceled( |
| sSnapshotCallbacksOpaque, name); |
| } |
| |
| static int onLoadVmStart(const char* name) { |
| return sSnapshotCallbacks.ops[SNAPSHOT_LOAD].onStart( |
| sSnapshotCallbacksOpaque, name); |
| } |
| |
| static void onLoadVmEnd(const char* name, int res) { |
| sSnapshotCallbacks.ops[SNAPSHOT_LOAD].onEnd(sSnapshotCallbacksOpaque, name, |
| res); |
| } |
| |
| static void onLoadVmQuickFail(const char* name, int res) { |
| sSnapshotCallbacks.ops[SNAPSHOT_LOAD].onQuickFail(sSnapshotCallbacksOpaque, |
| name, res); |
| } |
| |
| static bool loadVmQueryCanceled(const char* name) { |
| return sSnapshotCallbacks.ops[SNAPSHOT_LOAD].isCanceled( |
| sSnapshotCallbacksOpaque, name); |
| } |
| |
| static int onDelVmStart(const char* name) { |
| return sSnapshotCallbacks.ops[SNAPSHOT_DEL].onStart( |
| sSnapshotCallbacksOpaque, name); |
| } |
| |
| static void onDelVmEnd(const char* name, int res) { |
| sSnapshotCallbacks.ops[SNAPSHOT_DEL].onEnd(sSnapshotCallbacksOpaque, name, |
| res); |
| } |
| |
| static void onDelVmQuickFail(const char* name, int res) { |
| sSnapshotCallbacks.ops[SNAPSHOT_DEL].onQuickFail(sSnapshotCallbacksOpaque, |
| name, res); |
| } |
| |
| static bool delVmQueryCanceled(const char* name) { |
| return sSnapshotCallbacks.ops[SNAPSHOT_DEL].isCanceled( |
| sSnapshotCallbacksOpaque, name); |
| } |
| |
| static const QEMUSnapshotCallbacks sQemuSnapshotCallbacks = { |
| .savevm = {onSaveVmStart, onSaveVmEnd, onSaveVmQuickFail, |
| saveVmQueryCanceled}, |
| .loadvm = {onLoadVmStart, onLoadVmEnd, onLoadVmQuickFail, |
| loadVmQueryCanceled}, |
| .delvm = {onDelVmStart, onDelVmEnd, onDelVmQuickFail, |
| delVmQueryCanceled}}; |
| |
| static const QEMUFileHooks sSaveHooks = { |
| // before_ram_iterate |
| [](QEMUFile* f, void* opaque, uint64_t flags, void* data) { |
| qemu_put_be64(f, RAM_SAVE_FLAG_HOOK); |
| if (flags == RAM_CONTROL_SETUP) { |
| // Register all blocks for saving. |
| qemu_ram_foreach_migrate_block_with_file_info( |
| [](const char* block_name, void* host_addr, |
| ram_addr_t offset, ram_addr_t length, uint32_t flags, |
| const char* path, bool readonly, void* opaque) { |
| auto relativePath = PathUtils::relativeTo( |
| android::snapshot::getSnapshotBaseDir(), |
| path); |
| |
| SnapshotRamBlock block = { |
| block_name, |
| (int64_t)offset, |
| (uint8_t*)host_addr, |
| (int64_t)length, |
| 0 /* page size to fill in later */, |
| flags, |
| relativePath, |
| readonly, |
| false /* init need restore to false */ |
| }; |
| |
| block.pageSize = (int32_t)qemu_ram_pagesize( |
| qemu_ram_block_by_name(block_name)); |
| sSnapshotCallbacks.ramOps.registerBlock( |
| sSnapshotCallbacksOpaque, SNAPSHOT_SAVE, |
| &block); |
| return 0; |
| }, |
| nullptr); |
| } |
| return 0; |
| }, |
| // after_ram_iterate |
| [](QEMUFile* f, void* opaque, uint64_t flags, void* data) { |
| if (flags == RAM_CONTROL_FINISH) { |
| return sSnapshotCallbacks.ramOps.savingComplete( |
| sSnapshotCallbacksOpaque); |
| } |
| return 0; |
| }, |
| // hook_ram_load |
| nullptr, |
| // save_page |
| [](QEMUFile* f, |
| void* opaque, |
| ram_addr_t block_offset, |
| ram_addr_t offset, |
| size_t size, |
| uint64_t* bytes_sent) { |
| sSnapshotCallbacks.ramOps.savePage(sSnapshotCallbacksOpaque, |
| (int64_t)block_offset, |
| (int64_t)offset, (int32_t)size); |
| // Must set |bytes_sent| to non-zero, otherwise QEMU will save the |
| // page in own way. |
| *bytes_sent = size; |
| return size_t(RAM_SAVE_CONTROL_DELAYED); |
| }, |
| }; |
| |
| static const QEMUFileHooks sLoadHooks = { |
| // before_ram_iterate |
| nullptr, |
| // after_ram_iterate |
| nullptr, |
| // hook_ram_load |
| [](QEMUFile* f, void* opaque, uint64_t flags, void* data) { |
| switch (flags) { |
| case RAM_CONTROL_BLOCK_REG: { |
| SnapshotRamBlock block; |
| block.id = static_cast<const char*>(data); |
| qemu_ram_foreach_migrate_block_with_file_info( |
| [](const char* block_name, void* host_addr, |
| ram_addr_t offset, ram_addr_t length, |
| uint32_t flags, const char* path, bool readonly, |
| void* opaque) { |
| auto block = |
| static_cast<SnapshotRamBlock*>(opaque); |
| if (strcmp(block->id, block_name) != 0) { |
| return 0; |
| } |
| |
| // Rebase path as relative to support migration |
| auto relativePath = PathUtils::relativeTo( |
| android::snapshot::getSnapshotBaseDir(), |
| path); |
| |
| block->startOffset = offset; |
| block->hostPtr = (uint8_t*)host_addr; |
| block->totalSize = length; |
| block->flags = flags; |
| block->path = relativePath; |
| block->readonly = readonly; |
| block->needRestoreFromRamFile = false; |
| return 1; |
| }, |
| &block); |
| |
| RAMBlock* const qemuBlock = |
| qemu_ram_block_by_name(block.id); |
| block.pageSize = (int32_t)qemu_ram_pagesize(qemuBlock); |
| sSnapshotCallbacks.ramOps.registerBlock( |
| sSnapshotCallbacksOpaque, SNAPSHOT_LOAD, &block); |
| break; |
| } |
| case RAM_CONTROL_HOOK: { |
| return sSnapshotCallbacks.ramOps.startLoading( |
| sSnapshotCallbacksOpaque); |
| } |
| } |
| return 0; |
| }}; |
| |
| static void set_skip_snapshot_save(bool used); |
| static void set_snapshot_callbacks(void* opaque, |
| const SnapshotCallbacks* callbacks) { |
| if (!opaque || !callbacks) { |
| sSnapshotCallbacks = {}; |
| sSnapshotCallbacksOpaque = nullptr; |
| qemu_set_snapshot_callbacks(nullptr); |
| migrate_set_file_hooks(nullptr, nullptr); |
| } else { |
| sSnapshotCallbacks = *callbacks; |
| sSnapshotCallbacksOpaque = opaque; |
| qemu_set_snapshot_callbacks(&sQemuSnapshotCallbacks); |
| qemu_set_ram_load_callback([](void* hostRam, uint64_t size) { |
| sSnapshotCallbacks.ramOps.loadRam(sSnapshotCallbacksOpaque, hostRam, |
| size); |
| }); |
| |
| switch (android::GetCurrentCpuAccelerator()) { |
| case android::CPU_ACCELERATOR_HVF: |
| set_address_translation_funcs(hvf_hva2gpa, hvf_gpa2hva); |
| set_memory_mapping_funcs(hvf_map_safe, hvf_unmap_safe, |
| hvf_protect_safe, hvf_remap_safe, |
| NULL); |
| break; |
| case android::CPU_ACCELERATOR_HAX: |
| set_address_translation_funcs(hax_hva2gpa, hax_gpa2hva); |
| set_memory_mapping_funcs(NULL, NULL, hax_gpa_protect, NULL, |
| hax_gpa_protection_supported); |
| break; |
| case android::CPU_ACCELERATOR_WHPX: |
| set_address_translation_funcs(0, whpx_gpa2hva); |
| break; |
| case android::CPU_ACCELERATOR_GVM: |
| set_address_translation_funcs(gvm_hva2gpa, gvm_gpa2hva); |
| break; |
| default: // KVM |
| #ifdef __linux__ |
| set_address_translation_funcs(0, kvm_gpa2hva); |
| #endif |
| break; |
| } |
| |
| migrate_set_file_hooks(&sSaveHooks, &sLoadHooks); |
| } |
| } |
| |
| static void map_user_backed_ram(uint64_t gpa, void* hva, uint64_t size) { |
| android::RecursiveScopedVmLock vmlock; |
| qemu_user_backed_ram_map( |
| gpa, hva, size, |
| USER_BACKED_RAM_FLAGS_READ | USER_BACKED_RAM_FLAGS_WRITE); |
| } |
| |
| static void unmap_user_backed_ram(uint64_t gpa, uint64_t size) { |
| android::RecursiveScopedVmLock vmlock; |
| qemu_user_backed_ram_unmap(gpa, size); |
| } |
| |
| static void get_vm_config(VmConfiguration* out) { |
| out->numberOfCpuCores = smp_cpus * smp_cores * smp_threads; |
| out->ramSizeBytes = int64_t(ram_size); |
| if (whpx_enabled()) { |
| out->hypervisorType = HV_WHPX; |
| } else if (hax_enabled()) { |
| out->hypervisorType = HV_HAXM; |
| } else if (hvf_enabled()) { |
| out->hypervisorType = HV_HVF; |
| } else if (kvm_enabled()) { |
| out->hypervisorType = HV_KVM; |
| } else if (tcg_enabled()) { |
| out->hypervisorType = HV_NONE; |
| } else { |
| out->hypervisorType = HV_UNKNOWN; |
| } |
| } |
| |
| static void set_failure_reason(const char* name, int failure_reason) { |
| (void)name; // TODO |
| sFailureReason = (android::snapshot::FailureReason)failure_reason; |
| } |
| |
| static void set_exiting() { |
| sExiting = true; |
| } |
| |
| static void allow_real_audio(bool allow) { |
| qemu_allow_real_audio(allow); |
| } |
| |
| static bool is_real_audio_allowed() { |
| return qemu_is_real_audio_allowed(); |
| } |
| |
| static bool need_skip_snapshot_save = false; |
| |
| static void set_skip_snapshot_save(bool used) { |
| need_skip_snapshot_save = true; |
| } |
| |
| static bool is_snapshot_save_skipped() { |
| return need_skip_snapshot_save; |
| } |
| |
| static void system_reset_request() { |
| qemu_system_reset_request(SHUTDOWN_CAUSE_GUEST_RESET); |
| } |
| |
| static void system_shutdown_request() { |
| qemu_system_shutdown_request(SHUTDOWN_CAUSE_GUEST_SHUTDOWN); |
| } |
| |
| static bool vm_pause() { |
| qmp_stop(nullptr); |
| return true; |
| } |
| |
| static bool vm_resume() { |
| Error* err = nullptr; |
| qmp_cont(&err); |
| return err == nullptr; |
| ; |
| } |
| |
| static void* physical_memory_get_addr(uint64_t gpa) { |
| if (!gpa2hva_call) { |
| fprintf(stderr, "%s: ERROR: No gpa2hva!\n", __func__); |
| return nullptr; |
| } |
| bool found; |
| void* res = gpa2hva_call(gpa, &found); |
| return found ? res : nullptr; |
| } |
| |
| static bool flush_all_drives() { |
| bool success = true; |
| BlockBackend* blk = NULL; |
| |
| while ((blk = blk_all_next(blk)) != NULL) { |
| AioContext* aio_context = blk_get_aio_context(blk); |
| int ret; |
| aio_context_acquire(aio_context); |
| if (blk_is_inserted(blk)) { |
| ret = blk_flush(blk); |
| if (ret < 0) { |
| success = false; |
| LOG(WARNING) << "Failed to flush: " << blk_name(blk); |
| } |
| } |
| aio_context_release(aio_context); |
| } |
| |
| return success; |
| } |
| |
| bool qemu_snapshot_export_qcow(const char* snapshot, |
| const char* dest, |
| void* opaque, |
| LineConsumerCallback errConsumer) { |
| // Exporting works like this: |
| // 1. Pause the emulator, we don't want any writes to happen |
| // 2. Flush all the drives, so we don't have a messed up qcow2 state |
| // 3. Copy all the qcow2 to a temporary |
| // 4. Rebase on top of an empty image, forcing us to pull all relevant |
| // data from the read only sections of the base image (likely nop) |
| // 5. Move the standalone qcows out of the way.. |
| // 6. Wake up the emulator. |
| // |
| // TODO(jansene) Qemu supports live migration, so this should be possible |
| // without pausing the emulator. |
| |
| android::RecursiveScopedVmLock vmlock; |
| bool wasVmRunning = runstate_is_running() != 0; |
| bool success = true; |
| vm_stop(RUN_STATE_SAVE_VM); |
| |
| // Next we flush all the drives, this guarantees that the qcow2's are in a |
| // consistent state, and we can safely copy them. |
| flush_all_drives(); |
| |
| // Now we copy over the qcow2 overlays.. |
| auto drives = qcow2_drives(); |
| for (auto drive : drives) { |
| auto overlay = drive["file"]["filename"].get<std::string>(); |
| StringView qcow; |
| if (!PathUtils::split(overlay, nullptr, &qcow)) { |
| success = false; |
| std::string err = "Failed to extract basename from " + overlay; |
| errConsumer(opaque, err.c_str(), err.size()); |
| continue; |
| } |
| auto tmp_overlay = |
| pj(android::snapshot::getAvdDir(), "tmp_" + qcow.str()); |
| auto final_overlay = pj(dest, qcow); |
| |
| // Copy and rebase on an empty image... |
| if (path_copy_file(tmp_overlay.c_str(), overlay.c_str()) != 0) { |
| success = false; |
| std::string err = std::string("Failed to copy ") + overlay + |
| " to " + tmp_overlay + " due to " + |
| std::to_string(errno); |
| |
| errConsumer(opaque, err.c_str(), err.size()); |
| } |
| // TODO(jansene): remove unused snapshots from the temporary overlay. |
| // (i.e. those with a different name/id than snapshot) See qemu-img.c |
| // static int img_snapshot(int argc, char **argv) on how to list & |
| // remove. |
| success = rebase_on_top_of(tmp_overlay, "empty.qcow2", opaque, |
| errConsumer) && |
| success; |
| |
| // And move it to the export destination.. |
| if (std::rename(tmp_overlay.c_str(), final_overlay.c_str()) != 0) { |
| std::string err = "Failed to rename " + tmp_overlay + " to " + |
| final_overlay + |
| ", errno: " + std::to_string(errno); |
| path_delete_file(tmp_overlay.c_str()); |
| errConsumer(opaque, err.c_str(), err.size()); |
| success = false; |
| } |
| } |
| |
| if (wasVmRunning && !sExiting) { |
| vm_start(); |
| } |
| |
| return success; |
| } |
| |
| static const QAndroidVmOperations sQAndroidVmOperations = { |
| .vmStop = qemu_vm_stop, |
| .vmStart = qemu_vm_start, |
| .vmReset = system_reset_request, |
| .vmShutdown = system_shutdown_request, |
| .vmPause = vm_pause, |
| .vmResume = vm_resume, |
| .vmIsRunning = qemu_vm_is_running, |
| .snapshotList = qemu_snapshot_list, |
| .snapshotSave = qemu_snapshot_save, |
| .snapshotLoad = qemu_snapshot_load, |
| .snapshotDelete = qemu_snapshot_delete, |
| .snapshotRemap = qemu_snapshot_remap, |
| .snapshotExport = qemu_snapshot_export_qcow, |
| .setSnapshotCallbacks = set_snapshot_callbacks, |
| .mapUserBackedRam = map_user_backed_ram, |
| .unmapUserBackedRam = unmap_user_backed_ram, |
| .getVmConfiguration = get_vm_config, |
| .setFailureReason = set_failure_reason, |
| .setExiting = set_exiting, |
| .allowRealAudio = allow_real_audio, |
| .physicalMemoryGetAddr = physical_memory_get_addr, |
| .isRealAudioAllowed = is_real_audio_allowed, |
| .setSkipSnapshotSave = set_skip_snapshot_save, |
| .isSnapshotSaveSkipped = is_snapshot_save_skipped, |
| }; |
| |
| const QAndroidVmOperations* const gQAndroidVmOperations = |
| &sQAndroidVmOperations; |