Systemd/src/import/qcow2-util.c
Lennart Poettering 0c69794138 tree-wide: remove Lennart's copyright lines
These lines are generally out-of-date, incomplete and unnecessary. With
SPDX and git repository much more accurate and fine grained information
about licensing and authorship is available, hence let's drop the
per-file copyright notice. Of course, removing copyright lines of others
is problematic, hence this commit only removes my own lines and leaves
all others untouched. It might be nicer if sooner or later those could
go away too, making git the only and accurate source of authorship
information.
2018-06-14 10:20:20 +02:00

335 lines
9.7 KiB
C

/* SPDX-License-Identifier: LGPL-2.1+ */
#include <zlib.h>
#include "alloc-util.h"
#include "btrfs-util.h"
#include "qcow2-util.h"
#include "sparse-endian.h"
#include "util.h"
#define QCOW2_MAGIC 0x514649fb
#define QCOW2_COPIED (1ULL << 63)
#define QCOW2_COMPRESSED (1ULL << 62)
#define QCOW2_ZERO (1ULL << 0)
typedef struct _packed_ Header {
be32_t magic;
be32_t version;
be64_t backing_file_offset;
be32_t backing_file_size;
be32_t cluster_bits;
be64_t size;
be32_t crypt_method;
be32_t l1_size;
be64_t l1_table_offset;
be64_t refcount_table_offset;
be32_t refcount_table_clusters;
be32_t nb_snapshots;
be64_t snapshots_offset;
/* The remainder is only present on QCOW3 */
be64_t incompatible_features;
be64_t compatible_features;
be64_t autoclear_features;
be32_t refcount_order;
be32_t header_length;
} Header;
#define HEADER_MAGIC(header) be32toh((header)->magic)
#define HEADER_VERSION(header) be32toh((header)->version)
#define HEADER_CLUSTER_BITS(header) be32toh((header)->cluster_bits)
#define HEADER_CLUSTER_SIZE(header) (1ULL << HEADER_CLUSTER_BITS(header))
#define HEADER_L2_BITS(header) (HEADER_CLUSTER_BITS(header) - 3)
#define HEADER_SIZE(header) be64toh((header)->size)
#define HEADER_CRYPT_METHOD(header) be32toh((header)->crypt_method)
#define HEADER_L1_SIZE(header) be32toh((header)->l1_size)
#define HEADER_L2_SIZE(header) (HEADER_CLUSTER_SIZE(header)/sizeof(uint64_t))
#define HEADER_L1_TABLE_OFFSET(header) be64toh((header)->l1_table_offset)
static uint32_t HEADER_HEADER_LENGTH(const Header *h) {
if (HEADER_VERSION(h) < 3)
return offsetof(Header, incompatible_features);
return be32toh(h->header_length);
}
static int copy_cluster(
int sfd, uint64_t soffset,
int dfd, uint64_t doffset,
uint64_t cluster_size,
void *buffer) {
ssize_t l;
int r;
r = btrfs_clone_range(sfd, soffset, dfd, doffset, cluster_size);
if (r >= 0)
return r;
l = pread(sfd, buffer, cluster_size, soffset);
if (l < 0)
return -errno;
if ((uint64_t) l != cluster_size)
return -EIO;
l = pwrite(dfd, buffer, cluster_size, doffset);
if (l < 0)
return -errno;
if ((uint64_t) l != cluster_size)
return -EIO;
return 0;
}
static int decompress_cluster(
int sfd, uint64_t soffset,
int dfd, uint64_t doffset,
uint64_t compressed_size,
uint64_t cluster_size,
void *buffer1,
void *buffer2) {
_cleanup_free_ void *large_buffer = NULL;
z_stream s = {};
uint64_t sz;
ssize_t l;
int r;
if (compressed_size > cluster_size) {
/* The usual cluster buffer doesn't suffice, let's
* allocate a larger one, temporarily */
large_buffer = malloc(compressed_size);
if (!large_buffer)
return -ENOMEM;
buffer1 = large_buffer;
}
l = pread(sfd, buffer1, compressed_size, soffset);
if (l < 0)
return -errno;
if ((uint64_t) l != compressed_size)
return -EIO;
s.next_in = buffer1;
s.avail_in = compressed_size;
s.next_out = buffer2;
s.avail_out = cluster_size;
r = inflateInit2(&s, -12);
if (r != Z_OK)
return -EIO;
r = inflate(&s, Z_FINISH);
sz = (uint8_t*) s.next_out - (uint8_t*) buffer2;
inflateEnd(&s);
if (r != Z_STREAM_END || sz != cluster_size)
return -EIO;
l = pwrite(dfd, buffer2, cluster_size, doffset);
if (l < 0)
return -errno;
if ((uint64_t) l != cluster_size)
return -EIO;
return 0;
}
static int normalize_offset(
const Header *header,
uint64_t p,
uint64_t *ret,
bool *compressed,
uint64_t *compressed_size) {
uint64_t q;
q = be64toh(p);
if (q & QCOW2_COMPRESSED) {
uint64_t sz, csize_shift, csize_mask;
if (!compressed)
return -EOPNOTSUPP;
csize_shift = 64 - 2 - (HEADER_CLUSTER_BITS(header) - 8);
csize_mask = (1ULL << (HEADER_CLUSTER_BITS(header) - 8)) - 1;
sz = (((q >> csize_shift) & csize_mask) + 1) * 512 - (q & 511);
q &= ((1ULL << csize_shift) - 1);
if (compressed_size)
*compressed_size = sz;
*compressed = true;
} else {
if (compressed) {
*compressed = false;
*compressed_size = 0;
}
if (q & QCOW2_ZERO) {
/* We make no distinction between zero blocks and holes */
*ret = 0;
return 0;
}
q &= ~QCOW2_COPIED;
}
*ret = q;
return q > 0; /* returns positive if not a hole */
}
static int verify_header(const Header *header) {
assert(header);
if (HEADER_MAGIC(header) != QCOW2_MAGIC)
return -EBADMSG;
if (!IN_SET(HEADER_VERSION(header), 2, 3))
return -EOPNOTSUPP;
if (HEADER_CRYPT_METHOD(header) != 0)
return -EOPNOTSUPP;
if (HEADER_CLUSTER_BITS(header) < 9) /* 512K */
return -EBADMSG;
if (HEADER_CLUSTER_BITS(header) > 21) /* 2MB */
return -EBADMSG;
if (HEADER_SIZE(header) % HEADER_CLUSTER_SIZE(header) != 0)
return -EBADMSG;
if (HEADER_L1_SIZE(header) > 32*1024*1024) /* 32MB */
return -EBADMSG;
if (HEADER_VERSION(header) == 3) {
if (header->incompatible_features != 0)
return -EOPNOTSUPP;
if (HEADER_HEADER_LENGTH(header) < sizeof(Header))
return -EBADMSG;
}
return 0;
}
int qcow2_convert(int qcow2_fd, int raw_fd) {
_cleanup_free_ void *buffer1 = NULL, *buffer2 = NULL;
_cleanup_free_ be64_t *l1_table = NULL, *l2_table = NULL;
uint64_t sz, i;
Header header;
ssize_t l;
int r;
l = pread(qcow2_fd, &header, sizeof(header), 0);
if (l < 0)
return -errno;
if (l != sizeof(header))
return -EIO;
r = verify_header(&header);
if (r < 0)
return r;
l1_table = new(be64_t, HEADER_L1_SIZE(&header));
if (!l1_table)
return -ENOMEM;
l2_table = malloc(HEADER_CLUSTER_SIZE(&header));
if (!l2_table)
return -ENOMEM;
buffer1 = malloc(HEADER_CLUSTER_SIZE(&header));
if (!buffer1)
return -ENOMEM;
buffer2 = malloc(HEADER_CLUSTER_SIZE(&header));
if (!buffer2)
return -ENOMEM;
/* Empty the file if it exists, we rely on zero bits */
if (ftruncate(raw_fd, 0) < 0)
return -errno;
if (ftruncate(raw_fd, HEADER_SIZE(&header)) < 0)
return -errno;
sz = sizeof(uint64_t) * HEADER_L1_SIZE(&header);
l = pread(qcow2_fd, l1_table, sz, HEADER_L1_TABLE_OFFSET(&header));
if (l < 0)
return -errno;
if ((uint64_t) l != sz)
return -EIO;
for (i = 0; i < HEADER_L1_SIZE(&header); i ++) {
uint64_t l2_begin, j;
r = normalize_offset(&header, l1_table[i], &l2_begin, NULL, NULL);
if (r < 0)
return r;
if (r == 0)
continue;
l = pread(qcow2_fd, l2_table, HEADER_CLUSTER_SIZE(&header), l2_begin);
if (l < 0)
return -errno;
if ((uint64_t) l != HEADER_CLUSTER_SIZE(&header))
return -EIO;
for (j = 0; j < HEADER_L2_SIZE(&header); j++) {
uint64_t data_begin, p, compressed_size;
bool compressed;
p = ((i << HEADER_L2_BITS(&header)) + j) << HEADER_CLUSTER_BITS(&header);
r = normalize_offset(&header, l2_table[j], &data_begin, &compressed, &compressed_size);
if (r < 0)
return r;
if (r == 0)
continue;
if (compressed)
r = decompress_cluster(
qcow2_fd, data_begin,
raw_fd, p,
compressed_size, HEADER_CLUSTER_SIZE(&header),
buffer1, buffer2);
else
r = copy_cluster(
qcow2_fd, data_begin,
raw_fd, p,
HEADER_CLUSTER_SIZE(&header), buffer1);
if (r < 0)
return r;
}
}
return 0;
}
int qcow2_detect(int fd) {
be32_t id;
ssize_t l;
l = pread(fd, &id, sizeof(id), 0);
if (l < 0)
return -errno;
if (l != sizeof(id))
return -EIO;
return htobe32(QCOW2_MAGIC) == id;
}