Commit 15d35bc55724d424439967fe8d1550ff9e3221bd
1 parent
b71d1c2e
block-vpc: Write support (Kevin Wolf)
Add write support for VHD images. Signed-off-by: Kevin Wolf <kwolf@suse.de> Signed-off-by: Anthony Liguori <aliguori@us.ibm.com> git-svn-id: svn://svn.savannah.nongnu.org/qemu/trunk@6458 c046a42c-6fe2-441c-8c8c-71466251a162
Showing
1 changed file
with
150 additions
and
16 deletions
block-vpc.c
... | ... | @@ -2,6 +2,7 @@ |
2 | 2 | * Block driver for Conectix/Microsoft Virtual PC images |
3 | 3 | * |
4 | 4 | * Copyright (c) 2005 Alex Beregszaszi |
5 | + * Copyright (c) 2009 Kevin Wolf <kwolf@suse.de> | |
5 | 6 | * |
6 | 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy |
7 | 8 | * of this software and associated documentation files (the "Software"), to deal |
... | ... | @@ -107,10 +108,16 @@ struct vhd_dyndisk_header { |
107 | 108 | typedef struct BDRVVPCState { |
108 | 109 | BlockDriverState *hd; |
109 | 110 | |
111 | + uint8_t footer_buf[HEADER_SIZE]; | |
112 | + uint64_t free_data_block_offset; | |
110 | 113 | int max_table_entries; |
111 | 114 | uint32_t *pagetable; |
115 | + uint64_t bat_offset; | |
116 | + uint64_t last_bitmap_offset; | |
112 | 117 | |
113 | 118 | uint32_t block_size; |
119 | + uint32_t bitmap_size; | |
120 | + | |
114 | 121 | #ifdef CACHE |
115 | 122 | uint8_t *pageentry_u8; |
116 | 123 | uint32_t *pageentry_u32; |
... | ... | @@ -135,16 +142,14 @@ static int vpc_open(BlockDriverState *bs, const char *filename, int flags) |
135 | 142 | struct vhd_dyndisk_header* dyndisk_header; |
136 | 143 | uint8_t buf[HEADER_SIZE]; |
137 | 144 | |
138 | - bs->read_only = 1; // no write support yet | |
139 | - | |
140 | 145 | ret = bdrv_file_open(&s->hd, filename, flags); |
141 | 146 | if (ret < 0) |
142 | 147 | return ret; |
143 | 148 | |
144 | - if (bdrv_pread(s->hd, 0, buf, HEADER_SIZE) != HEADER_SIZE) | |
149 | + if (bdrv_pread(s->hd, 0, s->footer_buf, HEADER_SIZE) != HEADER_SIZE) | |
145 | 150 | goto fail; |
146 | 151 | |
147 | - footer = (struct vhd_footer*) buf; | |
152 | + footer = (struct vhd_footer*) s->footer_buf; | |
148 | 153 | if (strncmp(footer->creator, "conectix", 8)) |
149 | 154 | goto fail; |
150 | 155 | |
... | ... | @@ -158,26 +163,41 @@ static int vpc_open(BlockDriverState *bs, const char *filename, int flags) |
158 | 163 | != HEADER_SIZE) |
159 | 164 | goto fail; |
160 | 165 | |
161 | - footer = NULL; | |
162 | 166 | dyndisk_header = (struct vhd_dyndisk_header*) buf; |
163 | 167 | |
164 | 168 | if (strncmp(dyndisk_header->magic, "cxsparse", 8)) |
165 | 169 | goto fail; |
166 | 170 | |
167 | 171 | |
172 | + s->block_size = be32_to_cpu(dyndisk_header->block_size); | |
173 | + s->bitmap_size = ((s->block_size / (8 * 512)) + 511) & ~511; | |
174 | + | |
168 | 175 | s->max_table_entries = be32_to_cpu(dyndisk_header->max_table_entries); |
169 | 176 | s->pagetable = qemu_malloc(s->max_table_entries * 4); |
170 | 177 | if (!s->pagetable) |
171 | 178 | goto fail; |
172 | 179 | |
173 | - if (bdrv_pread(s->hd, be64_to_cpu(dyndisk_header->table_offset), | |
174 | - s->pagetable, s->max_table_entries * 4) != s->max_table_entries * 4) | |
180 | + s->bat_offset = be64_to_cpu(dyndisk_header->table_offset); | |
181 | + if (bdrv_pread(s->hd, s->bat_offset, s->pagetable, | |
182 | + s->max_table_entries * 4) != s->max_table_entries * 4) | |
175 | 183 | goto fail; |
176 | 184 | |
177 | - for (i = 0; i < s->max_table_entries; i++) | |
178 | - be32_to_cpus(&s->pagetable[i]); | |
185 | + s->free_data_block_offset = | |
186 | + (s->bat_offset + (s->max_table_entries * 4) + 511) & ~511; | |
187 | + | |
188 | + for (i = 0; i < s->max_table_entries; i++) { | |
189 | + be32_to_cpus(&s->pagetable[i]); | |
190 | + if (s->pagetable[i] != 0xFFFFFFFF) { | |
191 | + int64_t next = (512 * (int64_t) s->pagetable[i]) + | |
192 | + s->bitmap_size + s->block_size; | |
193 | + | |
194 | + if (next> s->free_data_block_offset) | |
195 | + s->free_data_block_offset = next; | |
196 | + } | |
197 | + } | |
198 | + | |
199 | + s->last_bitmap_offset = (int64_t) -1; | |
179 | 200 | |
180 | - s->block_size = be32_to_cpu(dyndisk_header->block_size); | |
181 | 201 | #ifdef CACHE |
182 | 202 | s->pageentry_u8 = qemu_malloc(512); |
183 | 203 | if (!s->pageentry_u8) |
... | ... | @@ -196,8 +216,12 @@ static int vpc_open(BlockDriverState *bs, const char *filename, int flags) |
196 | 216 | /* |
197 | 217 | * Returns the absolute byte offset of the given sector in the image file. |
198 | 218 | * If the sector is not allocated, -1 is returned instead. |
219 | + * | |
220 | + * The parameter write must be 1 if the offset will be used for a write | |
221 | + * operation (the block bitmaps is updated then), 0 otherwise. | |
199 | 222 | */ |
200 | -static inline int64_t get_sector_offset(BlockDriverState *bs, int64_t sector_num) | |
223 | +static inline int64_t get_sector_offset(BlockDriverState *bs, | |
224 | + int64_t sector_num, int write) | |
201 | 225 | { |
202 | 226 | BDRVVPCState *s = bs->opaque; |
203 | 227 | uint64_t offset = sector_num * 512; |
... | ... | @@ -207,11 +231,24 @@ static inline int64_t get_sector_offset(BlockDriverState *bs, int64_t sector_num |
207 | 231 | pagetable_index = offset / s->block_size; |
208 | 232 | pageentry_index = (offset % s->block_size) / 512; |
209 | 233 | |
210 | - if (pagetable_index > s->max_table_entries || s->pagetable[pagetable_index] == 0xffffffff) | |
211 | - return -1; // not allocated | |
234 | + if (pagetable_index >= s->max_table_entries || s->pagetable[pagetable_index] == 0xffffffff) | |
235 | + return -1; // not allocated | |
212 | 236 | |
213 | 237 | bitmap_offset = 512 * s->pagetable[pagetable_index]; |
214 | - block_offset = bitmap_offset + 512 + (512 * pageentry_index); | |
238 | + block_offset = bitmap_offset + s->bitmap_size + (512 * pageentry_index); | |
239 | + | |
240 | + // We must ensure that we don't write to any sectors which are marked as | |
241 | + // unused in the bitmap. We get away with setting all bits in the block | |
242 | + // bitmap each time we write to a new block. This might cause Virtual PC to | |
243 | + // miss sparse read optimization, but it's not a problem in terms of | |
244 | + // correctness. | |
245 | + if (write && (s->last_bitmap_offset != bitmap_offset)) { | |
246 | + uint8_t bitmap[s->bitmap_size]; | |
247 | + | |
248 | + s->last_bitmap_offset = bitmap_offset; | |
249 | + memset(bitmap, 0xff, s->bitmap_size); | |
250 | + bdrv_pwrite(s->hd, bitmap_offset, bitmap, s->bitmap_size); | |
251 | + } | |
215 | 252 | |
216 | 253 | // printf("sector: %" PRIx64 ", index: %x, offset: %x, bioff: %" PRIx64 ", bloff: %" PRIx64 "\n", |
217 | 254 | // sector_num, pagetable_index, pageentry_index, |
... | ... | @@ -248,6 +285,75 @@ static inline int64_t get_sector_offset(BlockDriverState *bs, int64_t sector_num |
248 | 285 | return block_offset; |
249 | 286 | } |
250 | 287 | |
288 | +/* | |
289 | + * Writes the footer to the end of the image file. This is needed when the | |
290 | + * file grows as it overwrites the old footer | |
291 | + * | |
292 | + * Returns 0 on success and < 0 on error | |
293 | + */ | |
294 | +static int rewrite_footer(BlockDriverState* bs) | |
295 | +{ | |
296 | + int ret; | |
297 | + BDRVVPCState *s = bs->opaque; | |
298 | + int64_t offset = s->free_data_block_offset; | |
299 | + | |
300 | + ret = bdrv_pwrite(s->hd, offset, s->footer_buf, HEADER_SIZE); | |
301 | + if (ret < 0) | |
302 | + return ret; | |
303 | + | |
304 | + return 0; | |
305 | +} | |
306 | + | |
307 | +/* | |
308 | + * Allocates a new block. This involves writing a new footer and updating | |
309 | + * the Block Allocation Table to use the space at the old end of the image | |
310 | + * file (overwriting the old footer) | |
311 | + * | |
312 | + * Returns the sectors' offset in the image file on success and < 0 on error | |
313 | + */ | |
314 | +static int64_t alloc_block(BlockDriverState* bs, int64_t sector_num) | |
315 | +{ | |
316 | + BDRVVPCState *s = bs->opaque; | |
317 | + int64_t bat_offset; | |
318 | + uint32_t index, bat_value; | |
319 | + int ret; | |
320 | + uint8_t bitmap[s->bitmap_size]; | |
321 | + | |
322 | + // Check if sector_num is valid | |
323 | + if ((sector_num < 0) || (sector_num > bs->total_sectors)) | |
324 | + return -1; | |
325 | + | |
326 | + // Write entry into in-memory BAT | |
327 | + index = (sector_num * 512) / s->block_size; | |
328 | + if (s->pagetable[index] != 0xFFFFFFFF) | |
329 | + return -1; | |
330 | + | |
331 | + s->pagetable[index] = s->free_data_block_offset / 512; | |
332 | + | |
333 | + // Initialize the block's bitmap | |
334 | + memset(bitmap, 0xff, s->bitmap_size); | |
335 | + bdrv_pwrite(s->hd, s->free_data_block_offset, bitmap, s->bitmap_size); | |
336 | + | |
337 | + // Write new footer (the old one will be overwritten) | |
338 | + s->free_data_block_offset += s->block_size + s->bitmap_size; | |
339 | + ret = rewrite_footer(bs); | |
340 | + if (ret < 0) | |
341 | + goto fail; | |
342 | + | |
343 | + // Write BAT entry to disk | |
344 | + bat_offset = s->bat_offset + (4 * index); | |
345 | + bat_value = be32_to_cpu(s->pagetable[index]); | |
346 | + ret = bdrv_pwrite(s->hd, bat_offset, &bat_value, 4); | |
347 | + if (ret < 0) | |
348 | + goto fail; | |
349 | + | |
350 | + return get_sector_offset(bs, sector_num, 0); | |
351 | + | |
352 | +fail: | |
353 | + s->free_data_block_offset -= (s->block_size + s->bitmap_size); | |
354 | + return -1; | |
355 | +} | |
356 | + | |
251 | 357 | static int vpc_read(BlockDriverState *bs, int64_t sector_num, |
252 | 358 | uint8_t *buf, int nb_sectors) |
253 | 359 | { |
... | ... | @@ -256,7 +362,7 @@ static int vpc_read(BlockDriverState *bs, int64_t sector_num, |
256 | 362 | int64_t offset; |
257 | 363 | |
258 | 364 | while (nb_sectors > 0) { |
259 | - offset = get_sector_offset(bs, sector_num); | |
365 | + offset = get_sector_offset(bs, sector_num, 0); | |
260 | 366 | |
261 | 367 | if (offset == -1) { |
262 | 368 | memset(buf, 0, 512); |
... | ... | @@ -273,6 +379,34 @@ static int vpc_read(BlockDriverState *bs, int64_t sector_num, |
273 | 379 | return 0; |
274 | 380 | } |
275 | 381 | |
382 | +static int vpc_write(BlockDriverState *bs, int64_t sector_num, | |
383 | + const uint8_t *buf, int nb_sectors) | |
384 | +{ | |
385 | + BDRVVPCState *s = bs->opaque; | |
386 | + int64_t offset; | |
387 | + int ret; | |
388 | + | |
389 | + while (nb_sectors > 0) { | |
390 | + offset = get_sector_offset(bs, sector_num, 1); | |
391 | + | |
392 | + if (offset == -1) { | |
393 | + offset = alloc_block(bs, sector_num); | |
394 | + if (offset < 0) | |
395 | + return -1; | |
396 | + } | |
397 | + | |
398 | + ret = bdrv_pwrite(s->hd, offset, buf, 512); | |
399 | + if (ret != 512) | |
400 | + return -1; | |
401 | + | |
402 | + nb_sectors--; | |
403 | + sector_num++; | |
404 | + buf += 512; | |
405 | + } | |
406 | + | |
407 | + return 0; | |
408 | +} | |
409 | + | |
276 | 410 | static void vpc_close(BlockDriverState *bs) |
277 | 411 | { |
278 | 412 | BDRVVPCState *s = bs->opaque; |
... | ... | @@ -289,6 +423,6 @@ BlockDriver bdrv_vpc = { |
289 | 423 | vpc_probe, |
290 | 424 | vpc_open, |
291 | 425 | vpc_read, |
292 | - NULL, | |
426 | + vpc_write, | |
293 | 427 | vpc_close, |
294 | 428 | }; | ... | ... |