1 | /*
|
---|
2 | * Copyright (c) 2021 Erik Kučák
|
---|
3 | * All rights reserved.
|
---|
4 | *
|
---|
5 | * Redistribution and use in source and binary forms, with or without
|
---|
6 | * modification, are permitted provided that the following conditions
|
---|
7 | * are met:
|
---|
8 | *
|
---|
9 | * - Redistributions of source code must retain the above copyright
|
---|
10 | * notice, this list of conditions and the following disclaimer.
|
---|
11 | * - Redistributions in binary form must reproduce the above copyright
|
---|
12 | * notice, this list of conditions and the following disclaimer in the
|
---|
13 | * documentation and/or other materials provided with the distribution.
|
---|
14 | * - The name of the author may not be used to endorse or promote products
|
---|
15 | * derived from this software without specific prior written permission.
|
---|
16 | *
|
---|
17 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
---|
18 | * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
---|
19 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
---|
20 | * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
---|
21 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
---|
22 | * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
---|
23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
---|
24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
---|
25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
---|
26 | * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
---|
27 | */
|
---|
28 |
|
---|
29 | /** @addtogroup qcow_bd
|
---|
30 | * @{
|
---|
31 | */
|
---|
32 |
|
---|
33 | /**
|
---|
34 | * @file
|
---|
35 | * @brief QCOW file block device driver
|
---|
36 | *
|
---|
37 | * Allows accessing a file as a block device in QCOW format. Useful for, e.g., mounting
|
---|
38 | * a disk image.
|
---|
39 | */
|
---|
40 |
|
---|
41 | #include "qcow_bd.h"
|
---|
42 |
|
---|
43 | static FILE *img;
|
---|
44 | static QCowHeader header;
|
---|
45 | static QcowState state;
|
---|
46 |
|
---|
47 | static service_id_t service_id;
|
---|
48 | static bd_srvs_t bd_srvs;
|
---|
49 | static fibril_mutex_t dev_lock;
|
---|
50 | static void print_usage(void);
|
---|
51 | static errno_t qcow_bd_init(const char *fname);
|
---|
52 | static void qcow_bd_connection(ipc_call_t *icall, void *);
|
---|
53 |
|
---|
54 | static errno_t qcow_bd_open(bd_srvs_t *, bd_srv_t *);
|
---|
55 | static errno_t qcow_bd_close(bd_srv_t *);
|
---|
56 | static errno_t qcow_bd_read_blocks(bd_srv_t *, aoff64_t, size_t, void *, size_t);
|
---|
57 | static errno_t qcow_bd_write_blocks(bd_srv_t *, aoff64_t, size_t, const void *, size_t);
|
---|
58 | static errno_t qcow_bd_get_block_size(bd_srv_t *, size_t *);
|
---|
59 | static errno_t qcow_bd_get_num_blocks(bd_srv_t *, aoff64_t *);
|
---|
60 |
|
---|
61 | static bd_ops_t qcow_bd_ops = {
|
---|
62 | .open = qcow_bd_open,
|
---|
63 | .close = qcow_bd_close,
|
---|
64 | .read_blocks = qcow_bd_read_blocks,
|
---|
65 | .write_blocks = qcow_bd_write_blocks,
|
---|
66 | .get_block_size = qcow_bd_get_block_size,
|
---|
67 | .get_num_blocks = qcow_bd_get_num_blocks
|
---|
68 | };
|
---|
69 |
|
---|
70 | int main(int argc, char **argv)
|
---|
71 | {
|
---|
72 | errno_t rc;
|
---|
73 | char *image_name;
|
---|
74 | char *device_name;
|
---|
75 | category_id_t disk_cat;
|
---|
76 |
|
---|
77 | printf(NAME ": File-backed block device driver in QCOW format\n");
|
---|
78 |
|
---|
79 | state.block_size = DEFAULT_BLOCK_SIZE;
|
---|
80 |
|
---|
81 | ++argv;
|
---|
82 | --argc;
|
---|
83 | while (*argv != NULL && (*argv)[0] == '-') {
|
---|
84 | /* Option */
|
---|
85 | if (str_cmp(*argv, "-b") == 0) {
|
---|
86 | if (argc < 2) {
|
---|
87 | fprintf(stderr, "Argument missing.\n");
|
---|
88 | print_usage();
|
---|
89 | return -1;
|
---|
90 | }
|
---|
91 |
|
---|
92 | rc = str_size_t(argv[1], NULL, 10, true, &state.block_size);
|
---|
93 | if (rc != EOK || state.block_size == 0) {
|
---|
94 | fprintf(stderr, "Invalid block size '%s'.\n", argv[1]);
|
---|
95 | print_usage();
|
---|
96 | return -1;
|
---|
97 | }
|
---|
98 | ++argv;
|
---|
99 | --argc;
|
---|
100 | } else {
|
---|
101 | fprintf(stderr, "Invalid option '%s'.\n", *argv);
|
---|
102 | print_usage();
|
---|
103 | return -1;
|
---|
104 | }
|
---|
105 | ++argv;
|
---|
106 | --argc;
|
---|
107 | }
|
---|
108 |
|
---|
109 | if (argc < 2) {
|
---|
110 | fprintf(stderr, "Missing arguments.\n");
|
---|
111 | print_usage();
|
---|
112 | return -1;
|
---|
113 | }
|
---|
114 |
|
---|
115 | image_name = argv[0];
|
---|
116 | device_name = argv[1];
|
---|
117 |
|
---|
118 | if (qcow_bd_init(image_name) != EOK)
|
---|
119 | return -1;
|
---|
120 |
|
---|
121 | rc = loc_service_register(device_name, &service_id);
|
---|
122 | if (rc != EOK) {
|
---|
123 | fprintf(stderr, "%s: Unable to register device '%s': %s.\n",
|
---|
124 | NAME, device_name, str_error(rc));
|
---|
125 | return rc;
|
---|
126 | }
|
---|
127 |
|
---|
128 | rc = loc_category_get_id("disk", &disk_cat, IPC_FLAG_BLOCKING);
|
---|
129 | if (rc != EOK) {
|
---|
130 | fprintf(stderr, "%s: Failed resolving category 'disk': %s\n", NAME, str_error(rc));
|
---|
131 | return rc;
|
---|
132 | }
|
---|
133 |
|
---|
134 | rc = loc_service_add_to_cat(service_id, disk_cat);
|
---|
135 | if (rc != EOK) {
|
---|
136 | fprintf(stderr, "%s: Failed adding %s to category: %s",
|
---|
137 | NAME, device_name, str_error(rc));
|
---|
138 | return rc;
|
---|
139 | }
|
---|
140 |
|
---|
141 | printf("%s: Accepting connections\n", NAME);
|
---|
142 | task_retval(0);
|
---|
143 | async_manager();
|
---|
144 |
|
---|
145 | /* Not reached */
|
---|
146 | return 0;
|
---|
147 | }
|
---|
148 |
|
---|
149 | static void print_usage(void)
|
---|
150 | {
|
---|
151 | printf("Usage: " NAME " [-b <block_size>] <image_file> <device_name>\n");
|
---|
152 | }
|
---|
153 |
|
---|
154 | static errno_t compute_table_sizes()
|
---|
155 | {
|
---|
156 | /* Computing sizes in bytes */
|
---|
157 | state.cluster_size = 1 << header.cluster_bits;
|
---|
158 | state.l2_size = (1 << header.l2_bits) * 8;
|
---|
159 |
|
---|
160 | /* Computing size of L1 table in bytes */
|
---|
161 | state.l1_size = state.cluster_size * (1 << header.l2_bits);
|
---|
162 | if (header.size % state.l1_size != 0)
|
---|
163 | state.l1_size = (header.size / state.l1_size) + 1;
|
---|
164 | else
|
---|
165 | state.l1_size = header.size / state.l1_size;
|
---|
166 |
|
---|
167 | state.l1_size *= 8;
|
---|
168 |
|
---|
169 | /* Computing number of blocks */
|
---|
170 | if (header.size % state.block_size != 0)
|
---|
171 | state.num_blocks = (header.size / state.block_size) + 1;
|
---|
172 | else
|
---|
173 | state.num_blocks = (header.size / state.block_size);
|
---|
174 |
|
---|
175 | return EOK;
|
---|
176 | }
|
---|
177 |
|
---|
178 | static errno_t read_l1_table()
|
---|
179 | {
|
---|
180 | /* Allocating memory for l1_table */
|
---|
181 | state.l1_table_offset = header.l1_table_offset;
|
---|
182 | state.l2_references = (uint64_t*)malloc(state.l1_size);
|
---|
183 |
|
---|
184 | /* Reading all L2 table references from L1 table */
|
---|
185 | clearerr(img);
|
---|
186 | printf("READING 2,5\n");
|
---|
187 | if (fseek(img, state.l1_table_offset, SEEK_SET) < 0)
|
---|
188 | return EIO;
|
---|
189 |
|
---|
190 | size_t n_rd = fread(state.l2_references, state.l1_size, 1, img);
|
---|
191 |
|
---|
192 | if (n_rd < 1)
|
---|
193 | return EINVAL;
|
---|
194 |
|
---|
195 | /* Clearing last bit in all references which tells us whether cluster is compressed */
|
---|
196 | for (uint64_t i = 0; i < state.l1_size / sizeof(uint64_t); i++) {
|
---|
197 | state.l2_references[i] = __builtin_bswap64(state.l2_references[i]);
|
---|
198 | if (state.l2_references[i] & QCOW_OFLAG_COMPRESSED) {
|
---|
199 | fprintf(stderr, "Compression is not supported!\n");
|
---|
200 | return ENOTSUP;
|
---|
201 | }
|
---|
202 | }
|
---|
203 |
|
---|
204 | if (ferror(img))
|
---|
205 | return EIO; /* Read error */
|
---|
206 | return EOK;
|
---|
207 | }
|
---|
208 |
|
---|
209 | static errno_t qcow_bd_init(const char *fname)
|
---|
210 | {
|
---|
211 | /* Register driver */
|
---|
212 | bd_srvs_init(&bd_srvs);
|
---|
213 | bd_srvs.ops = &qcow_bd_ops;
|
---|
214 |
|
---|
215 | async_set_fallback_port_handler(qcow_bd_connection, NULL);
|
---|
216 | errno_t rc = loc_server_register(NAME);
|
---|
217 | if (rc != EOK) {
|
---|
218 | fprintf(stderr, "%s: Unable to register driver.\n", NAME);
|
---|
219 | return rc;
|
---|
220 | }
|
---|
221 |
|
---|
222 | /* Try to open file */
|
---|
223 | img = fopen(fname, "rb+");
|
---|
224 | if (img == NULL)
|
---|
225 | return EINVAL;
|
---|
226 |
|
---|
227 | if (fseek(img, 0, SEEK_END) != 0) {
|
---|
228 | fclose(img);
|
---|
229 | return EIO;
|
---|
230 | }
|
---|
231 |
|
---|
232 | if (fseek(img, 0, SEEK_SET) < 0)
|
---|
233 | return EIO;
|
---|
234 |
|
---|
235 | /* Read the file header */
|
---|
236 | size_t n_rd = fread(&header, sizeof(header), 1, img);
|
---|
237 |
|
---|
238 | if (n_rd < 1)
|
---|
239 | return EINVAL;
|
---|
240 |
|
---|
241 | /* Swap values to big-endian */
|
---|
242 | header.magic = __builtin_bswap32(header.magic);
|
---|
243 | header.version = __builtin_bswap32(header.version);
|
---|
244 | header.backing_file_offset = __builtin_bswap64(header.backing_file_offset);
|
---|
245 | header.backing_file_size = __builtin_bswap32(header.backing_file_size);
|
---|
246 | header.mtime = __builtin_bswap32(header.mtime);
|
---|
247 | header.size = __builtin_bswap64(header.size);
|
---|
248 | header.crypt_method = __builtin_bswap32(header.crypt_method);
|
---|
249 | header.l1_table_offset = __builtin_bswap64(header.l1_table_offset);
|
---|
250 |
|
---|
251 | /* Verify all values from file header */
|
---|
252 | if (ferror(img))
|
---|
253 | return EIO;
|
---|
254 |
|
---|
255 | if (header.magic == QCOW_MAGIC) {
|
---|
256 | errno_t error = compute_table_sizes();
|
---|
257 | if (error != EOK) {
|
---|
258 | return error;
|
---|
259 | }
|
---|
260 | error = read_l1_table();
|
---|
261 | if (error != EOK) {
|
---|
262 | return error;
|
---|
263 | }
|
---|
264 | }
|
---|
265 |
|
---|
266 | if (header.version != QCOW_VERSION) {
|
---|
267 | fprintf(stderr, "Version: %d is not supported!\n", header.version);
|
---|
268 | return ENOTSUP;
|
---|
269 | }
|
---|
270 |
|
---|
271 | if (header.crypt_method != QCOW_CRYPT_NONE) {
|
---|
272 | fprintf(stderr, "Encryption is not supported!\n");
|
---|
273 | return ENOTSUP;
|
---|
274 | }
|
---|
275 |
|
---|
276 | /* Initialize mutex variable */
|
---|
277 | fibril_mutex_initialize(&dev_lock);
|
---|
278 |
|
---|
279 | return EOK;
|
---|
280 | }
|
---|
281 |
|
---|
282 | static void qcow_bd_connection(ipc_call_t *icall, void *arg)
|
---|
283 | {
|
---|
284 | bd_conn(icall, &bd_srvs);
|
---|
285 | }
|
---|
286 |
|
---|
287 | /** Open device. */
|
---|
288 | static errno_t qcow_bd_open(bd_srvs_t *bds, bd_srv_t *bd)
|
---|
289 | {
|
---|
290 | return EOK;
|
---|
291 | }
|
---|
292 |
|
---|
293 | /** Close device. */
|
---|
294 | static errno_t qcow_bd_close(bd_srv_t *bd)
|
---|
295 | {
|
---|
296 | free(state.l2_references);
|
---|
297 | fclose(img);
|
---|
298 | return EOK;
|
---|
299 | }
|
---|
300 |
|
---|
301 | /** From the offset of the given block compute its offset which is relative from the start of qcow file. */
|
---|
302 | static uint64_t get_block_offset(uint64_t offset, errno_t *error)
|
---|
303 | {
|
---|
304 | /* Compute l1 table index from the offset */
|
---|
305 | uint64_t l1_table_index_bit_shift = header.cluster_bits + header.l2_bits;
|
---|
306 | uint64_t l1_table_index = (offset & 0x7fffffffffffffffULL) >> l1_table_index_bit_shift;
|
---|
307 |
|
---|
308 | /* Reading l2 reference from the l1 table */
|
---|
309 | uint64_t l2_table_reference = state.l2_references[l1_table_index];
|
---|
310 |
|
---|
311 | if (l2_table_reference == QCOW_UNALLOCATED_REFERENCE)
|
---|
312 | return QCOW_UNALLOCATED_REFERENCE;
|
---|
313 |
|
---|
314 | /* Compute l2 table index from the offset */
|
---|
315 | uint64_t l2_table_index = (offset >> header.cluster_bits) & (state.l2_size - 1);
|
---|
316 |
|
---|
317 | /* Reading cluster reference from the l2 table */
|
---|
318 | if (fseek(img, l2_table_reference + l2_table_index * sizeof(uint64_t), SEEK_SET) < 0){
|
---|
319 | *error = EIO;
|
---|
320 | return EIO;
|
---|
321 | }
|
---|
322 |
|
---|
323 | uint64_t cluster_reference;
|
---|
324 | size_t n_rd = fread(&cluster_reference, sizeof(uint64_t), 1, img);
|
---|
325 |
|
---|
326 | if (n_rd < 1) {
|
---|
327 | *error = EINVAL;
|
---|
328 | return EINVAL;
|
---|
329 | }
|
---|
330 |
|
---|
331 | cluster_reference = __builtin_bswap64(cluster_reference);
|
---|
332 |
|
---|
333 | if (cluster_reference & QCOW_OFLAG_COMPRESSED) {
|
---|
334 | fprintf(stderr, "Compression is not supported!\n");
|
---|
335 | *error = ENOTSUP;
|
---|
336 | return ENOTSUP;
|
---|
337 | }
|
---|
338 |
|
---|
339 | if (cluster_reference == QCOW_UNALLOCATED_REFERENCE)
|
---|
340 | return QCOW_UNALLOCATED_REFERENCE;
|
---|
341 |
|
---|
342 | /* Compute cluster block offset from the offset */
|
---|
343 | uint64_t cluster_block_bit_mask = ~(0xffffffffffffffffULL << header.cluster_bits);
|
---|
344 | uint64_t cluster_block_offset = offset & cluster_block_bit_mask;
|
---|
345 |
|
---|
346 | return cluster_reference + cluster_block_offset;
|
---|
347 | }
|
---|
348 |
|
---|
349 | /** Read blocks from the device. */
|
---|
350 | static errno_t qcow_bd_read_blocks(bd_srv_t *bd, uint64_t ba, size_t cnt, void *buf,
|
---|
351 | size_t size)
|
---|
352 | {
|
---|
353 | if (size < cnt * state.block_size)
|
---|
354 | return EINVAL;
|
---|
355 |
|
---|
356 | /* Check whether access is within device address bounds. */
|
---|
357 | if (ba + cnt > state.num_blocks) {
|
---|
358 | fprintf(stderr, NAME ": Accessed blocks %" PRIuOFF64 "-%" PRIuOFF64 ", while "
|
---|
359 | "max block number is %" PRIuOFF64 ".\n", ba, ba + cnt - 1,
|
---|
360 | state.num_blocks - 1);
|
---|
361 | return ELIMIT;
|
---|
362 | }
|
---|
363 |
|
---|
364 | fibril_mutex_lock(&dev_lock);
|
---|
365 |
|
---|
366 | for (uint64_t i = 0; i < cnt; i++) {
|
---|
367 | /* Compute block offset which is relative from the start of qcow file */
|
---|
368 | errno_t error = EOK;
|
---|
369 | uint64_t block_offset = get_block_offset((ba + i) * state.block_size, &error);
|
---|
370 | if (error != EOK)
|
---|
371 | return error;
|
---|
372 |
|
---|
373 | /* If there is empty reference then continue */
|
---|
374 | if (block_offset == QCOW_UNALLOCATED_REFERENCE)
|
---|
375 | continue;
|
---|
376 |
|
---|
377 | clearerr(img);
|
---|
378 | if (fseek(img, block_offset, SEEK_SET) < 0) {
|
---|
379 | fibril_mutex_unlock(&dev_lock);
|
---|
380 | return EIO;
|
---|
381 | }
|
---|
382 |
|
---|
383 | size_t n_rd = fread(buf + i * state.block_size, state.block_size, 1, img);
|
---|
384 |
|
---|
385 | if (n_rd < 1) {
|
---|
386 | fibril_mutex_unlock(&dev_lock);
|
---|
387 | return EINVAL;
|
---|
388 | }
|
---|
389 |
|
---|
390 | if (ferror(img)) {
|
---|
391 | fibril_mutex_unlock(&dev_lock);
|
---|
392 | return EIO;
|
---|
393 | }
|
---|
394 | }
|
---|
395 |
|
---|
396 | fibril_mutex_unlock(&dev_lock);
|
---|
397 | return EOK;
|
---|
398 | }
|
---|
399 |
|
---|
400 | /** Write blocks to the device. */
|
---|
401 | static errno_t qcow_bd_write_blocks(bd_srv_t *bd, uint64_t ba, size_t cnt,
|
---|
402 | const void *buf, size_t size)
|
---|
403 | {
|
---|
404 | // TODO
|
---|
405 | return EOK;
|
---|
406 | }
|
---|
407 |
|
---|
408 | /** Get device block size. */
|
---|
409 | static errno_t qcow_bd_get_block_size(bd_srv_t *bd, size_t *rsize)
|
---|
410 | {
|
---|
411 | *rsize = state.block_size;
|
---|
412 | return EOK;
|
---|
413 | }
|
---|
414 |
|
---|
415 | /** Get number of blocks on device. */
|
---|
416 | static errno_t qcow_bd_get_num_blocks(bd_srv_t *bd, aoff64_t *rnb)
|
---|
417 | {
|
---|
418 | *rnb = state.num_blocks;
|
---|
419 | return EOK;
|
---|
420 | }
|
---|
421 |
|
---|
422 | /**
|
---|
423 | * @}
|
---|
424 | */ |
---|