Ryan Grimm
2006-Aug-21 20:55 UTC
[Xen-devel] [PATCH 5 of 6] qcow plugin for dm-userspace userspace tool
# HG changeset patch # User Ryan Grimm <grimm@us.ibm.com> # Date 1156190590 18000 # Node ID 77a518e8f44d1c0c89dcb93e5a3713da6c5e6131 # Parent 6a7ea5a3554309b4e37e3e8bcbd6a11b8cdc4386 qcow plugin for dm-userspace userspace tool Signed-off-by: Ryan Grimm <grimm@us.ibm.com> Signed-off-by: Dan Smith <danms@us.ibm.com> diff -r 6a7ea5a35543 -r 77a518e8f44d tools/cowd/configure.in --- a/tools/cowd/configure.in Mon Aug 21 15:03:09 2006 -0500 +++ b/tools/cowd/configure.in Mon Aug 21 15:03:10 2006 -0500 @@ -95,11 +95,13 @@ AC_SUBST(GLOBAL_CFLAGS) AC_CONFIG_FILES([Makefile plugins/Makefile + plugins/qcow/Makefile plugins/dscow/Makefile]) # This just makes it easier to run cowd from the source directory # for testing mkdir -p lib +ln -sf ../plugins/qcow/.libs/libcowd_qcow.so.0 lib/libcowd_qcow.so ln -sf ../plugins/dscow/.libs/libcowd_dscow.so.0 lib/libcowd_dscow.so ln -sf ../plugins/dscow/.libs/libcowd_dscow.la lib/libcowd_dscow.la diff -r 6a7ea5a35543 -r 77a518e8f44d tools/cowd/plugins/Makefile.am --- a/tools/cowd/plugins/Makefile.am Mon Aug 21 15:03:09 2006 -0500 +++ b/tools/cowd/plugins/Makefile.am Mon Aug 21 15:03:10 2006 -0500 @@ -1,1 +1,1 @@ SUBDIRS = dscow -SUBDIRS = dscow +SUBDIRS = dscow qcow diff -r 6a7ea5a35543 -r 77a518e8f44d tools/cowd/plugins/qcow/Makefile.am --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tools/cowd/plugins/qcow/Makefile.am Mon Aug 21 15:03:10 2006 -0500 @@ -0,0 +1,6 @@ +lib_LTLIBRARIES = libcowd_qcow.la +libcowd_qcow_la_CFLAGS = -I../.. -I../../../../module @GLOBAL_CFLAGS@ +libcowd_qcow_la_SOURCES = qcow_plugin.c qcow_ops.c qcow.h qcow_ops.h + +#noinst_PROGRAMS = qcow_test +#qcow_test_SOURCES = qcow_test.c qcow_ops.c ../../util.c diff -r 6a7ea5a35543 -r 77a518e8f44d tools/cowd/plugins/qcow/qcow.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tools/cowd/plugins/qcow/qcow.h Mon Aug 21 15:03:10 2006 -0500 @@ -0,0 +1,167 @@ +#ifndef __QCOW_H +#define __QCOW_H + +#include <sys/types.h> +#include <stdint.h> + +#define NAME "qcow: " +#define L2_CACHE_SIZE 16 + +#define QCOW_MAGIC ((''Q'' << 24) | (''F'' << 16) | (''I'' << 8) | 0xFB) + +typedef uint64_t qcow_te ; + +struct qcow_header { + uint32_t magic; + uint32_t version; + uint64_t backing_filename_offset; + uint32_t backing_filename_size; + uint32_t mtime; + uint64_t size; + uint8_t cluster_bits; + uint8_t l2_bits; + uint32_t crypto_method; + uint64_t l1_table_offset; +}; + +struct l1_entry { + int dirty; + uint64_t loc_on_disk; + qcow_te *table; +}; + +struct l2_cache { + uint64_t l1_index; + struct l1_entry *l2; + uint32_t hits; +}; + +struct qcow { + struct qcow_header header; + struct l1_entry *table; + int l1_dirty; + + char *filename; + + struct l2_cache *l2_cache[L2_CACHE_SIZE]; + int l2_cache_counter; + + uint64_t next_avail_block; + + /* This is the offset we use for the file. The problem is, + the kernel module expects to remap whole aligned blocks. + Since we can interleave L2 blocks with differently-sized */ + uint64_t offset; + + int fd; + +}; + +static inline uint64_t qcow_num_l1(struct qcow_header *h) +{ + uint64_t nonl1 = (1 << h->cluster_bits) * (1 << h->l2_bits); + + if (h->size % nonl1) + return (h->size / nonl1) + 1; + else + return (h->size / nonl1); +} + +static inline uint64_t qcow_num_l2(struct qcow_header *h) +{ + return 1 << h->l2_bits; +} + +static inline uint64_t qcow_block_size(struct qcow_header *h) +{ + return 1 << h->cluster_bits; +} + +static inline uint64_t qcow_cmask(struct qcow_header *h) +{ + return (1 << h->cluster_bits) - 1; +} + +static inline uint64_t qcow_l2mask(struct qcow_header *h) +{ + return ((1 << h->l2_bits) - 1) << h->cluster_bits; +} + +static inline uint64_t qcow_l1mask(struct qcow_header *h) +{ + return ~(qcow_cmask(h) | qcow_l2mask(h)); +} + +/* These are i386, little-endian. + * Clearly this needs to be generalized. + */ +static inline uint64_t ntohll(uint64_t value) +{ + uint32_t a, b; + + a = value >> 32; + b = value & 0xFFFFFFFF; + + return (((uint64_t)ntohl(b)) << 32) | ntohl(a); +} + +static inline uint64_t htonll(uint64_t value) +{ + uint32_t a, b; + + a = value >> 32; + b = value & 0xFFFFFFFF; + + return (((uint64_t)htonl(b)) << 32) | htonl(a); +} + +static inline uint64_t qcow_get_l1_entry(struct qcow *qcow, uint64_t sector) +{ + uint64_t entry; + + entry = sector & qcow_l1mask(&qcow->header); + entry = entry >> + (qcow->header.l2_bits + qcow->header.cluster_bits); + +// printf("L1 entry: %llx %llx\n", +// sector & qcow_l1mask(&qcow->header), +// entry); + + return entry; +} + +static inline uint64_t qcow_get_l2_entry(struct qcow *qcow, uint64_t sector) +{ + return (sector & qcow_l2mask(&qcow->header)) >> + (qcow->header.cluster_bits); +} + +static inline uint64_t qcow_make_te(struct qcow *qcow, + uint64_t l1_entry, + uint64_t l2_entry, + uint64_t index) +{ + uint64_t te = 0; + + te |= (l1_entry << (qcow->header.l2_bits + qcow->header.cluster_bits)); + te |= (l2_entry << (qcow->header.cluster_bits)); + te |= (index & qcow_cmask(&qcow->header)); + + return te; +} + +static inline uint64_t qcow_calc_offset(struct qcow *qcow, + uint64_t sector) +{ + return sector % qcow_block_size(&qcow->header); +} + +static inline uint64_t qcow_align_to_block(struct qcow *qcow, + uint64_t sector) +{ + return sector / qcow_block_size(&qcow->header); +} + + + +#endif diff -r 6a7ea5a35543 -r 77a518e8f44d tools/cowd/plugins/qcow/qcow_ops.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tools/cowd/plugins/qcow/qcow_ops.c Mon Aug 21 15:03:10 2006 -0500 @@ -0,0 +1,603 @@ +/* + * Copyright (C) International Business Machines Corp., 2006 + * Author: Dan Smith <danms@us.ibm.com> + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License. See the file COPYING in the main directory + * of this archive for more details. + * + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <time.h> +#include <sys/types.h> +#include <unistd.h> + +#include <netinet/in.h> /* for endian ops */ + +#include "qcow.h" +#include "qcow_ops.h" + +#define OPS_DEBUG 0 + +#define LOG_ABOVE (128 << 20) + +/* + * Update our pointer to the end of the highest-block, if appropriate. + * This is used to allocate new blocks and tables, because it''s the + * end of the last chunk of real data. + */ +static int qcow_seen_block(struct qcow *qcow, + qcow_te sector, + qcow_te block_size) +{ + uint64_t end; + + end = sector + block_size; + + if (end > qcow->header.size) { + fprintf(stderr, + "*** ERROR: Saw block %llu beyond end of %llu\n", + end, qcow->header.size); + return -1; + } + + if (qcow->next_avail_block < end) { + qcow->next_avail_block = end; + return 1; + } else { + return 0; + } +} + +void qcow_init_qcow(struct qcow *qcow) +{ + qcow->table = NULL; + qcow->filename = NULL; + qcow->l1_dirty = 0; + qcow->next_avail_block = 0; +} + +/* + * Read in the qcow header from @fd, storing it in @header + * If @w!=0, then write instead. (Although not yet :) + */ +int qcow_rw_header(int fd, struct qcow_header *header, int w) +{ + int ret; + loff_t offset = 0; + + if (dio_lseek(fd, offset, SEEK_SET) != offset) { + fprintf(stderr, "Offset was %lli instead of 0\n", offset); + perror("dio_lseek"); + return 0; + } + + if (w) + ret = dio_write(fd, header, sizeof(*header)); + else + ret = dio_read(fd, header, sizeof(*header)); + + if (ret == 0) { + fprintf(stderr, "Read 0 bytes for header (%i)!\n", + sizeof(*header)); + return 0; + } else if (ret < 0) { + perror("qcow-header"); + return 0; + } + + /* Convert endianess */ + header->magic = ntohl(header->magic); + header->version = ntohl(header->version); + header->backing_filename_offset + ntohll(header->backing_filename_offset); + header->backing_filename_size = ntohl(header->backing_filename_size); + header->mtime = ntohl(header->mtime); + header->size = ntohll(header->size); + header->crypto_method = ntohl(header->crypto_method); + header->l1_table_offset = ntohll(header->l1_table_offset); + + return 1; +} + +/* + * Read the backing filename information + */ +int qcow_read_backing_info(struct qcow *qcow) +{ + int ret; + + qcow->filename = (char *)malloc(qcow->header.backing_filename_size+1); + + ret = dio_lseek(qcow->fd, + qcow->header.backing_filename_offset, + SEEK_SET); + if (ret != qcow->header.backing_filename_offset) { + fprintf(stderr, "lseek to %llu\n", + qcow->header.backing_filename_offset); + perror("qcow-lseek"); + return 0; + } + + ret = dio_read(qcow->fd, qcow->filename, + qcow->header.backing_filename_size); + if (ret != qcow->header.backing_filename_size) { + fprintf(stderr, "qcow-read: EOF while reading filename\n"); + return 0; + } + qcow->filename[qcow->header.backing_filename_size] = ''\0''; + + return 1; +} + +/* + * Allocate memory for the L1 table + */ +int qcow_init_from_header(struct qcow *qcow) +{ + qcow->table = (struct l1_entry *)calloc(qcow_num_l1(&qcow->header), + sizeof(struct l1_entry)); + + if (qcow->table == NULL) { + fprintf(stderr, + NAME "*** failed to calloc %llu x %i for table\n", + qcow_num_l1(&qcow->header), sizeof(qcow_te*)); + return 0; + } + + return 1; +} + +/* + * Write @table to @fd:@pos, @length entries + */ +static int qcow_write_table(int fd, uint64_t pos, + uint64_t length, qcow_te *table) +{ + qcow_te *disk_table; + int64_t i; + + disk_table = (qcow_te *)calloc(length, + sizeof(qcow_te)); + if (!disk_table) + goto bad; + + for (i=0; i < length; i++) + disk_table[i] = htonll(table[i]); + + if (dio_lseek(fd, pos, SEEK_SET) != pos) { + perror("write_table lseek"); + goto bad; + } + + if (OPS_DEBUG) + fprintf(stderr, "Writing %llu bytes @ %llu\n", + length * sizeof(qcow_te), pos); + + i = dio_write(fd, disk_table, length * sizeof(qcow_te)); + + if (i != (length * sizeof(qcow_te))) { + fprintf(stderr, + "Short write: %lli/%llu\n", + i, + length * sizeof(qcow_te)); + + goto bad; + } else if (i < 0) { + perror("write"); + goto bad; + } + + return 1; + + bad: + return 0; +} + +/* + * Read @table from @fd:@pos, @length entries + */ +static int qcow_read_table(int fd, uint64_t pos, + uint64_t length, qcow_te *table) +{ + qcow_te *disk_table; + int64_t i; + + disk_table = (qcow_te *)calloc(length, + sizeof(qcow_te)); + if (!disk_table) + goto bad; + + if (dio_lseek(fd, pos, SEEK_SET) != pos) { + perror("lseek"); + goto bad; + } + + i = dio_read(fd, disk_table, length * sizeof(qcow_te)); + + if (i != (length * sizeof(qcow_te))) { + fprintf(stderr, + "Short read: %lli/%llu\n", + i, + length * sizeof(qcow_te)); + goto bad; + } else if (i < 0) { + perror("read"); + goto bad; + } + + for (i=0; i < length; i++) + table[i] = htonll(disk_table[i]); + + return 1; + + bad: + return 0; +} + +static int qcow_load_l2(struct qcow *qcow, qcow_te index) +{ + int ret; + struct l1_entry *entry; + + entry = &qcow->table[index]; + + if (entry->loc_on_disk == 0) { + fprintf(stderr, + "*** ERROR: Trying to load non-existent L2 %llu\n", + index); + return 0; + } + + if (entry->table != NULL) + fprintf(stderr, + "*** WARNING: Reloading L2 table %llu\n", + index); + + entry->table = calloc(qcow_num_l2(&qcow->header), + sizeof(qcow_te)); + + if (entry->table == NULL) + return 0; + + ret = qcow_read_table(qcow->fd, + entry->loc_on_disk, + qcow_num_l2(&qcow->header), + entry->table); + + return ret; +} + +/* + * Allocate a new L2 table on disk + */ +static int alloc_new_l2_on_disk(struct qcow *qcow, uint64_t l1_entry) +{ + uint64_t num, count, j; + struct l1_entry *entry; + + if (qcow_num_l2(&qcow->header) < qcow_block_size(&qcow->header)) + num = qcow_block_size(&qcow->header) / + qcow_num_l2(&qcow->header); + + else + num = 1; + + qcow->l1_dirty = 1; + + entry = &qcow->table[l1_entry]; + + entry->dirty = 1; + entry->table = calloc(qcow_num_l2(&qcow->header), sizeof(qcow_te)); + entry->loc_on_disk = qcow->next_avail_block; + qcow_seen_block(qcow, entry->loc_on_disk, + qcow_num_l2(&qcow->header) * sizeof(qcow_te)); + + printf("New L2 entry %llu @ %llu\n", + l1_entry, entry->loc_on_disk); + + /* We try to distribute the extra to other non-allocated L2s, + so that we don''t get too far away from our block + alignment */ + count = num - 1; + + /* Right now, we ignore the case that we might''ve allocated + too much space and didn''t use it all. In the normal case + of 512-byte blocks, this isn''t an issue */ + return 1; /* FIXME */ + + j = 0; + while ((count > 0) && (j < qcow_num_l1(&qcow->header))) { + if (qcow->table[j].loc_on_disk == 0) { + qcow->table[j].loc_on_disk = qcow->next_avail_block; + qcow_seen_block(qcow, + qcow->table[j].loc_on_disk, + qcow_num_l2(&qcow->header) * + sizeof(qcow_te)); + count--; + } + j++; + } + + return 1; +} + +/* Write out any dirty L2 tables, and the L1 table if needed */ +int qcow_write_tables(struct qcow *qcow) +{ + uint64_t l1_size; + uint64_t l2_size; + qcow_te *l1_table; + uint64_t i; + int ret; + + l1_size = qcow_num_l1(&qcow->header); + l2_size = qcow_num_l2(&qcow->header); + + for (i=0; i < l1_size; i++) { + + if (qcow->table[i].dirty && qcow->table[i].loc_on_disk) { + qcow->table[i].dirty = 0; + if (OPS_DEBUG) + fprintf(stderr, "writing L2 %llu @ %llu\n", + i, qcow->table[i].loc_on_disk); + ret = qcow_write_table(qcow->fd, + qcow->table[i].loc_on_disk, + l2_size, + qcow->table[i].table); + + if (ret != 1) { + fprintf(stderr, + NAME "failed to write L2 %i\n", i); + return 0; + } + } + } + + if (qcow->l1_dirty) { + l1_table = (qcow_te *)calloc(l1_size, + sizeof(qcow_te)); + + for (i=0; i < l1_size; i++) { + l1_table[i] = qcow->table[i].loc_on_disk; + if (l1_table[i]) + printf("Writing L1 entry %llu: %llu\n", + i, l1_table[i]); + } + ret = qcow_write_table(qcow->fd, qcow->header.l1_table_offset, + l1_size, l1_table); + + free(l1_table); + + qcow->l1_dirty = 0; + + if (ret != 1) { + fprintf(stderr, NAME "failed to write L1\n"); + return 0; + } + } + + return 1; +} + +/* + * Read in the L1 table. We read L2 tables as needed based on the + * information in the l1. + */ +int qcow_read_tables(struct qcow *qcow) +{ + int i; + uint64_t numL1; + qcow_te *disk_table; + + numL1 = qcow_num_l1(&qcow->header); + + disk_table = (qcow_te *)calloc(numL1, + sizeof(qcow_te)); + + dio_lseek(qcow->fd, qcow->header.l1_table_offset, SEEK_SET); + dio_read(qcow->fd, disk_table, numL1 * sizeof(qcow_te)); + + for (i=0; i < numL1; i++) { + + qcow->table[i].table = NULL; + qcow->table[i].dirty = 0; + + qcow->table[i].loc_on_disk = ntohll(disk_table[i]); + } + + if (OPS_DEBUG) + qcow_print_header(qcow); + + return 1; +} + + +/* Map the block of logical to physical */ +int qcow_make_mapping(struct qcow *qcow, uint64_t logical, uint64_t physical) +{ + uint64_t l1_entry; + uint64_t l2_entry; + + l1_entry = qcow_get_l1_entry(qcow, logical); + l2_entry = qcow_get_l2_entry(qcow, logical); + + if (l1_entry >= qcow_num_l1(&qcow->header)) { + fprintf(stderr, "*** ERROR: L1 of %llu >= %llu, max\n", + l1_entry, + qcow_num_l1(&qcow->header)); + fprintf(stderr, " Offending new map: %llu -> %llu\n", + logical, physical); + return 0; + } + + if (l2_entry >= qcow_num_l2(&qcow->header)) { + fprintf(stderr, "*** ERRROR: L2 of %llu >= %llu, max\n", + l2_entry, + qcow_num_l2(&qcow->header)); + return 0; + } + + if (logical >= LOG_ABOVE) { + printf("Making mapping for %llu -> %llu\n" + " l1e: %llu l2e: %llu\n", + logical, physical, l1_entry, l2_entry); + } + + if (qcow->table[l1_entry].table == NULL) { + if (qcow->table[l1_entry].loc_on_disk == 0) { + + if (!alloc_new_l2_on_disk(qcow, l1_entry)) { + fprintf(stderr, + "Failed to alloc table for %llu\n", + l1_entry); + return 0; + } + + qcow->l1_dirty = 1; + } else { + if (!qcow_load_l2(qcow, l1_entry)) { + fprintf(stderr, + "Failed to load L2 %llu\n", + l1_entry); + return 0; + } + } + } + + qcow->table[l1_entry].table[l2_entry] = physical; + qcow->table[l1_entry].dirty = 1; + + return 1; +} + +/* + * Make a new mapping for @logical by selecting the next available + * block + */ +uint64_t qcow_make_new_mapping(struct qcow *qcow, uint64_t logical) +{ + uint64_t new_block; + + new_block = qcow->next_avail_block; + + /* Align to the next 512-byte boundary */ + /* FIXME: Do we want to leak space like this? */ + if (new_block % 512) { + new_block += (512 - (new_block % 512)); + } + + qcow_seen_block(qcow, new_block, qcow_block_size(&qcow->header)); + + if (qcow_make_mapping(qcow, logical, new_block)) + return new_block; + else + return 0; /* Block 0 is error, because it contains the + header and L1, etc*/ + +} + +/* Return the location that @logical maps to, 0 if unmapped */ +uint64_t qcow_get_mapping(struct qcow *qcow, uint64_t logical) +{ + uint64_t l1_entry; + uint64_t l2_entry; + uint64_t physical; + qcow_te *l2_table; + + l1_entry = qcow_get_l1_entry(qcow, logical); + l2_entry = qcow_get_l2_entry(qcow, logical); + + if (l1_entry >= qcow_num_l1(&qcow->header)) { + fprintf(stderr, "*** ERROR: L1 of %llu >= %llu, max\n", + l1_entry, + qcow_num_l1(&qcow->header)); + fprintf(stderr, " Offending map request: %llu (%llx)\n", + logical, logical); + fprintf(stderr, " l1_entry: %llu\n", l1_entry); + fprintf(stderr, " l2_entry: %llu\n", l2_entry); + + return 0; + } + + if (l2_entry >= qcow_num_l2(&qcow->header)) { + fprintf(stderr, "*** ERROR: L2 of %llu >= %llu, max\n", + l2_entry, + qcow_num_l2(&qcow->header)); + return 0; + } + + if (qcow->table[l1_entry].table == NULL) { + if (qcow->table[l1_entry].loc_on_disk == 0) { + /* Not mapped if no L2 table */ + return 0; + } else { + /* Need to load it in */ + if (!qcow_load_l2(qcow, l1_entry)) + return 0; + } + } + + l2_table = qcow->table[l1_entry].table; + + physical = l2_table[l2_entry]; + + if (OPS_DEBUG && 0) { + printf("L1 entry: %llu\n", l1_entry); + printf("L2 entry: %llu\n", l2_entry); + printf("Physical Block: %llu (%llx)\n", physical, physical); + printf("\n"); + } + + return physical; +} + +/* + * Return 1 if any of the tables need flushing, 0 otherwise + */ +int qcow_is_anything_dirty(struct qcow *qcow) +{ + uint64_t i; + + if (qcow->l1_dirty) + return 1; + + for (i=0; i < qcow_num_l1(&qcow->header); i++) { + if (qcow->table[i].dirty) + return 1; + } + + return 0; +} + +/* + * Print out the header in human-readable format + */ +void qcow_print_header(struct qcow *qcow) +{ + printf("=== QCOW HEADER ===\n"); + printf("Magic: %x (should be %x)\n", qcow->header.magic, + QCOW_MAGIC); + printf("Version: %x\n", qcow->header.version); + printf("File Offset: %llu\n", qcow->header.backing_filename_offset); + printf("File Size: %u\n", qcow->header.backing_filename_size); + printf("Mod Time: %u\n", qcow->header.mtime); + printf("Size: %llu\n", qcow->header.size); + printf("Cluster Bits: %hhu\n", qcow->header.cluster_bits); + printf("L2 Bits: %hhu\n", qcow->header.l2_bits); + printf("Crypto Method: %x\n", qcow->header.crypto_method); + printf("L1 Table @: %llu\n", qcow->header.l1_table_offset); + printf("Backing File: %s\n", qcow->filename); + + printf("\nCalculated Information:\n"); + + printf("Next Avail: %llu\n", qcow->next_avail_block); + printf("Cluster Mask: %016llx\n", qcow_cmask(&qcow->header)); + printf("L2 Mask: %016llx\n", qcow_l2mask(&qcow->header)); + printf("L1 Mask: %016llx\n", qcow_l1mask(&qcow->header)); + printf("Num L1: %llu\n", qcow_num_l1(&qcow->header)); + printf("Num L2: %llu\n", qcow_num_l2(&qcow->header)); +} diff -r 6a7ea5a35543 -r 77a518e8f44d tools/cowd/plugins/qcow/qcow_ops.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tools/cowd/plugins/qcow/qcow_ops.h Mon Aug 21 15:03:10 2006 -0500 @@ -0,0 +1,15 @@ +#ifndef __QCOW_OPS_H +#define __QCOW_OPS_H + +#include <stdint.h> + +#include "qcow.h" + +int qcow_rw_header(int fd, struct qcow_header *header, int write); +int qcow_read_backing_info(struct qcow *qcow); +int qcow_read_l1_table(struct qcow *qcow); +uint64_t qcow_get_mapping(struct qcow *qcow, uint64_t logical); + +void qcow_print_header(struct qcow *qcow); + +#endif diff -r 6a7ea5a35543 -r 77a518e8f44d tools/cowd/plugins/qcow/qcow_plugin.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tools/cowd/plugins/qcow/qcow_plugin.c Mon Aug 21 15:03:10 2006 -0500 @@ -0,0 +1,519 @@ +/* + * Copyright (C) International Business Machines Corp., 2006 + * Author: Dan Smith <danms@us.ibm.com> + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License. See the file COPYING in the main directory + * of this archive for more details. + * + */ + +#define _LARGEFILE64_SOURCE + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <unistd.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <getopt.h> +#include <stdint.h> + +#include <cowd_plugin.h> + +#include <libdevmapper.h> + +#include "qcow.h" +#include "qcow_ops.h" + +#define MAX_PATH 256 +#define ERR_LEN 256 + +#define LOOP_SET_FD 0x4C00 +#define LOOP_CLR_FD 0x4C01 + +char errmsg[ERR_LEN]; + +struct qcow_private { + struct qcow qcow; + int init; + + char base_path[MAX_PATH]; + char qcow_path[MAX_PATH]; + + char base_dev[MAX_PATH]; + char qcow_dev[MAX_PATH]; + + dev_t base_dev_t; + dev_t qcow_dev_t; + + unsigned long qcow_size; + + int debug; +}; + +static dev_t *qcow_get_devs(struct cow_device *dev, int *count) +{ + struct qcow_private *prv = dev->plugin_private; + struct stat s; + dev_t *devs; + + devs = malloc(sizeof(*dev) * 2); + if (!devs) { + count = 0; + return NULL; + } + + devs[0] = prv->base_dev_t; + devs[1] = prv->qcow_dev_t; + + *count = 2; + return devs; +} + +/* + * We use the loop driver to make the base and cow files available to + * the kernel. The problem is, we can''t grow the cow file underneath + * the loop device. This function "inflates" the cow file to the + * maximum size it could be before we hook it up to the loop driver. + * This isn''t *too* bad because it''s just a sparse file. + */ +static int qcow_inflate(struct qcow_private *prv) +{ + struct qcow_header header; + int fd; + uint64_t offset; + + fd = open(prv->qcow_path, O_RDWR | O_LARGEFILE); + if (fd < 0) { + perror("open"); + return PLUGIN_FAIL; + } + + if (read(fd, &header, sizeof(header)) != sizeof(header)) { + perror("read"); + return PLUGIN_FAIL; + } + + header.size = ntohll(header.size); + + offset = lseek64(fd, header.size, SEEK_SET); + + if (prv->debug) + printf("## Inflating %s to %llu (%llu)\n", + prv->qcow_path, offset, header.size); + + if (offset != header.size) { + fprintf(stderr, "Failed to lseek to %llu\n", header.size); + return PLUGIN_FAIL; + } + + if (write(fd, &fd, 1) != 1) { + perror("write"); + close(fd); + return PLUGIN_FAIL; + } + + close(fd); + return PLUGIN_OK; +} + +/* + * This "deflates" the sparse cow file back to the appropriate size + */ +static void qcow_deflate(struct qcow_private *prv) +{ + printf("### Truncating %s to %llu\n", + prv->qcow_path, prv->qcow.next_avail_block); + truncate(prv->qcow_path, prv->qcow.next_avail_block); +} + +/* + * Process the subset of arguments we were given + */ +static void qcow_process_args(int argc, char **argv, struct qcow_private *prv) +{ + int c, optidx = 0; + int i; + static struct option lopts[] = { + {"qcow", 1, 0, ''q''}, + {"base", 1, 0, ''b''}, + {"debug", 0, 0, ''d''}, + {0, 0, 0, 0 } + }; + + strcpy(prv->qcow_path, argv[1]); + return; + + /* getopt doesn''t like the way I''ve arranged the strings for + some reason, so we just use the first arg as the qcow file + for now */ + + for (i=0; i<argc; i++) + fprintf(stderr, "Arg %i: %s\n", i, argv[i]); + + while (1) { + fprintf(stderr, "Going... %s\n", argv[0]); + c = getopt_long(argc, argv, "db:q:", lopts, &optidx); + if (c == -1) + break; + + fprintf(stderr, "Got: %c\n", c); + + switch (c) { + + case ''b'': + strncpy(prv->base_path, optarg, MAX_PATH); + break; + case ''q'': + strncpy(prv->qcow_path, optarg, MAX_PATH); + break; + case ''d'': + prv->debug = 1; + break; + }; + + } +} + +#if 0 +/* + * Hook up @path to @dev + * + * NB: This doesn''t work at the moment! + */ +static int qcow_loop_setup(const char *path, char *dev) +{ + int fd, lfd; + int i, ret; + char ldevpath[256]; + + fd = open(path, O_RDWR); + if (fd < 0) + return 0; + + for (i = 0; i < 8; i++) { + snprintf(ldevpath, 256, "/dev/loop%i", i); + lfd = open(ldevpath, O_RDWR); + if (lfd < 0) { + fprintf(stderr, "Failed to open %s\n", ldevpath); + continue; + } + ret = ioctl(lfd, LOOP_SET_FD, fd); + close(lfd); + if (ret == 0) { + strcpy(dev, ldevpath); + close(fd); + return 1; + } else { + fprintf(stderr, "ioctl() failed:\n"); + perror(ldevpath); + } + } + + close(fd); + + printf("No free loops for file: %s\n", path); + + return 0; +} + +/* + * Detach @dev from its backing file + * + * NB: This isn''t used at the moment! + */ +static int qcow_loop_destroy(const char *dev) +{ + int lfd; + int ret; + + lfd = open(dev, O_RDWR); + if (lfd < 0) + return 0; + + ret = ioctl(lfd, LOOP_CLR_FD, 0); + + if (ret == 0) + return 1; + else + return 0; +} +#endif + +/* + * Call the appropriate qcow functions to initialize all our metadata + * and accounting information. + */ +static int qcow_read_metadata(struct cow_device *dev, int force_init) +{ + struct qcow_private *prv = dev->plugin_private; + int ret; + + if (force_init) { + /* At some point, we want to be able to format a + qcow file outselves */ + fprintf(stderr, "The QCOW plugin doesn''t support init yet\n"); + return PLUGIN_FAIL; + } + + if (! prv->init) { + if (prv->debug) + printf("Init qcow from header\n"); + ret = qcow_init_from_header(&prv->qcow); + if (! ret) + return PLUGIN_FAIL; + + dev->block_size = 1 << prv->qcow.header.cluster_bits; + if (prv->debug) + fprintf(stderr, "Block size: %u\n", dev->block_size); + + dev->blocks = get_device_blocks(prv->base_dev) / + (dev->block_size / 512); + + if (prv->debug) + fprintf(stderr, "Blocks: %lu\n", dev->blocks); + } + + if (prv->debug) + printf("Reading tables\n"); + + ret = qcow_read_tables(&prv->qcow); + if (! ret) + return PLUGIN_FAIL; + + prv->init = 1; + + return PLUGIN_OK; + +} + +static int qcow_init(struct cow_device *dev, int debug) +{ + + struct qcow_private *prv; + struct stat s; + + if (debug) + printf(NAME "init\n"); + + memset(errmsg, 0, ERR_LEN); + + prv = (struct qcow_private *)malloc(sizeof(*prv)); + prv->qcow.fd = -1; + prv->init = 0; + prv->debug = debug; + prv->base_path[0] = ''\0''; + prv->qcow_path[0] = ''\0''; + qcow_init_qcow(&prv->qcow); + + qcow_process_args(dev->plugin_num_args, dev->plugin_args, prv); + + if (prv->qcow_path[0] == ''\0'') { + snprintf(errmsg, ERR_LEN, "Path to qcow file is required!\n"); + return PLUGIN_FAIL; + } + + prv->qcow.next_avail_block = get_file_size(prv->qcow_path); + prv->qcow_size = get_file_size(prv->qcow_path); + + /* Dirty hack to get around the fact that loop-backed files + can''t grow */ + qcow_inflate(prv); + + if (prv->debug) + printf("Opening %s\n", prv->qcow_path); + + prv->qcow.fd = dio_open(prv->qcow_path, O_RDWR); + if (prv->qcow.fd < 0) { + snprintf(errmsg, ERR_LEN, + "Failed to open %s with O_DIRECT: %s\n", + prv->qcow_path, strerror(prv->qcow.fd)); + return PLUGIN_FAIL; + } + + if (prv->debug) + printf("### Preset next_avail_block to %llu\n", + prv->qcow.next_avail_block); + + /* Init the qcow header information */ + qcow_rw_header(prv->qcow.fd, &prv->qcow.header, 0); + qcow_read_backing_info(&prv->qcow); + qcow_print_header(&prv->qcow); + + /* FIXME: REMOVE */ + qcow_print_header(&prv->qcow); + + /* Get the files/devices setup for device-mapper */ + if (prv->base_path[0] == ''\0'') + strcpy(prv->base_path, prv->qcow.filename); + + /* FIXME: Need real loop support!! */ + + if (is_file(prv->base_path)) { + sprintf(prv->base_dev, "/dev/loop0"); + loop_destroy(prv->base_dev); + loop_setup(prv->base_dev, prv->base_path); + } + + if (is_file(prv->qcow_path)) { + sprintf(prv->qcow_dev, "/dev/loop1"); + loop_destroy(prv->qcow_dev); + loop_setup(prv->qcow_dev, prv->qcow_path); + } + +#if 0 + if (is_file(prv->qcow_path)) + qcow_loop_setup(prv->qcow_path, prv->qcow_dev); + else + strcpy(prv->qcow_dev, prv->qcow_path); + + if (is_file(prv->base_path)) + qcow_loop_setup(prv->base_path, prv->base_dev); + else + strcpy(prv->base_dev, prv->base_path); +#endif + + stat(prv->base_dev, &s); + prv->base_dev_t = s.st_rdev; + + stat(prv->qcow_dev, &s); + prv->qcow_dev_t = s.st_rdev; + + if (prv->debug) + printf("Base Device: %s Cow Device: %s\n", + prv->base_dev, prv->qcow_dev); + + dev->plugin_private = prv; + + return qcow_read_metadata(dev, 0); + +} + +static void qcow_cleanup(struct cow_device *dev) +{ + /* FIXME: Do something more useful here */ + struct qcow_private *prv = dev->plugin_private; + + close(prv->qcow.fd); + + /* FIXME: Need to check if these are actually loops */ + loop_destroy(prv->base_dev); + loop_destroy(prv->qcow_dev); + + qcow_deflate(prv); + + /* Free some stuff */ +} + + +static int qcow_write_metadata(struct cow_device *dev) +{ + struct qcow_private *prv = dev->plugin_private; + int ret; + + if (prv->debug) + printf("Writing metadata!\n"); + + ret = qcow_write_tables(&prv->qcow); + + if (! ret) + return PLUGIN_FAIL; + else + return PLUGIN_OK; +} + +static int qcow_map_block(struct cow_device *dev, + struct dmu_map_data *map) +{ + + struct qcow_private *prv = dev->plugin_private; + uint64_t tmp; + uint64_t org, org_block; + + /* + * FIXME: This is super ugly + */ + + /* Convert to start byte position of cluster */ + org_block = dmu_map_get_block(map); + org = org_block << prv->qcow.header.cluster_bits; + + if (prv->debug && 0) + fprintf(stderr, "Looking for existing map for %llu\n", + org_block); + + tmp = qcow_get_mapping(&prv->qcow, org); + if ((tmp == 0) && dmu_map_is_write(map)) { + /* NEW mapping for WRITE access: + Remap to somewhere in the cow device */ + if (prv->debug && 0) + fprintf(stderr, "Not found, mapping...\n"); + + tmp = qcow_make_new_mapping(&prv->qcow, (uint64_t)org); + + dmu_map_set_block(map, qcow_align_to_block(&prv->qcow, tmp)); + dmu_map_set_offset(map, qcow_calc_offset(&prv->qcow, tmp)); + + dmu_map_set_copy_src_dev(map, prv->base_dev_t); + dmu_map_set_dest_dev(map, prv->qcow_dev_t); + + } else if ((tmp == 0) && !dmu_map_is_write(map)) { + /* NEW mapping for READ access: + Remap to the same place in the base device */ + dmu_map_set_block(map, org_block); + + dmu_map_set_dest_dev(map, prv->base_dev_t); + + } else if ((tmp == 0) && dmu_map_is_write(map)) { + /* This should not be allowed */ + return PLUGIN_FAIL; + } else { + /* OLD mapping (access doesn''t matter): + Remap to the correct location in the cow device */ + dmu_map_set_block(map, + qcow_align_to_block(&prv->qcow, tmp)); + dmu_map_set_offset(map, + qcow_calc_offset(&prv->qcow, tmp)); + if (prv->debug) + fprintf(stderr, + "Found existing map for %llu: %llx (%llu)\n", + org, tmp, tmp); + + dmu_map_set_dest_dev(map, prv->qcow_dev_t); + } + + if (tmp > prv->qcow.header.size) { + snprintf(errmsg, ERR_LEN, + "Tried to map a block beyond end of device: " + "%llu > %llu\n", + tmp, prv->qcow.header.size); + return PLUGIN_FAIL; + } + + return PLUGIN_OK; +} + +static bool qcow_need_flush(struct cow_device *dev) +{ + + struct qcow_private *prv = dev->plugin_private; + + return qcow_is_anything_dirty(&prv->qcow); +} + +int load_plugin(struct cowd_plugin *p) +{ + + p->init_plugin = qcow_init; + p->write_metadata = qcow_write_metadata; + p->map_prepare = qcow_map_block; + p->cleanup_plugin = qcow_cleanup; + p->need_flush = qcow_need_flush; + p->errmsg = errmsg; + p->get_devs = qcow_get_devs; + + return 1; + +} diff -r 6a7ea5a35543 -r 77a518e8f44d tools/cowd/plugins/qcow/qcow_test.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tools/cowd/plugins/qcow/qcow_test.c Mon Aug 21 15:03:10 2006 -0500 @@ -0,0 +1,74 @@ +/* + * Copyright (C) International Business Machines Corp., 2006 + * Author: Dan Smith <danms@us.ibm.com> + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License. See the file COPYING in the main directory + * of this archive for more details. + * + */ + +#include <stdio.h> +#include <fcntl.h> +#include <stdlib.h> +#include <stdint.h> + +#include "qcow.h" +#include "qcow_ops.h" + +#define READ_COUNT 256 + +int main(int argc, char **argv) +{ + + int fd; + struct qcow qcow; + uint64_t physical; + uint64_t tmp; + char buf[READ_COUNT]; + int i; + + fd = open(argv[1], O_RDWR); + if (fd < 0) { + perror(argv[1]); + exit(1); + } + + qcow_init_qcow(&qcow); + + qcow.fd = fd; + + qcow_rw_header(fd, &qcow.header, 0); + + qcow_read_backing_info(&qcow); + + qcow_init_from_header(&qcow); + + qcow_read_tables(&qcow); + + qcow_print_header(&qcow); + +// for (i=2; i < argc; i++) { +// tmp = strtoull(argv[i], NULL, 10); +// physical = qcow_get_mapping(&qcow, tmp); +// +// if (physical != 0) { +// printf("Mapping for %llu is: %llu\n", +// tmp, physical); +// +// lseek(fd, physical, SEEK_SET); +// read(fd, buf, READ_COUNT); +// buf[READ_COUNT-1] = 0; +// printf(" Data is: %s ...\n", buf); +// } else { +// physical = qcow_make_new_mapping(&qcow, tmp); +// printf("New mapping for %llu is: %llu\n", +// tmp, physical); +// } +// +// printf("\n"); +// } + +// qcow_write_tables(&qcow); + +} _______________________________________________ Xen-devel mailing list Xen-devel@lists.xensource.com http://lists.xensource.com/xen-devel
Ryan Grimm
2006-Aug-25 21:24 UTC
[Xen-devel] [PATCH 5 of 6] qcow plugin for dm-userspace userspace tool
Signed-off-by: Ryan Grimm <grimm@us.ibm.com> Signed-off-by: Dan Smith <danms@us.ibm.com> # HG changeset patch # User Ryan Grimm <grimm@us.ibm.com> # Date 1156536096 18000 # Node ID be4574d288030b64d4623dd33505d0990185a6b9 # Parent a3656acd770b4f21ad54fa961032ff39562058eb qcow plugin for dm-userspace userspace tool diff -r a3656acd770b -r be4574d28803 tools/cowd/configure.in --- a/tools/cowd/configure.in Fri Aug 25 15:01:35 2006 -0500 +++ b/tools/cowd/configure.in Fri Aug 25 15:01:36 2006 -0500 @@ -95,11 +95,13 @@ AC_SUBST(GLOBAL_CFLAGS) AC_CONFIG_FILES([Makefile plugins/Makefile + plugins/qcow/Makefile plugins/dscow/Makefile]) # This just makes it easier to run cowd from the source directory # for testing mkdir -p lib +ln -sf ../plugins/qcow/.libs/libcowd_qcow.so.0 lib/libcowd_qcow.so ln -sf ../plugins/dscow/.libs/libcowd_dscow.so.0 lib/libcowd_dscow.so ln -sf ../plugins/dscow/.libs/libcowd_dscow.la lib/libcowd_dscow.la diff -r a3656acd770b -r be4574d28803 tools/cowd/plugins/Makefile.am --- a/tools/cowd/plugins/Makefile.am Fri Aug 25 15:01:35 2006 -0500 +++ b/tools/cowd/plugins/Makefile.am Fri Aug 25 15:01:36 2006 -0500 @@ -1,1 +1,1 @@ SUBDIRS = dscow -SUBDIRS = dscow +SUBDIRS = dscow qcow diff -r a3656acd770b -r be4574d28803 tools/cowd/plugins/qcow/Makefile.am --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tools/cowd/plugins/qcow/Makefile.am Fri Aug 25 15:01:36 2006 -0500 @@ -0,0 +1,3 @@ +lib_LTLIBRARIES = libcowd_qcow.la +libcowd_qcow_la_CFLAGS = -I../.. -I../../../../module @GLOBAL_CFLAGS@ +libcowd_qcow_la_SOURCES = qcow_plugin.c qcow_ops.c qcow.h qcow_ops.h diff -r a3656acd770b -r be4574d28803 tools/cowd/plugins/qcow/qcow.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tools/cowd/plugins/qcow/qcow.h Fri Aug 25 15:01:36 2006 -0500 @@ -0,0 +1,167 @@ +#ifndef __QCOW_H +#define __QCOW_H + +#include <sys/types.h> +#include <stdint.h> + +#define NAME "qcow: " +#define L2_CACHE_SIZE 16 + +#define QCOW_MAGIC ((''Q'' << 24) | (''F'' << 16) | (''I'' << 8) | 0xFB) + +typedef uint64_t qcow_te ; + +struct qcow_header { + uint32_t magic; + uint32_t version; + uint64_t backing_filename_offset; + uint32_t backing_filename_size; + uint32_t mtime; + uint64_t size; + uint8_t cluster_bits; + uint8_t l2_bits; + uint32_t crypto_method; + uint64_t l1_table_offset; +}; + +struct l1_entry { + int dirty; + uint64_t loc_on_disk; + qcow_te *table; +}; + +struct l2_cache { + uint64_t l1_index; + struct l1_entry *l2; + uint32_t hits; +}; + +struct qcow { + struct qcow_header header; + struct l1_entry *table; + int l1_dirty; + + char *filename; + + struct l2_cache *l2_cache[L2_CACHE_SIZE]; + int l2_cache_counter; + + uint64_t next_avail_block; + + /* This is the offset we use for the file. The problem is, + the kernel module expects to remap whole aligned blocks. + Since we can interleave L2 blocks with differently-sized */ + uint64_t offset; + + int fd; + +}; + +static inline uint64_t qcow_num_l1(struct qcow_header *h) +{ + uint64_t nonl1 = (1 << h->cluster_bits) * (1 << h->l2_bits); + + if (h->size % nonl1) + return (h->size / nonl1) + 1; + else + return (h->size / nonl1); +} + +static inline uint64_t qcow_num_l2(struct qcow_header *h) +{ + return 1 << h->l2_bits; +} + +static inline uint64_t qcow_block_size(struct qcow_header *h) +{ + return 1 << h->cluster_bits; +} + +static inline uint64_t qcow_cmask(struct qcow_header *h) +{ + return (1 << h->cluster_bits) - 1; +} + +static inline uint64_t qcow_l2mask(struct qcow_header *h) +{ + return ((1 << h->l2_bits) - 1) << h->cluster_bits; +} + +static inline uint64_t qcow_l1mask(struct qcow_header *h) +{ + return ~(qcow_cmask(h) | qcow_l2mask(h)); +} + +/* These are i386, little-endian. + * Clearly this needs to be generalized. + */ +static inline uint64_t ntohll(uint64_t value) +{ + uint32_t a, b; + + a = value >> 32; + b = value & 0xFFFFFFFF; + + return (((uint64_t)ntohl(b)) << 32) | ntohl(a); +} + +static inline uint64_t htonll(uint64_t value) +{ + uint32_t a, b; + + a = value >> 32; + b = value & 0xFFFFFFFF; + + return (((uint64_t)htonl(b)) << 32) | htonl(a); +} + +static inline uint64_t qcow_get_l1_entry(struct qcow *qcow, uint64_t sector) +{ + uint64_t entry; + + entry = sector & qcow_l1mask(&qcow->header); + entry = entry >> + (qcow->header.l2_bits + qcow->header.cluster_bits); + +// printf("L1 entry: %llx %llx\n", +// sector & qcow_l1mask(&qcow->header), +// entry); + + return entry; +} + +static inline uint64_t qcow_get_l2_entry(struct qcow *qcow, uint64_t sector) +{ + return (sector & qcow_l2mask(&qcow->header)) >> + (qcow->header.cluster_bits); +} + +static inline uint64_t qcow_make_te(struct qcow *qcow, + uint64_t l1_entry, + uint64_t l2_entry, + uint64_t index) +{ + uint64_t te = 0; + + te |= (l1_entry << (qcow->header.l2_bits + qcow->header.cluster_bits)); + te |= (l2_entry << (qcow->header.cluster_bits)); + te |= (index & qcow_cmask(&qcow->header)); + + return te; +} + +static inline uint64_t qcow_calc_offset(struct qcow *qcow, + uint64_t sector) +{ + return sector % qcow_block_size(&qcow->header); +} + +static inline uint64_t qcow_align_to_block(struct qcow *qcow, + uint64_t sector) +{ + return sector / qcow_block_size(&qcow->header); +} + + + +#endif diff -r a3656acd770b -r be4574d28803 tools/cowd/plugins/qcow/qcow_ops.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tools/cowd/plugins/qcow/qcow_ops.c Fri Aug 25 15:01:36 2006 -0500 @@ -0,0 +1,603 @@ +/* + * Copyright (C) International Business Machines Corp., 2006 + * Author: Dan Smith <danms@us.ibm.com> + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License. See the file COPYING in the main directory + * of this archive for more details. + * + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <time.h> +#include <sys/types.h> +#include <unistd.h> + +#include <netinet/in.h> /* for endian ops */ + +#include "qcow.h" +#include "qcow_ops.h" + +#define OPS_DEBUG 0 + +#define LOG_ABOVE (128 << 20) + +/* + * Update our pointer to the end of the highest-block, if appropriate. + * This is used to allocate new blocks and tables, because it''s the + * end of the last chunk of real data. + */ +static int qcow_seen_block(struct qcow *qcow, + qcow_te sector, + qcow_te block_size) +{ + uint64_t end; + + end = sector + block_size; + + if (end > qcow->header.size) { + fprintf(stderr, + "*** ERROR: Saw block %llu beyond end of %llu\n", + end, qcow->header.size); + return -1; + } + + if (qcow->next_avail_block < end) { + qcow->next_avail_block = end; + return 1; + } else { + return 0; + } +} + +void qcow_init_qcow(struct qcow *qcow) +{ + qcow->table = NULL; + qcow->filename = NULL; + qcow->l1_dirty = 0; + qcow->next_avail_block = 0; +} + +/* + * Read in the qcow header from @fd, storing it in @header + * If @w!=0, then write instead. (Although not yet :) + */ +int qcow_rw_header(int fd, struct qcow_header *header, int w) +{ + int ret; + loff_t offset = 0; + + if (dio_lseek(fd, offset, SEEK_SET) != offset) { + fprintf(stderr, "Offset was %lli instead of 0\n", offset); + perror("dio_lseek"); + return 0; + } + + if (w) + ret = dio_write(fd, header, sizeof(*header)); + else + ret = dio_read(fd, header, sizeof(*header)); + + if (ret == 0) { + fprintf(stderr, "Read 0 bytes for header (%i)!\n", + sizeof(*header)); + return 0; + } else if (ret < 0) { + perror("qcow-header"); + return 0; + } + + /* Convert endianess */ + header->magic = ntohl(header->magic); + header->version = ntohl(header->version); + header->backing_filename_offset + ntohll(header->backing_filename_offset); + header->backing_filename_size = ntohl(header->backing_filename_size); + header->mtime = ntohl(header->mtime); + header->size = ntohll(header->size); + header->crypto_method = ntohl(header->crypto_method); + header->l1_table_offset = ntohll(header->l1_table_offset); + + return 1; +} + +/* + * Read the backing filename information + */ +int qcow_read_backing_info(struct qcow *qcow) +{ + int ret; + + qcow->filename = (char *)malloc(qcow->header.backing_filename_size+1); + + ret = dio_lseek(qcow->fd, + qcow->header.backing_filename_offset, + SEEK_SET); + if (ret != qcow->header.backing_filename_offset) { + fprintf(stderr, "lseek to %llu\n", + qcow->header.backing_filename_offset); + perror("qcow-lseek"); + return 0; + } + + ret = dio_read(qcow->fd, qcow->filename, + qcow->header.backing_filename_size); + if (ret != qcow->header.backing_filename_size) { + fprintf(stderr, "qcow-read: EOF while reading filename\n"); + return 0; + } + qcow->filename[qcow->header.backing_filename_size] = ''\0''; + + return 1; +} + +/* + * Allocate memory for the L1 table + */ +int qcow_init_from_header(struct qcow *qcow) +{ + qcow->table = (struct l1_entry *)calloc(qcow_num_l1(&qcow->header), + sizeof(struct l1_entry)); + + if (qcow->table == NULL) { + fprintf(stderr, + NAME "*** failed to calloc %llu x %i for table\n", + qcow_num_l1(&qcow->header), sizeof(qcow_te*)); + return 0; + } + + return 1; +} + +/* + * Write @table to @fd:@pos, @length entries + */ +static int qcow_write_table(int fd, uint64_t pos, + uint64_t length, qcow_te *table) +{ + qcow_te *disk_table; + int64_t i; + + disk_table = (qcow_te *)calloc(length, + sizeof(qcow_te)); + if (!disk_table) + goto bad; + + for (i=0; i < length; i++) + disk_table[i] = htonll(table[i]); + + if (dio_lseek(fd, pos, SEEK_SET) != pos) { + perror("write_table lseek"); + goto bad; + } + + if (OPS_DEBUG) + fprintf(stderr, "Writing %llu bytes @ %llu\n", + length * sizeof(qcow_te), pos); + + i = dio_write(fd, disk_table, length * sizeof(qcow_te)); + + if (i != (length * sizeof(qcow_te))) { + fprintf(stderr, + "Short write: %lli/%llu\n", + i, + length * sizeof(qcow_te)); + + goto bad; + } else if (i < 0) { + perror("write"); + goto bad; + } + + return 1; + + bad: + return 0; +} + +/* + * Read @table from @fd:@pos, @length entries + */ +static int qcow_read_table(int fd, uint64_t pos, + uint64_t length, qcow_te *table) +{ + qcow_te *disk_table; + int64_t i; + + disk_table = (qcow_te *)calloc(length, + sizeof(qcow_te)); + if (!disk_table) + goto bad; + + if (dio_lseek(fd, pos, SEEK_SET) != pos) { + perror("lseek"); + goto bad; + } + + i = dio_read(fd, disk_table, length * sizeof(qcow_te)); + + if (i != (length * sizeof(qcow_te))) { + fprintf(stderr, + "Short read: %lli/%llu\n", + i, + length * sizeof(qcow_te)); + goto bad; + } else if (i < 0) { + perror("read"); + goto bad; + } + + for (i=0; i < length; i++) + table[i] = htonll(disk_table[i]); + + return 1; + + bad: + return 0; +} + +static int qcow_load_l2(struct qcow *qcow, qcow_te index) +{ + int ret; + struct l1_entry *entry; + + entry = &qcow->table[index]; + + if (entry->loc_on_disk == 0) { + fprintf(stderr, + "*** ERROR: Trying to load non-existent L2 %llu\n", + index); + return 0; + } + + if (entry->table != NULL) + fprintf(stderr, + "*** WARNING: Reloading L2 table %llu\n", + index); + + entry->table = calloc(qcow_num_l2(&qcow->header), + sizeof(qcow_te)); + + if (entry->table == NULL) + return 0; + + ret = qcow_read_table(qcow->fd, + entry->loc_on_disk, + qcow_num_l2(&qcow->header), + entry->table); + + return ret; +} + +/* + * Allocate a new L2 table on disk + */ +static int alloc_new_l2_on_disk(struct qcow *qcow, uint64_t l1_entry) +{ + uint64_t num, count, j; + struct l1_entry *entry; + + if (qcow_num_l2(&qcow->header) < qcow_block_size(&qcow->header)) + num = qcow_block_size(&qcow->header) / + qcow_num_l2(&qcow->header); + + else + num = 1; + + qcow->l1_dirty = 1; + + entry = &qcow->table[l1_entry]; + + entry->dirty = 1; + entry->table = calloc(qcow_num_l2(&qcow->header), sizeof(qcow_te)); + entry->loc_on_disk = qcow->next_avail_block; + qcow_seen_block(qcow, entry->loc_on_disk, + qcow_num_l2(&qcow->header) * sizeof(qcow_te)); + + printf("New L2 entry %llu @ %llu\n", + l1_entry, entry->loc_on_disk); + + /* We try to distribute the extra to other non-allocated L2s, + so that we don''t get too far away from our block + alignment */ + count = num - 1; + + /* Right now, we ignore the case that we might''ve allocated + too much space and didn''t use it all. In the normal case + of 512-byte blocks, this isn''t an issue */ + return 1; /* FIXME */ + + j = 0; + while ((count > 0) && (j < qcow_num_l1(&qcow->header))) { + if (qcow->table[j].loc_on_disk == 0) { + qcow->table[j].loc_on_disk = qcow->next_avail_block; + qcow_seen_block(qcow, + qcow->table[j].loc_on_disk, + qcow_num_l2(&qcow->header) * + sizeof(qcow_te)); + count--; + } + j++; + } + + return 1; +} + +/* Write out any dirty L2 tables, and the L1 table if needed */ +int qcow_write_tables(struct qcow *qcow) +{ + uint64_t l1_size; + uint64_t l2_size; + qcow_te *l1_table; + uint64_t i; + int ret; + + l1_size = qcow_num_l1(&qcow->header); + l2_size = qcow_num_l2(&qcow->header); + + for (i=0; i < l1_size; i++) { + + if (qcow->table[i].dirty && qcow->table[i].loc_on_disk) { + qcow->table[i].dirty = 0; + if (OPS_DEBUG) + fprintf(stderr, "writing L2 %llu @ %llu\n", + i, qcow->table[i].loc_on_disk); + ret = qcow_write_table(qcow->fd, + qcow->table[i].loc_on_disk, + l2_size, + qcow->table[i].table); + + if (ret != 1) { + fprintf(stderr, + NAME "failed to write L2 %i\n", i); + return 0; + } + } + } + + if (qcow->l1_dirty) { + l1_table = (qcow_te *)calloc(l1_size, + sizeof(qcow_te)); + + for (i=0; i < l1_size; i++) { + l1_table[i] = qcow->table[i].loc_on_disk; + if (l1_table[i]) + printf("Writing L1 entry %llu: %llu\n", + i, l1_table[i]); + } + ret = qcow_write_table(qcow->fd, qcow->header.l1_table_offset, + l1_size, l1_table); + + free(l1_table); + + qcow->l1_dirty = 0; + + if (ret != 1) { + fprintf(stderr, NAME "failed to write L1\n"); + return 0; + } + } + + return 1; +} + +/* + * Read in the L1 table. We read L2 tables as needed based on the + * information in the l1. + */ +int qcow_read_tables(struct qcow *qcow) +{ + int i; + uint64_t numL1; + qcow_te *disk_table; + + numL1 = qcow_num_l1(&qcow->header); + + disk_table = (qcow_te *)calloc(numL1, + sizeof(qcow_te)); + + dio_lseek(qcow->fd, qcow->header.l1_table_offset, SEEK_SET); + dio_read(qcow->fd, disk_table, numL1 * sizeof(qcow_te)); + + for (i=0; i < numL1; i++) { + + qcow->table[i].table = NULL; + qcow->table[i].dirty = 0; + + qcow->table[i].loc_on_disk = ntohll(disk_table[i]); + } + + if (OPS_DEBUG) + qcow_print_header(qcow); + + return 1; +} + + +/* Map the block of logical to physical */ +int qcow_make_mapping(struct qcow *qcow, uint64_t logical, uint64_t physical) +{ + uint64_t l1_entry; + uint64_t l2_entry; + + l1_entry = qcow_get_l1_entry(qcow, logical); + l2_entry = qcow_get_l2_entry(qcow, logical); + + if (l1_entry >= qcow_num_l1(&qcow->header)) { + fprintf(stderr, "*** ERROR: L1 of %llu >= %llu, max\n", + l1_entry, + qcow_num_l1(&qcow->header)); + fprintf(stderr, " Offending new map: %llu -> %llu\n", + logical, physical); + return 0; + } + + if (l2_entry >= qcow_num_l2(&qcow->header)) { + fprintf(stderr, "*** ERRROR: L2 of %llu >= %llu, max\n", + l2_entry, + qcow_num_l2(&qcow->header)); + return 0; + } + + if (logical >= LOG_ABOVE) { + printf("Making mapping for %llu -> %llu\n" + " l1e: %llu l2e: %llu\n", + logical, physical, l1_entry, l2_entry); + } + + if (qcow->table[l1_entry].table == NULL) { + if (qcow->table[l1_entry].loc_on_disk == 0) { + + if (!alloc_new_l2_on_disk(qcow, l1_entry)) { + fprintf(stderr, + "Failed to alloc table for %llu\n", + l1_entry); + return 0; + } + + qcow->l1_dirty = 1; + } else { + if (!qcow_load_l2(qcow, l1_entry)) { + fprintf(stderr, + "Failed to load L2 %llu\n", + l1_entry); + return 0; + } + } + } + + qcow->table[l1_entry].table[l2_entry] = physical; + qcow->table[l1_entry].dirty = 1; + + return 1; +} + +/* + * Make a new mapping for @logical by selecting the next available + * block + */ +uint64_t qcow_make_new_mapping(struct qcow *qcow, uint64_t logical) +{ + uint64_t new_block; + + new_block = qcow->next_avail_block; + + /* Align to the next 512-byte boundary */ + /* FIXME: Do we want to leak space like this? */ + if (new_block % 512) { + new_block += (512 - (new_block % 512)); + } + + qcow_seen_block(qcow, new_block, qcow_block_size(&qcow->header)); + + if (qcow_make_mapping(qcow, logical, new_block)) + return new_block; + else + return 0; /* Block 0 is error, because it contains the + header and L1, etc*/ + +} + +/* Return the location that @logical maps to, 0 if unmapped */ +uint64_t qcow_get_mapping(struct qcow *qcow, uint64_t logical) +{ + uint64_t l1_entry; + uint64_t l2_entry; + uint64_t physical; + qcow_te *l2_table; + + l1_entry = qcow_get_l1_entry(qcow, logical); + l2_entry = qcow_get_l2_entry(qcow, logical); + + if (l1_entry >= qcow_num_l1(&qcow->header)) { + fprintf(stderr, "*** ERROR: L1 of %llu >= %llu, max\n", + l1_entry, + qcow_num_l1(&qcow->header)); + fprintf(stderr, " Offending map request: %llu (%llx)\n", + logical, logical); + fprintf(stderr, " l1_entry: %llu\n", l1_entry); + fprintf(stderr, " l2_entry: %llu\n", l2_entry); + + return 0; + } + + if (l2_entry >= qcow_num_l2(&qcow->header)) { + fprintf(stderr, "*** ERROR: L2 of %llu >= %llu, max\n", + l2_entry, + qcow_num_l2(&qcow->header)); + return 0; + } + + if (qcow->table[l1_entry].table == NULL) { + if (qcow->table[l1_entry].loc_on_disk == 0) { + /* Not mapped if no L2 table */ + return 0; + } else { + /* Need to load it in */ + if (!qcow_load_l2(qcow, l1_entry)) + return 0; + } + } + + l2_table = qcow->table[l1_entry].table; + + physical = l2_table[l2_entry]; + + if (OPS_DEBUG && 0) { + printf("L1 entry: %llu\n", l1_entry); + printf("L2 entry: %llu\n", l2_entry); + printf("Physical Block: %llu (%llx)\n", physical, physical); + printf("\n"); + } + + return physical; +} + +/* + * Return 1 if any of the tables need flushing, 0 otherwise + */ +int qcow_is_anything_dirty(struct qcow *qcow) +{ + uint64_t i; + + if (qcow->l1_dirty) + return 1; + + for (i=0; i < qcow_num_l1(&qcow->header); i++) { + if (qcow->table[i].dirty) + return 1; + } + + return 0; +} + +/* + * Print out the header in human-readable format + */ +void qcow_print_header(struct qcow *qcow) +{ + printf("=== QCOW HEADER ===\n"); + printf("Magic: %x (should be %x)\n", qcow->header.magic, + QCOW_MAGIC); + printf("Version: %x\n", qcow->header.version); + printf("File Offset: %llu\n", qcow->header.backing_filename_offset); + printf("File Size: %u\n", qcow->header.backing_filename_size); + printf("Mod Time: %u\n", qcow->header.mtime); + printf("Size: %llu\n", qcow->header.size); + printf("Cluster Bits: %hhu\n", qcow->header.cluster_bits); + printf("L2 Bits: %hhu\n", qcow->header.l2_bits); + printf("Crypto Method: %x\n", qcow->header.crypto_method); + printf("L1 Table @: %llu\n", qcow->header.l1_table_offset); + printf("Backing File: %s\n", qcow->filename); + + printf("\nCalculated Information:\n"); + + printf("Next Avail: %llu\n", qcow->next_avail_block); + printf("Cluster Mask: %016llx\n", qcow_cmask(&qcow->header)); + printf("L2 Mask: %016llx\n", qcow_l2mask(&qcow->header)); + printf("L1 Mask: %016llx\n", qcow_l1mask(&qcow->header)); + printf("Num L1: %llu\n", qcow_num_l1(&qcow->header)); + printf("Num L2: %llu\n", qcow_num_l2(&qcow->header)); +} diff -r a3656acd770b -r be4574d28803 tools/cowd/plugins/qcow/qcow_ops.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tools/cowd/plugins/qcow/qcow_ops.h Fri Aug 25 15:01:36 2006 -0500 @@ -0,0 +1,15 @@ +#ifndef __QCOW_OPS_H +#define __QCOW_OPS_H + +#include <stdint.h> + +#include "qcow.h" + +int qcow_rw_header(int fd, struct qcow_header *header, int write); +int qcow_read_backing_info(struct qcow *qcow); +int qcow_read_l1_table(struct qcow *qcow); +uint64_t qcow_get_mapping(struct qcow *qcow, uint64_t logical); + +void qcow_print_header(struct qcow *qcow); + +#endif diff -r a3656acd770b -r be4574d28803 tools/cowd/plugins/qcow/qcow_plugin.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tools/cowd/plugins/qcow/qcow_plugin.c Fri Aug 25 15:01:36 2006 -0500 @@ -0,0 +1,519 @@ +/* + * Copyright (C) International Business Machines Corp., 2006 + * Author: Dan Smith <danms@us.ibm.com> + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License. See the file COPYING in the main directory + * of this archive for more details. + * + */ + +#define _LARGEFILE64_SOURCE + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <unistd.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <getopt.h> +#include <stdint.h> + +#include <cowd_plugin.h> + +#include <libdevmapper.h> + +#include "qcow.h" +#include "qcow_ops.h" + +#define MAX_PATH 256 +#define ERR_LEN 256 + +#define LOOP_SET_FD 0x4C00 +#define LOOP_CLR_FD 0x4C01 + +char errmsg[ERR_LEN]; + +struct qcow_private { + struct qcow qcow; + int init; + + char base_path[MAX_PATH]; + char qcow_path[MAX_PATH]; + + char base_dev[MAX_PATH]; + char qcow_dev[MAX_PATH]; + + dev_t base_dev_t; + dev_t qcow_dev_t; + + unsigned long qcow_size; + + int debug; +}; + +static dev_t *qcow_get_devs(struct cow_device *dev, int *count) +{ + struct qcow_private *prv = dev->plugin_private; + struct stat s; + dev_t *devs; + + devs = malloc(sizeof(*dev) * 2); + if (!devs) { + count = 0; + return NULL; + } + + devs[0] = prv->base_dev_t; + devs[1] = prv->qcow_dev_t; + + *count = 2; + return devs; +} + +/* + * We use the loop driver to make the base and cow files available to + * the kernel. The problem is, we can''t grow the cow file underneath + * the loop device. This function "inflates" the cow file to the + * maximum size it could be before we hook it up to the loop driver. + * This isn''t *too* bad because it''s just a sparse file. + */ +static int qcow_inflate(struct qcow_private *prv) +{ + struct qcow_header header; + int fd; + uint64_t offset; + + fd = open(prv->qcow_path, O_RDWR | O_LARGEFILE); + if (fd < 0) { + perror("open"); + return PLUGIN_FAIL; + } + + if (read(fd, &header, sizeof(header)) != sizeof(header)) { + perror("read"); + return PLUGIN_FAIL; + } + + header.size = ntohll(header.size); + + offset = lseek64(fd, header.size, SEEK_SET); + + if (prv->debug) + printf("## Inflating %s to %llu (%llu)\n", + prv->qcow_path, offset, header.size); + + if (offset != header.size) { + fprintf(stderr, "Failed to lseek to %llu\n", header.size); + return PLUGIN_FAIL; + } + + if (write(fd, &fd, 1) != 1) { + perror("write"); + close(fd); + return PLUGIN_FAIL; + } + + close(fd); + return PLUGIN_OK; +} + +/* + * This "deflates" the sparse cow file back to the appropriate size + */ +static void qcow_deflate(struct qcow_private *prv) +{ + printf("### Truncating %s to %llu\n", + prv->qcow_path, prv->qcow.next_avail_block); + truncate(prv->qcow_path, prv->qcow.next_avail_block); +} + +/* + * Process the subset of arguments we were given + */ +static void qcow_process_args(int argc, char **argv, struct qcow_private *prv) +{ + int c, optidx = 0; + int i; + static struct option lopts[] = { + {"qcow", 1, 0, ''q''}, + {"base", 1, 0, ''b''}, + {"debug", 0, 0, ''d''}, + {0, 0, 0, 0 } + }; + + strcpy(prv->qcow_path, argv[1]); + return; + + /* getopt doesn''t like the way I''ve arranged the strings for + some reason, so we just use the first arg as the qcow file + for now */ + + for (i=0; i<argc; i++) + fprintf(stderr, "Arg %i: %s\n", i, argv[i]); + + while (1) { + fprintf(stderr, "Going... %s\n", argv[0]); + c = getopt_long(argc, argv, "db:q:", lopts, &optidx); + if (c == -1) + break; + + fprintf(stderr, "Got: %c\n", c); + + switch (c) { + + case ''b'': + strncpy(prv->base_path, optarg, MAX_PATH); + break; + case ''q'': + strncpy(prv->qcow_path, optarg, MAX_PATH); + break; + case ''d'': + prv->debug = 1; + break; + }; + + } +} + +#if 0 +/* + * Hook up @path to @dev + * + * NB: This doesn''t work at the moment! + */ +static int qcow_loop_setup(const char *path, char *dev) +{ + int fd, lfd; + int i, ret; + char ldevpath[256]; + + fd = open(path, O_RDWR); + if (fd < 0) + return 0; + + for (i = 0; i < 8; i++) { + snprintf(ldevpath, 256, "/dev/loop%i", i); + lfd = open(ldevpath, O_RDWR); + if (lfd < 0) { + fprintf(stderr, "Failed to open %s\n", ldevpath); + continue; + } + ret = ioctl(lfd, LOOP_SET_FD, fd); + close(lfd); + if (ret == 0) { + strcpy(dev, ldevpath); + close(fd); + return 1; + } else { + fprintf(stderr, "ioctl() failed:\n"); + perror(ldevpath); + } + } + + close(fd); + + printf("No free loops for file: %s\n", path); + + return 0; +} + +/* + * Detach @dev from its backing file + * + * NB: This isn''t used at the moment! + */ +static int qcow_loop_destroy(const char *dev) +{ + int lfd; + int ret; + + lfd = open(dev, O_RDWR); + if (lfd < 0) + return 0; + + ret = ioctl(lfd, LOOP_CLR_FD, 0); + + if (ret == 0) + return 1; + else + return 0; +} +#endif + +/* + * Call the appropriate qcow functions to initialize all our metadata + * and accounting information. + */ +static int qcow_read_metadata(struct cow_device *dev, int force_init) +{ + struct qcow_private *prv = dev->plugin_private; + int ret; + + if (force_init) { + /* At some point, we want to be able to format a + qcow file outselves */ + fprintf(stderr, "The QCOW plugin doesn''t support init yet\n"); + return PLUGIN_FAIL; + } + + if (! prv->init) { + if (prv->debug) + printf("Init qcow from header\n"); + ret = qcow_init_from_header(&prv->qcow); + if (! ret) + return PLUGIN_FAIL; + + dev->block_size = 1 << prv->qcow.header.cluster_bits; + if (prv->debug) + fprintf(stderr, "Block size: %u\n", dev->block_size); + + dev->blocks = get_device_blocks(prv->base_dev) / + (dev->block_size / 512); + + if (prv->debug) + fprintf(stderr, "Blocks: %lu\n", dev->blocks); + } + + if (prv->debug) + printf("Reading tables\n"); + + ret = qcow_read_tables(&prv->qcow); + if (! ret) + return PLUGIN_FAIL; + + prv->init = 1; + + return PLUGIN_OK; + +} + +static int qcow_init(struct cow_device *dev, int debug) +{ + + struct qcow_private *prv; + struct stat s; + + if (debug) + printf(NAME "init\n"); + + memset(errmsg, 0, ERR_LEN); + + prv = (struct qcow_private *)malloc(sizeof(*prv)); + prv->qcow.fd = -1; + prv->init = 0; + prv->debug = debug; + prv->base_path[0] = ''\0''; + prv->qcow_path[0] = ''\0''; + qcow_init_qcow(&prv->qcow); + + qcow_process_args(dev->plugin_num_args, dev->plugin_args, prv); + + if (prv->qcow_path[0] == ''\0'') { + snprintf(errmsg, ERR_LEN, "Path to qcow file is required!\n"); + return PLUGIN_FAIL; + } + + prv->qcow.next_avail_block = get_file_size(prv->qcow_path); + prv->qcow_size = get_file_size(prv->qcow_path); + + /* Dirty hack to get around the fact that loop-backed files + can''t grow */ + qcow_inflate(prv); + + if (prv->debug) + printf("Opening %s\n", prv->qcow_path); + + prv->qcow.fd = dio_open(prv->qcow_path, O_RDWR); + if (prv->qcow.fd < 0) { + snprintf(errmsg, ERR_LEN, + "Failed to open %s with O_DIRECT: %s\n", + prv->qcow_path, strerror(prv->qcow.fd)); + return PLUGIN_FAIL; + } + + if (prv->debug) + printf("### Preset next_avail_block to %llu\n", + prv->qcow.next_avail_block); + + /* Init the qcow header information */ + qcow_rw_header(prv->qcow.fd, &prv->qcow.header, 0); + qcow_read_backing_info(&prv->qcow); + qcow_print_header(&prv->qcow); + + /* FIXME: REMOVE */ + qcow_print_header(&prv->qcow); + + /* Get the files/devices setup for device-mapper */ + if (prv->base_path[0] == ''\0'') + strcpy(prv->base_path, prv->qcow.filename); + + /* FIXME: Need real loop support!! */ + + if (is_file(prv->base_path)) { + sprintf(prv->base_dev, "/dev/loop0"); + loop_destroy(prv->base_dev); + loop_setup(prv->base_dev, prv->base_path); + } + + if (is_file(prv->qcow_path)) { + sprintf(prv->qcow_dev, "/dev/loop1"); + loop_destroy(prv->qcow_dev); + loop_setup(prv->qcow_dev, prv->qcow_path); + } + +#if 0 + if (is_file(prv->qcow_path)) + qcow_loop_setup(prv->qcow_path, prv->qcow_dev); + else + strcpy(prv->qcow_dev, prv->qcow_path); + + if (is_file(prv->base_path)) + qcow_loop_setup(prv->base_path, prv->base_dev); + else + strcpy(prv->base_dev, prv->base_path); +#endif + + stat(prv->base_dev, &s); + prv->base_dev_t = s.st_rdev; + + stat(prv->qcow_dev, &s); + prv->qcow_dev_t = s.st_rdev; + + if (prv->debug) + printf("Base Device: %s Cow Device: %s\n", + prv->base_dev, prv->qcow_dev); + + dev->plugin_private = prv; + + return qcow_read_metadata(dev, 0); + +} + +static void qcow_cleanup(struct cow_device *dev) +{ + /* FIXME: Do something more useful here */ + struct qcow_private *prv = dev->plugin_private; + + close(prv->qcow.fd); + + /* FIXME: Need to check if these are actually loops */ + loop_destroy(prv->base_dev); + loop_destroy(prv->qcow_dev); + + qcow_deflate(prv); + + /* Free some stuff */ +} + + +static int qcow_write_metadata(struct cow_device *dev) +{ + struct qcow_private *prv = dev->plugin_private; + int ret; + + if (prv->debug) + printf("Writing metadata!\n"); + + ret = qcow_write_tables(&prv->qcow); + + if (! ret) + return PLUGIN_FAIL; + else + return PLUGIN_OK; +} + +static int qcow_map_block(struct cow_device *dev, + struct dmu_map_data *map) +{ + + struct qcow_private *prv = dev->plugin_private; + uint64_t tmp; + uint64_t org, org_block; + + /* + * FIXME: This is super ugly + */ + + /* Convert to start byte position of cluster */ + org_block = dmu_map_get_block(map); + org = org_block << prv->qcow.header.cluster_bits; + + if (prv->debug && 0) + fprintf(stderr, "Looking for existing map for %llu\n", + org_block); + + tmp = qcow_get_mapping(&prv->qcow, org); + if ((tmp == 0) && dmu_map_is_write(map)) { + /* NEW mapping for WRITE access: + Remap to somewhere in the cow device */ + if (prv->debug && 0) + fprintf(stderr, "Not found, mapping...\n"); + + tmp = qcow_make_new_mapping(&prv->qcow, (uint64_t)org); + + dmu_map_set_block(map, qcow_align_to_block(&prv->qcow, tmp)); + dmu_map_set_offset(map, qcow_calc_offset(&prv->qcow, tmp)); + + dmu_map_set_copy_src_dev(map, prv->base_dev_t); + dmu_map_set_dest_dev(map, prv->qcow_dev_t); + + } else if ((tmp == 0) && !dmu_map_is_write(map)) { + /* NEW mapping for READ access: + Remap to the same place in the base device */ + dmu_map_set_block(map, org_block); + + dmu_map_set_dest_dev(map, prv->base_dev_t); + + } else if ((tmp == 0) && dmu_map_is_write(map)) { + /* This should not be allowed */ + return PLUGIN_FAIL; + } else { + /* OLD mapping (access doesn''t matter): + Remap to the correct location in the cow device */ + dmu_map_set_block(map, + qcow_align_to_block(&prv->qcow, tmp)); + dmu_map_set_offset(map, + qcow_calc_offset(&prv->qcow, tmp)); + if (prv->debug) + fprintf(stderr, + "Found existing map for %llu: %llx (%llu)\n", + org, tmp, tmp); + + dmu_map_set_dest_dev(map, prv->qcow_dev_t); + } + + if (tmp > prv->qcow.header.size) { + snprintf(errmsg, ERR_LEN, + "Tried to map a block beyond end of device: " + "%llu > %llu\n", + tmp, prv->qcow.header.size); + return PLUGIN_FAIL; + } + + return PLUGIN_OK; +} + +static bool qcow_need_flush(struct cow_device *dev) +{ + + struct qcow_private *prv = dev->plugin_private; + + return qcow_is_anything_dirty(&prv->qcow); +} + +int load_plugin(struct cowd_plugin *p) +{ + + p->init_plugin = qcow_init; + p->write_metadata = qcow_write_metadata; + p->map_prepare = qcow_map_block; + p->cleanup_plugin = qcow_cleanup; + p->need_flush = qcow_need_flush; + p->errmsg = errmsg; + p->get_devs = qcow_get_devs; + + return 1; + +} _______________________________________________ Xen-devel mailing list Xen-devel@lists.xensource.com http://lists.xensource.com/xen-devel