MFC r268236,268264,268524,268646,268802,269021:

This brings VHD support to mkimg(1); both dynamic and fixed file formats.
Dynamic VHD and VMDK file images are now sparsely written, meaning that
"free" sectors do not occupy space.

Relnotes: yes
This commit is contained in:
marcel 2014-07-28 02:07:16 +00:00
parent 920342975e
commit 37a9f7be2f
11 changed files with 519 additions and 22 deletions

View File

@ -9,6 +9,7 @@ CFLAGS+=-DSPARSE_WRITE
# List of formats to support
SRCS+= \
raw.c \
vhd.c \
vmdk.c
# List of schemes to support

View File

@ -39,6 +39,9 @@ __FBSDID("$FreeBSD$");
#include "mkimg.h"
#include "scheme.h"
#ifndef APM_ENT_TYPE_APPLE_BOOT
#define APM_ENT_TYPE_APPLE_BOOT "Apple_Bootstrap"
#endif
#ifndef APM_ENT_TYPE_FREEBSD_NANDFS
#define APM_ENT_TYPE_FREEBSD_NANDFS "FreeBSD-nandfs"
#endif

View File

@ -211,7 +211,7 @@ gpt_mktbl(u_int tblsz)
STAILQ_FOREACH(part, &partlist, link) {
ent = tbl + part->index;
gpt_uuid_enc(&ent->ent_type, ALIAS_TYPE2PTR(part->type));
uuidgen(&uuid, 1);
mkimg_uuid(&uuid);
gpt_uuid_enc(&ent->ent_uuid, &uuid);
le64enc(&ent->ent_lba_start, part->block);
le64enc(&ent->ent_lba_end, part->block + part->size - 1);
@ -279,7 +279,7 @@ gpt_write(lba_t imgsz, void *bootcode)
le32enc(&hdr->hdr_size, offsetof(struct gpt_hdr, padding));
le64enc(&hdr->hdr_lba_start, 2 + tblsz);
le64enc(&hdr->hdr_lba_end, imgsz - tblsz - 2);
uuidgen(&uuid, 1);
mkimg_uuid(&uuid);
gpt_uuid_enc(&hdr->hdr_uuid, &uuid);
le32enc(&hdr->hdr_entries, nparts);
le32enc(&hdr->hdr_entsz, sizeof(struct gpt_ent));

View File

@ -93,22 +93,50 @@ image_copyin(lba_t blk, int fd, uint64_t *sizep)
int
image_copyout(int fd)
{
int error;
error = image_copyout_region(fd, 0, image_size);
if (!error)
error = image_copyout_done(fd);
return (error);
}
int
image_copyout_done(int fd)
{
off_t ofs;
int error;
ofs = lseek(fd, 0L, SEEK_CUR);
if (ofs == -1)
return (0);
error = (ftruncate(fd, ofs) == -1) ? errno : 0;
return (error);
}
int
image_copyout_region(int fd, lba_t blk, lba_t size)
{
char *buffer;
off_t ofs;
size_t sz;
ssize_t rdsz, wrsz;
int error;
ofs = lseek(fd, 0L, SEEK_CUR);
if (lseek(image_fd, 0, SEEK_SET) != 0)
blk *= secsz;
if (lseek(image_fd, blk, SEEK_SET) != blk)
return (errno);
buffer = malloc(BUFFER_SIZE);
if (buffer == NULL)
return (errno);
error = 0;
while (1) {
rdsz = read(image_fd, buffer, BUFFER_SIZE);
size *= secsz;
while (size > 0) {
sz = (BUFFER_SIZE < size) ? BUFFER_SIZE : size;
rdsz = read(image_fd, buffer, sz);
if (rdsz <= 0) {
error = (rdsz < 0) ? errno : 0;
break;
@ -120,17 +148,40 @@ image_copyout(int fd)
error = errno;
break;
}
assert(wrsz == rdsz);
size -= rdsz;
}
free(buffer);
if (error)
return (error);
ofs = lseek(fd, 0L, SEEK_CUR);
if (ofs == -1)
return (errno);
error = (ftruncate(fd, ofs) == -1) ? errno : 0;
return (error);
}
int
image_data(lba_t blk, lba_t size)
{
char *buffer, *p;
blk *= secsz;
if (lseek(image_fd, blk, SEEK_SET) != blk)
return (1);
size *= secsz;
buffer = malloc(size);
if (buffer == NULL)
return (1);
if (read(image_fd, buffer, size) != (ssize_t)size) {
free(buffer);
return (1);
}
p = buffer;
while (size > 0 && *p == '\0')
size--, p++;
free(buffer);
return ((size == 0) ? 0 : 1);
}
lba_t
image_get_size(void)
{

View File

@ -33,6 +33,9 @@ typedef int64_t lba_t;
int image_copyin(lba_t blk, int fd, uint64_t *sizep);
int image_copyout(int fd);
int image_copyout_done(int fd);
int image_copyout_region(int fd, lba_t blk, lba_t size);
int image_data(lba_t blk, lba_t size);
lba_t image_get_size(void);
int image_init(void);
int image_set_size(lba_t blk);

View File

@ -24,12 +24,12 @@
.\"
.\" $FreeBSD$
.\"
.Dd July 2, 2014
.Dd July 4, 2014
.Dt MKIMG 1
.Os
.Sh NAME
.Nm mkimg
.Nd "utility to make a disk image"
.Nd "utility to make disk images"
.Sh SYNOPSIS
.Nm
.Op Fl H Ar heads
@ -40,6 +40,7 @@
.Op Fl f Ar format
.Op Fl o Ar outfile
.Op Fl v
.Op Fl y
.Fl s Ar scheme
.Fl p Ar partition
.Op Fl p Ar partition ...
@ -111,6 +112,16 @@ option increases the level of output that the
.Nm
utility prints.
.Pp
The
.Op Fl y
option is used for testing purposes only and is not to be used in production.
When present, the
.Nm
utility will generate predictable values for Universally Unique Identifiers
(UUIDs) and time stamps so that consecutive runs of the
.Nm
utility will create images that are identical.
.Pp
For a complete list of supported partitioning schemes or supported output
format, or for a detailed description of how to specify partitions, run the
.Nm

View File

@ -31,6 +31,7 @@ __FBSDID("$FreeBSD$");
#include <sys/queue.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/uuid.h>
#include <errno.h>
#include <err.h>
#include <fcntl.h>
@ -50,6 +51,7 @@ __FBSDID("$FreeBSD$");
struct partlisthead partlist = STAILQ_HEAD_INITIALIZER(partlist);
u_int nparts = 0;
u_int unit_testing;
u_int verbose;
u_int ncyls = 0;
@ -73,6 +75,8 @@ usage(const char *why)
fprintf(stderr, "\t-o <file>\t- file to write image into\n");
fprintf(stderr, "\t-p <partition>\n");
fprintf(stderr, "\t-s <scheme>\n");
fprintf(stderr, "\t-v\t\t- increase verbosity\n");
fprintf(stderr, "\t-y\t\t- [developers] enable unit test\n");
fprintf(stderr, "\t-H <num>\t- number of heads to simulate\n");
fprintf(stderr, "\t-P <num>\t- physical sector size\n");
fprintf(stderr, "\t-S <num>\t- logical sector size\n");
@ -258,6 +262,22 @@ sparse_write(int fd, const void *ptr, size_t sz)
}
#endif /* SPARSE_WRITE */
void
mkimg_uuid(struct uuid *uuid)
{
static uint8_t gen[sizeof(struct uuid)];
u_int i;
if (!unit_testing) {
uuidgen(uuid, 1);
return;
}
for (i = 0; i < sizeof(gen); i++)
gen[i]++;
memcpy(uuid, gen, sizeof(uuid_t));
}
static void
mkimg(void)
{
@ -337,7 +357,7 @@ main(int argc, char *argv[])
bcfd = -1;
outfd = 1; /* Write to stdout by default */
while ((c = getopt(argc, argv, "b:f:o:p:s:vH:P:S:T:")) != -1) {
while ((c = getopt(argc, argv, "b:f:o:p:s:vyH:P:S:T:")) != -1) {
switch (c) {
case 'b': /* BOOT CODE */
if (bcfd != -1)
@ -373,6 +393,9 @@ main(int argc, char *argv[])
if (error)
errc(EX_DATAERR, error, "scheme");
break;
case 'y':
unit_testing++;
break;
case 'v':
verbose++;
break;

View File

@ -50,6 +50,7 @@ struct part {
extern STAILQ_HEAD(partlisthead, part) partlist;
extern u_int nparts;
extern u_int unit_testing;
extern u_int verbose;
extern u_int ncyls;
@ -71,4 +72,7 @@ round_block(lba_t n)
ssize_t sparse_write(int, const void *, size_t);
#endif
struct uuid;
void mkimg_uuid(struct uuid *);
#endif /* _MKIMG_MKIMG_H_ */

View File

@ -28,7 +28,6 @@
__FBSDID("$FreeBSD$");
#include <sys/types.h>
#include <sys/apm.h>
#include <sys/endian.h>
#include <sys/errno.h>
#include <stdlib.h>

385
usr.bin/mkimg/vhd.c Normal file
View File

@ -0,0 +1,385 @@
/*-
* Copyright (c) 2014 Marcel Moolenaar
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. 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.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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 <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include <sys/types.h>
#include <sys/endian.h>
#include <sys/errno.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <uuid.h>
#include "image.h"
#include "format.h"
#include "mkimg.h"
#ifndef __has_extension
#define __has_extension(x) 0
#endif
/*
* General notes:
* o File is in network byte order.
* o The timestamp is seconds since 1/1/2000 12:00:00 AM UTC
*
* This file is divided in 3 parts:
* 1. Common definitions
* 2. Dynamic VHD support
* 3. Fixed VHD support
*/
/*
* PART 1: Common definitions
*/
#define VHD_SECTOR_SIZE 512
#define VHD_BLOCK_SIZE (4096 * VHD_SECTOR_SIZE) /* 2MB blocks */
struct vhd_footer {
uint64_t cookie;
#define VHD_FOOTER_COOKIE 0x636f6e6563746978
uint32_t features;
#define VHD_FEATURES_TEMPORARY 0x01
#define VHD_FEATURES_RESERVED 0x02
uint32_t version;
#define VHD_VERSION 0x00010000
uint64_t data_offset;
uint32_t timestamp;
uint32_t creator_tool;
#define VHD_CREATOR_TOOL 0x2a696d67 /* FreeBSD mkimg */
uint32_t creator_version;
#define VHD_CREATOR_VERSION 0x00010000
uint32_t creator_os;
#define VHD_CREATOR_OS 0x46425344
uint64_t original_size;
uint64_t current_size;
uint16_t cylinders;
uint8_t heads;
uint8_t sectors;
uint32_t disk_type;
#define VHD_DISK_TYPE_FIXED 2
#define VHD_DISK_TYPE_DYNAMIC 3
#define VHD_DISK_TYPE_DIFF 4
uint32_t checksum;
uuid_t id;
uint8_t saved_state;
uint8_t _reserved[427];
};
#if __has_extension(c_static_assert)
_Static_assert(sizeof(struct vhd_footer) == VHD_SECTOR_SIZE,
"Wrong size for footer");
#endif
static uint32_t
vhd_checksum(void *buf, size_t sz)
{
uint8_t *p = buf;
uint32_t sum;
size_t ofs;
sum = 0;
for (ofs = 0; ofs < sz; ofs++)
sum += p[ofs];
return (~sum);
}
static void
vhd_geometry(struct vhd_footer *footer, uint64_t image_size)
{
lba_t imgsz;
long cth;
/* Respect command line options if possible. */
if (nheads > 1 && nheads < 256 &&
nsecs > 1 && nsecs < 256 &&
ncyls < 65536) {
be16enc(&footer->cylinders, ncyls);
footer->heads = nheads;
footer->sectors = nsecs;
return;
}
imgsz = image_size / VHD_SECTOR_SIZE;
if (imgsz > 65536 * 16 * 255)
imgsz = 65536 * 16 * 255;
if (imgsz >= 65535 * 16 * 63) {
be16enc(&footer->cylinders, imgsz / (16 * 255));
footer->heads = 16;
footer->sectors = 255;
return;
}
footer->sectors = 17;
cth = imgsz / 17;
footer->heads = (cth + 1023) / 1024;
if (footer->heads < 4)
footer->heads = 4;
if (cth >= (footer->heads * 1024) || footer->heads > 16) {
footer->heads = 16;
footer->sectors = 31;
cth = imgsz / 31;
}
if (cth >= (footer->heads * 1024)) {
footer->heads = 16;
footer->sectors = 63;
cth = imgsz / 63;
}
be16enc(&footer->cylinders, cth / footer->heads);
}
static uint32_t
vhd_timestamp(void)
{
time_t t;
if (!unit_testing) {
t = time(NULL);
return (t - 0x386d4380);
}
return (0x01234567);
}
static void
vhd_uuid_enc(void *buf, const uuid_t *uuid)
{
uint8_t *p = buf;
int i;
be32enc(p, uuid->time_low);
be16enc(p + 4, uuid->time_mid);
be16enc(p + 6, uuid->time_hi_and_version);
p[8] = uuid->clock_seq_hi_and_reserved;
p[9] = uuid->clock_seq_low;
for (i = 0; i < _UUID_NODE_LEN; i++)
p[10 + i] = uuid->node[i];
}
static void
vhd_make_footer(struct vhd_footer *footer, uint64_t image_size,
uint32_t disk_type, uint64_t data_offset)
{
uuid_t id;
memset(footer, 0, sizeof(*footer));
be64enc(&footer->cookie, VHD_FOOTER_COOKIE);
be32enc(&footer->features, VHD_FEATURES_RESERVED);
be32enc(&footer->version, VHD_VERSION);
be64enc(&footer->data_offset, data_offset);
be32enc(&footer->timestamp, vhd_timestamp());
be32enc(&footer->creator_tool, VHD_CREATOR_TOOL);
be32enc(&footer->creator_version, VHD_CREATOR_VERSION);
be32enc(&footer->creator_os, VHD_CREATOR_OS);
be64enc(&footer->original_size, image_size);
be64enc(&footer->current_size, image_size);
vhd_geometry(footer, image_size);
be32enc(&footer->disk_type, disk_type);
mkimg_uuid(&id);
vhd_uuid_enc(&footer->id, &id);
be32enc(&footer->checksum, vhd_checksum(footer, sizeof(*footer)));
}
/*
* We round the image size to 2MB for both the dynamic and
* fixed VHD formats. For dynamic VHD, this is needed to
* have the image size be a multiple of the grain size. For
* fixed VHD this is not really needed, but makes sure that
* it's easy to convert from fixed VHD to dynamic VHD.
*/
static int
vhd_resize(lba_t imgsz)
{
uint64_t imagesz;
imagesz = imgsz * secsz;
imagesz = (imagesz + VHD_BLOCK_SIZE - 1) & ~(VHD_BLOCK_SIZE - 1);
return (image_set_size(imagesz / secsz));
}
/*
* PART 2: Dynamic VHD support
*
* Notes:
* o File layout:
* copy of disk footer
* dynamic disk header
* block allocation table (BAT)
* data blocks
* disk footer
*/
struct vhd_dyn_header {
uint64_t cookie;
#define VHD_HEADER_COOKIE 0x6378737061727365
uint64_t data_offset;
uint64_t table_offset;
uint32_t version;
uint32_t max_entries;
uint32_t block_size;
uint32_t checksum;
uuid_t parent_id;
uint32_t parent_timestamp;
char _reserved1[4];
uint16_t parent_name[256]; /* UTF-16 */
struct {
uint32_t code;
uint32_t data_space;
uint32_t data_length;
uint32_t _reserved;
uint64_t data_offset;
} parent_locator[8];
char _reserved2[256];
};
#if __has_extension(c_static_assert)
_Static_assert(sizeof(struct vhd_dyn_header) == VHD_SECTOR_SIZE * 2,
"Wrong size for header");
#endif
static int
vhd_dyn_write(int fd)
{
struct vhd_footer footer;
struct vhd_dyn_header header;
uint64_t imgsz;
lba_t blk, blkcnt, nblks;
uint32_t *bat;
void *bitmap;
size_t batsz;
uint32_t sector;
int bat_entries, error, entry;
imgsz = image_get_size() * secsz;
bat_entries = imgsz / VHD_BLOCK_SIZE;
vhd_make_footer(&footer, imgsz, VHD_DISK_TYPE_DYNAMIC, sizeof(footer));
if (sparse_write(fd, &footer, sizeof(footer)) < 0)
return (errno);
memset(&header, 0, sizeof(header));
be64enc(&header.cookie, VHD_HEADER_COOKIE);
be64enc(&header.data_offset, ~0ULL);
be64enc(&header.table_offset, sizeof(footer) + sizeof(header));
be32enc(&header.version, VHD_VERSION);
be32enc(&header.max_entries, bat_entries);
be32enc(&header.block_size, VHD_BLOCK_SIZE);
be32enc(&header.checksum, vhd_checksum(&header, sizeof(header)));
if (sparse_write(fd, &header, sizeof(header)) < 0)
return (errno);
batsz = bat_entries * sizeof(uint32_t);
batsz = (batsz + VHD_SECTOR_SIZE - 1) & ~(VHD_SECTOR_SIZE - 1);
bat = malloc(batsz);
if (bat == NULL)
return (errno);
memset(bat, 0xff, batsz);
blkcnt = VHD_BLOCK_SIZE / secsz;
sector = (sizeof(footer) + sizeof(header) + batsz) / VHD_SECTOR_SIZE;
for (entry = 0; entry < bat_entries; entry++) {
blk = entry * blkcnt;
if (image_data(blk, blkcnt)) {
be32enc(&bat[entry], sector);
sector += (VHD_BLOCK_SIZE / VHD_SECTOR_SIZE) + 1;
}
}
if (sparse_write(fd, bat, batsz) < 0) {
free(bat);
return (errno);
}
free(bat);
bitmap = malloc(VHD_SECTOR_SIZE);
if (bitmap == NULL)
return (errno);
memset(bitmap, 0xff, VHD_SECTOR_SIZE);
blk = 0;
blkcnt = VHD_BLOCK_SIZE / secsz;
nblks = image_get_size();
while (blk < nblks) {
if (!image_data(blk, blkcnt)) {
blk += blkcnt;
continue;
}
if (sparse_write(fd, bitmap, VHD_SECTOR_SIZE) < 0) {
error = errno;
break;
}
error = image_copyout_region(fd, blk, blkcnt);
if (error)
break;
blk += blkcnt;
}
free(bitmap);
if (blk != nblks)
return (error);
if (sparse_write(fd, &footer, sizeof(footer)) < 0)
return (errno);
return (0);
}
static struct mkimg_format vhd_dyn_format = {
.name = "vhd",
.description = "Virtual Hard Disk",
.resize = vhd_resize,
.write = vhd_dyn_write,
};
FORMAT_DEFINE(vhd_dyn_format);
/*
* PART 2: Fixed VHD
*/
static int
vhd_fix_write(int fd)
{
struct vhd_footer footer;
uint64_t imgsz;
int error;
error = image_copyout(fd);
if (!error) {
imgsz = image_get_size() * secsz;
vhd_make_footer(&footer, imgsz, VHD_DISK_TYPE_FIXED, ~0ULL);
if (sparse_write(fd, &footer, sizeof(footer)) < 0)
error = errno;
}
return (error);
}
static struct mkimg_format vhd_fix_format = {
.name = "vhdf",
.description = "Fixed Virtual Hard Disk",
.resize = vhd_resize,
.write = vhd_fix_write,
};
FORMAT_DEFINE(vhd_fix_format);

View File

@ -28,7 +28,6 @@
__FBSDID("$FreeBSD$");
#include <sys/types.h>
#include <sys/apm.h>
#include <sys/endian.h>
#include <sys/errno.h>
#include <stdint.h>
@ -115,8 +114,9 @@ vmdk_write(int fd)
char *buf, *desc;
off_t cur, lim;
uint64_t imagesz;
lba_t blkofs, blkcnt;
size_t gdsz, gtsz;
uint32_t sec;
uint32_t sec, cursec;
int error, desc_len, n, ngrains, ngts;
imagesz = (image_get_size() * secsz) / VMDK_SECTOR_SIZE;
@ -179,8 +179,15 @@ vmdk_write(int fd)
return (ENOMEM);
}
for (n = 0; n < ngrains; n++)
le32enc(gt + n, sec + n * grainsz);
cursec = sec;
blkcnt = (grainsz * VMDK_SECTOR_SIZE) / secsz;
for (n = 0; n < ngrains; n++) {
blkofs = n * blkcnt;
if (image_data(blkofs, blkcnt)) {
le32enc(gt + n, cursec);
cursec += grainsz;
}
}
error = 0;
if (!error && sparse_write(fd, &hdr, VMDK_SECTOR_SIZE) < 0)
@ -211,9 +218,19 @@ vmdk_write(int fd)
if (buf != NULL)
free(buf);
}
if (!error)
error = image_copyout(fd);
return (error);
if (error)
return (error);
blkcnt = (grainsz * VMDK_SECTOR_SIZE) / secsz;
for (n = 0; n < ngrains; n++) {
blkofs = n * blkcnt;
if (image_data(blkofs, blkcnt)) {
error = image_copyout_region(fd, blkofs, blkcnt);
if (error)
return (error);
}
}
return (image_copyout_done(fd));
}
static struct mkimg_format vmdk_format = {