qemu解析qcow文件

旧的 QEMU 图像格式,支持备份文件、紧凑图像文件、加密和压缩。
backing_file
基础镜像的文件名(参见create子命令)
encryption
此选项已弃用,相当于encrypt.format=aes
encrypt.format
如果设置为aes,则图像将使用 128 位 AES-CBC 加密。加密密钥由参数给出encrypt.key-secret。现代密码学标准认为这种加密格式存在缺陷,存在许多针对qcow2图像格式的先前列举的设计问题。
系统模拟器不再支持使用此功能。仅命令行实用程序仍支持此功能,用于数据释放和与旧版 QEMU 的互操作性。
需要本机加密的用户应该使用qcow2格式代替encrypt.format=luks。
encrypt.key-secret
secret提供包含加密密钥的对象的 ID ( encrypt.format=aes)。
1、注册qcow块格式
static BlockDriver bdrv_qcow = {
    .format_name    = "qcow",
    .instance_size    = sizeof(BDRVQcowState),
    .bdrv_probe        = qcow_probe,
    .bdrv_open        = qcow_open,
    .bdrv_close        = qcow_close,
    .bdrv_child_perm        = bdrv_default_perms,
    .bdrv_reopen_prepare    = qcow_reopen_prepare,
    .bdrv_co_create         = qcow_co_create,
    .bdrv_co_create_opts    = qcow_co_create_opts,
    .bdrv_has_zero_init     = bdrv_has_zero_init_1,
    .is_format              = true,
    .supports_backing       = true,
    .bdrv_refresh_limits    = qcow_refresh_limits,

    .bdrv_co_preadv         = qcow_co_preadv,
    .bdrv_co_pwritev        = qcow_co_pwritev,
    .bdrv_co_block_status   = qcow_co_block_status,

    .bdrv_make_empty        = qcow_make_empty,
    .bdrv_co_pwritev_compressed = qcow_co_pwritev_compressed,
    .bdrv_get_info          = qcow_get_info,

    .create_opts            = &qcow_create_opts,
    .strong_runtime_opts    = qcow_strong_runtime_opts,
};

static void bdrv_qcow_init(void)
{
    bdrv_register(&bdrv_qcow);
}

block_init(bdrv_qcow_init);
2、打开qcow文件
static int qcow_open(BlockDriverState *bs, QDict *options, int flags, Error **errp)
{
    BDRVQcowState *s = bs->opaque;
    unsigned int len, i, shift;
    int ret;
    QCowHeader header;
    QCryptoBlockOpenOptions *crypto_opts = NULL;
    unsigned int cflags = 0;
    QDict *encryptopts = NULL;
    const char *encryptfmt;
    qdict_extract_subqdict(options, &encryptopts, "encrypt.");
    encryptfmt = qdict_get_try_str(encryptopts, "format");

    // 打开qcow文件
    bs->file = bdrv_open_child(NULL, options, "file", bs, &child_of_bds, BDRV_CHILD_IMAGE, false, errp);
    if (!bs->file) {
        ret = -EINVAL;
        goto fail;
    }

    // 读取并解析qcow头部
    ret = bdrv_pread(bs->file, 0, &header, sizeof(header));
    if (ret < 0) {
        goto fail;
    }
    header.magic = be32_to_cpu(header.magic);
    header.version = be32_to_cpu(header.version);
    header.backing_file_offset = be64_to_cpu(header.backing_file_offset);
    header.backing_file_size = be32_to_cpu(header.backing_file_size);
    header.mtime = be32_to_cpu(header.mtime);
    header.size = be64_to_cpu(header.size);
    header.crypt_method = be32_to_cpu(header.crypt_method);
    header.l1_table_offset = be64_to_cpu(header.l1_table_offset);

    // 验证有效性
    if (header.magic != QCOW_MAGIC) {
        error_setg(errp, "Image not in qcow format");
        ret = -EINVAL;
        goto fail;
    }
    if (header.version != QCOW_VERSION) {
        error_setg(errp, "qcow (v%d) does not support qcow version %" PRIu32, QCOW_VERSION, header.version);
        if (header.version == 2 || header.version == 3) {
            error_append_hint(errp, "Try the 'qcow2' driver instead.\n");
        }
        ret = -ENOTSUP;
        goto fail;
    }

    if (header.size <= 1) {
        error_setg(errp, "Image size is too small (must be at least 2 bytes)");
        ret = -EINVAL;
        goto fail;
    }

    if (header.cluster_bits < 9 || header.cluster_bits > 16) {
        error_setg(errp, "Cluster size must be between 512 and 64k");
        ret = -EINVAL;
        goto fail;
    }

    /* l2_bits specifies number of entries; storing a uint64_t in each entry,
     * so bytes = num_entries << 3. */
    if (header.l2_bits < 9 - 3 || header.l2_bits > 16 - 3) {
        error_setg(errp, "L2 table size must be between 512 and 64k");
        ret = -EINVAL;
        goto fail;
    }

    s->crypt_method_header = header.crypt_method;
    if (s->crypt_method_header) {
        if (bdrv_uses_whitelist() &&
            s->crypt_method_header == QCOW_CRYPT_AES) {
            error_setg(errp,"Use of AES-CBC encrypted qcow images is no longer supported in system emulators");
            error_append_hint(errp,You can use 'qemu-img convert' to convert your image to an alternative supported format, such "
                              "as unencrypted qcow, or raw with the LUKS format instead.\n");
            ret = -ENOSYS;
            goto fail;
        }
        if (s->crypt_method_header == QCOW_CRYPT_AES) {
            if (encryptfmt && !g_str_equal(encryptfmt, "aes")) {
                error_setg(errp, "Header reported 'aes' encryption format but options specify '%s'", encryptfmt);
                ret = -EINVAL;
                goto fail;
            }
            qdict_put_str(encryptopts, "format", "qcow");
            crypto_opts = block_crypto_open_opts_init(encryptopts, errp);
            if (!crypto_opts) {
                ret = -EINVAL;
                goto fail;
            }

            if (flags & BDRV_O_NO_IO) {
                cflags |= QCRYPTO_BLOCK_OPEN_NO_IO;
            }
            s->crypto = qcrypto_block_open(crypto_opts, "encrypt.", NULL, NULL, cflags, 1, errp);
            if (!s->crypto) {
                ret = -EINVAL;
                goto fail;
            }
        } else {
            error_setg(errp, "invalid encryption method in qcow header");
            ret = -EINVAL;
            goto fail;
        }
        bs->encrypted = true;
    } else {
        if (encryptfmt) {
            error_setg(errp, "No encryption in image header, but options " "specified format '%s'", encryptfmt);
            ret = -EINVAL;
            goto fail;
        }
    }
    s->cluster_bits = header.cluster_bits;
    s->cluster_size = 1 << s->cluster_bits;
    s->l2_bits = header.l2_bits;
    s->l2_size = 1 << s->l2_bits;
    bs->total_sectors = header.size / 512;
    s->cluster_offset_mask = (1LL << (63 - s->cluster_bits)) - 1;

    /* read the level 1 table */
    shift = s->cluster_bits + s->l2_bits;
    if (header.size > UINT64_MAX - (1LL << shift)) {
        error_setg(errp, "Image too large");
        ret = -EINVAL;
        goto fail;
    } else {
        uint64_t l1_size = (header.size + (1LL << shift) - 1) >> shift;
        if (l1_size > INT_MAX / sizeof(uint64_t)) {
            error_setg(errp, "Image too large");
            ret = -EINVAL;
            goto fail;
        }
        s->l1_size = l1_size;
    }

    s->l1_table_offset = header.l1_table_offset;
    s->l1_table = g_try_new(uint64_t, s->l1_size);
    if (s->l1_table == NULL) {
        error_setg(errp, "Could not allocate memory for L1 table");
        ret = -ENOMEM;
        goto fail;
    }

    // 读取并解析1级缓存
    ret = bdrv_pread(bs->file, s->l1_table_offset, s->l1_table, s->l1_size * sizeof(uint64_t));
    if (ret < 0) {
        goto fail;
    }

    for(i = 0;i < s->l1_size; i++) {
        s->l1_table[i] = be64_to_cpu(s->l1_table[i]);
    }

    // 2级缓存
    /* alloc L2 cache (max. 64k * 16 * 8 = 8 MB) */
    s->l2_cache = qemu_try_blockalign(bs->file->bs, s->l2_size * L2_CACHE_SIZE * sizeof(uint64_t));
    if (s->l2_cache == NULL) {
        error_setg(errp, "Could not allocate L2 table cache");
        ret = -ENOMEM;
        goto fail;
    }
    s->cluster_cache = g_malloc(s->cluster_size);
    s->cluster_data = g_malloc(s->cluster_size);
    s->cluster_cache_offset = -1;

    /* read the backing file name */
    if (header.backing_file_offset != 0) {
        len = header.backing_file_size;
        if (len > 1023 || len >= sizeof(bs->backing_file)) {
            error_setg(errp, "Backing file name too long");
            ret = -EINVAL;
            goto fail;
        }
        ret = bdrv_pread(bs->file, header.backing_file_offset, bs->auto_backing_file, len);
        if (ret < 0) {
            goto fail;
        }
        bs->auto_backing_file[len] = '\0';
        pstrcpy(bs->backing_file, sizeof(bs->backing_file), bs->auto_backing_file);
    }

    // 使用 qcow 镜像时禁用迁移
    error_setg(&s->migration_blocker, "The qcow format used by node '%s' does not support live migration", bdrv_get_device_or_node_name(bs));
    ret = migrate_add_blocker(s->migration_blocker, errp);
    if (ret < 0) {
        error_free(s->migration_blocker);
        goto fail;
    }

    qobject_unref(encryptopts);
    qapi_free_QCryptoBlockOpenOptions(crypto_opts);
    qemu_co_mutex_init(&s->lock);
    return 0;
fail:
    g_free(s->l1_table);
    qemu_vfree(s->l2_cache);
    g_free(s->cluster_cache);
    g_free(s->cluster_data);
    qcrypto_block_free(s->crypto);
    qobject_unref(encryptopts);
    qapi_free_QCryptoBlockOpenOptions(crypto_opts);
    return ret;
}
3、读取qcow文件
static coroutine_fn int qcow_co_preadv(BlockDriverState *bs, uint64_t offset,
                                       uint64_t bytes, QEMUIOVector *qiov,
                                       int flags)
{
    BDRVQcowState *s = bs->opaque;
    int offset_in_cluster;
    int ret = 0, n;
    uint64_t cluster_offset;
    uint8_t *buf;
    void *orig_buf;

    assert(!flags);
    if (qiov->niov > 1) {
        buf = orig_buf = qemu_try_blockalign(bs, qiov->size);
        if (buf == NULL) {
            return -ENOMEM;
        }
    } else {
        orig_buf = NULL;
        buf = (uint8_t *)qiov->iov->iov_base;
    }

    qemu_co_mutex_lock(&s->lock);
    while (bytes != 0) {
        /* prepare next request */
        ret = get_cluster_offset(bs, offset, 0, 0, 0, 0, &cluster_offset);
        if (ret < 0) {
            break;
        }

        offset_in_cluster = offset & (s->cluster_size - 1);
        n = s->cluster_size - offset_in_cluster;
        if (n > bytes) {
            n = bytes;
        }

        if (!cluster_offset) {
            if (bs->backing) {
                /* read from the base image */
                qemu_co_mutex_unlock(&s->lock);
                /* qcow2 emits this on bs->file instead of bs->backing */
                BLKDBG_EVENT(bs->file, BLKDBG_READ_BACKING_AIO);
                ret = bdrv_co_pread(bs->backing, offset, n, buf, 0);
                qemu_co_mutex_lock(&s->lock);
                if (ret < 0) {
                    break;
                }
            } else {
                /* Note: in this case, no need to wait */
                memset(buf, 0, n);
            }
        } else if (cluster_offset & QCOW_OFLAG_COMPRESSED) {
            /* add AIO support for compressed blocks ? */
            if (decompress_cluster(bs, cluster_offset) < 0) {
                ret = -EIO;
                break;
            }
            memcpy(buf, s->cluster_cache + offset_in_cluster, n);
        } else {
            if ((cluster_offset & 511) != 0) {
                ret = -EIO;
                break;
            }
            qemu_co_mutex_unlock(&s->lock);
            BLKDBG_EVENT(bs->file, BLKDBG_READ_AIO);
            ret = bdrv_co_pread(bs->file, cluster_offset + offset_in_cluster, n, buf, 0);
            qemu_co_mutex_lock(&s->lock);
            if (ret < 0) {
                break;
            }
            if (bs->encrypted) {
                assert(s->crypto);
                if (qcrypto_block_decrypt(s->crypto,
                                          offset, buf, n, NULL) < 0) {
                    ret = -EIO;
                    break;
                }
            }
        }
        ret = 0;
        bytes -= n;
        offset += n;
        buf += n;
    }

    qemu_co_mutex_unlock(&s->lock);
    if (qiov->niov > 1) {
        qemu_iovec_from_buf(qiov, 0, orig_buf, qiov->size);
        qemu_vfree(orig_buf);
    }
    return ret;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值