freebsd-skq/usr.bin/mkimg/mkimg.c
marcel 1603ce9a33 Add the -c option for specifying the capacity of the disk image. When
a capcity is given, no partitions are required. When no partitions are
given, no scheme needs to be specified either. This makes it possible
to create an entirely empty disk image. To add an empty partitioning
table, specify the scheme.

Bump the version to 20150222.
2015-02-22 04:50:47 +00:00

639 lines
15 KiB
C

/*-
* Copyright (c) 2013,2014 Juniper Networks, 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:
* 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/linker_set.h>
#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>
#include <getopt.h>
#include <libutil.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sysexits.h>
#include <unistd.h>
#include "image.h"
#include "format.h"
#include "mkimg.h"
#include "scheme.h"
#define LONGOPT_FORMATS 0x01000001
#define LONGOPT_SCHEMES 0x01000002
#define LONGOPT_VERSION 0x01000003
static struct option longopts[] = {
{ "formats", no_argument, NULL, LONGOPT_FORMATS },
{ "schemes", no_argument, NULL, LONGOPT_SCHEMES },
{ "version", no_argument, NULL, LONGOPT_VERSION },
{ NULL, 0, NULL, 0 }
};
static uint64_t capacity;
struct partlisthead partlist = STAILQ_HEAD_INITIALIZER(partlist);
u_int nparts = 0;
u_int unit_testing;
u_int verbose;
u_int ncyls = 0;
u_int nheads = 1;
u_int nsecs = 1;
u_int secsz = 512;
u_int blksz = 0;
static void
print_formats(int usage)
{
struct mkimg_format *f, **f_iter;
const char *sep;
if (usage) {
fprintf(stderr, " formats:\n");
SET_FOREACH(f_iter, formats) {
f = *f_iter;
fprintf(stderr, "\t%s\t- %s\n", f->name,
f->description);
}
} else {
sep = "";
SET_FOREACH(f_iter, formats) {
f = *f_iter;
printf("%s%s", sep, f->name);
sep = " ";
}
putchar('\n');
}
}
static void
print_schemes(int usage)
{
struct mkimg_scheme *s, **s_iter;
const char *sep;
if (usage) {
fprintf(stderr, " schemes:\n");
SET_FOREACH(s_iter, schemes) {
s = *s_iter;
fprintf(stderr, "\t%s\t- %s\n", s->name,
s->description);
}
} else {
sep = "";
SET_FOREACH(s_iter, schemes) {
s = *s_iter;
printf("%s%s", sep, s->name);
sep = " ";
}
putchar('\n');
}
}
static void
print_version(void)
{
u_int width;
#ifdef __LP64__
width = 64;
#else
width = 32;
#endif
printf("mkimg %u (%u-bit)\n", MKIMG_VERSION, width);
}
static void
usage(const char *why)
{
warnx("error: %s", why);
fputc('\n', stderr);
fprintf(stderr, "usage: %s <options>\n", getprogname());
fprintf(stderr, " options:\n");
fprintf(stderr, "\t--formats\t- list image formats\n");
fprintf(stderr, "\t--schemes\t- list partition schemes\n");
fprintf(stderr, "\t--version\t- show version information\n");
fputc('\n', stderr);
fprintf(stderr, "\t-b <file>\t- file containing boot code\n");
fprintf(stderr, "\t-c <num>\t- capacity (in bytes) of the disk\n");
fprintf(stderr, "\t-f <format>\n");
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");
fprintf(stderr, "\t-T <num>\t- number of tracks to simulate\n");
fputc('\n', stderr);
print_formats(1);
fputc('\n', stderr);
print_schemes(1);
fputc('\n', stderr);
fprintf(stderr, " partition specification:\n");
fprintf(stderr, "\t<t>[/<l>]::<size>\t- empty partition of given "
"size\n");
fprintf(stderr, "\t<t>[/<l>]:=<file>\t- partition content and size "
"are determined\n\t\t\t\t by the named file\n");
fprintf(stderr, "\t<t>[/<l>]:-<cmd>\t- partition content and size "
"are taken from\n\t\t\t\t the output of the command to run\n");
fprintf(stderr, "\t-\t\t\t- unused partition entry\n");
fprintf(stderr, "\t where:\n");
fprintf(stderr, "\t\t<t>\t- scheme neutral partition type\n");
fprintf(stderr, "\t\t<l>\t- optional scheme-dependent partition "
"label\n");
exit(EX_USAGE);
}
static int
parse_uint32(uint32_t *valp, uint32_t min, uint32_t max, const char *arg)
{
uint64_t val;
if (expand_number(arg, &val) == -1)
return (errno);
if (val > UINT_MAX || val < (uint64_t)min || val > (uint64_t)max)
return (EINVAL);
*valp = (uint32_t)val;
return (0);
}
static int
parse_uint64(uint64_t *valp, uint64_t min, uint64_t max, const char *arg)
{
uint64_t val;
if (expand_number(arg, &val) == -1)
return (errno);
if (val < min || val > max)
return (EINVAL);
*valp = val;
return (0);
}
static int
pwr_of_two(u_int nr)
{
return (((nr & (nr - 1)) == 0) ? 1 : 0);
}
/*
* A partition specification has the following format:
* <type> ':' <kind> <contents>
* where:
* type the partition type alias
* kind the interpretation of the contents specification
* ':' contents holds the size of an empty partition
* '=' contents holds the name of a file to read
* '-' contents holds a command to run; the output of
* which is the contents of the partition.
* contents the specification of a partition's contents
*
* A specification that is a single dash indicates an unused partition
* entry.
*/
static int
parse_part(const char *spec)
{
struct part *part;
char *sep;
size_t len;
int error;
if (strcmp(spec, "-") == 0) {
nparts++;
return (0);
}
part = calloc(1, sizeof(struct part));
if (part == NULL)
return (ENOMEM);
sep = strchr(spec, ':');
if (sep == NULL) {
error = EINVAL;
goto errout;
}
len = sep - spec + 1;
if (len < 2) {
error = EINVAL;
goto errout;
}
part->alias = malloc(len);
if (part->alias == NULL) {
error = ENOMEM;
goto errout;
}
strlcpy(part->alias, spec, len);
spec = sep + 1;
switch (*spec) {
case ':':
part->kind = PART_KIND_SIZE;
break;
case '=':
part->kind = PART_KIND_FILE;
break;
case '-':
part->kind = PART_KIND_PIPE;
break;
default:
error = EINVAL;
goto errout;
}
spec++;
part->contents = strdup(spec);
if (part->contents == NULL) {
error = ENOMEM;
goto errout;
}
spec = part->alias;
sep = strchr(spec, '/');
if (sep != NULL) {
*sep++ = '\0';
if (strlen(part->alias) == 0 || strlen(sep) == 0) {
error = EINVAL;
goto errout;
}
part->label = strdup(sep);
if (part->label == NULL) {
error = ENOMEM;
goto errout;
}
}
part->index = nparts;
STAILQ_INSERT_TAIL(&partlist, part, link);
nparts++;
return (0);
errout:
if (part->alias != NULL)
free(part->alias);
free(part);
return (error);
}
#if defined(SPARSE_WRITE)
ssize_t
sparse_write(int fd, const void *ptr, size_t sz)
{
const char *buf, *p;
off_t ofs;
size_t len;
ssize_t wr, wrsz;
buf = ptr;
wrsz = 0;
p = memchr(buf, 0, sz);
while (sz > 0) {
len = (p != NULL) ? (size_t)(p - buf) : sz;
if (len > 0) {
len = (len + secsz - 1) & ~(secsz - 1);
if (len > sz)
len = sz;
wr = write(fd, buf, len);
if (wr < 0)
return (-1);
} else {
while (len < sz && *p++ == '\0')
len++;
if (len < sz)
len &= ~(secsz - 1);
if (len == 0)
continue;
ofs = lseek(fd, len, SEEK_CUR);
if (ofs < 0)
return (-1);
wr = len;
}
buf += wr;
sz -= wr;
wrsz += wr;
p = memchr(buf, 0, sz);
}
return (wrsz);
}
#endif /* SPARSE_WRITE */
void
mkimg_chs(lba_t lba, u_int maxcyl, u_int *cylp, u_int *hdp, u_int *secp)
{
u_int hd, sec;
*cylp = *hdp = *secp = ~0U;
if (nsecs == 1 || nheads == 1)
return;
sec = lba % nsecs + 1;
lba /= nsecs;
hd = lba % nheads;
lba /= nheads;
if (lba > maxcyl)
return;
*cylp = lba;
*hdp = hd;
*secp = sec;
}
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 int
capacity_resize(lba_t end)
{
lba_t capsz;
capsz = (capacity + secsz - 1) / secsz;
if (end >= capsz)
return (0);
return (image_set_size(capsz));
}
static void
mkimg(void)
{
FILE *fp;
struct part *part;
lba_t block;
off_t bytesize;
int error, fd;
/* First check partition information */
STAILQ_FOREACH(part, &partlist, link) {
error = scheme_check_part(part);
if (error)
errc(EX_DATAERR, error, "partition %d", part->index+1);
}
block = scheme_metadata(SCHEME_META_IMG_START, 0);
STAILQ_FOREACH(part, &partlist, link) {
block = scheme_metadata(SCHEME_META_PART_BEFORE, block);
if (verbose)
fprintf(stderr, "partition %d: starting block %llu "
"... ", part->index + 1, (long long)block);
part->block = block;
switch (part->kind) {
case PART_KIND_SIZE:
if (expand_number(part->contents, &bytesize) == -1)
error = errno;
break;
case PART_KIND_FILE:
fd = open(part->contents, O_RDONLY, 0);
if (fd != -1) {
error = image_copyin(block, fd, &bytesize);
close(fd);
} else
error = errno;
break;
case PART_KIND_PIPE:
fp = popen(part->contents, "r");
if (fp != NULL) {
fd = fileno(fp);
error = image_copyin(block, fd, &bytesize);
pclose(fp);
} else
error = errno;
break;
}
if (error)
errc(EX_IOERR, error, "partition %d", part->index + 1);
part->size = (bytesize + secsz - 1) / secsz;
if (verbose) {
bytesize = part->size * secsz;
fprintf(stderr, "size %llu bytes (%llu blocks)\n",
(long long)bytesize, (long long)part->size);
}
block = scheme_metadata(SCHEME_META_PART_AFTER,
part->block + part->size);
}
block = scheme_metadata(SCHEME_META_IMG_END, block);
error = image_set_size(block);
if (!error)
error = capacity_resize(block);
if (!error)
error = format_resize(block);
if (error)
errc(EX_IOERR, error, "image sizing");
block = image_get_size();
ncyls = block / (nsecs * nheads);
error = scheme_write(block);
if (error)
errc(EX_IOERR, error, "writing metadata");
}
int
main(int argc, char *argv[])
{
int bcfd, outfd;
int c, error;
bcfd = -1;
outfd = 1; /* Write to stdout by default */
while ((c = getopt_long(argc, argv, "b:c:f:o:p:s:vyH:P:S:T:",
longopts, NULL)) != -1) {
switch (c) {
case 'b': /* BOOT CODE */
if (bcfd != -1)
usage("multiple bootcode given");
bcfd = open(optarg, O_RDONLY, 0);
if (bcfd == -1)
err(EX_UNAVAILABLE, "%s", optarg);
break;
case 'c': /* CAPACITY */
error = parse_uint64(&capacity, 1, OFF_MAX, optarg);
if (error)
errc(EX_DATAERR, error, "capacity in bytes");
break;
case 'f': /* OUTPUT FORMAT */
if (format_selected() != NULL)
usage("multiple formats given");
error = format_select(optarg);
if (error)
errc(EX_DATAERR, error, "format");
break;
case 'o': /* OUTPUT FILE */
if (outfd != 1)
usage("multiple output files given");
outfd = open(optarg, O_WRONLY | O_CREAT | O_TRUNC,
S_IWUSR | S_IRUSR | S_IRGRP | S_IROTH);
if (outfd == -1)
err(EX_CANTCREAT, "%s", optarg);
break;
case 'p': /* PARTITION */
error = parse_part(optarg);
if (error)
errc(EX_DATAERR, error, "partition");
break;
case 's': /* SCHEME */
if (scheme_selected() != NULL)
usage("multiple schemes given");
error = scheme_select(optarg);
if (error)
errc(EX_DATAERR, error, "scheme");
break;
case 'y':
unit_testing++;
break;
case 'v':
verbose++;
break;
case 'H': /* GEOMETRY: HEADS */
error = parse_uint32(&nheads, 1, 255, optarg);
if (error)
errc(EX_DATAERR, error, "number of heads");
break;
case 'P': /* GEOMETRY: PHYSICAL SECTOR SIZE */
error = parse_uint32(&blksz, 512, INT_MAX+1U, optarg);
if (error == 0 && !pwr_of_two(blksz))
error = EINVAL;
if (error)
errc(EX_DATAERR, error, "physical sector size");
break;
case 'S': /* GEOMETRY: LOGICAL SECTOR SIZE */
error = parse_uint32(&secsz, 512, INT_MAX+1U, optarg);
if (error == 0 && !pwr_of_two(secsz))
error = EINVAL;
if (error)
errc(EX_DATAERR, error, "logical sector size");
break;
case 'T': /* GEOMETRY: TRACK SIZE */
error = parse_uint32(&nsecs, 1, 63, optarg);
if (error)
errc(EX_DATAERR, error, "track size");
break;
case LONGOPT_FORMATS:
print_formats(0);
exit(EX_OK);
/*NOTREACHED*/
case LONGOPT_SCHEMES:
print_schemes(0);
exit(EX_OK);
/*NOTREACHED*/
case LONGOPT_VERSION:
print_version();
exit(EX_OK);
/*NOTREACHED*/
default:
usage("unknown option");
}
}
if (argc > optind)
usage("trailing arguments");
if (scheme_selected() == NULL && nparts > 0)
usage("no scheme");
if (nparts == 0 && capacity == 0)
usage("no partitions");
if (secsz > blksz) {
if (blksz != 0)
errx(EX_DATAERR, "the physical block size cannot "
"be smaller than the sector size");
blksz = secsz;
}
if (secsz > scheme_max_secsz())
errx(EX_DATAERR, "maximum sector size supported is %u; "
"size specified is %u", scheme_max_secsz(), secsz);
if (nparts > scheme_max_parts())
errx(EX_DATAERR, "%d partitions supported; %d given",
scheme_max_parts(), nparts);
if (format_selected() == NULL)
format_select("raw");
if (bcfd != -1) {
error = scheme_bootcode(bcfd);
close(bcfd);
if (error)
errc(EX_DATAERR, error, "boot code");
}
if (verbose) {
fprintf(stderr, "Logical sector size: %u\n", secsz);
fprintf(stderr, "Physical block size: %u\n", blksz);
fprintf(stderr, "Sectors per track: %u\n", nsecs);
fprintf(stderr, "Number of heads: %u\n", nheads);
fputc('\n', stderr);
if (scheme_selected())
fprintf(stderr, "Partitioning scheme: %s\n",
scheme_selected()->name);
fprintf(stderr, "Output file format: %s\n",
format_selected()->name);
fputc('\n', stderr);
}
error = image_init();
if (error)
errc(EX_OSERR, error, "cannot initialize");
mkimg();
if (verbose) {
fputc('\n', stderr);
fprintf(stderr, "Number of cylinders: %u\n", ncyls);
}
error = format_write(outfd);
if (error)
errc(EX_IOERR, error, "writing image");
return (0);
}