From 2f7dfc2e6c4bf387d21a872fcbcc5db3cf049077 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erik=20Ku=C4=8D=C3=A1k?= Date: Tue, 7 Sep 2021 23:38:15 +0200 Subject: [PATCH 1/4] Added driver for QCOW1 format --- uspace/srv/bd/qcow_bd/doc/doxygroups.h | 4 + uspace/srv/bd/qcow_bd/meson.build | 30 ++ uspace/srv/bd/qcow_bd/qcow_bd.c | 424 +++++++++++++++++++++++++ uspace/srv/bd/qcow_bd/qcow_bd.h | 91 ++++++ uspace/srv/meson.build | 1 + 5 files changed, 550 insertions(+) create mode 100644 uspace/srv/bd/qcow_bd/doc/doxygroups.h create mode 100644 uspace/srv/bd/qcow_bd/meson.build create mode 100644 uspace/srv/bd/qcow_bd/qcow_bd.c create mode 100644 uspace/srv/bd/qcow_bd/qcow_bd.h diff --git a/uspace/srv/bd/qcow_bd/doc/doxygroups.h b/uspace/srv/bd/qcow_bd/doc/doxygroups.h new file mode 100644 index 0000000000..0a6af24a55 --- /dev/null +++ b/uspace/srv/bd/qcow_bd/doc/doxygroups.h @@ -0,0 +1,4 @@ +/** @addtogroup qcow_bd qcow_bd + * @brief QCOW file block device driver + * @ingroup srvs + */ diff --git a/uspace/srv/bd/qcow_bd/meson.build b/uspace/srv/bd/qcow_bd/meson.build new file mode 100644 index 0000000000..69526a356e --- /dev/null +++ b/uspace/srv/bd/qcow_bd/meson.build @@ -0,0 +1,30 @@ +# +# Copyright (c) 2021 Erik Kučák +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# - Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# - Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# - The name of the author may not be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +deps = [ 'device' ] +src = files('qcow_bd.c') diff --git a/uspace/srv/bd/qcow_bd/qcow_bd.c b/uspace/srv/bd/qcow_bd/qcow_bd.c new file mode 100644 index 0000000000..95c6200a4d --- /dev/null +++ b/uspace/srv/bd/qcow_bd/qcow_bd.c @@ -0,0 +1,424 @@ +/* + * Copyright (c) 2021 Erik Kučák + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup qcow_bd + * @{ + */ + +/** + * @file + * @brief QCOW file block device driver + * + * Allows accessing a file as a block device in QCOW format. Useful for, e.g., mounting + * a disk image. + */ + +#include "qcow_bd.h" + +static FILE *img; +static QCowHeader header; +static QcowState state; + +static service_id_t service_id; +static bd_srvs_t bd_srvs; +static fibril_mutex_t dev_lock; +static void print_usage(void); +static errno_t qcow_bd_init(const char *fname); +static void qcow_bd_connection(ipc_call_t *icall, void *); + +static errno_t qcow_bd_open(bd_srvs_t *, bd_srv_t *); +static errno_t qcow_bd_close(bd_srv_t *); +static errno_t qcow_bd_read_blocks(bd_srv_t *, aoff64_t, size_t, void *, size_t); +static errno_t qcow_bd_write_blocks(bd_srv_t *, aoff64_t, size_t, const void *, size_t); +static errno_t qcow_bd_get_block_size(bd_srv_t *, size_t *); +static errno_t qcow_bd_get_num_blocks(bd_srv_t *, aoff64_t *); + +static bd_ops_t qcow_bd_ops = { + .open = qcow_bd_open, + .close = qcow_bd_close, + .read_blocks = qcow_bd_read_blocks, + .write_blocks = qcow_bd_write_blocks, + .get_block_size = qcow_bd_get_block_size, + .get_num_blocks = qcow_bd_get_num_blocks +}; + +int main(int argc, char **argv) +{ + errno_t rc; + char *image_name; + char *device_name; + category_id_t disk_cat; + + printf(NAME ": File-backed block device driver in QCOW format\n"); + + state.block_size = DEFAULT_BLOCK_SIZE; + + ++argv; + --argc; + while (*argv != NULL && (*argv)[0] == '-') { + /* Option */ + if (str_cmp(*argv, "-b") == 0) { + if (argc < 2) { + fprintf(stderr, "Argument missing.\n"); + print_usage(); + return -1; + } + + rc = str_size_t(argv[1], NULL, 10, true, &state.block_size); + if (rc != EOK || state.block_size == 0) { + fprintf(stderr, "Invalid block size '%s'.\n", argv[1]); + print_usage(); + return -1; + } + ++argv; + --argc; + } else { + fprintf(stderr, "Invalid option '%s'.\n", *argv); + print_usage(); + return -1; + } + ++argv; + --argc; + } + + if (argc < 2) { + fprintf(stderr, "Missing arguments.\n"); + print_usage(); + return -1; + } + + image_name = argv[0]; + device_name = argv[1]; + + if (qcow_bd_init(image_name) != EOK) + return -1; + + rc = loc_service_register(device_name, &service_id); + if (rc != EOK) { + fprintf(stderr, "%s: Unable to register device '%s': %s.\n", + NAME, device_name, str_error(rc)); + return rc; + } + + rc = loc_category_get_id("disk", &disk_cat, IPC_FLAG_BLOCKING); + if (rc != EOK) { + fprintf(stderr, "%s: Failed resolving category 'disk': %s\n", NAME, str_error(rc)); + return rc; + } + + rc = loc_service_add_to_cat(service_id, disk_cat); + if (rc != EOK) { + fprintf(stderr, "%s: Failed adding %s to category: %s", + NAME, device_name, str_error(rc)); + return rc; + } + + printf("%s: Accepting connections\n", NAME); + task_retval(0); + async_manager(); + + /* Not reached */ + return 0; +} + +static void print_usage(void) +{ + printf("Usage: " NAME " [-b ] \n"); +} + +static errno_t compute_table_sizes() +{ + /* Computing sizes in bytes */ + state.cluster_size = 1 << header.cluster_bits; + state.l2_size = (1 << header.l2_bits) * 8; + + /* Computing size of L1 table in bytes */ + state.l1_size = state.cluster_size * (1 << header.l2_bits); + if (header.size % state.l1_size != 0) + state.l1_size = (header.size / state.l1_size) + 1; + else + state.l1_size = header.size / state.l1_size; + + state.l1_size *= 8; + + /* Computing number of blocks */ + if (header.size % state.block_size != 0) + state.num_blocks = (header.size / state.block_size) + 1; + else + state.num_blocks = (header.size / state.block_size); + + return EOK; +} + +static errno_t read_l1_table() +{ + /* Allocating memory for l1_table */ + state.l1_table_offset = header.l1_table_offset; + state.l2_references = (uint64_t*)malloc(state.l1_size); + + /* Reading all L2 table references from L1 table */ + clearerr(img); + printf("READING 2,5\n"); + if (fseek(img, state.l1_table_offset, SEEK_SET) < 0) + return EIO; + + size_t n_rd = fread(state.l2_references, state.l1_size, 1, img); + + if (n_rd < 1) + return EINVAL; + + /* Clearing last bit in all references which tells us whether cluster is compressed */ + for (uint64_t i = 0; i < state.l1_size / sizeof(uint64_t); i++) { + state.l2_references[i] = __builtin_bswap64(state.l2_references[i]); + if (state.l2_references[i] & QCOW_OFLAG_COMPRESSED) { + fprintf(stderr, "Compression is not supported!\n"); + return ENOTSUP; + } + } + + if (ferror(img)) + return EIO; /* Read error */ + return EOK; +} + +static errno_t qcow_bd_init(const char *fname) +{ + /* Register driver */ + bd_srvs_init(&bd_srvs); + bd_srvs.ops = &qcow_bd_ops; + + async_set_fallback_port_handler(qcow_bd_connection, NULL); + errno_t rc = loc_server_register(NAME); + if (rc != EOK) { + fprintf(stderr, "%s: Unable to register driver.\n", NAME); + return rc; + } + + /* Try to open file */ + img = fopen(fname, "rb+"); + if (img == NULL) + return EINVAL; + + if (fseek(img, 0, SEEK_END) != 0) { + fclose(img); + return EIO; + } + + if (fseek(img, 0, SEEK_SET) < 0) + return EIO; + + /* Read the file header */ + size_t n_rd = fread(&header, sizeof(header), 1, img); + + if (n_rd < 1) + return EINVAL; + + /* Swap values to big-endian */ + header.magic = __builtin_bswap32(header.magic); + header.version = __builtin_bswap32(header.version); + header.backing_file_offset = __builtin_bswap64(header.backing_file_offset); + header.backing_file_size = __builtin_bswap32(header.backing_file_size); + header.mtime = __builtin_bswap32(header.mtime); + header.size = __builtin_bswap64(header.size); + header.crypt_method = __builtin_bswap32(header.crypt_method); + header.l1_table_offset = __builtin_bswap64(header.l1_table_offset); + + /* Verify all values from file header */ + if (ferror(img)) + return EIO; + + if (header.magic == QCOW_MAGIC) { + errno_t error = compute_table_sizes(); + if (error != EOK) { + return error; + } + error = read_l1_table(); + if (error != EOK) { + return error; + } + } + + if (header.version != QCOW_VERSION) { + fprintf(stderr, "Version: %d is not supported!\n", header.version); + return ENOTSUP; + } + + if (header.crypt_method != QCOW_CRYPT_NONE) { + fprintf(stderr, "Encryption is not supported!\n"); + return ENOTSUP; + } + + /* Initialize mutex variable */ + fibril_mutex_initialize(&dev_lock); + + return EOK; +} + +static void qcow_bd_connection(ipc_call_t *icall, void *arg) +{ + bd_conn(icall, &bd_srvs); +} + +/** Open device. */ +static errno_t qcow_bd_open(bd_srvs_t *bds, bd_srv_t *bd) +{ + return EOK; +} + +/** Close device. */ +static errno_t qcow_bd_close(bd_srv_t *bd) +{ + free(state.l2_references); + fclose(img); + return EOK; +} + +/** From the offset of the given block compute its offset which is relative from the start of qcow file. */ +static uint64_t get_block_offset(uint64_t offset, errno_t *error) +{ + /* Compute l1 table index from the offset */ + uint64_t l1_table_index_bit_shift = header.cluster_bits + header.l2_bits; + uint64_t l1_table_index = (offset & 0x7fffffffffffffffULL) >> l1_table_index_bit_shift; + + /* Reading l2 reference from the l1 table */ + uint64_t l2_table_reference = state.l2_references[l1_table_index]; + + if (l2_table_reference == QCOW_UNALLOCATED_REFERENCE) + return QCOW_UNALLOCATED_REFERENCE; + + /* Compute l2 table index from the offset */ + uint64_t l2_table_index = (offset >> header.cluster_bits) & (state.l2_size - 1); + + /* Reading cluster reference from the l2 table */ + if (fseek(img, l2_table_reference + l2_table_index * sizeof(uint64_t), SEEK_SET) < 0){ + *error = EIO; + return EIO; + } + + uint64_t cluster_reference; + size_t n_rd = fread(&cluster_reference, sizeof(uint64_t), 1, img); + + if (n_rd < 1) { + *error = EINVAL; + return EINVAL; + } + + cluster_reference = __builtin_bswap64(cluster_reference); + + if (cluster_reference & QCOW_OFLAG_COMPRESSED) { + fprintf(stderr, "Compression is not supported!\n"); + *error = ENOTSUP; + return ENOTSUP; + } + + if (cluster_reference == QCOW_UNALLOCATED_REFERENCE) + return QCOW_UNALLOCATED_REFERENCE; + + /* Compute cluster block offset from the offset */ + uint64_t cluster_block_bit_mask = ~(0xffffffffffffffffULL << header.cluster_bits); + uint64_t cluster_block_offset = offset & cluster_block_bit_mask; + + return cluster_reference + cluster_block_offset; +} + +/** Read blocks from the device. */ +static errno_t qcow_bd_read_blocks(bd_srv_t *bd, uint64_t ba, size_t cnt, void *buf, + size_t size) +{ + if (size < cnt * state.block_size) + return EINVAL; + + /* Check whether access is within device address bounds. */ + if (ba + cnt > state.num_blocks) { + fprintf(stderr, NAME ": Accessed blocks %" PRIuOFF64 "-%" PRIuOFF64 ", while " + "max block number is %" PRIuOFF64 ".\n", ba, ba + cnt - 1, + state.num_blocks - 1); + return ELIMIT; + } + + fibril_mutex_lock(&dev_lock); + + for (uint64_t i = 0; i < cnt; i++) { + /* Compute block offset which is relative from the start of qcow file */ + errno_t error = EOK; + uint64_t block_offset = get_block_offset((ba + i) * state.block_size, &error); + if (error != EOK) + return error; + + /* If there is empty reference then continue */ + if (block_offset == QCOW_UNALLOCATED_REFERENCE) + continue; + + clearerr(img); + if (fseek(img, block_offset, SEEK_SET) < 0) { + fibril_mutex_unlock(&dev_lock); + return EIO; + } + + size_t n_rd = fread(buf + i * state.block_size, state.block_size, 1, img); + + if (n_rd < 1) { + fibril_mutex_unlock(&dev_lock); + return EINVAL; + } + + if (ferror(img)) { + fibril_mutex_unlock(&dev_lock); + return EIO; + } + } + + fibril_mutex_unlock(&dev_lock); + return EOK; +} + +/** Write blocks to the device. */ +static errno_t qcow_bd_write_blocks(bd_srv_t *bd, uint64_t ba, size_t cnt, + const void *buf, size_t size) +{ + // TODO + return EOK; +} + +/** Get device block size. */ +static errno_t qcow_bd_get_block_size(bd_srv_t *bd, size_t *rsize) +{ + *rsize = state.block_size; + return EOK; +} + +/** Get number of blocks on device. */ +static errno_t qcow_bd_get_num_blocks(bd_srv_t *bd, aoff64_t *rnb) +{ + *rnb = state.num_blocks; + return EOK; +} + +/** + * @} + */ \ No newline at end of file diff --git a/uspace/srv/bd/qcow_bd/qcow_bd.h b/uspace/srv/bd/qcow_bd/qcow_bd.h new file mode 100644 index 0000000000..9da11c8389 --- /dev/null +++ b/uspace/srv/bd/qcow_bd/qcow_bd.h @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2021 Erik Kučák + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup qcow_bd + * @{ + */ +/** @file QCOW block device driver definitions. + */ + +#ifndef __QCOW_BD_H__ +#define __QCOW_BD_H__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define NAME "qcow_bd" +#define DEFAULT_BLOCK_SIZE 512 +#define QCOW_MAGIC (('Q' << 24) | ('F' << 16) | ('I' << 8) | 0xfb) +#define QCOW_VERSION 1 +#define QCOW_CRYPT_NONE 0 +#define QCOW_OFLAG_COMPRESSED (1LL << 63) +#define QCOW_UNALLOCATED_REFERENCE 0 + +typedef struct __attribute__ ((__packed__)) QCowHeader { + uint32_t magic; + uint32_t version; + uint64_t backing_file_offset; + uint32_t backing_file_size; + uint32_t mtime; + uint64_t size; + uint8_t cluster_bits; + uint8_t l2_bits; + uint16_t unused; + uint32_t crypt_method; + uint64_t l1_table_offset; +} QCowHeader; + +typedef struct QcowState { + uint64_t cluster_size; + size_t block_size; + aoff64_t num_blocks; + uint64_t l2_size; + uint64_t l1_size; + uint64_t l1_table_offset; + uint64_t *l2_references; +} QcowState; + +#endif + +/** @} + */ \ No newline at end of file diff --git a/uspace/srv/meson.build b/uspace/srv/meson.build index 4536385a2b..cbcaed572f 100644 --- a/uspace/srv/meson.build +++ b/uspace/srv/meson.build @@ -29,6 +29,7 @@ srvs = [ 'audio/hound', 'bd/file_bd', + 'bd/qcow_bd', 'bd/rd', 'bd/sata_bd', 'bd/vbd', From bdad8460a62c8e5f00446b559442915a24dd0a62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erik=20Ku=C4=8D=C3=A1k?= Date: Wed, 8 Sep 2021 15:31:58 +0200 Subject: [PATCH 2/4] Fixed bugs from the review --- uspace/srv/bd/qcow_bd/qcow_bd.c | 140 ++++++++++++++++---------------- uspace/srv/bd/qcow_bd/qcow_bd.h | 1 - 2 files changed, 72 insertions(+), 69 deletions(-) diff --git a/uspace/srv/bd/qcow_bd/qcow_bd.c b/uspace/srv/bd/qcow_bd/qcow_bd.c index 95c6200a4d..d1fffc39b5 100644 --- a/uspace/srv/bd/qcow_bd/qcow_bd.c +++ b/uspace/srv/bd/qcow_bd/qcow_bd.c @@ -151,7 +151,7 @@ static void print_usage(void) printf("Usage: " NAME " [-b ] \n"); } -static errno_t compute_table_sizes() +static void initialize_state() { /* Computing sizes in bytes */ state.cluster_size = 1 << header.cluster_bits; @@ -172,38 +172,7 @@ static errno_t compute_table_sizes() else state.num_blocks = (header.size / state.block_size); - return EOK; -} - -static errno_t read_l1_table() -{ - /* Allocating memory for l1_table */ state.l1_table_offset = header.l1_table_offset; - state.l2_references = (uint64_t*)malloc(state.l1_size); - - /* Reading all L2 table references from L1 table */ - clearerr(img); - printf("READING 2,5\n"); - if (fseek(img, state.l1_table_offset, SEEK_SET) < 0) - return EIO; - - size_t n_rd = fread(state.l2_references, state.l1_size, 1, img); - - if (n_rd < 1) - return EINVAL; - - /* Clearing last bit in all references which tells us whether cluster is compressed */ - for (uint64_t i = 0; i < state.l1_size / sizeof(uint64_t); i++) { - state.l2_references[i] = __builtin_bswap64(state.l2_references[i]); - if (state.l2_references[i] & QCOW_OFLAG_COMPRESSED) { - fprintf(stderr, "Compression is not supported!\n"); - return ENOTSUP; - } - } - - if (ferror(img)) - return EIO; /* Read error */ - return EOK; } static errno_t qcow_bd_init(const char *fname) @@ -221,22 +190,32 @@ static errno_t qcow_bd_init(const char *fname) /* Try to open file */ img = fopen(fname, "rb+"); - if (img == NULL) + if (img == NULL) { + fprintf(stderr, "File opening failed!\n"); return EINVAL; + } if (fseek(img, 0, SEEK_END) != 0) { + fprintf(stderr, "Seeking end of file failed!\n"); fclose(img); return EIO; } - if (fseek(img, 0, SEEK_SET) < 0) + if (fseek(img, 0, SEEK_SET) < 0) { + fprintf(stderr, "Seeking file header failed!\n"); + fclose(img); return EIO; + } /* Read the file header */ size_t n_rd = fread(&header, sizeof(header), 1, img); - if (n_rd < 1) + if (n_rd < 1) { + fprintf(stderr, "Reading file header failed!\n"); + fclose(img); return EINVAL; + } + /* Swap values to big-endian */ header.magic = __builtin_bswap32(header.magic); @@ -248,28 +227,24 @@ static errno_t qcow_bd_init(const char *fname) header.crypt_method = __builtin_bswap32(header.crypt_method); header.l1_table_offset = __builtin_bswap64(header.l1_table_offset); - /* Verify all values from file header */ - if (ferror(img)) + if (ferror(img)) { + fclose(img); return EIO; - - if (header.magic == QCOW_MAGIC) { - errno_t error = compute_table_sizes(); - if (error != EOK) { - return error; - } - error = read_l1_table(); - if (error != EOK) { - return error; - } } + /* Verify all values from file header */ + if (header.magic == QCOW_MAGIC) + initialize_state(); + if (header.version != QCOW_VERSION) { - fprintf(stderr, "Version: %d is not supported!\n", header.version); + fprintf(stderr, "Version QCOW%d is not supported!\n", header.version); + fclose(img); return ENOTSUP; } if (header.crypt_method != QCOW_CRYPT_NONE) { fprintf(stderr, "Encryption is not supported!\n"); + fclose(img); return ENOTSUP; } @@ -293,38 +268,59 @@ static errno_t qcow_bd_open(bd_srvs_t *bds, bd_srv_t *bd) /** Close device. */ static errno_t qcow_bd_close(bd_srv_t *bd) { - free(state.l2_references); fclose(img); return EOK; } /** From the offset of the given block compute its offset which is relative from the start of qcow file. */ -static uint64_t get_block_offset(uint64_t offset, errno_t *error) +static errno_t get_block_offset(uint64_t *offset) { /* Compute l1 table index from the offset */ uint64_t l1_table_index_bit_shift = header.cluster_bits + header.l2_bits; - uint64_t l1_table_index = (offset & 0x7fffffffffffffffULL) >> l1_table_index_bit_shift; + uint64_t l1_table_index = (*offset & 0x7fffffffffffffffULL) >> l1_table_index_bit_shift; /* Reading l2 reference from the l1 table */ - uint64_t l2_table_reference = state.l2_references[l1_table_index]; + if (fseek(img, state.l1_table_offset + l1_table_index * sizeof(uint64_t), SEEK_SET) < 0) { + fprintf(stderr, "Seeking l2 reference in l1 table failed!\n"); + return EIO; + } + + + uint64_t l2_table_reference; + size_t n_rd = fread(&l2_table_reference, sizeof(uint64_t), 1, img); + + if (n_rd < 1) { + fprintf(stderr, "Reading l2 reference from l1 table failed!\n"); + return EINVAL; + } + + l2_table_reference = __builtin_bswap64(l2_table_reference); + + if (l2_table_reference & QCOW_OFLAG_COMPRESSED) { + fprintf(stderr, "Compression is not supported!\n"); + return ENOTSUP; + } + + if (l2_table_reference == QCOW_UNALLOCATED_REFERENCE) { + *offset = QCOW_UNALLOCATED_REFERENCE; + return EOK; + } - if (l2_table_reference == QCOW_UNALLOCATED_REFERENCE) - return QCOW_UNALLOCATED_REFERENCE; /* Compute l2 table index from the offset */ - uint64_t l2_table_index = (offset >> header.cluster_bits) & (state.l2_size - 1); + uint64_t l2_table_index = (*offset >> header.cluster_bits) & (state.l2_size - 1); /* Reading cluster reference from the l2 table */ - if (fseek(img, l2_table_reference + l2_table_index * sizeof(uint64_t), SEEK_SET) < 0){ - *error = EIO; + if (fseek(img, l2_table_reference + l2_table_index * sizeof(uint64_t), SEEK_SET) < 0) { + fprintf(stderr, "Seeking cluster reference in l2 table failed!\n"); return EIO; } uint64_t cluster_reference; - size_t n_rd = fread(&cluster_reference, sizeof(uint64_t), 1, img); + n_rd = fread(&cluster_reference, sizeof(uint64_t), 1, img); if (n_rd < 1) { - *error = EINVAL; + fprintf(stderr, "Reading cluster reference from l2 table failed!\n"); return EINVAL; } @@ -332,26 +328,31 @@ static uint64_t get_block_offset(uint64_t offset, errno_t *error) if (cluster_reference & QCOW_OFLAG_COMPRESSED) { fprintf(stderr, "Compression is not supported!\n"); - *error = ENOTSUP; return ENOTSUP; } - if (cluster_reference == QCOW_UNALLOCATED_REFERENCE) - return QCOW_UNALLOCATED_REFERENCE; + if (cluster_reference == QCOW_UNALLOCATED_REFERENCE) { + *offset = QCOW_UNALLOCATED_REFERENCE; + return EOK; + } /* Compute cluster block offset from the offset */ uint64_t cluster_block_bit_mask = ~(0xffffffffffffffffULL << header.cluster_bits); - uint64_t cluster_block_offset = offset & cluster_block_bit_mask; + uint64_t cluster_block_offset = *offset & cluster_block_bit_mask; - return cluster_reference + cluster_block_offset; + *offset = cluster_reference + cluster_block_offset; + return EOK; } /** Read blocks from the device. */ static errno_t qcow_bd_read_blocks(bd_srv_t *bd, uint64_t ba, size_t cnt, void *buf, size_t size) { - if (size < cnt * state.block_size) + if (size < cnt * state.block_size){ + fprintf(stderr, "Error: trying to read block behind the file"); return EINVAL; + } + /* Check whether access is within device address bounds. */ if (ba + cnt > state.num_blocks) { @@ -365,10 +366,13 @@ static errno_t qcow_bd_read_blocks(bd_srv_t *bd, uint64_t ba, size_t cnt, void * for (uint64_t i = 0; i < cnt; i++) { /* Compute block offset which is relative from the start of qcow file */ - errno_t error = EOK; - uint64_t block_offset = get_block_offset((ba + i) * state.block_size, &error); - if (error != EOK) + uint64_t block_offset = (ba + i) * state.block_size; + errno_t error = get_block_offset(&block_offset); + if (error != EOK) { + fibril_mutex_unlock(&dev_lock); return error; + } + /* If there is empty reference then continue */ if (block_offset == QCOW_UNALLOCATED_REFERENCE) diff --git a/uspace/srv/bd/qcow_bd/qcow_bd.h b/uspace/srv/bd/qcow_bd/qcow_bd.h index 9da11c8389..47b4e51991 100644 --- a/uspace/srv/bd/qcow_bd/qcow_bd.h +++ b/uspace/srv/bd/qcow_bd/qcow_bd.h @@ -82,7 +82,6 @@ typedef struct QcowState { uint64_t l2_size; uint64_t l1_size; uint64_t l1_table_offset; - uint64_t *l2_references; } QcowState; #endif From d5309df7ca2cff3ef965502eca2097e45c2649a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erik=20Ku=C4=8D=C3=A1k?= Date: Tue, 14 Sep 2021 08:37:21 +0200 Subject: [PATCH 3/4] Fixed problem with big endian --- uspace/srv/bd/qcow_bd/qcow_bd.c | 40 +++++++++++++++++++++++---------- 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/uspace/srv/bd/qcow_bd/qcow_bd.c b/uspace/srv/bd/qcow_bd/qcow_bd.c index d1fffc39b5..03bf90a548 100644 --- a/uspace/srv/bd/qcow_bd/qcow_bd.c +++ b/uspace/srv/bd/qcow_bd/qcow_bd.c @@ -47,6 +47,7 @@ static QcowState state; static service_id_t service_id; static bd_srvs_t bd_srvs; static fibril_mutex_t dev_lock; +static bool isLittleEndian(); static void print_usage(void); static errno_t qcow_bd_init(const char *fname); static void qcow_bd_connection(ipc_call_t *icall, void *); @@ -175,6 +176,12 @@ static void initialize_state() state.l1_table_offset = header.l1_table_offset; } +/* Return 0 for if this system uses big endian, 1 for little endian. */ +static bool isLittleEndian() { + volatile uint32_t i = 0x01234567; + return (*((uint8_t*)(&i))) == 0x67; +} + static errno_t qcow_bd_init(const char *fname) { /* Register driver */ @@ -218,14 +225,16 @@ static errno_t qcow_bd_init(const char *fname) /* Swap values to big-endian */ - header.magic = __builtin_bswap32(header.magic); - header.version = __builtin_bswap32(header.version); - header.backing_file_offset = __builtin_bswap64(header.backing_file_offset); - header.backing_file_size = __builtin_bswap32(header.backing_file_size); - header.mtime = __builtin_bswap32(header.mtime); - header.size = __builtin_bswap64(header.size); - header.crypt_method = __builtin_bswap32(header.crypt_method); - header.l1_table_offset = __builtin_bswap64(header.l1_table_offset); + if (isLittleEndian()) { + header.magic = __builtin_bswap32(header.magic); + header.version = __builtin_bswap32(header.version); + header.backing_file_offset = __builtin_bswap64(header.backing_file_offset); + header.backing_file_size = __builtin_bswap32(header.backing_file_size); + header.mtime = __builtin_bswap32(header.mtime); + header.size = __builtin_bswap64(header.size); + header.crypt_method = __builtin_bswap32(header.crypt_method); + header.l1_table_offset = __builtin_bswap64(header.l1_table_offset); + } if (ferror(img)) { fclose(img); @@ -233,8 +242,13 @@ static errno_t qcow_bd_init(const char *fname) } /* Verify all values from file header */ - if (header.magic == QCOW_MAGIC) - initialize_state(); + if (header.magic != QCOW_MAGIC) { + fprintf(stderr, "File is not in QCOW format!\n"); + fclose(img); + return ENOTSUP; + } + + initialize_state(); if (header.version != QCOW_VERSION) { fprintf(stderr, "Version QCOW%d is not supported!\n", header.version); @@ -294,7 +308,8 @@ static errno_t get_block_offset(uint64_t *offset) return EINVAL; } - l2_table_reference = __builtin_bswap64(l2_table_reference); + if (isLittleEndian()) + l2_table_reference = __builtin_bswap64(l2_table_reference); if (l2_table_reference & QCOW_OFLAG_COMPRESSED) { fprintf(stderr, "Compression is not supported!\n"); @@ -324,7 +339,8 @@ static errno_t get_block_offset(uint64_t *offset) return EINVAL; } - cluster_reference = __builtin_bswap64(cluster_reference); + if (isLittleEndian()) + cluster_reference = __builtin_bswap64(cluster_reference); if (cluster_reference & QCOW_OFLAG_COMPRESSED) { fprintf(stderr, "Compression is not supported!\n"); From 2ce943a86a6fbca13a3d2809d0aa9d9dcd1cf398 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erik=20Ku=C4=8D=C3=A1k?= Date: Wed, 15 Sep 2021 17:54:18 +0200 Subject: [PATCH 4/4] Fixed last bug with L2_index --- uspace/srv/bd/qcow_bd/qcow_bd.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/uspace/srv/bd/qcow_bd/qcow_bd.c b/uspace/srv/bd/qcow_bd/qcow_bd.c index 03bf90a548..a6e1571a80 100644 --- a/uspace/srv/bd/qcow_bd/qcow_bd.c +++ b/uspace/srv/bd/qcow_bd/qcow_bd.c @@ -323,8 +323,8 @@ static errno_t get_block_offset(uint64_t *offset) /* Compute l2 table index from the offset */ - uint64_t l2_table_index = (*offset >> header.cluster_bits) & (state.l2_size - 1); - + uint64_t l2_table_shift = (1 << header.l2_bits) - 1; + uint64_t l2_table_index = (*offset >> header.cluster_bits) & l2_table_shift; /* Reading cluster reference from the l2 table */ if (fseek(img, l2_table_reference + l2_table_index * sizeof(uint64_t), SEEK_SET) < 0) { fprintf(stderr, "Seeking cluster reference in l2 table failed!\n");