| /* |
| * qcow2.c --- Functions to generate qcow2 formatted disk images. This |
| * format is used originally by QEMU for virtual machines, and stores the |
| * filesystem data on disk in a packed format to avoid creating sparse |
| * image files that need lots of seeking to read and write. |
| * |
| * The qcow2 format supports zlib compression, but that is not yet |
| * implemented. |
| * |
| * It is possible to directly mount a qcow2 image using qemu-nbd: |
| * |
| * [root]# modprobe nbd max_part=63 |
| * [root]# qemu-nbd -c /dev/nbd0 image.img |
| * [root]# mount /dev/nbd0p1 /mnt/qemu |
| * |
| * Format details at http://people.gnome.org/~markmc/qcow-image-format.html |
| * |
| * Copyright (C) 2010 Red Hat, Inc., Lukas Czerner <lczerner@redhat.com> |
| * |
| * %Begin-Header% |
| * This file may be redistributed under the terms of the GNU Public |
| * License. |
| * %End-Header% |
| */ |
| |
| #ifndef _LARGEFILE_SOURCE |
| #define _LARGEFILE_SOURCE |
| #endif |
| #ifndef _LARGEFILE64_SOURCE |
| #define _LARGEFILE64_SOURCE |
| #endif |
| |
| #include "config.h" |
| #include <fcntl.h> |
| #include <grp.h> |
| #include <pwd.h> |
| #include <stdio.h> |
| #ifdef HAVE_STDLIB_H |
| #include <stdlib.h> |
| #endif |
| #include <string.h> |
| #include <time.h> |
| #include <unistd.h> |
| #include <fcntl.h> |
| #include <errno.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <assert.h> |
| |
| #include "ext2fs/ext2fs.h" |
| #include "qcow2.h" |
| |
| /* Functions for converting qcow2 image into raw image */ |
| |
| struct ext2_qcow2_hdr *qcow2_read_header(int fd) |
| { |
| void *buffer = NULL; |
| struct ext2_qcow2_hdr *hdr = NULL; |
| size_t size; |
| errcode_t ret; |
| |
| ret = ext2fs_get_mem(sizeof(struct ext2_qcow2_hdr), &buffer); |
| if (ret) |
| return NULL; |
| memset(buffer, 0, sizeof(struct ext2_qcow2_hdr)); |
| |
| if (ext2fs_llseek(fd, 0, SEEK_SET < 0)) { |
| ext2fs_free_mem(&buffer); |
| return NULL; |
| } |
| |
| size = read(fd, buffer, sizeof(struct ext2_qcow2_hdr)); |
| if (size != sizeof(struct ext2_qcow2_hdr)) { |
| ext2fs_free_mem(&buffer); |
| return NULL; |
| } |
| |
| hdr = (struct ext2_qcow2_hdr *)(buffer); |
| |
| if ((ext2fs_be32_to_cpu(hdr->magic) != QCOW_MAGIC) || |
| (ext2fs_be32_to_cpu(hdr->version) != 2)) { |
| ext2fs_free_mem(&hdr); |
| return NULL; |
| } |
| |
| return hdr; |
| } |
| |
| static int qcow2_read_l1_table(struct ext2_qcow2_image *img) |
| { |
| int fd = img->fd; |
| size_t size, l1_size = img->l1_size * sizeof(blk64_t); |
| blk64_t *table; |
| errcode_t ret; |
| |
| ret = ext2fs_get_memzero(l1_size, &table); |
| if (ret) |
| return ret; |
| |
| if (ext2fs_llseek(fd, img->l1_offset, SEEK_SET) < 0) { |
| ext2fs_free_mem(&table); |
| return errno; |
| } |
| |
| size = read(fd, table, l1_size); |
| if (size != l1_size) { |
| ext2fs_free_mem(&table); |
| return errno; |
| } |
| |
| img->l1_table = table; |
| |
| return 0; |
| } |
| |
| static int qcow2_read_l2_table(struct ext2_qcow2_image *img, |
| ext2_off64_t offset, blk64_t **l2_table) |
| { |
| int fd = img->fd; |
| size_t size; |
| |
| assert(*l2_table); |
| |
| if (ext2fs_llseek(fd, offset, SEEK_SET) < 0) |
| return errno; |
| |
| size = read(fd, *l2_table, img->cluster_size); |
| if (size != img->cluster_size) |
| return errno; |
| |
| return 0; |
| } |
| |
| static int qcow2_copy_data(int fdin, int fdout, ext2_off64_t off_in, |
| ext2_off64_t off_out, void *buf, size_t count) |
| { |
| size_t size; |
| |
| assert(buf); |
| |
| if (ext2fs_llseek(fdout, off_out, SEEK_SET) < 0) |
| return errno; |
| |
| if (ext2fs_llseek(fdin, off_in, SEEK_SET) < 0) |
| return errno; |
| |
| size = read(fdin, buf, count); |
| if (size != count) |
| return errno; |
| |
| size = write(fdout, buf, count); |
| if (size != count) |
| return errno; |
| |
| return 0; |
| } |
| |
| |
| int qcow2_write_raw_image(int qcow2_fd, int raw_fd, |
| struct ext2_qcow2_hdr *hdr) |
| { |
| struct ext2_qcow2_image img; |
| errcode_t ret = 0; |
| unsigned int l1_index, l2_index; |
| ext2_off64_t offset; |
| blk64_t *l1_table, *l2_table = NULL; |
| void *copy_buf = NULL; |
| size_t size; |
| |
| if (hdr->crypt_method) |
| return -QCOW_ENCRYPTED; |
| |
| img.fd = qcow2_fd; |
| img.hdr = hdr; |
| img.l2_cache = NULL; |
| img.l1_table = NULL; |
| img.cluster_bits = ext2fs_be32_to_cpu(hdr->cluster_bits); |
| img.cluster_size = 1 << img.cluster_bits; |
| img.l1_size = ext2fs_be32_to_cpu(hdr->l1_size); |
| img.l1_offset = ext2fs_be64_to_cpu(hdr->l1_table_offset); |
| img.l2_size = 1 << (img.cluster_bits - 3); |
| img.image_size = ext2fs_be64_to_cpu(hdr->size); |
| |
| |
| ret = ext2fs_get_memzero(img.cluster_size, &l2_table); |
| if (ret) |
| goto out; |
| |
| ret = ext2fs_get_memzero(1 << img.cluster_bits, ©_buf); |
| if (ret) |
| goto out; |
| |
| if (ext2fs_llseek(raw_fd, 0, SEEK_SET) < 0) { |
| ret = errno; |
| goto out; |
| } |
| |
| ret = qcow2_read_l1_table(&img); |
| if (ret) |
| goto out; |
| |
| l1_table = img.l1_table; |
| /* Walk through l1 table */ |
| for (l1_index = 0; l1_index < img.l1_size; l1_index++) { |
| ext2_off64_t off_out; |
| |
| offset = ext2fs_be64_to_cpu(l1_table[l1_index]) & |
| ~QCOW_OFLAG_COPIED; |
| |
| if ((offset > img.image_size) || |
| (offset <= 0)) |
| continue; |
| |
| if (offset & QCOW_OFLAG_COMPRESSED) { |
| ret = -QCOW_COMPRESSED; |
| goto out; |
| } |
| |
| ret = qcow2_read_l2_table(&img, offset, &l2_table); |
| if (ret) |
| break; |
| |
| /* Walk through l2 table and copy data blocks into raw image */ |
| for (l2_index = 0; l2_index < img.l2_size; l2_index++) { |
| offset = ext2fs_be64_to_cpu(l2_table[l2_index]) & |
| ~QCOW_OFLAG_COPIED; |
| |
| if (offset == 0) |
| continue; |
| |
| off_out = (l1_index * img.l2_size) + |
| l2_index; |
| off_out <<= img.cluster_bits; |
| ret = qcow2_copy_data(qcow2_fd, raw_fd, offset, |
| off_out, copy_buf, img.cluster_size); |
| if (ret) |
| goto out; |
| } |
| } |
| |
| /* Resize the output image to the filesystem size */ |
| if (ext2fs_llseek(raw_fd, img.image_size - 1, SEEK_SET) < 0) { |
| ret = errno; |
| goto out; |
| } |
| |
| ((char *)copy_buf)[0] = 0; |
| size = write(raw_fd, copy_buf, 1); |
| if (size != 1) { |
| ret = errno; |
| goto out; |
| } |
| |
| out: |
| if (copy_buf) |
| ext2fs_free_mem(©_buf); |
| if (img.l1_table) |
| ext2fs_free_mem(&img.l1_table); |
| if (l2_table) |
| ext2fs_free_mem(&l2_table); |
| return ret; |
| } |