旧的 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;
}