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 | }; | ... | ... |