| // 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 "android-qemu2-glue/drive-share.h" |
| |
| #include <cstdint> |
| #include <cstdio> |
| #include <cstring> |
| #include <memory> |
| #include <string> |
| #include <unordered_map> |
| #include <vector> |
| |
| #include "android/base/files/FileShareOpen.h" |
| #include "android/base/files/PathUtils.h" |
| #include "android/base/memory/LazyInstance.h" |
| #include "android/globals.h" |
| #include "android/multi-instance.h" |
| #include "android/utils/bufprint.h" |
| #include "android/utils/eintr_wrapper.h" |
| #include "android/utils/path.h" |
| #include "android/utils/tempfile.h" |
| #include "android/utils/file_io.h" |
| |
| extern "C" { |
| #include "block/block.h" |
| #include "qapi/error.h" |
| #include "qapi/qmp/qdict.h" |
| #include "qapi/qmp/qstring.h" |
| #include "qemu/config-file.h" |
| #include "qemu/cutils.h" |
| #include "qemu/error-report.h" |
| #include "qemu/option.h" |
| #include "qemu/option_int.h" |
| #include "qemu/osdep.h" |
| #include "sysemu/block-backend.h" |
| #include "sysemu/blockdev.h" |
| #include "sysemu/sysemu.h" |
| } |
| |
| #define DEBUG 0 |
| |
| #if DEBUG |
| #define D(...) printf(__VA_ARGS__) |
| #else |
| #define D(...) ((void)0) |
| #endif |
| |
| namespace { |
| struct DriveShare { |
| std::vector<DriveInfo*> driveInfoList = {}; |
| std::vector<QemuOpts*> qemuOptsList = {}; |
| std::unordered_map<std::string, std::string> srcImagePaths = {}; |
| BlockInterfaceType blockDefaultType; |
| }; |
| static android::base::LazyInstance<DriveShare> sDriveShare = LAZY_INSTANCE_INIT; |
| struct DriveInitParam { |
| BlockInterfaceType blockDefaultType; |
| const char* snapshotName; |
| android::base::FileShare shareMode; |
| bool baseNeedApplySnapshot; |
| }; |
| } // namespace |
| |
| #define QCOW2_SUFFIX "qcow2" |
| |
| typedef struct { |
| const char* drive; |
| const char* backing_image_path; |
| } DriveBackingPair; |
| |
| static bool read_file_to_buf(const char* file, uint8_t* buf, size_t size) { |
| int fd = HANDLE_EINTR(open(file, O_RDONLY | O_BINARY)); |
| if (fd < 0) { |
| return false; |
| } |
| ssize_t ret = HANDLE_EINTR(read(fd, buf, size)); |
| IGNORE_EINTR(close(fd)); |
| if (ret != size) { |
| return false; |
| } |
| return true; |
| } |
| |
| static bool parseQemuOptForQcow2(bool wipeData) { |
| /* First, determine if any of the backing images have been altered. |
| * QCoW2 images won't work in that case, and need to be recreated (this |
| * will obliterate previous snapshots). |
| * The most reliable way to do this is to cache some sort of checksum of |
| * the image files, but we go the easier (and faster) route and cache the |
| * version number that is specified in build.prop. |
| */ |
| const char* avd_data_dir = avdInfo_getContentPath(android_avdInfo); |
| static const char sysimg_version_number_cache_basename[] = |
| "version_num.cache"; |
| char* sysimg_version_number_cache_path = |
| path_join(avd_data_dir, sysimg_version_number_cache_basename); |
| bool reset_version_number_cache = false; |
| if (!path_exists(sysimg_version_number_cache_path)) { |
| /* File with previously saved version number doesn't exist, |
| * we'll create it later. |
| */ |
| reset_version_number_cache = true; |
| } else { |
| FILE* vn_cache_file = android_fopen(sysimg_version_number_cache_path, "r"); |
| int sysimg_version_number = -1; |
| /* If the file with version number contained an error, or the |
| * saved version number doesn't match the current one, we'll |
| * update it later. |
| */ |
| reset_version_number_cache = |
| vn_cache_file == NULL || |
| fscanf(vn_cache_file, "%d", &sysimg_version_number) != 1 || |
| sysimg_version_number != |
| avdInfo_getSysImgIncrementalVersion(android_avdInfo); |
| if (vn_cache_file) { |
| fclose(vn_cache_file); |
| } |
| } |
| |
| /* List of paths to all images that can be mounted.*/ |
| const DriveBackingPair image_paths[] = { |
| {"system", android_hw->disk_systemPartition_path && |
| android_hw->disk_systemPartition_path[0] |
| ? android_hw->disk_systemPartition_path |
| : android_hw->disk_systemPartition_initPath}, |
| {"vendor", android_hw->disk_vendorPartition_path && |
| android_hw->disk_vendorPartition_path[0] |
| ? android_hw->disk_vendorPartition_path |
| : android_hw->disk_vendorPartition_initPath}, |
| {"cache", android_hw->disk_cachePartition_path}, |
| {"userdata", android_hw->disk_dataPartition_path}, |
| {"sdcard", android_hw->hw_sdCard_path}, |
| {"encrypt", android_hw->disk_encryptionKeyPartition_path}, |
| }; |
| /* List of paths to all images for cros.*/ |
| const DriveBackingPair image_paths_hw_arc[] = { |
| {"system", android_hw->disk_systemPartition_initPath}, |
| {"vendor", android_hw->disk_vendorPartition_initPath}, |
| {"userdata", android_hw->disk_dataPartition_path}, |
| }; |
| int count = sizeof(image_paths) / sizeof(image_paths[0]); |
| const DriveBackingPair* images = image_paths; |
| if (android_hw->hw_arc) { |
| count = sizeof(image_paths_hw_arc) / sizeof(image_paths_hw_arc[0]); |
| images = image_paths_hw_arc; |
| } |
| int p; |
| QemuOptsList* optList = qemu_find_opts("drive"); |
| for (p = 0; p < count; p++) { |
| QemuOpts* opts = qemu_opts_find(optList, images[p].drive); |
| const char* qcow2_image_path = nullptr; |
| if (opts) { |
| qcow2_image_path = qemu_opt_get(opts, "file"); |
| sDriveShare->srcImagePaths.emplace(images[p].drive, |
| qcow2_image_path); |
| } |
| const char* backing_image_path = images[p].backing_image_path; |
| if (!backing_image_path || *backing_image_path == '\0') { |
| /* If the path is NULL or empty, just ignore it.*/ |
| continue; |
| } |
| char* image_basename = path_basename(backing_image_path); |
| char* qcow2_path_buffer = nullptr; |
| bool need_create_tmp = false; |
| // ChromeOS and Android pass parameters differently |
| if (android_hw->hw_arc) { |
| if (p < 2) { |
| /* System & vendor image are special cases, the backing image is |
| * in the SDK folder, but the QCoW2 image that the emulator |
| * uses is created on a per-AVD basis and is placed in the |
| * AVD's data folder. |
| */ |
| const char qcow2_suffix[] = "." QCOW2_SUFFIX; |
| size_t path_size = |
| strlen(image_basename) + sizeof(qcow2_suffix) + 1; |
| char* image_qcow2_basename = (char*)malloc(path_size); |
| bufprint(image_qcow2_basename, image_qcow2_basename + path_size, |
| "%s%s", image_basename, qcow2_suffix); |
| qcow2_path_buffer = |
| path_join(avd_data_dir, image_qcow2_basename); |
| free(image_qcow2_basename); |
| } else { |
| /* For all the other images except system image, |
| * just create another file alongside them |
| * with a 'qcow2' extension |
| */ |
| const char qcow2_suffix[] = "." QCOW2_SUFFIX; |
| size_t path_size = |
| strlen(backing_image_path) + sizeof(qcow2_suffix) + 1; |
| qcow2_path_buffer = (char*)malloc(path_size); |
| bufprint(qcow2_path_buffer, qcow2_path_buffer + path_size, |
| "%s%s", backing_image_path, qcow2_suffix); |
| } |
| qcow2_image_path = qcow2_path_buffer; |
| sDriveShare->srcImagePaths[images[p].drive] = qcow2_image_path; |
| } else { |
| const char qcow2_suffix[] = "." QCOW2_SUFFIX; |
| if (!qcow2_image_path || |
| strcmp(qcow2_suffix, qcow2_image_path + |
| strlen(qcow2_image_path) - |
| strlen(qcow2_suffix))) { |
| // We are not using qcow2 |
| continue; |
| } |
| if (!android::base::PathUtils::isAbsolute(qcow2_image_path)) { |
| qcow2_path_buffer = path_join(avd_data_dir, qcow2_image_path); |
| // bug: 130353176 |
| // bug: 130724870 |
| if (path_exists(qcow2_path_buffer)) { |
| sDriveShare->srcImagePaths[images[p].drive] = qcow2_path_buffer; |
| } else { |
| sDriveShare->srcImagePaths[images[p].drive] = qcow2_image_path; |
| } |
| } |
| } |
| |
| Error* img_creation_error = NULL; |
| if (!path_exists(qcow2_image_path) || wipeData || |
| reset_version_number_cache) { |
| const char* fmt = "raw"; |
| uint8_t buf[BLOCK_PROBE_BUF_SIZE]; |
| BlockDriver* drv; |
| drv = bdrv_find_format("qcow2"); |
| if (drv && read_file_to_buf(backing_image_path, buf, sizeof(buf)) && |
| drv->bdrv_probe(buf, sizeof(buf), backing_image_path) >= 100) { |
| fmt = "qcow2"; |
| } |
| bdrv_img_create(qcow2_image_path, QCOW2_SUFFIX, |
| /*absolute path only for sys vendor*/ |
| p < 2 ? backing_image_path : image_basename, fmt, |
| NULL, -1, 0, true, &img_creation_error); |
| } |
| free(image_basename); |
| free(qcow2_path_buffer); |
| if (img_creation_error) { |
| error_report("%s", error_get_pretty(img_creation_error)); |
| error_free(img_creation_error); |
| return false; |
| } |
| } |
| |
| /* Update version number cache if necessary. */ |
| if (reset_version_number_cache) { |
| FILE* vn_cache_file = android_fopen(sysimg_version_number_cache_path, "w"); |
| if (vn_cache_file) { |
| fprintf(vn_cache_file, "%d\n", |
| avdInfo_getSysImgIncrementalVersion(android_avdInfo)); |
| fclose(vn_cache_file); |
| } |
| } |
| free(sysimg_version_number_cache_path); |
| |
| return true; |
| } |
| |
| static bool needRemount(QemuOpts* opts) { |
| QemuOpt* opt = qemu_opt_find(opts, "read-only"); |
| return !opt || !(!strcmp("on", opt->str) || |
| (opt->desc && opt->desc->type == QEMU_OPT_BOOL && |
| opt->value.boolean)); |
| } |
| |
| static bool needCreateTmp(const char* id, |
| android::base::FileShare shareMode, |
| QemuOpts* opts) { |
| return shareMode == android::base::FileShare::Read && needRemount(opts) && |
| android::base::PathUtils::extension( |
| sDriveShare->srcImagePaths[id]) == ".qcow2"; |
| } |
| |
| static bool createEmptySnapshot(BlockDriverState* bs, |
| const char* snapshotName) { |
| QEMUSnapshotInfo sn; |
| memset(&sn, 0, sizeof(sn)); |
| pstrcpy(sn.name, sizeof(sn.name), snapshotName); |
| |
| qemu_timeval tv; |
| qemu_gettimeofday(&tv); |
| sn.date_sec = tv.tv_sec; |
| sn.date_nsec = tv.tv_usec * 1000; |
| |
| // bdrv_snapshot_create returns 0 on success |
| return 0 == bdrv_snapshot_create(bs, &sn); |
| } |
| |
| static std::string initDrivePath(const char* id, |
| android::base::FileShare shareMode, |
| QemuOpts* opts, |
| bool skipInitQCow2) { |
| assert(sDriveShare->srcImagePaths.count(id)); |
| if (needCreateTmp(id, shareMode, opts)) { |
| // Create a temp qcow2-on-qcow2 |
| Error* img_creation_error = NULL; |
| TempFile* img = tempfile_create_with_ext(".qcow2"); |
| const char* imgPath = tempfile_path(img); |
| if (skipInitQCow2) { |
| return imgPath; |
| } |
| bdrv_img_create(imgPath, QCOW2_SUFFIX, |
| sDriveShare->srcImagePaths[id].c_str(), "qcow2", |
| nullptr, -1, 0, true, &img_creation_error); |
| if (img_creation_error) { |
| tempfile_close(img); |
| error_report("%s", error_get_pretty(img_creation_error)); |
| error_free(img_creation_error); |
| return ""; |
| } |
| return imgPath; |
| } else { |
| return sDriveShare->srcImagePaths[id]; |
| } |
| } |
| |
| static void mirrorTmpCache(const char* dst, const char* src) { |
| // Cache image doesn't work well with bdrv_snapshot_create. |
| // It complaints when loading a snapshot. |
| // Thus we directly copy the qcow2 file. |
| // TODO (yahan@): figure out why |
| path_copy_file(dst, src); |
| Error *local_err = NULL; |
| |
| BlockDriverState* bs = bdrv_open( |
| dst, nullptr, nullptr, BDRV_O_RDWR | BDRV_O_NO_BACKING, &local_err); |
| if (!bs) { |
| error_report("Could not open '%s': ", dst); |
| error_free(local_err); |
| return; |
| } |
| char* absPath = nullptr; |
| char* backingFile = android_hw->disk_cachePartition_path; |
| if (!android::base::PathUtils::isAbsolute(backingFile)) { |
| absPath = |
| path_join(avdInfo_getContentPath(android_avdInfo), backingFile); |
| // bug: 130724870 |
| if (path_exists(absPath)) { |
| backingFile = absPath; |
| } |
| } |
| D("backing cache.img path: %s\n", backingFile); |
| int res = bdrv_change_backing_file(bs, backingFile, NULL); |
| D("cache changing backing file result: %d\n", res); |
| bdrv_unref(bs); |
| free(absPath); |
| } |
| |
| // This is for C-style function pointer |
| extern "C" { |
| static int drive_init(void* opaque, QemuOpts* opts, Error** errp) { |
| DriveInitParam* param = (DriveInitParam*)opaque; |
| const char* id = opts->id; |
| if (id) { |
| std::string path = initDrivePath(id, param->shareMode, opts, false); |
| qemu_opt_set(opts, "file", path.c_str(), errp); |
| if (needCreateTmp(id, param->shareMode, opts) && param->snapshotName) { |
| if (strcmp(id, "cache")) { |
| int res = 0; |
| if (param->baseNeedApplySnapshot) { |
| // handle the snapshot |
| BlockBackend* blk = |
| blk_new_open(sDriveShare->srcImagePaths[id].c_str(), NULL, |
| qdict_new(), BDRV_O_RDWR, nullptr); |
| assert(blk); |
| BlockDriverState* bs = blk_bs(blk); |
| assert(bs); |
| // bdrv_snapshot_goto can fail during first boot or wipe data |
| res = bdrv_snapshot_goto(bs, param->snapshotName, nullptr); |
| blk_flush(blk); |
| blk_unref(blk); |
| } |
| if (res == 0) { |
| // Create an empty snapshot in the qcow2-on-qcow2 |
| BlockBackend* blkNew = blk_new_open( |
| path.c_str(), NULL, qdict_new(), BDRV_O_RDWR, nullptr); |
| assert(blkNew); |
| createEmptySnapshot(blk_bs(blkNew), param->snapshotName); |
| blk_unref(blkNew); |
| } |
| } else { |
| mirrorTmpCache(path.c_str(), |
| sDriveShare->srcImagePaths[id].c_str()); |
| } |
| } |
| } |
| DriveInfo* driveInfo = drive_new(opts, param->blockDefaultType); |
| return nullptr == driveInfo; |
| } |
| |
| static int drive_reinit(void* opaque, QemuOpts* opts, Error** errp) { |
| const char* id = opts->id; |
| D("Re-init drive %s\n", id); |
| if (!needRemount(opts)) { |
| D("%s dont need remount\n", id); |
| return 0; |
| } |
| DriveInitParam* param = (DriveInitParam*)opaque; |
| const char* snapshotName = param->snapshotName; |
| BlockBackend* blk = blk_by_name(id); |
| if (!blk) { |
| error_setg(errp, "%s not found", id); |
| return 1; |
| } |
| |
| BlockDriverState* oldbs = blk_bs(blk); |
| AioContext* aioCtx = bdrv_get_aio_context(oldbs); |
| aio_context_acquire(aioCtx); |
| bool isCache = !strcmp(id, "cache"); |
| if (needCreateTmp(id, param->shareMode, opts) && !isCache) { |
| bdrv_flush(oldbs); |
| // Set the base file to use the snapshot |
| int res = bdrv_snapshot_goto(oldbs, snapshotName, errp); |
| if (res) { |
| error_setg(errp, "bdrv_snapshot_goto failed %s <- %s", id, |
| snapshotName); |
| aio_context_release(aioCtx); |
| return 1; |
| } |
| res = bdrv_commit(oldbs); |
| } |
| blk_flush(blk); |
| blk_remove_bs(blk); |
| aio_context_release(aioCtx); |
| if (param->shareMode == android::base::FileShare::Write) { |
| // Updating from read to write, delete temp files from the previous |
| // read |
| const char* oldPath = qemu_opt_get(opts, "file"); |
| D("Closing old image %s\n", oldPath); |
| tempfile_unref_and_close(oldPath); |
| } |
| // Don't write file contents if it is for cache |
| std::string path = initDrivePath(id, param->shareMode, opts, isCache); |
| if (needCreateTmp(id, param->shareMode, opts) && isCache) { |
| mirrorTmpCache(path.c_str(), |
| sDriveShare->srcImagePaths[id].c_str()); |
| } |
| |
| // Mount the drive |
| qemu_opt_set(opts, "file", path.c_str(), errp); |
| |
| // Setup qemu opts for the drive. |
| // For opts, please refer to drive_new() and blockdev_init() for what needs |
| // to be set. |
| // (drive_new also sets dinfo and error handlings. We will not touch dinfo |
| // and we don't set error handlings. We also skip the renaming in drive_new |
| // because it should have been renamed in the first time of initialization.) |
| QDict* bs_opts = qdict_new(); |
| qemu_opts_to_qdict(opts, bs_opts); |
| QemuOpts* legacy_opts = |
| qemu_opts_create(&qemu_legacy_drive_opts, NULL, 0, nullptr); |
| qemu_opts_absorb_qdict(legacy_opts, bs_opts, errp); |
| QemuOpts* drive_opts = qemu_opts_find(&qemu_common_drive_opts, id); |
| if (drive_opts) { |
| qemu_opts_del(drive_opts); |
| drive_opts = nullptr; |
| } |
| drive_opts = qemu_opts_create(&qemu_common_drive_opts, id, 1, nullptr); |
| qemu_opts_absorb_qdict(drive_opts, bs_opts, errp); |
| |
| qdict_set_default_str(bs_opts, BDRV_OPT_CACHE_DIRECT, "off"); |
| qdict_set_default_str(bs_opts, BDRV_OPT_CACHE_NO_FLUSH, "off"); |
| qdict_set_default_str(bs_opts, BDRV_OPT_READ_ONLY, "off"); |
| qdict_del(bs_opts, "id"); |
| Error* local_err = NULL; |
| |
| BlockDriverState* bs = |
| bdrv_open(path.c_str(), nullptr, bs_opts, 0, &local_err); |
| if (!bs) { |
| D("drive %s open failure: %s", path.c_str(), |
| error_get_pretty(local_err)); |
| error_report("%s", error_get_pretty(local_err)); |
| error_free(local_err); |
| return 1; |
| } |
| |
| if (needCreateTmp(id, param->shareMode, opts) && !isCache) { |
| // fake an empty snapshot |
| // We don't worry if it fails. |
| createEmptySnapshot(bs, param->snapshotName); |
| } |
| |
| int res = blk_insert_bs(blk, bs, errp); |
| bdrv_unref(bs); |
| D("Re-init drive %s %s\n", id, res == 0 ? "Success" : "Failed"); |
| return res; |
| } |
| } |
| |
| static std::string getReadSnapshotFileName() { |
| const char* kReadSnapshotFileName = "read-snapshot.txt"; |
| char* avdPath = path_getAvdContentPath(android_hw->avd_name); |
| if (avdPath) { |
| std::string baseSnapshotFileName(avdPath); |
| free(avdPath); |
| return baseSnapshotFileName + PATH_SEP + kReadSnapshotFileName; |
| } else { |
| return kReadSnapshotFileName; |
| } |
| } |
| |
| static bool isBaseOnDifferentSnapshot(const char* snapshot_name) { |
| std::string baseSnapshotFileName = getReadSnapshotFileName(); |
| FILE* baseSnapshotNameFile = fsopenWithTimeout(baseSnapshotFileName.c_str(), |
| "r", android::base::FileShare::Read, 5000); |
| if (!baseSnapshotNameFile) { |
| return true; |
| } |
| // We need to compare if snapshot_name is the same as the name in |
| // baseSnapshotNameFile. |
| // To do so, we don't need to read the full name in baseSnapshotFileName. |
| // We only need to read the portion that is slightly larger than |
| // snapshot_name. (If it is larger then they are not the same.) |
| const int bufferSize = strlen(snapshot_name) + 2; |
| std::unique_ptr<char[]> buffer(new char[bufferSize]); |
| fgets(buffer.get(), bufferSize, baseSnapshotNameFile); |
| fclose(baseSnapshotNameFile); |
| return strcmp(snapshot_name, buffer.get()); |
| } |
| |
| static bool updateDriveShareMode(const char* snapshotName, |
| android::base::FileShare shareMode) { |
| D("Update drive share mode %d\n", shareMode); |
| DriveInitParam param = {sDriveShare->blockDefaultType, snapshotName, |
| shareMode}; |
| Error* error = NULL; |
| int res = qemu_opts_foreach(qemu_find_opts("drive"), drive_reinit, ¶m, |
| &error); |
| if (res) { |
| error_report("%s", error_get_pretty(error)); |
| error_free(error); |
| return false; |
| } |
| return true; |
| } |
| |
| extern "C" int android_drive_share_init(bool wipe_data, |
| bool read_only, |
| const char* snapshot_name, |
| BlockInterfaceType blockDefaultType) { |
| if (!parseQemuOptForQcow2(wipe_data)) { |
| return -1; |
| } |
| |
| android::multiinstance::setUpdateDriveShareModeFunc(updateDriveShareMode); |
| bool needApplySnapshot = read_only && isBaseOnDifferentSnapshot(snapshot_name); |
| DriveInitParam param = {blockDefaultType, snapshot_name, |
| read_only ? android::base::FileShare::Read |
| : android::base::FileShare::Write, |
| needApplySnapshot}; |
| FILE* baseSnapshotNameFile = nullptr; |
| if (needApplySnapshot || !read_only) { |
| // For read-only we open a file to record the snapshot name |
| // For writable we wipe that snapshot name because it might change later |
| std::string baseSnapshotFileName = getReadSnapshotFileName(); |
| baseSnapshotNameFile = fsopenWithTimeout(baseSnapshotFileName.c_str(), |
| "w", android::base::FileShare::Write, 5000); |
| } |
| sDriveShare->blockDefaultType = blockDefaultType; |
| if (qemu_opts_foreach(qemu_find_opts("drive"), drive_init, ¶m, NULL)) { |
| if (baseSnapshotNameFile) { |
| fclose(baseSnapshotNameFile); |
| } |
| return -1; |
| } |
| if (baseSnapshotNameFile) { |
| if (read_only && snapshot_name) { |
| fprintf(baseSnapshotNameFile, "%s", snapshot_name); |
| } |
| fclose(baseSnapshotNameFile); |
| } |
| return 0; |
| } |