mirror of
https://github.com/systemd/systemd.git
synced 2024-11-12 15:21:19 +03:00
15411c0cb1
Replace ENOTSUP by EOPNOTSUPP as this is what linux actually uses.
354 lines
10 KiB
C
354 lines
10 KiB
C
/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
|
|
|
|
/***
|
|
This file is part of systemd.
|
|
|
|
Copyright 2015 Lennart Poettering
|
|
|
|
systemd is free software; you can redistribute it and/or modify it
|
|
under the terms of the GNU Lesser General Public License as published by
|
|
the Free Software Foundation; either version 2.1 of the License, or
|
|
(at your option) any later version.
|
|
|
|
systemd is distributed in the hope that it will be useful, but
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
Lesser General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Lesser General Public License
|
|
along with systemd; If not, see <http://www.gnu.org/licenses/>.
|
|
***/
|
|
|
|
#include <zlib.h>
|
|
|
|
#include "util.h"
|
|
#include "sparse-endian.h"
|
|
#include "qcow2-util.h"
|
|
#include "btrfs-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 (HEADER_VERSION(header) != 2 &&
|
|
HEADER_VERSION(header) != 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;
|
|
}
|