cloudstack/tools/vhd-tools/vhd/lib/vhd-util-resize.c
2011-02-28 19:41:14 -08:00

1132 lines
24 KiB
C

/* Copyright (c) 2008, XenSource Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of XenSource Inc. nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
* OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <syslog.h>
#include <inttypes.h>
#include <sys/mman.h>
#include "libvhd-journal.h"
#if 1
#define DFPRINTF(_f, _a...) fprintf(stdout, _f, ##_a)
#else
#define DFPRINTF(_f, _a...) ((void)0)
#endif
#define EPRINTF(_f, _a...) \
do { \
syslog(LOG_INFO, "%s: " _f, __func__, ##_a); \
DFPRINTF(_f, _a); \
} while (0)
typedef struct vhd_block {
uint32_t block;
uint32_t offset;
} vhd_block_t;
TEST_FAIL_EXTERN_VARS;
static inline uint32_t
secs_to_blocks_down(vhd_context_t *vhd, uint64_t secs)
{
return secs / vhd->spb;
}
static uint32_t
secs_to_blocks_up(vhd_context_t *vhd, uint64_t secs)
{
uint32_t blocks;
blocks = secs / vhd->spb;
if (secs % vhd->spb)
blocks++;
return blocks;
}
static int
vhd_fixed_shrink(vhd_journal_t *journal, uint64_t secs)
{
int err;
uint64_t new_eof;
vhd_context_t *vhd;
vhd = &journal->vhd;
new_eof = vhd->footer.curr_size - vhd_sectors_to_bytes(secs);
if (new_eof <= sizeof(vhd_footer_t))
return -EINVAL;
err = ftruncate(vhd->fd, new_eof);
if (err)
return errno;
vhd->footer.curr_size = new_eof;
return vhd_write_footer(vhd, &vhd->footer);
}
static int
vhd_write_zeros(vhd_journal_t *journal, off_t off, uint64_t size)
{
int err;
char *buf;
vhd_context_t *vhd;
uint64_t bytes, map;
vhd = &journal->vhd;
map = MIN(size, VHD_BLOCK_SIZE);
err = vhd_seek(vhd, off, SEEK_SET);
if (err)
return err;
buf = mmap(0, map, PROT_READ, MAP_SHARED | MAP_ANON, -1, 0);
if (buf == MAP_FAILED)
return -errno;
do {
bytes = MIN(size, map);
err = vhd_write(vhd, buf, bytes);
if (err)
break;
size -= bytes;
} while (size);
munmap(buf, map);
return err;
}
static int
vhd_fixed_grow(vhd_journal_t *journal, uint64_t secs)
{
int err;
vhd_context_t *vhd;
uint64_t size, eof, new_eof;
size = vhd_sectors_to_bytes(secs);
vhd = &journal->vhd;
err = vhd_seek(vhd, 0, SEEK_END);
if (err)
goto out;
eof = vhd_position(vhd);
if (eof == (off_t)-1) {
err = -errno;
goto out;
}
err = vhd_write_zeros(journal, eof - sizeof(vhd_footer_t), size);
if (err)
goto out;
new_eof = eof + size;
err = vhd_seek(vhd, new_eof, SEEK_SET);
if (err)
goto out;
vhd->footer.curr_size += size;
err = vhd_write_footer(vhd, &vhd->footer);
if (err)
goto out;
err = 0;
out:
return err;
}
static int
vhd_fixed_resize(vhd_journal_t *journal, uint64_t size)
{
int err;
vhd_context_t *vhd;
uint64_t cur_secs, new_secs;
vhd = &journal->vhd;
cur_secs = vhd->footer.curr_size >> VHD_SECTOR_SHIFT;
new_secs = size << (20 - VHD_SECTOR_SHIFT);
if (cur_secs == new_secs)
return 0;
else if (cur_secs > new_secs)
err = vhd_fixed_shrink(journal, cur_secs - new_secs);
else
err = vhd_fixed_grow(journal, new_secs - cur_secs);
return err;
}
static inline void
swap(vhd_block_t *list, int a, int b)
{
vhd_block_t tmp;
tmp = list[a];
list[a] = list[b];
list[b] = tmp;
}
static int
partition(vhd_block_t *list, int left, int right, int pidx)
{
int i, sidx;
long long pval;
sidx = left;
pval = list[pidx].offset;
swap(list, pidx, right);
for (i = left; i < right; i++)
if (list[i].offset >= pval) {
swap(list, sidx, i);
++sidx;
}
swap(list, right, sidx);
return sidx;
}
static void
quicksort(vhd_block_t *list, int left, int right)
{
int pidx, new_pidx;
if (right < left)
return;
pidx = left;
new_pidx = partition(list, left, right, pidx);
quicksort(list, left, new_pidx - 1);
quicksort(list, new_pidx + 1, right);
}
static int
vhd_move_block(vhd_journal_t *journal, uint32_t src, off_t offset)
{
int err;
char *buf;
size_t size;
vhd_context_t *vhd;
off_t off, src_off;
buf = NULL;
vhd = &journal->vhd;
off = offset;
size = vhd_sectors_to_bytes(vhd->bm_secs);
src_off = vhd->bat.bat[src];
if (src_off == DD_BLK_UNUSED)
return -EINVAL;
src_off = vhd_sectors_to_bytes(src_off);
err = vhd_journal_add_block(journal, src,
VHD_JOURNAL_DATA | VHD_JOURNAL_METADATA);
if (err)
goto out;
err = vhd_read_bitmap(vhd, src, &buf);
if (err)
goto out;
err = vhd_seek(vhd, off, SEEK_SET);
if (err)
goto out;
err = vhd_write(vhd, buf, size);
if (err)
goto out;
free(buf);
buf = NULL;
off += size;
size = vhd_sectors_to_bytes(vhd->spb);
err = vhd_read_block(vhd, src, &buf);
if (err)
goto out;
err = vhd_seek(vhd, off, SEEK_SET);
if (err)
goto out;
err = vhd_write(vhd, buf, size);
if (err)
goto out;
vhd->bat.bat[src] = offset >> VHD_SECTOR_SHIFT;
err = vhd_write_zeros(journal, src_off,
vhd_sectors_to_bytes(vhd->bm_secs + vhd->spb));
out:
free(buf);
return err;
}
static int
vhd_clobber_block(vhd_journal_t *journal, uint32_t src, uint32_t dest)
{
int err;
off_t off;
vhd_context_t *vhd;
vhd = &journal->vhd;
off = vhd_sectors_to_bytes(vhd->bat.bat[dest]);
err = vhd_journal_add_block(journal, dest,
VHD_JOURNAL_DATA | VHD_JOURNAL_METADATA);
if (err)
return err;
err = vhd_move_block(journal, src, off);
if (err)
return err;
vhd->bat.bat[dest] = DD_BLK_UNUSED;
return 0;
}
/*
* remove a list of blocks from the vhd file
* if a block to be removed:
* - resides at the end of the file: simply clear its bat entry
* - resides elsewhere: move the last block in the file into its position
* and update the bat to reflect this
*/
static int
vhd_defrag_shrink(vhd_journal_t *journal,
vhd_block_t *original_free_list, int free_cnt)
{
vhd_context_t *vhd;
int i, j, free_idx, err;
vhd_block_t *blocks, *free_list;
err = 0;
blocks = NULL;
free_list = NULL;
vhd = &journal->vhd;
blocks = malloc(vhd->bat.entries * sizeof(vhd_block_t));
if (!blocks) {
err = -ENOMEM;
goto out;
}
free_list = malloc(free_cnt * sizeof(vhd_block_t));
if (!free_list) {
err = -ENOMEM;
goto out;
}
for (i = 0; i < vhd->bat.entries; i++) {
blocks[i].block = i;
blocks[i].offset = vhd->bat.bat[i];
}
memcpy(free_list, original_free_list,
free_cnt * sizeof(vhd_block_t));
/* sort both the to-free list and the bat list
* in order of descending file offset */
quicksort(free_list, 0, free_cnt - 1);
quicksort(blocks, 0, vhd->bat.entries - 1);
for (i = 0, free_idx = 0;
i < vhd->bat.entries && free_idx < free_cnt; i++) {
vhd_block_t *b = blocks + i;
if (b->offset == DD_BLK_UNUSED)
continue;
for (j = free_idx; j < free_cnt; j++)
if (b->block == free_list[j].block) {
/* the last block in the file is in the list of
* blocks to remove; no need to shuffle the
* data -- just clear the bat entry */
vhd->bat.bat[free_list[j].block] = DD_BLK_UNUSED;
free_idx++;
continue;
}
err = vhd_clobber_block(journal, b->block,
free_list[free_idx++].block);
if (err)
goto out;
}
/* clear any bat entries for blocks we did not shuffle */
for (i = free_idx; i < free_cnt; i++)
vhd->bat.bat[free_list[i].block] = DD_BLK_UNUSED;
out:
free(blocks);
free(free_list);
return err;
}
static int
vhd_clear_bat_entries(vhd_journal_t *journal, uint32_t entries)
{
int i, err;
vhd_context_t *vhd;
off_t orig_map_off, new_map_off;
uint32_t orig_entries, new_entries;
vhd = &journal->vhd;
orig_entries = vhd->header.max_bat_size;
new_entries = orig_entries - entries;
if (vhd_has_batmap(vhd)) {
err = vhd_batmap_header_offset(vhd, &orig_map_off);
if (err)
return err;
}
/* update header */
vhd->header.max_bat_size = new_entries;
err = vhd_write_header(vhd, &vhd->header);
if (err)
return err;
/* update footer */
vhd->footer.curr_size = (uint64_t)new_entries * vhd->header.block_size;
vhd->footer.geometry = vhd_chs(vhd->footer.curr_size);
err = vhd_write_footer(vhd, &vhd->footer);
if (err)
return err;
/* update bat -- we don't reclaim space, just clear entries */
for (i = new_entries; i < orig_entries; i++)
vhd->bat.bat[i] = 0;
err = vhd_write_bat(vhd, &vhd->bat);
if (err)
return err;
/* update this after write_bat so the end of the bat is zeored */
vhd->bat.entries = new_entries;
if (!vhd_has_batmap(vhd))
return 0;
/* zero out old batmap header if new header has moved */
err = vhd_batmap_header_offset(vhd, &new_map_off);
if (err)
return err;
if (orig_map_off != new_map_off) {
size_t size;
size = vhd_bytes_padded(sizeof(struct dd_batmap_hdr));
err = vhd_write_zeros(journal, orig_map_off, size);
if (err)
return err;
}
/* update batmap -- clear entries for freed blocks */
for (i = new_entries; i < orig_entries; i++)
vhd_batmap_clear(vhd, &vhd->batmap, i);
err = vhd_write_batmap(vhd, &vhd->batmap);
if (err)
return err;
return 0;
}
static int
vhd_dynamic_shrink(vhd_journal_t *journal, uint64_t secs)
{
off_t eof;
uint32_t blocks;
vhd_context_t *vhd;
int i, j, err, free_cnt;
struct vhd_block *free_list;
printf("dynamic shrink not fully implemented\n");
return -ENOSYS;
eof = 0;
free_cnt = 0;
free_list = NULL;
vhd = &journal->vhd;
blocks = secs_to_blocks_down(vhd, secs);
if (blocks == 0)
return 0;
if (vhd_has_batmap(vhd)) {
err = vhd_get_batmap(vhd);
if (err)
return err;
}
free_list = malloc(blocks * sizeof(struct vhd_block));
if (!free_list)
return -ENOMEM;
for (i = vhd->bat.entries - 1, j = 0; i >= 0 && j < blocks; i--, j++) {
uint32_t blk = vhd->bat.bat[i];
if (blk != DD_BLK_UNUSED) {
free_list[free_cnt].block = i;
free_list[free_cnt].offset = blk;
free_cnt++;
}
}
if (free_cnt) {
err = vhd_defrag_shrink(journal, free_list, free_cnt);
if (err)
goto out;
}
err = vhd_clear_bat_entries(journal, blocks);
if (err)
goto out;
/* remove data beyond footer */
err = vhd_end_of_data(vhd, &eof);
if (err)
goto out;
err = ftruncate(vhd->fd, eof + sizeof(vhd_footer_t));
if (err) {
err = -errno;
goto out;
}
err = 0;
out:
free(free_list);
return err;
}
static inline void
vhd_first_data_block(vhd_context_t *vhd, vhd_block_t *block)
{
int i;
uint32_t blk;
memset(block, 0, sizeof(vhd_block_t));
for (i = 0; i < vhd->bat.entries; i++) {
blk = vhd->bat.bat[i];
if (blk != DD_BLK_UNUSED) {
if (!block->offset || blk < block->offset) {
block->block = i;
block->offset = blk;
}
}
}
}
static inline uint32_t
vhd_next_block_offset(vhd_context_t *vhd)
{
int i;
uint32_t blk, end, spp, next;
next = 0;
spp = getpagesize() >> VHD_SECTOR_SHIFT;
for (i = 0; i < vhd->bat.entries; i++) {
blk = vhd->bat.bat[i];
if (blk != DD_BLK_UNUSED) {
end = blk + vhd->spb + vhd->bm_secs;
next = MAX(next, end);
}
}
return next;
}
static inline int
in_range(off_t off, off_t start, off_t size)
{
return (start < off && start + size > off);
}
#define SKIP_HEADER 0x01
#define SKIP_BAT 0x02
#define SKIP_BATMAP 0x04
#define SKIP_PLOC 0x08
#define SKIP_DATA 0x10
static inline int
skip_check(int mode, int type)
{
return mode & type;
}
static int
vhd_check_for_clobber(vhd_context_t *vhd, off_t off, int mode)
{
int i, n;
char *msg;
size_t size;
vhd_block_t fb;
vhd_parent_locator_t *loc;
msg = NULL;
if (!vhd_type_dynamic(vhd))
return 0;
if (off < VHD_SECTOR_SIZE) {
msg = "backup footer";
goto fail;
}
if (!skip_check(mode, SKIP_HEADER))
if (in_range(off,
vhd->footer.data_offset, sizeof(vhd_header_t))) {
msg = "header";
goto fail;
}
if (!skip_check(mode, SKIP_BAT))
if (in_range(off, vhd->header.table_offset,
vhd_bytes_padded(vhd->header.max_bat_size *
sizeof(uint32_t)))) {
msg = "bat";
goto fail;
}
if (!skip_check(mode, SKIP_BATMAP))
if (vhd_has_batmap(vhd) &&
in_range(off, vhd->batmap.header.batmap_offset,
vhd_bytes_padded(vhd->batmap.header.batmap_size))) {
msg = "batmap";
goto fail;
}
if (!skip_check(mode, SKIP_PLOC)) {
n = sizeof(vhd->header.loc) / sizeof(vhd_parent_locator_t);
for (i = 0; i < n; i++) {
loc = vhd->header.loc + i;
if (loc->code == PLAT_CODE_NONE)
continue;
size = vhd_parent_locator_size(loc);
if (in_range(off, loc->data_offset, size)) {
msg = "parent locator";
goto fail;
}
}
}
if (!skip_check(mode, SKIP_DATA)) {
vhd_first_data_block(vhd, &fb);
if (fb.offset && in_range(off,
vhd_sectors_to_bytes(fb.offset),
VHD_BLOCK_SIZE)) {
msg = "data block";
goto fail;
}
}
return 0;
fail:
EPRINTF("write to 0x%08"PRIx64" would clobber %s\n", off, msg);
return -EINVAL;
}
/*
* take any metadata after the bat (@eob) and shift it
*/
static int
vhd_shift_metadata(vhd_journal_t *journal, off_t eob,
size_t bat_needed, size_t map_needed)
{
int i, n, err;
vhd_context_t *vhd;
size_t size_needed;
char *buf, **locators;
vhd_parent_locator_t *loc;
vhd = &journal->vhd;
size_needed = bat_needed + map_needed;
n = sizeof(vhd->header.loc) / sizeof(vhd_parent_locator_t);
locators = calloc(n, sizeof(char *));
if (!locators)
return -ENOMEM;
for (i = 0; i < n; i++) {
size_t size;
loc = vhd->header.loc + i;
if (loc->code == PLAT_CODE_NONE)
continue;
if (loc->data_offset < eob)
continue;
size = vhd_parent_locator_size(loc);
err = posix_memalign((void **)&buf, VHD_SECTOR_SIZE, size);
if (err) {
err = -err;
buf = NULL;
goto out;
}
err = vhd_seek(vhd, loc->data_offset, SEEK_SET);
if (err)
goto out;
err = vhd_read(vhd, buf, size);
if (err)
goto out;
locators[i] = buf;
}
for (i = 0; i < n; i++) {
off_t off;
size_t size;
if (!locators[i])
continue;
loc = vhd->header.loc + i;
off = loc->data_offset + size_needed;
size = vhd_parent_locator_size(loc);
if (vhd_check_for_clobber(vhd, off + size, SKIP_PLOC)) {
EPRINTF("%s: shifting locator %d would clobber data\n",
vhd->file, i);
return -EINVAL;
}
err = vhd_seek(vhd, off, SEEK_SET);
if (err)
goto out;
err = vhd_write(vhd, locators[i], size);
if (err)
goto out;
free(locators[i]);
locators[i] = NULL;
loc->data_offset = off;
/* write the new header after writing the new bat */
}
if (vhd_has_batmap(vhd) && vhd->batmap.header.batmap_offset > eob) {
vhd->batmap.header.batmap_offset += bat_needed;
/* write the new batmap after writing the new bat */
}
err = 0;
out:
for (i = 0; i < n; i++)
free(locators[i]);
free(locators);
return err;
}
static int
vhd_add_bat_entries(vhd_journal_t *journal, int entries)
{
int i, err;
off_t off;
vhd_bat_t new_bat;
vhd_context_t *vhd;
uint32_t new_entries;
vhd_batmap_t new_batmap;
uint64_t bat_size, new_bat_size, map_size, new_map_size;
vhd = &journal->vhd;
new_entries = vhd->header.max_bat_size + entries;
bat_size = vhd_bytes_padded(vhd->header.max_bat_size *
sizeof(uint32_t));
new_bat_size = vhd_bytes_padded(new_entries * sizeof(uint32_t));
map_size = vhd_bytes_padded((vhd->header.max_bat_size + 7) >> 3);
new_map_size = vhd_bytes_padded((new_entries + 7) >> 3);
off = vhd->header.table_offset + new_bat_size;
if (vhd_check_for_clobber(vhd, off, SKIP_BAT | SKIP_BATMAP)) {
EPRINTF("%s: writing new bat of 0x%"PRIx64" bytes "
"at 0x%08"PRIx64" would clobber data\n",
vhd->file, new_bat_size, vhd->header.table_offset);
return -EINVAL;
}
if (vhd_has_batmap(vhd)) {
off = vhd->batmap.header.batmap_offset + new_map_size;
if (vhd_check_for_clobber(vhd, off, 0)) {
EPRINTF("%s: writing new batmap of 0x%"PRIx64" bytes"
" at 0x%08"PRIx64" would clobber data\n", vhd->file,
new_map_size, vhd->batmap.header.batmap_offset);
return -EINVAL;
}
}
/* update header */
vhd->header.max_bat_size = new_entries;
err = vhd_write_header(vhd, &vhd->header);
if (err)
return err;
/* update footer */
vhd->footer.curr_size = (uint64_t)new_entries * vhd->header.block_size;
vhd->footer.geometry = vhd_chs(vhd->footer.curr_size);
vhd->footer.checksum = vhd_checksum_footer(&vhd->footer);
err = vhd_write_footer(vhd, &vhd->footer);
if (err)
return err;
/* allocate new bat */
err = posix_memalign((void **)&new_bat.bat, VHD_SECTOR_SIZE, new_bat_size);
if (err)
return -err;
new_bat.spb = vhd->bat.spb;
new_bat.entries = new_entries;
memcpy(new_bat.bat, vhd->bat.bat, bat_size);
for (i = vhd->bat.entries; i < new_entries; i++)
new_bat.bat[i] = DD_BLK_UNUSED;
/* write new bat */
err = vhd_write_bat(vhd, &new_bat);
if (err) {
free(new_bat.bat);
return err;
}
/* update in-memory bat */
free(vhd->bat.bat);
vhd->bat = new_bat;
if (!vhd_has_batmap(vhd))
return 0;
/* allocate new batmap */
err = posix_memalign((void **)&new_batmap.map,
VHD_SECTOR_SIZE, new_map_size);
if (err)
return err;
new_batmap.header = vhd->batmap.header;
new_batmap.header.batmap_size = secs_round_up_no_zero(new_map_size);
memcpy(new_batmap.map, vhd->batmap.map, map_size);
memset(new_batmap.map + map_size, 0, new_map_size - map_size);
/* write new batmap */
err = vhd_write_batmap(vhd, &new_batmap);
if (err) {
free(new_batmap.map);
return err;
}
/* update in-memory batmap */
free(vhd->batmap.map);
vhd->batmap = new_batmap;
return 0;
}
static int
vhd_dynamic_grow(vhd_journal_t *journal, uint64_t secs)
{
int i, err;
off_t eob, eom;
vhd_context_t *vhd;
vhd_block_t first_block;
uint64_t blocks, size_needed;
uint64_t bat_needed, bat_size, bat_avail, bat_bytes, bat_secs;
uint64_t map_needed, map_size, map_avail, map_bytes, map_secs;
vhd = &journal->vhd;
size_needed = 0;
bat_needed = 0;
map_needed = 0;
/* number of vhd blocks to add */
blocks = secs_to_blocks_up(vhd, secs);
/* size in bytes needed for new bat entries */
bat_needed = blocks * sizeof(uint32_t);
map_needed = (blocks >> 3) + 1;
/* available bytes in current bat */
bat_bytes = vhd->header.max_bat_size * sizeof(uint32_t);
bat_secs = secs_round_up_no_zero(bat_bytes);
bat_size = vhd_sectors_to_bytes(bat_secs);
bat_avail = bat_size - bat_bytes;
if (vhd_has_batmap(vhd)) {
/* avaliable bytes in current batmap */
map_bytes = (vhd->header.max_bat_size + 7) >> 3;
map_secs = vhd->batmap.header.batmap_size;
map_size = vhd_sectors_to_bytes(map_secs);
map_avail = map_size - map_bytes;
} else {
map_needed = 0;
map_avail = 0;
}
/* we have enough space already; just extend the bat */
if (bat_needed <= bat_avail && map_needed <= map_avail)
goto add_entries;
/* we need to add new sectors to the bat */
if (bat_needed > bat_avail) {
bat_needed -= bat_avail;
bat_needed = vhd_bytes_padded(bat_needed);
} else
bat_needed = 0;
/* we need to add new sectors to the batmap */
if (map_needed > map_avail) {
map_needed -= map_avail;
map_needed = vhd_bytes_padded(map_needed);
} else
map_needed = 0;
/* how many additional bytes do we need? */
size_needed = bat_needed + map_needed;
/* calculate space between end of headers and beginning of data */
err = vhd_end_of_headers(vhd, &eom);
if (err)
return err;
eob = vhd->header.table_offset + vhd_sectors_to_bytes(bat_secs);
vhd_first_data_block(vhd, &first_block);
/* no blocks allocated; just shift post-bat metadata */
if (!first_block.offset)
goto shift_metadata;
/*
* not enough space --
* move vhd data blocks to the end of the file to make room
*/
do {
off_t new_off, bm_size, gap_size;
new_off = vhd_sectors_to_bytes(vhd_next_block_offset(vhd));
/* data region of segment should begin on page boundary */
bm_size = vhd_sectors_to_bytes(vhd->bm_secs);
if ((new_off + bm_size) % 4096) {
gap_size = 4096 - ((new_off + bm_size) % 4096);
err = vhd_write_zeros(journal, new_off, gap_size);
if (err)
return err;
new_off += gap_size;
}
err = vhd_move_block(journal, first_block.block, new_off);
if (err)
return err;
vhd_first_data_block(vhd, &first_block);
} while (eom + size_needed >= vhd_sectors_to_bytes(first_block.offset));
TEST_FAIL_AT(FAIL_RESIZE_DATA_MOVED);
shift_metadata:
/* shift any metadata after the bat to make room for new bat sectors */
err = vhd_shift_metadata(journal, eob, bat_needed, map_needed);
if (err)
return err;
TEST_FAIL_AT(FAIL_RESIZE_METADATA_MOVED);
add_entries:
return vhd_add_bat_entries(journal, blocks);
}
static int
vhd_dynamic_resize(vhd_journal_t *journal, uint64_t size)
{
int err;
vhd_context_t *vhd;
uint64_t cur_secs, new_secs;
vhd = &journal->vhd;
cur_secs = vhd->footer.curr_size >> VHD_SECTOR_SHIFT;
new_secs = size << (20 - VHD_SECTOR_SHIFT);
if (cur_secs == new_secs)
return 0;
err = vhd_get_header(vhd);
if (err)
return err;
err = vhd_get_bat(vhd);
if (err)
return err;
if (vhd_has_batmap(vhd)) {
err = vhd_get_batmap(vhd);
if (err)
return err;
}
if (cur_secs > new_secs)
err = vhd_dynamic_shrink(journal, cur_secs - new_secs);
else
err = vhd_dynamic_grow(journal, new_secs - cur_secs);
return err;
}
static int
vhd_util_resize_check_creator(const char *name)
{
int err;
vhd_context_t vhd;
err = vhd_open(&vhd, name, VHD_OPEN_RDONLY | VHD_OPEN_STRICT);
if (err) {
printf("error opening %s: %d\n", name, err);
return err;
}
if (!vhd_creator_tapdisk(&vhd)) {
printf("%s not created by xen; resize not supported\n", name);
err = -EINVAL;
}
vhd_close(&vhd);
return err;
}
int
vhd_util_resize(int argc, char **argv)
{
char *name, *jname;
uint64_t size;
int c, err, jerr;
vhd_journal_t journal;
vhd_context_t *vhd;
err = -EINVAL;
size = 0;
name = NULL;
jname = NULL;
optind = 0;
while ((c = getopt(argc, argv, "n:j:s:h")) != -1) {
switch (c) {
case 'n':
name = optarg;
break;
case 'j':
jname = optarg;
break;
case 's':
err = 0;
size = strtoull(optarg, NULL, 10);
break;
case 'h':
default:
goto usage;
}
}
if (err || !name || !jname || argc != optind)
goto usage;
err = vhd_util_resize_check_creator(name);
if (err)
return err;
libvhd_set_log_level(1);
err = vhd_journal_create(&journal, name, jname);
if (err) {
printf("creating journal failed: %d\n", err);
return err;
}
vhd = &journal.vhd;
err = vhd_get_footer(vhd);
if (err)
goto out;
TEST_FAIL_AT(FAIL_RESIZE_BEGIN);
if (vhd_type_dynamic(vhd))
err = vhd_dynamic_resize(&journal, size);
else
err = vhd_fixed_resize(&journal, size);
TEST_FAIL_AT(FAIL_RESIZE_END);
out:
if (err) {
printf("resize failed: %d\n", err);
jerr = vhd_journal_revert(&journal);
} else
jerr = vhd_journal_commit(&journal);
if (jerr) {
printf("closing journal failed: %d\n", jerr);
vhd_journal_close(&journal);
} else
vhd_journal_remove(&journal);
return (err ? : jerr);
usage:
printf("options: <-n name> <-j journal> <-s size (in MB)> [-h help]\n");
return -EINVAL;
}