blob: 547edc02e5660d2b13852d2a6ca6d84a53d9c6fd [file] [log] [blame]
lh9ed821d2023-04-07 01:36:19 -07001/*
2 * qcow2.c --- Functions to generate qcow2 formatted disk images. This
3 * format is used originally by QEMU for virtual machines, and stores the
4 * filesystem data on disk in a packed format to avoid creating sparse
5 * image files that need lots of seeking to read and write.
6 *
7 * The qcow2 format supports zlib compression, but that is not yet
8 * implemented.
9 *
10 * It is possible to directly mount a qcow2 image using qemu-nbd:
11 *
12 * [root]# modprobe nbd max_part=63
13 * [root]# qemu-nbd -c /dev/nbd0 image.img
14 * [root]# mount /dev/nbd0p1 /mnt/qemu
15 *
16 * Format details at http://people.gnome.org/~markmc/qcow-image-format.html
17 *
18 * Copyright (C) 2010 Red Hat, Inc., Lukas Czerner <lczerner@redhat.com>
19 *
20 * %Begin-Header%
21 * This file may be redistributed under the terms of the GNU Public
22 * License.
23 * %End-Header%
24 */
25
26#define _LARGEFILE_SOURCE
27#define _LARGEFILE64_SOURCE
28
29#include "config.h"
30#include <fcntl.h>
31#include <grp.h>
32#include <pwd.h>
33#include <stdio.h>
34#ifdef HAVE_STDLIB_H
35#include <stdlib.h>
36#endif
37#include <string.h>
38#include <time.h>
39#include <unistd.h>
40#include <fcntl.h>
41#include <errno.h>
42#include <sys/stat.h>
43#include <sys/types.h>
44#include <assert.h>
45
46#include "ext2fs/ext2fs.h"
47#include "qcow2.h"
48
49/* Functions for converting qcow2 image into raw image */
50
51struct ext2_qcow2_hdr *qcow2_read_header(int fd)
52{
53 void *buffer = NULL;
54 struct ext2_qcow2_hdr *hdr = NULL;
55 size_t size;
56 errcode_t ret;
57
58 ret = ext2fs_get_mem(sizeof(struct ext2_qcow2_hdr), &buffer);
59 if (ret)
60 return NULL;
61 memset(buffer, 0, sizeof(struct ext2_qcow2_hdr));
62
63 if (ext2fs_llseek(fd, 0, SEEK_SET < 0)) {
64 ext2fs_free_mem(&buffer);
65 return NULL;
66 }
67
68 size = read(fd, buffer, sizeof(struct ext2_qcow2_hdr));
69 if (size != sizeof(struct ext2_qcow2_hdr)) {
70 ext2fs_free_mem(&buffer);
71 return NULL;
72 }
73
74 hdr = (struct ext2_qcow2_hdr *)(buffer);
75
76 if ((ext2fs_be32_to_cpu(hdr->magic) != QCOW_MAGIC) ||
77 (ext2fs_be32_to_cpu(hdr->version) != 2)) {
78 ext2fs_free_mem(&hdr);
79 return NULL;
80 }
81
82 return hdr;
83}
84
85static int qcow2_read_l1_table(struct ext2_qcow2_image *img)
86{
87 int fd = img->fd;
88 size_t size, l1_size = img->l1_size * sizeof(blk64_t);
89 blk64_t *table;
90 errcode_t ret;
91
92 ret = ext2fs_get_memzero(l1_size, &table);
93 if (ret)
94 return ret;
95
96 if (ext2fs_llseek(fd, img->l1_offset, SEEK_SET) < 0) {
97 ext2fs_free_mem(&table);
98 return errno;
99 }
100
101 size = read(fd, table, l1_size);
102 if (size != l1_size) {
103 ext2fs_free_mem(&table);
104 return errno;
105 }
106
107 img->l1_table = table;
108
109 return 0;
110}
111
112static int qcow2_read_l2_table(struct ext2_qcow2_image *img,
113 ext2_off64_t offset, blk64_t **l2_table)
114{
115 int fd = img->fd;
116 size_t size;
117
118 assert(*l2_table);
119
120 if (ext2fs_llseek(fd, offset, SEEK_SET) < 0)
121 return errno;
122
123 size = read(fd, *l2_table, img->cluster_size);
124 if (size != img->cluster_size)
125 return errno;
126
127 return 0;
128}
129
130static int qcow2_copy_data(int fdin, int fdout, ext2_off64_t off_in,
131 ext2_off64_t off_out, void *buf, size_t count)
132{
133 size_t size;
134
135 assert(buf);
136
137 if (ext2fs_llseek(fdout, off_out, SEEK_SET) < 0)
138 return errno;
139
140 if (ext2fs_llseek(fdin, off_in, SEEK_SET) < 0)
141 return errno;
142
143 size = read(fdin, buf, count);
144 if (size != count)
145 return errno;
146
147 size = write(fdout, buf, count);
148 if (size != count)
149 return errno;
150
151 return 0;
152}
153
154
155int qcow2_write_raw_image(int qcow2_fd, int raw_fd,
156 struct ext2_qcow2_hdr *hdr)
157{
158 struct ext2_qcow2_image img;
159 errcode_t ret = 0;
160 unsigned int l1_index, l2_index;
161 ext2_off64_t offset;
162 blk64_t *l1_table, *l2_table = NULL;
163 void *copy_buf = NULL;
164 size_t size;
165
166 if (hdr->crypt_method)
167 return -QCOW_ENCRYPTED;
168
169 img.fd = qcow2_fd;
170 img.hdr = hdr;
171 img.l2_cache = NULL;
172 img.l1_table = NULL;
173 img.cluster_bits = ext2fs_be32_to_cpu(hdr->cluster_bits);
174 img.cluster_size = 1 << img.cluster_bits;
175 img.l1_size = ext2fs_be32_to_cpu(hdr->l1_size);
176 img.l1_offset = ext2fs_be64_to_cpu(hdr->l1_table_offset);
177 img.l2_size = 1 << (img.cluster_bits - 3);
178 img.image_size = ext2fs_be64_to_cpu(hdr->size);
179
180
181 ret = ext2fs_get_memzero(img.cluster_size, &l2_table);
182 if (ret)
183 goto out;
184
185 ret = ext2fs_get_memzero(1 << img.cluster_bits, &copy_buf);
186 if (ret)
187 goto out;
188
189 if (ext2fs_llseek(raw_fd, 0, SEEK_SET) < 0) {
190 ret = errno;
191 goto out;
192 }
193
194 ret = qcow2_read_l1_table(&img);
195 if (ret)
196 goto out;
197
198 l1_table = img.l1_table;
199 /* Walk through l1 table */
200 for (l1_index = 0; l1_index < img.l1_size; l1_index++) {
201 ext2_off64_t off_out;
202
203 offset = ext2fs_be64_to_cpu(l1_table[l1_index]) &
204 ~QCOW_OFLAG_COPIED;
205
206 if ((offset > img.image_size) ||
207 (offset <= 0))
208 continue;
209
210 if (offset & QCOW_OFLAG_COMPRESSED) {
211 ret = -QCOW_COMPRESSED;
212 goto out;
213 }
214
215 ret = qcow2_read_l2_table(&img, offset, &l2_table);
216 if (ret)
217 break;
218
219 /* Walk through l2 table and copy data blocks into raw image */
220 for (l2_index = 0; l2_index < img.l2_size; l2_index++) {
221 offset = ext2fs_be64_to_cpu(l2_table[l2_index]) &
222 ~QCOW_OFLAG_COPIED;
223
224 if (offset == 0)
225 continue;
226
227 off_out = (l1_index * img.l2_size) +
228 l2_index;
229 off_out <<= img.cluster_bits;
230 ret = qcow2_copy_data(qcow2_fd, raw_fd, offset,
231 off_out, copy_buf, img.cluster_size);
232 if (ret)
233 goto out;
234 }
235 }
236
237 /* Resize the output image to the filesystem size */
238 if (ext2fs_llseek(raw_fd, img.image_size - 1, SEEK_SET) < 0)
239 return errno;
240
241 ((char *)copy_buf)[0] = 0;
242 size = write(raw_fd, copy_buf, 1);
243 if (size != 1) {
244 ret = errno;
245 goto out;
246 }
247
248out:
249 if (copy_buf)
250 ext2fs_free_mem(&copy_buf);
251 if (img.l1_table)
252 ext2fs_free_mem(&img.l1_table);
253 if (l2_table)
254 ext2fs_free_mem(&l2_table);
255 return ret;
256}