source: mainline/uspace/srv/bd/qcow_bd/qcow_bd.c@ 2f7dfc2e

Last change on this file since 2f7dfc2e was 2f7dfc2e, checked in by Erik Kučák <riko98@…>, 4 years ago

Added driver for QCOW1 format

  • Property mode set to 100644
File size: 11.5 KB
Line 
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
43static FILE *img;
44static QCowHeader header;
45static QcowState state;
46
47static service_id_t service_id;
48static bd_srvs_t bd_srvs;
49static fibril_mutex_t dev_lock;
50static void print_usage(void);
51static errno_t qcow_bd_init(const char *fname);
52static void qcow_bd_connection(ipc_call_t *icall, void *);
53
54static errno_t qcow_bd_open(bd_srvs_t *, bd_srv_t *);
55static errno_t qcow_bd_close(bd_srv_t *);
56static errno_t qcow_bd_read_blocks(bd_srv_t *, aoff64_t, size_t, void *, size_t);
57static errno_t qcow_bd_write_blocks(bd_srv_t *, aoff64_t, size_t, const void *, size_t);
58static errno_t qcow_bd_get_block_size(bd_srv_t *, size_t *);
59static errno_t qcow_bd_get_num_blocks(bd_srv_t *, aoff64_t *);
60
61static 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
70int 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
149static void print_usage(void)
150{
151 printf("Usage: " NAME " [-b <block_size>] <image_file> <device_name>\n");
152}
153
154static 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
178static 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
209static 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
282static void qcow_bd_connection(ipc_call_t *icall, void *arg)
283{
284 bd_conn(icall, &bd_srvs);
285}
286
287/** Open device. */
288static errno_t qcow_bd_open(bd_srvs_t *bds, bd_srv_t *bd)
289{
290 return EOK;
291}
292
293/** Close device. */
294static 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. */
302static 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. */
350static 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. */
401static 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. */
409static 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. */
416static 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 */
Note: See TracBrowser for help on using the repository browser.