Index: head/usr.bin/mkimg/image.c =================================================================== --- head/usr.bin/mkimg/image.c (revision 306328) +++ head/usr.bin/mkimg/image.c (revision 306329) @@ -1,727 +1,726 @@ /*- * Copyright (c) 2014 Juniper Networks, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "image.h" #include "mkimg.h" struct chunk { STAILQ_ENTRY(chunk) ch_list; size_t ch_size; /* Size of chunk in bytes. */ lba_t ch_block; /* Block address in image. */ union { struct { off_t ofs; /* Offset in backing file. */ int fd; /* FD of backing file. */ } file; struct { void *ptr; /* Pointer to data in memory */ } mem; } ch_u; u_int ch_type; #define CH_TYPE_ZEROES 0 /* Chunk is a gap (no data). */ #define CH_TYPE_FILE 1 /* File-backed chunk. */ #define CH_TYPE_MEMORY 2 /* Memory-backed chunk */ }; static STAILQ_HEAD(chunk_head, chunk) image_chunks; static u_int image_nchunks; static char image_swap_file[PATH_MAX]; static int image_swap_fd = -1; static u_int image_swap_pgsz; static off_t image_swap_size; static lba_t image_size; static int is_empty_sector(void *buf) { uint64_t *p = buf; size_t n, max; assert(((uintptr_t)p & 3) == 0); max = secsz / sizeof(uint64_t); for (n = 0; n < max; n++) { if (p[n] != 0UL) return (0); } return (1); } /* * Swap file handlng. */ static off_t image_swap_alloc(size_t size) { off_t ofs; size_t unit; unit = (secsz > image_swap_pgsz) ? secsz : image_swap_pgsz; assert((unit & (unit - 1)) == 0); size = (size + unit - 1) & ~(unit - 1); ofs = image_swap_size; image_swap_size += size; if (ftruncate(image_swap_fd, image_swap_size) == -1) { image_swap_size = ofs; ofs = -1LL; } return (ofs); } /* * Image chunk handling. */ static struct chunk * image_chunk_find(lba_t blk) { static struct chunk *last = NULL; struct chunk *ch; ch = (last != NULL && last->ch_block <= blk) ? last : STAILQ_FIRST(&image_chunks); while (ch != NULL) { if (ch->ch_block <= blk && (lba_t)(ch->ch_block + (ch->ch_size / secsz)) > blk) { last = ch; break; } ch = STAILQ_NEXT(ch, ch_list); } return (ch); } static size_t image_chunk_grow(struct chunk *ch, size_t sz) { size_t dsz, newsz; newsz = ch->ch_size + sz; if (newsz > ch->ch_size) { ch->ch_size = newsz; return (0); } /* We would overflow -- create new chunk for remainder. */ dsz = SIZE_MAX - ch->ch_size; assert(dsz < sz); ch->ch_size = SIZE_MAX; return (sz - dsz); } static struct chunk * image_chunk_memory(struct chunk *ch, lba_t blk) { struct chunk *new; void *ptr; ptr = calloc(1, secsz); if (ptr == NULL) return (NULL); if (ch->ch_block < blk) { new = malloc(sizeof(*new)); if (new == NULL) { free(ptr); return (NULL); } memcpy(new, ch, sizeof(*new)); ch->ch_size = (blk - ch->ch_block) * secsz; new->ch_block = blk; new->ch_size -= ch->ch_size; STAILQ_INSERT_AFTER(&image_chunks, ch, new, ch_list); image_nchunks++; ch = new; } if (ch->ch_size > secsz) { new = malloc(sizeof(*new)); if (new == NULL) { free(ptr); return (NULL); } memcpy(new, ch, sizeof(*new)); ch->ch_size = secsz; new->ch_block++; new->ch_size -= secsz; STAILQ_INSERT_AFTER(&image_chunks, ch, new, ch_list); image_nchunks++; } ch->ch_type = CH_TYPE_MEMORY; ch->ch_u.mem.ptr = ptr; return (ch); } static int image_chunk_skipto(lba_t to) { struct chunk *ch; lba_t from; size_t sz; ch = STAILQ_LAST(&image_chunks, chunk, ch_list); from = (ch != NULL) ? ch->ch_block + (ch->ch_size / secsz) : 0LL; assert(from <= to); /* Nothing to do? */ if (from == to) return (0); /* Avoid bugs due to overflows. */ if ((uintmax_t)(to - from) > (uintmax_t)(SIZE_MAX / secsz)) return (EFBIG); sz = (to - from) * secsz; if (ch != NULL && ch->ch_type == CH_TYPE_ZEROES) { sz = image_chunk_grow(ch, sz); if (sz == 0) return (0); from = ch->ch_block + (ch->ch_size / secsz); } ch = malloc(sizeof(*ch)); if (ch == NULL) return (ENOMEM); memset(ch, 0, sizeof(*ch)); ch->ch_block = from; ch->ch_size = sz; ch->ch_type = CH_TYPE_ZEROES; STAILQ_INSERT_TAIL(&image_chunks, ch, ch_list); image_nchunks++; return (0); } static int image_chunk_append(lba_t blk, size_t sz, off_t ofs, int fd) { struct chunk *ch; ch = STAILQ_LAST(&image_chunks, chunk, ch_list); if (ch != NULL && ch->ch_type == CH_TYPE_FILE) { if (fd == ch->ch_u.file.fd && blk == (lba_t)(ch->ch_block + (ch->ch_size / secsz)) && ofs == (off_t)(ch->ch_u.file.ofs + ch->ch_size)) { sz = image_chunk_grow(ch, sz); if (sz == 0) return (0); blk = ch->ch_block + (ch->ch_size / secsz); ofs = ch->ch_u.file.ofs + ch->ch_size; } } ch = malloc(sizeof(*ch)); if (ch == NULL) return (ENOMEM); memset(ch, 0, sizeof(*ch)); ch->ch_block = blk; ch->ch_size = sz; ch->ch_type = CH_TYPE_FILE; ch->ch_u.file.ofs = ofs; ch->ch_u.file.fd = fd; STAILQ_INSERT_TAIL(&image_chunks, ch, ch_list); image_nchunks++; return (0); } static int image_chunk_copyin(lba_t blk, void *buf, size_t sz, off_t ofs, int fd) { uint8_t *p = buf; int error; error = 0; sz = (sz + secsz - 1) & ~(secsz - 1); while (!error && sz > 0) { if (is_empty_sector(p)) error = image_chunk_skipto(blk + 1); else error = image_chunk_append(blk, secsz, ofs, fd); blk++; p += secsz; sz -= secsz; ofs += secsz; } return (error); } /* * File mapping support. */ static void * image_file_map(int fd, off_t ofs, size_t sz) { void *ptr; size_t unit; int flags, prot; unit = (secsz > image_swap_pgsz) ? secsz : image_swap_pgsz; assert((unit & (unit - 1)) == 0); flags = MAP_NOCORE | MAP_NOSYNC | MAP_SHARED; /* Allow writing to our swap file only. */ prot = PROT_READ | ((fd == image_swap_fd) ? PROT_WRITE : 0); sz = (sz + unit - 1) & ~(unit - 1); ptr = mmap(NULL, sz, prot, flags, fd, ofs); return ((ptr == MAP_FAILED) ? NULL : ptr); } static int image_file_unmap(void *buffer, size_t sz) { size_t unit; unit = (secsz > image_swap_pgsz) ? secsz : image_swap_pgsz; sz = (sz + unit - 1) & ~(unit - 1); if (madvise(buffer, sz, MADV_DONTNEED) != 0) warn("madvise"); munmap(buffer, sz); return (0); } /* * Input/source file handling. */ static int image_copyin_stream(lba_t blk, int fd, uint64_t *sizep) { char *buffer; uint64_t bytesize; off_t swofs; size_t iosz; ssize_t rdsz; int error; /* * This makes sure we're doing I/O in multiples of the page * size as well as of the sector size. 2MB is the minimum * by virtue of secsz at least 512 bytes and the page size * at least 4K bytes. */ iosz = secsz * image_swap_pgsz; bytesize = 0; do { swofs = image_swap_alloc(iosz); if (swofs == -1LL) return (errno); buffer = image_file_map(image_swap_fd, swofs, iosz); if (buffer == NULL) return (errno); rdsz = read(fd, buffer, iosz); if (rdsz > 0) error = image_chunk_copyin(blk, buffer, rdsz, swofs, image_swap_fd); else if (rdsz < 0) error = errno; else error = 0; image_file_unmap(buffer, iosz); /* XXX should we relinguish unused swap space? */ if (error) return (error); bytesize += rdsz; blk += (rdsz + secsz - 1) / secsz; } while (rdsz > 0); if (sizep != NULL) *sizep = bytesize; return (0); } static int image_copyin_mapped(lba_t blk, int fd, uint64_t *sizep) { off_t cur, data, end, hole, pos; void *buf; uint64_t bytesize; size_t iosz, sz; int error; /* * We'd like to know the size of the file and we must * be able to seek in order to mmap(2). If this isn't * possible, then treat the file as a stream/pipe. */ end = lseek(fd, 0L, SEEK_END); if (end == -1L) return (image_copyin_stream(blk, fd, sizep)); /* * We need the file opened for the duration and our * caller is going to close the file. Make a dup(2) * so that control the faith of the descriptor. */ fd = dup(fd); if (fd == -1) return (errno); iosz = secsz * image_swap_pgsz; bytesize = 0; cur = pos = 0; error = 0; while (!error && cur < end) { hole = lseek(fd, cur, SEEK_HOLE); if (hole == -1) hole = end; data = lseek(fd, cur, SEEK_DATA); if (data == -1) data = end; /* * Treat the entire file as data if sparse files * are not supported by the underlying file system. */ if (hole == end && data == end) data = cur; if (cur == hole && data > hole) { hole = pos; pos = data & ~((uint64_t)secsz - 1); blk += (pos - hole) / secsz; error = image_chunk_skipto(blk); bytesize += pos - hole; cur = data; } else if (cur == data && hole > data) { data = pos; pos = (hole + secsz - 1) & ~((uint64_t)secsz - 1); while (data < pos) { sz = (pos - data > (off_t)iosz) ? iosz : (size_t)(pos - data); buf = image_file_map(fd, data, sz); if (buf != NULL) { error = image_chunk_copyin(blk, buf, sz, data, fd); image_file_unmap(buf, sz); } else error = errno; blk += sz / secsz; bytesize += sz; data += sz; } cur = hole; } else { /* * I don't know what this means or whether it * can happen at all... */ - error = EDOOFUS; - break; + assert(0); } } if (error) close(fd); if (!error && sizep != NULL) *sizep = bytesize; return (error); } int image_copyin(lba_t blk, int fd, uint64_t *sizep) { struct stat sb; int error; error = image_chunk_skipto(blk); if (!error) { if (fstat(fd, &sb) == -1 || !S_ISREG(sb.st_mode)) error = image_copyin_stream(blk, fd, sizep); else error = image_copyin_mapped(blk, fd, sizep); } return (error); } /* * Output/sink file handling. */ int image_copyout(int fd) { int error; error = image_copyout_region(fd, 0, image_size); if (!error) error = image_copyout_done(fd); return (error); } int image_copyout_done(int fd) { off_t ofs; int error; ofs = lseek(fd, 0L, SEEK_CUR); if (ofs == -1) return (0); error = (ftruncate(fd, ofs) == -1) ? errno : 0; return (error); } static int image_copyout_memory(int fd, size_t size, void *ptr) { if (write(fd, ptr, size) == -1) return (errno); return (0); } int image_copyout_zeroes(int fd, size_t count) { static uint8_t *zeroes = NULL; size_t sz; int error; if (lseek(fd, (off_t)count, SEEK_CUR) != -1) return (0); /* * If we can't seek, we must write. */ if (zeroes == NULL) { zeroes = calloc(1, secsz); if (zeroes == NULL) return (ENOMEM); } while (count > 0) { sz = (count > secsz) ? secsz : count; error = image_copyout_memory(fd, sz, zeroes); if (error) return (error); count -= sz; } return (0); } static int image_copyout_file(int fd, size_t size, int ifd, off_t iofs) { void *buf; size_t iosz, sz; int error; iosz = secsz * image_swap_pgsz; while (size > 0) { sz = (size > iosz) ? iosz : size; buf = image_file_map(ifd, iofs, sz); if (buf == NULL) return (errno); error = image_copyout_memory(fd, sz, buf); image_file_unmap(buf, sz); if (error) return (error); size -= sz; iofs += sz; } return (0); } int image_copyout_region(int fd, lba_t blk, lba_t size) { struct chunk *ch; size_t ofs, sz; int error; size *= secsz; while (size > 0) { ch = image_chunk_find(blk); if (ch == NULL) return (EINVAL); ofs = (blk - ch->ch_block) * secsz; sz = ch->ch_size - ofs; sz = ((lba_t)sz < size) ? sz : (size_t)size; switch (ch->ch_type) { case CH_TYPE_ZEROES: error = image_copyout_zeroes(fd, sz); break; case CH_TYPE_FILE: error = image_copyout_file(fd, sz, ch->ch_u.file.fd, ch->ch_u.file.ofs + ofs); break; case CH_TYPE_MEMORY: error = image_copyout_memory(fd, sz, ch->ch_u.mem.ptr); break; default: - return (EDOOFUS); + assert(0); } size -= sz; blk += sz / secsz; } return (0); } int image_data(lba_t blk, lba_t size) { struct chunk *ch; lba_t lim; while (1) { ch = image_chunk_find(blk); if (ch == NULL) return (0); if (ch->ch_type != CH_TYPE_ZEROES) return (1); lim = ch->ch_block + (ch->ch_size / secsz); if (lim >= blk + size) return (0); size -= lim - blk; blk = lim; } /*NOTREACHED*/ } lba_t image_get_size(void) { return (image_size); } int image_set_size(lba_t blk) { int error; error = image_chunk_skipto(blk); if (!error) image_size = blk; return (error); } int image_write(lba_t blk, void *buf, ssize_t len) { struct chunk *ch; while (len > 0) { if (!is_empty_sector(buf)) { ch = image_chunk_find(blk); if (ch == NULL) return (ENXIO); /* We may not be able to write to files. */ if (ch->ch_type == CH_TYPE_FILE) return (EINVAL); if (ch->ch_type == CH_TYPE_ZEROES) { ch = image_chunk_memory(ch, blk); if (ch == NULL) return (ENOMEM); } assert(ch->ch_type == CH_TYPE_MEMORY); memcpy(ch->ch_u.mem.ptr, buf, secsz); } blk++; buf = (char *)buf + secsz; len--; } return (0); } static void image_cleanup(void) { struct chunk *ch; while ((ch = STAILQ_FIRST(&image_chunks)) != NULL) { switch (ch->ch_type) { case CH_TYPE_FILE: /* We may be closing the same file multiple times. */ if (ch->ch_u.file.fd != -1) close(ch->ch_u.file.fd); break; case CH_TYPE_MEMORY: free(ch->ch_u.mem.ptr); break; default: break; } STAILQ_REMOVE_HEAD(&image_chunks, ch_list); free(ch); } if (image_swap_fd != -1) close(image_swap_fd); unlink(image_swap_file); } int image_init(void) { const char *tmpdir; STAILQ_INIT(&image_chunks); image_nchunks = 0; image_swap_size = 0; image_swap_pgsz = getpagesize(); if (atexit(image_cleanup) == -1) return (errno); if ((tmpdir = getenv("TMPDIR")) == NULL || *tmpdir == '\0') tmpdir = _PATH_TMP; snprintf(image_swap_file, sizeof(image_swap_file), "%s/mkimg-XXXXXX", tmpdir); image_swap_fd = mkstemp(image_swap_file); if (image_swap_fd == -1) return (errno); return (0); } Index: head/usr.bin/mkimg/qcow.c =================================================================== --- head/usr.bin/mkimg/qcow.c (revision 306328) +++ head/usr.bin/mkimg/qcow.c (revision 306329) @@ -1,370 +1,370 @@ /*- * Copyright (c) 2014 Marcel Moolenaar * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. */ #include __FBSDID("$FreeBSD$"); #include #include #include +#include #include #include #include #include #include #include "image.h" #include "format.h" #include "mkimg.h" /* Default cluster sizes. */ #define QCOW1_CLSTR_LOG2SZ 12 /* 4KB */ #define QCOW2_CLSTR_LOG2SZ 16 /* 64KB */ /* Flag bits in cluster offsets */ #define QCOW_CLSTR_COMPRESSED (1ULL << 62) #define QCOW_CLSTR_COPIED (1ULL << 63) struct qcow_header { uint32_t magic; #define QCOW_MAGIC 0x514649fb uint32_t version; #define QCOW_VERSION_1 1 #define QCOW_VERSION_2 2 uint64_t path_offset; uint32_t path_length; uint32_t clstr_log2sz; /* v2 only */ uint64_t disk_size; union { struct { uint8_t clstr_log2sz; uint8_t l2_log2sz; uint16_t _pad; uint32_t encryption; uint64_t l1_offset; } v1; struct { uint32_t encryption; uint32_t l1_entries; uint64_t l1_offset; uint64_t refcnt_offset; uint32_t refcnt_clstrs; uint32_t snapshot_count; uint64_t snapshot_offset; } v2; } u; }; static u_int clstr_log2sz; static uint64_t round_clstr(uint64_t ofs) { uint64_t clstrsz; clstrsz = 1UL << clstr_log2sz; return ((ofs + clstrsz - 1) & ~(clstrsz - 1)); } static int qcow_resize(lba_t imgsz, u_int version) { uint64_t imagesz; switch (version) { case QCOW_VERSION_1: clstr_log2sz = QCOW1_CLSTR_LOG2SZ; break; case QCOW_VERSION_2: clstr_log2sz = QCOW2_CLSTR_LOG2SZ; break; default: - return (EDOOFUS); + assert(0); } imagesz = round_clstr(imgsz * secsz); if (verbose) fprintf(stderr, "QCOW: image size = %ju, cluster size = %u\n", (uintmax_t)imagesz, (u_int)(1U << clstr_log2sz)); return (image_set_size(imagesz / secsz)); } static int qcow1_resize(lba_t imgsz) { return (qcow_resize(imgsz, QCOW_VERSION_1)); } static int qcow2_resize(lba_t imgsz) { return (qcow_resize(imgsz, QCOW_VERSION_2)); } static int qcow_write(int fd, u_int version) { struct qcow_header *hdr; uint64_t *l1tbl, *l2tbl, *rctbl; uint16_t *rcblk; uint64_t clstr_imgsz, clstr_l2tbls, clstr_l1tblsz; uint64_t clstr_rcblks, clstr_rctblsz; uint64_t n, imagesz, nclstrs, ofs, ofsflags; lba_t blk, blkofs, blk_imgsz; u_int l1clno, l2clno, rcclno; u_int blk_clstrsz, refcnt_clstrs; u_int clstrsz, l1idx, l2idx; int error; - if (clstr_log2sz == 0) - return (EDOOFUS); + assert(clstr_log2sz != 0); clstrsz = 1U << clstr_log2sz; blk_clstrsz = clstrsz / secsz; blk_imgsz = image_get_size(); imagesz = blk_imgsz * secsz; clstr_imgsz = imagesz >> clstr_log2sz; clstr_l2tbls = round_clstr(clstr_imgsz * 8) >> clstr_log2sz; clstr_l1tblsz = round_clstr(clstr_l2tbls * 8) >> clstr_log2sz; nclstrs = clstr_imgsz + clstr_l2tbls + clstr_l1tblsz + 1; clstr_rcblks = clstr_rctblsz = 0; do { n = clstr_rcblks + clstr_rctblsz; clstr_rcblks = round_clstr((nclstrs + n) * 2) >> clstr_log2sz; clstr_rctblsz = round_clstr(clstr_rcblks * 8) >> clstr_log2sz; } while (n < (clstr_rcblks + clstr_rctblsz)); /* * We got all the sizes in clusters. Start the layout. * 0 - header * 1 - L1 table * 2 - RC table (v2 only) * 3 - L2 tables * 4 - RC block (v2 only) * 5 - data */ l1clno = 1; rcclno = 0; rctbl = l2tbl = l1tbl = NULL; rcblk = NULL; hdr = calloc(1, clstrsz); if (hdr == NULL) return (errno); be32enc(&hdr->magic, QCOW_MAGIC); be32enc(&hdr->version, version); be64enc(&hdr->disk_size, imagesz); switch (version) { case QCOW_VERSION_1: ofsflags = 0; l2clno = l1clno + clstr_l1tblsz; hdr->u.v1.clstr_log2sz = clstr_log2sz; hdr->u.v1.l2_log2sz = clstr_log2sz - 3; be64enc(&hdr->u.v1.l1_offset, clstrsz * l1clno); break; case QCOW_VERSION_2: ofsflags = QCOW_CLSTR_COPIED; rcclno = l1clno + clstr_l1tblsz; l2clno = rcclno + clstr_rctblsz; be32enc(&hdr->clstr_log2sz, clstr_log2sz); be32enc(&hdr->u.v2.l1_entries, clstr_l2tbls); be64enc(&hdr->u.v2.l1_offset, clstrsz * l1clno); be64enc(&hdr->u.v2.refcnt_offset, clstrsz * rcclno); refcnt_clstrs = round_clstr(clstr_rcblks * 8) >> clstr_log2sz; be32enc(&hdr->u.v2.refcnt_clstrs, refcnt_clstrs); break; default: - return (EDOOFUS); + assert(0); } if (sparse_write(fd, hdr, clstrsz) < 0) { error = errno; goto out; } free(hdr); hdr = NULL; ofs = clstrsz * l2clno; nclstrs = 1 + clstr_l1tblsz + clstr_rctblsz; l1tbl = calloc(1, clstrsz * clstr_l1tblsz); if (l1tbl == NULL) { error = ENOMEM; goto out; } for (n = 0; n < clstr_imgsz; n++) { blk = n * blk_clstrsz; if (image_data(blk, blk_clstrsz)) { nclstrs++; l1idx = n >> (clstr_log2sz - 3); if (l1tbl[l1idx] == 0) { be64enc(l1tbl + l1idx, ofs + ofsflags); ofs += clstrsz; nclstrs++; } } } if (sparse_write(fd, l1tbl, clstrsz * clstr_l1tblsz) < 0) { error = errno; goto out; } clstr_rcblks = 0; do { n = clstr_rcblks; clstr_rcblks = round_clstr((nclstrs + n) * 2) >> clstr_log2sz; } while (n < clstr_rcblks); if (rcclno > 0) { rctbl = calloc(1, clstrsz * clstr_rctblsz); if (rctbl == NULL) { error = ENOMEM; goto out; } for (n = 0; n < clstr_rcblks; n++) { be64enc(rctbl + n, ofs); ofs += clstrsz; nclstrs++; } if (sparse_write(fd, rctbl, clstrsz * clstr_rctblsz) < 0) { error = errno; goto out; } free(rctbl); rctbl = NULL; } l2tbl = malloc(clstrsz); if (l2tbl == NULL) { error = ENOMEM; goto out; } for (l1idx = 0; l1idx < clstr_l2tbls; l1idx++) { if (l1tbl[l1idx] == 0) continue; memset(l2tbl, 0, clstrsz); blkofs = (lba_t)l1idx * blk_clstrsz * (clstrsz >> 3); for (l2idx = 0; l2idx < (clstrsz >> 3); l2idx++) { blk = blkofs + (lba_t)l2idx * blk_clstrsz; if (blk >= blk_imgsz) break; if (image_data(blk, blk_clstrsz)) { be64enc(l2tbl + l2idx, ofs + ofsflags); ofs += clstrsz; } } if (sparse_write(fd, l2tbl, clstrsz) < 0) { error = errno; goto out; } } free(l2tbl); l2tbl = NULL; free(l1tbl); l1tbl = NULL; if (rcclno > 0) { rcblk = calloc(1, clstrsz * clstr_rcblks); if (rcblk == NULL) { error = ENOMEM; goto out; } for (n = 0; n < nclstrs; n++) be16enc(rcblk + n, 1); if (sparse_write(fd, rcblk, clstrsz * clstr_rcblks) < 0) { error = errno; goto out; } free(rcblk); rcblk = NULL; } error = 0; for (n = 0; n < clstr_imgsz; n++) { blk = n * blk_clstrsz; if (image_data(blk, blk_clstrsz)) { error = image_copyout_region(fd, blk, blk_clstrsz); if (error) break; } } if (!error) error = image_copyout_done(fd); out: if (rcblk != NULL) free(rcblk); if (l2tbl != NULL) free(l2tbl); if (rctbl != NULL) free(rctbl); if (l1tbl != NULL) free(l1tbl); if (hdr != NULL) free(hdr); return (error); } static int qcow1_write(int fd) { return (qcow_write(fd, QCOW_VERSION_1)); } static int qcow2_write(int fd) { return (qcow_write(fd, QCOW_VERSION_2)); } static struct mkimg_format qcow1_format = { .name = "qcow", .description = "QEMU Copy-On-Write, version 1", .resize = qcow1_resize, .write = qcow1_write, }; FORMAT_DEFINE(qcow1_format); static struct mkimg_format qcow2_format = { .name = "qcow2", .description = "QEMU Copy-On-Write, version 2", .resize = qcow2_resize, .write = qcow2_write, }; FORMAT_DEFINE(qcow2_format);