335 lines
11 KiB
C
335 lines
11 KiB
C
/*
|
|
SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
UMKa - User-Mode KolibriOS developer tools
|
|
vdisk - virtual disk, qcow2 format
|
|
|
|
Copyright (C) 2023 Ivan Baravy <dunkaist@gmail.com>
|
|
*/
|
|
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include "../trace.h"
|
|
#include "qcow2.h"
|
|
#include "umkaio.h"
|
|
#include "em_inflate/em_inflate.h"
|
|
|
|
#define L1_MAX_LEN (32u*1024u*1024u)
|
|
#define L1_MAX_ENTRIES (L1_MAX_LEN / sizeof(uint64_t))
|
|
|
|
struct vdisk_qcow2 {
|
|
struct vdisk vdisk;
|
|
int fd;
|
|
size_t cluster_bits;
|
|
size_t cluster_size;
|
|
uint8_t *cluster;
|
|
uint8_t *cmp_cluster;
|
|
uint64_t l1_table_offset;
|
|
uint64_t l2_entry_cmp_x;
|
|
uint64_t l2_entry_cmp_offset_mask;
|
|
uint64_t l2_entry_cmp_sect_cnt_mask;
|
|
size_t header_length;
|
|
size_t refcount_order;
|
|
size_t refcount_table_clusters;
|
|
off_t refcount_table_offset;
|
|
size_t l1_size;
|
|
uint64_t sector_idx_mask;
|
|
uint64_t *l1;
|
|
uint64_t prev_cluster_index;
|
|
};
|
|
|
|
#define QCOW2_MAGIC "QFI\xfb"
|
|
|
|
struct qcow2_header {
|
|
char magic[4];
|
|
uint32_t version;
|
|
uint64_t back_file_offset;
|
|
uint32_t back_file_size;
|
|
uint32_t cluster_bits;
|
|
uint64_t size;
|
|
uint32_t crypt_method;
|
|
uint32_t l1_size;
|
|
uint64_t l1_table_offset;
|
|
uint64_t refcount_table_offset;
|
|
uint32_t refcount_table_clusters;
|
|
uint32_t nb_snapshots;
|
|
uint64_t snapshots_offset;
|
|
uint64_t incompatible_features;
|
|
uint64_t compatible_features;
|
|
uint64_t autoclear_features;
|
|
uint32_t refcount_order;
|
|
uint32_t header_length;
|
|
};
|
|
|
|
#define CLUSTER_FORMAT_STANDARD 0
|
|
#define CLUSTER_FORMAT_COMPRESSED 1
|
|
|
|
#define L1_ENTRY_OFFSET_MASK 0x00ffffffffffff00ULL
|
|
#define L1_ENTRY_STATUS_MASK 0x8000000000000000ULL
|
|
|
|
#define L2_ENTRY_STD_ZEROED 0x1ULL
|
|
#define L2_ENTRY_STD_OFFSET 0x00ffffffffffff00ULL
|
|
#define L2_ENTRY_FORMAT 0x4000000000000000ULL
|
|
#define L2_ENTRY_STATUS 0x8000000000000000ULL
|
|
|
|
static inline uint32_t
|
|
be32(void *p) {
|
|
uint8_t *x = p;
|
|
return ((uint32_t)x[3] << 0) + ((uint64_t)x[2] << 8)
|
|
+ ((uint64_t)x[1] << 16) + ((uint64_t)x[0] << 24);
|
|
}
|
|
|
|
static inline uint64_t
|
|
be64(void *p) {
|
|
uint8_t *x = p;
|
|
return ((uint64_t)x[7] << 0) + ((uint64_t)x[6] << 8)
|
|
+ ((uint64_t)x[5] << 16) + ((uint64_t)x[4] << 24)
|
|
+ ((uint64_t)x[3] << 32) + ((uint64_t)x[2] << 40)
|
|
+ ((uint64_t)x[1] << 48) + ((uint64_t)x[0] << 56);
|
|
}
|
|
|
|
static void
|
|
qcow2_read_guest_sector(struct vdisk_qcow2 *d, uint64_t sector, uint8_t *buf) {
|
|
uint64_t cluster_offset;
|
|
size_t l2_entries = d->cluster_size / sizeof(uint64_t);
|
|
uint64_t offset = sector * d->vdisk.sect_size;
|
|
uint64_t cluster_index = offset / d->cluster_size;
|
|
uint64_t l1_index = (cluster_index) / l2_entries;
|
|
uint64_t l2_index = (cluster_index) % l2_entries;
|
|
uint64_t l1_entry = d->l1[l1_index];
|
|
uint64_t l2_entry;
|
|
|
|
if (cluster_index == d->prev_cluster_index) {
|
|
memcpy(buf,
|
|
d->cluster + (sector & d->sector_idx_mask) * d->vdisk.sect_size,
|
|
d->vdisk.sect_size);
|
|
return;
|
|
}
|
|
d->prev_cluster_index = cluster_index;
|
|
|
|
uint64_t l2_table_offset = l1_entry & L1_ENTRY_OFFSET_MASK;
|
|
if (!l2_table_offset) {
|
|
memset(buf, 0, d->vdisk.sect_size);
|
|
return;
|
|
}
|
|
lseek(d->fd, l2_table_offset + l2_index*sizeof(l2_entry), SEEK_SET);
|
|
if (!io_read(d->fd, &l2_entry, sizeof(l2_entry), d->vdisk.io)) {
|
|
fprintf(stderr, "[vdisk.qcow2] can't read from image file: %s\n",
|
|
strerror(errno));
|
|
return;
|
|
}
|
|
l2_entry = be64(&l2_entry);
|
|
if ((l2_entry & L2_ENTRY_FORMAT) == CLUSTER_FORMAT_STANDARD) {
|
|
if (l2_entry & L2_ENTRY_STD_ZEROED) {
|
|
printf("[vdisk.qcow2] cluster 0x%" PRIx64 " is zeroed\n",
|
|
cluster_index);
|
|
memset(buf, 0, d->vdisk.sect_size);
|
|
return;
|
|
}
|
|
cluster_offset = l2_entry & L2_ENTRY_STD_OFFSET;
|
|
lseek(d->fd, cluster_offset, SEEK_SET);
|
|
if (!io_read(d->fd, d->cluster, d->cluster_size, d->vdisk.io)) {
|
|
fprintf(stderr, "[vdisk.qcow2] can't read from image file: %s\n",
|
|
strerror(errno));
|
|
return;
|
|
}
|
|
} else {
|
|
off_t cmp_offset = d->l2_entry_cmp_offset_mask & l2_entry;
|
|
printf("cmp_offset: 0x%" PRIx64 "\n", cmp_offset);
|
|
lseek(d->fd, cmp_offset, SEEK_SET);
|
|
size_t additional_sectors = (l2_entry & d->l2_entry_cmp_sect_cnt_mask)
|
|
>> d->l2_entry_cmp_x;
|
|
size_t cmp_size = 512 - (cmp_offset & 511) + additional_sectors*512;
|
|
if (!io_read(d->fd, d->cmp_cluster, d->cluster_size, d->vdisk.io)) {
|
|
fprintf(stderr, "[vdisk.qcow2] can't read from image file: %s\n",
|
|
strerror(errno));
|
|
return;
|
|
}
|
|
unsigned long dest_size = d->cluster_size;
|
|
em_inflate(d->cluster, dest_size, d->cmp_cluster, cmp_size);
|
|
}
|
|
memcpy(buf,
|
|
d->cluster + (sector & d->sector_idx_mask) * d->vdisk.sect_size,
|
|
d->vdisk.sect_size);
|
|
}
|
|
|
|
STDCALL void
|
|
vdisk_qcow2_close(void *userdata) {
|
|
COVERAGE_OFF();
|
|
struct vdisk_qcow2 *d = userdata;
|
|
if (d->fd) {
|
|
close(d->fd);
|
|
}
|
|
free(d->cluster);
|
|
free(d->cmp_cluster);
|
|
free(d->l1);
|
|
free(d);
|
|
COVERAGE_ON();
|
|
}
|
|
|
|
STDCALL int
|
|
vdisk_qcow2_read(void *userdata, void *buffer, off_t startsector,
|
|
size_t *numsectors) {
|
|
COVERAGE_OFF();
|
|
struct vdisk_qcow2 *d = userdata;
|
|
for (size_t i = 0; i < *numsectors; i++) {
|
|
qcow2_read_guest_sector(d, startsector + i, buffer);
|
|
buffer = (uint8_t*)buffer + d->vdisk.sect_size;
|
|
}
|
|
COVERAGE_ON();
|
|
return KOS_ERROR_SUCCESS;
|
|
}
|
|
|
|
STDCALL int
|
|
vdisk_qcow2_write(void *userdata, void *buffer, off_t startsector,
|
|
size_t *numsectors) {
|
|
COVERAGE_OFF();
|
|
struct vdisk_qcow2 *d = userdata;
|
|
(void)d;
|
|
(void)buffer;
|
|
(void)startsector;
|
|
(void)numsectors;
|
|
fprintf(stderr, "[vdisk.qcow2] writing is not implemented");
|
|
COVERAGE_ON();
|
|
return KOS_ERROR_UNSUPPORTED_FS;
|
|
}
|
|
|
|
struct vdisk*
|
|
vdisk_init_qcow2(const char *fname, struct umka_io *io) {
|
|
struct vdisk_qcow2 *d =
|
|
(struct vdisk_qcow2*)calloc(1, sizeof(struct vdisk_qcow2));
|
|
if (!d) {
|
|
fprintf(stderr, "[vdisk.qcow2] can't allocate memory: %s\n",
|
|
strerror(errno));
|
|
return NULL;
|
|
}
|
|
|
|
d->vdisk.diskfunc = (diskfunc_t) {.strucsize = sizeof(diskfunc_t),
|
|
.close = vdisk_qcow2_close,
|
|
.read = vdisk_qcow2_read,
|
|
.write = vdisk_qcow2_write,
|
|
};
|
|
d->vdisk.io = io;
|
|
d->prev_cluster_index = ~(uint64_t)0;
|
|
if (!(d->fd = open(fname, O_RDONLY))) {
|
|
fprintf(stderr, "[vdisk.qcow2] can't open file '%s': %s\n", fname,
|
|
strerror(errno));
|
|
vdisk_qcow2_close(d);
|
|
return NULL;
|
|
}
|
|
d->vdisk.sect_size = 512;
|
|
if (strstr(fname, "s4096") != NULL || strstr(fname, "s4k") != NULL) {
|
|
d->vdisk.sect_size = 4096;
|
|
}
|
|
|
|
struct qcow2_header header;
|
|
if (!io_read(d->fd, &header, sizeof(struct qcow2_header), d->vdisk.io)) {
|
|
fprintf(stderr, "[vdisk.qcow2] can't read from image file: %s\n",
|
|
strerror(errno));
|
|
vdisk_qcow2_close(d);
|
|
return NULL;
|
|
}
|
|
|
|
if (strncmp(header.magic, QCOW2_MAGIC, sizeof(header.magic))) {
|
|
fprintf(stderr, "[vdisk.qcow2] bad image signature: '%c%c%c%c'\n",
|
|
header.magic[0], header.magic[1], header.magic[2],
|
|
header.magic[3]);
|
|
vdisk_qcow2_close(d);
|
|
return NULL;
|
|
}
|
|
|
|
uint32_t version = be32(&header.version);
|
|
if (version != 3) {
|
|
fprintf(stderr, "[vdisk.qcow2] bad image format version: %" PRIu32 "\n",
|
|
version);
|
|
vdisk_qcow2_close(d);
|
|
return NULL;
|
|
}
|
|
|
|
d->cluster_bits = be32(&header.cluster_bits);
|
|
if (d->cluster_bits < 9 || d->cluster_bits > 21) {
|
|
fprintf(stderr, "[vdisk.qcow2] bad cluster_bits value: %u\n",
|
|
d->cluster_bits);
|
|
vdisk_qcow2_close(d);
|
|
return NULL;
|
|
}
|
|
d->cluster_size = 1 << d->cluster_bits;
|
|
d->sector_idx_mask = d->cluster_size / d->vdisk.sect_size - 1ULL;
|
|
|
|
d->l2_entry_cmp_x = 62 - (d->cluster_bits - 8);
|
|
d->l2_entry_cmp_offset_mask = (1ULL << d->l2_entry_cmp_x) - 1ULL;
|
|
d->l2_entry_cmp_sect_cnt_mask = ((1ULL << 62) - 1ULL)
|
|
^ d->l2_entry_cmp_offset_mask;
|
|
|
|
uint64_t size = be64(&header.size);
|
|
d->vdisk.sect_cnt = size / d->vdisk.sect_size;
|
|
uint32_t crypt_method = be32(&header.crypt_method);
|
|
if (crypt_method) {
|
|
fprintf(stderr, "[vdisk.qcow2] bad crypt_method: %u\n", crypt_method);
|
|
vdisk_qcow2_close(d);
|
|
return NULL;
|
|
}
|
|
|
|
d->l1_size = be32(&header.l1_size);
|
|
d->l1_table_offset = be64(&header.l1_table_offset);
|
|
d->refcount_table_offset = be64(&header.refcount_table_offset);
|
|
d->refcount_table_clusters = be32(&header.refcount_table_clusters);
|
|
|
|
uint64_t incompatible_features = be64(&header.incompatible_features);
|
|
if (incompatible_features) {
|
|
fprintf(stderr, "[vdisk.qcow2] unsupported incompatible_feature(s): 0x%"
|
|
PRIx64 "\n", incompatible_features);
|
|
vdisk_qcow2_close(d);
|
|
return NULL;
|
|
}
|
|
|
|
d->refcount_order = be32(&header.refcount_order);
|
|
if (d->refcount_order < 4 || d->refcount_order > 6) {
|
|
fprintf(stderr, "[vdisk.qcow2] bad refcount_order value: %u\n",
|
|
d->refcount_order);
|
|
vdisk_qcow2_close(d);
|
|
return NULL;
|
|
}
|
|
|
|
d->header_length = be32(&header.header_length);
|
|
d->cluster = (uint8_t*)malloc(d->cluster_size);
|
|
if (!d->cluster) {
|
|
fprintf(stderr, "[vdisk.qcow2] can't allocate memory: %s\n",
|
|
strerror(errno));
|
|
vdisk_qcow2_close(d);
|
|
return NULL;
|
|
}
|
|
|
|
d->cmp_cluster = (uint8_t*)malloc(d->cluster_size*2);
|
|
if (!d->cmp_cluster) {
|
|
fprintf(stderr, "[vdisk.qcow2] can't allocate memory: %s\n",
|
|
strerror(errno));
|
|
vdisk_qcow2_close(d);
|
|
return NULL;
|
|
}
|
|
|
|
d->l1 = (uint64_t*)malloc(d->l1_size * sizeof(uint64_t));
|
|
if (!d->l1) {
|
|
fprintf(stderr, "[vdisk.qcow2] can't allocate memory: %s\n",
|
|
strerror(errno));
|
|
vdisk_qcow2_close(d);
|
|
return NULL;
|
|
}
|
|
|
|
lseek(d->fd, d->l1_table_offset, SEEK_SET);
|
|
if (!io_read(d->fd, d->l1, d->l1_size * sizeof(uint64_t), d->vdisk.io)) {
|
|
fprintf(stderr, "[vdisk.qcow2] can't read from image file: %s\n",
|
|
strerror(errno));
|
|
vdisk_qcow2_close(d);
|
|
return NULL;
|
|
}
|
|
|
|
for (uint64_t *x = d->l1; x < d->l1 + d->l1_size; x++) {
|
|
*x = be64(x);
|
|
}
|
|
|
|
return (struct vdisk*)d;
|
|
}
|