1 /*- 2 * Copyright (c) 2013,2014 Juniper Networks, Inc. 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 24 * SUCH DAMAGE. 25 */ 26 27 #include <sys/param.h> 28 #include <sys/stat.h> 29 #include <errno.h> 30 #include <err.h> 31 #include <fcntl.h> 32 #include <getopt.h> 33 #include <libutil.h> 34 #include <limits.h> 35 #include <stdbool.h> 36 #include <stdint.h> 37 #include <stdio.h> 38 #include <stdlib.h> 39 #include <string.h> 40 #include <sysexits.h> 41 #include <unistd.h> 42 43 #include "image.h" 44 #include "format.h" 45 #include "mkimg.h" 46 #include "scheme.h" 47 48 #define LONGOPT_FORMATS 0x01000001 49 #define LONGOPT_SCHEMES 0x01000002 50 #define LONGOPT_VERSION 0x01000003 51 #define LONGOPT_CAPACITY 0x01000004 52 53 static struct option longopts[] = { 54 { "formats", no_argument, NULL, LONGOPT_FORMATS }, 55 { "schemes", no_argument, NULL, LONGOPT_SCHEMES }, 56 { "version", no_argument, NULL, LONGOPT_VERSION }, 57 { "capacity", required_argument, NULL, LONGOPT_CAPACITY }, 58 { NULL, 0, NULL, 0 } 59 }; 60 61 static uint64_t min_capacity = 0; 62 static uint64_t max_capacity = 0; 63 64 bool reproducible = false; 65 66 struct partlisthead partlist = TAILQ_HEAD_INITIALIZER(partlist); 67 u_int nparts = 0; 68 69 u_int unit_testing; 70 u_int verbose; 71 72 u_int ncyls = 0; 73 u_int nheads = 1; 74 u_int nsecs = 1; 75 u_int secsz = 512; 76 u_int blksz = 0; 77 uint32_t active_partition = 0; 78 79 static void 80 print_formats(int usage) 81 { 82 struct mkimg_format *f; 83 const char *sep; 84 85 if (usage) { 86 fprintf(stderr, " formats:\n"); 87 f = NULL; 88 while ((f = format_iterate(f)) != NULL) { 89 fprintf(stderr, "\t%s\t- %s\n", f->name, 90 f->description); 91 } 92 } else { 93 sep = ""; 94 f = NULL; 95 while ((f = format_iterate(f)) != NULL) { 96 printf("%s%s", sep, f->name); 97 sep = " "; 98 } 99 putchar('\n'); 100 } 101 } 102 103 static void 104 print_schemes(int usage) 105 { 106 struct mkimg_scheme *s; 107 const char *sep; 108 109 if (usage) { 110 fprintf(stderr, " schemes:\n"); 111 s = NULL; 112 while ((s = scheme_iterate(s)) != NULL) { 113 fprintf(stderr, "\t%s\t- %s\n", s->name, 114 s->description); 115 } 116 } else { 117 sep = ""; 118 s = NULL; 119 while ((s = scheme_iterate(s)) != NULL) { 120 printf("%s%s", sep, s->name); 121 sep = " "; 122 } 123 putchar('\n'); 124 } 125 } 126 127 static void 128 print_version(void) 129 { 130 u_int width; 131 132 #ifdef __LP64__ 133 width = 64; 134 #else 135 width = 32; 136 #endif 137 printf("mkimg %u (%u-bit)\n", MKIMG_VERSION, width); 138 } 139 140 static void 141 usage(const char *why) 142 { 143 144 warnx("error: %s", why); 145 fputc('\n', stderr); 146 fprintf(stderr, "usage: %s <options>\n", getprogname()); 147 148 fprintf(stderr, " options:\n"); 149 fprintf(stderr, "\t--formats\t- list image formats\n"); 150 fprintf(stderr, "\t--schemes\t- list partition schemes\n"); 151 fprintf(stderr, "\t--version\t- show version information\n"); 152 fputc('\n', stderr); 153 fprintf(stderr, "\t-a <num>\t- mark num'th partition as active\n"); 154 fprintf(stderr, "\t-b <file>\t- file containing boot code\n"); 155 fprintf(stderr, "\t-c <num>\t- minimum capacity (in bytes) of the disk\n"); 156 fprintf(stderr, "\t-C <num>\t- maximum capacity (in bytes) of the disk\n"); 157 fprintf(stderr, "\t-f <format>\n"); 158 fprintf(stderr, "\t-o <file>\t- file to write image into\n"); 159 fprintf(stderr, "\t-p <partition>\n"); 160 fprintf(stderr, "\t-s <scheme>\n"); 161 fprintf(stderr, "\t-v\t\t- increase verbosity\n"); 162 fprintf(stderr, "\t-y\t\t- [developers] enable unit test\n"); 163 fprintf(stderr, "\t-H <num>\t- number of heads to simulate\n"); 164 fprintf(stderr, "\t-P <num>\t- physical sector size\n"); 165 fprintf(stderr, "\t-S <num>\t- logical sector size\n"); 166 fprintf(stderr, "\t-T <num>\t- number of tracks to simulate\n"); 167 fputc('\n', stderr); 168 print_formats(1); 169 fputc('\n', stderr); 170 print_schemes(1); 171 fputc('\n', stderr); 172 fprintf(stderr, " partition specification:\n"); 173 fprintf(stderr, "\t<t>[/<l>]::<size>[:[+]<offset>]\t- " 174 "empty partition of given size and\n\t\t\t\t\t" 175 " optional relative or absolute offset\n"); 176 fprintf(stderr, "\t<t>[/<l>]:=<file>[:[+]offset]\t- partition " 177 "content and size are\n\t\t\t\t\t" 178 " determined by the named file and\n" 179 "\t\t\t\t\t optional relative or absolute offset\n"); 180 fprintf(stderr, "\t<t>[/<l>]:-<cmd>\t\t- partition content and size " 181 "are taken\n\t\t\t\t\t from the output of the command to run\n"); 182 fprintf(stderr, "\t-\t\t\t\t- unused partition entry\n"); 183 fprintf(stderr, "\t where:\n"); 184 fprintf(stderr, "\t\t<t>\t- scheme neutral partition type\n"); 185 fprintf(stderr, "\t\t<l>\t- optional scheme-dependent partition " 186 "label\n"); 187 188 exit(EX_USAGE); 189 } 190 191 static int 192 parse_uint32(uint32_t *valp, uint32_t min, uint32_t max, const char *arg) 193 { 194 uint64_t val; 195 196 if (expand_number(arg, &val) == -1) 197 return (errno); 198 if (val > UINT_MAX || val < (uint64_t)min || val > (uint64_t)max) 199 return (EINVAL); 200 *valp = (uint32_t)val; 201 return (0); 202 } 203 204 static int 205 parse_uint64(uint64_t *valp, uint64_t min, uint64_t max, const char *arg) 206 { 207 uint64_t val; 208 209 if (expand_number(arg, &val) == -1) 210 return (errno); 211 if (val < min || val > max) 212 return (EINVAL); 213 *valp = val; 214 return (0); 215 } 216 217 static int 218 pwr_of_two(u_int nr) 219 { 220 221 return (((nr & (nr - 1)) == 0) ? 1 : 0); 222 } 223 224 /* 225 * A partition specification has the following format: 226 * <type> ':' <kind> <contents> 227 * where: 228 * type the partition type alias 229 * kind the interpretation of the contents specification 230 * ':' contents holds the size of an empty partition 231 * '=' contents holds the name of a file to read 232 * '-' contents holds a command to run; the output of 233 * which is the contents of the partition. 234 * contents the specification of a partition's contents 235 * 236 * A specification that is a single dash indicates an unused partition 237 * entry. 238 */ 239 static int 240 parse_part(const char *spec) 241 { 242 struct part *part; 243 char *sep; 244 size_t len; 245 int error; 246 247 if (strcmp(spec, "-") == 0) { 248 nparts++; 249 return (0); 250 } 251 252 part = calloc(1, sizeof(struct part)); 253 if (part == NULL) 254 return (ENOMEM); 255 256 sep = strchr(spec, ':'); 257 if (sep == NULL) { 258 error = EINVAL; 259 goto errout; 260 } 261 len = sep - spec + 1; 262 if (len < 2) { 263 error = EINVAL; 264 goto errout; 265 } 266 part->alias = malloc(len); 267 if (part->alias == NULL) { 268 error = ENOMEM; 269 goto errout; 270 } 271 strlcpy(part->alias, spec, len); 272 spec = sep + 1; 273 274 switch (*spec) { 275 case ':': 276 part->kind = PART_KIND_SIZE; 277 break; 278 case '=': 279 part->kind = PART_KIND_FILE; 280 break; 281 case '-': 282 part->kind = PART_KIND_PIPE; 283 break; 284 default: 285 error = EINVAL; 286 goto errout; 287 } 288 spec++; 289 290 part->contents = strdup(spec); 291 if (part->contents == NULL) { 292 error = ENOMEM; 293 goto errout; 294 } 295 296 spec = part->alias; 297 sep = strchr(spec, '/'); 298 if (sep != NULL) { 299 *sep++ = '\0'; 300 if (strlen(part->alias) == 0 || strlen(sep) == 0) { 301 error = EINVAL; 302 goto errout; 303 } 304 part->label = strdup(sep); 305 if (part->label == NULL) { 306 error = ENOMEM; 307 goto errout; 308 } 309 } 310 311 part->index = nparts; 312 TAILQ_INSERT_TAIL(&partlist, part, link); 313 nparts++; 314 return (0); 315 316 errout: 317 if (part->alias != NULL) 318 free(part->alias); 319 free(part); 320 return (error); 321 } 322 323 #if defined(SPARSE_WRITE) 324 ssize_t 325 sparse_write(int fd, const void *ptr, size_t sz) 326 { 327 const char *buf, *p; 328 off_t ofs; 329 size_t len; 330 ssize_t wr, wrsz; 331 332 buf = ptr; 333 wrsz = 0; 334 p = memchr(buf, 0, sz); 335 while (sz > 0) { 336 len = (p != NULL) ? (size_t)(p - buf) : sz; 337 if (len > 0) { 338 len = (len + secsz - 1) & ~(secsz - 1); 339 if (len > sz) 340 len = sz; 341 wr = write(fd, buf, len); 342 if (wr < 0) 343 return (-1); 344 } else { 345 while (len < sz && *p++ == '\0') 346 len++; 347 if (len < sz) 348 len &= ~(secsz - 1); 349 if (len == 0) 350 continue; 351 ofs = lseek(fd, len, SEEK_CUR); 352 if (ofs < 0) 353 return (-1); 354 wr = len; 355 } 356 buf += wr; 357 sz -= wr; 358 wrsz += wr; 359 p = memchr(buf, 0, sz); 360 } 361 return (wrsz); 362 } 363 #endif /* SPARSE_WRITE */ 364 365 void 366 mkimg_chs(lba_t lba, u_int maxcyl, u_int *cylp, u_int *hdp, u_int *secp) 367 { 368 u_int hd, sec; 369 370 *cylp = *hdp = *secp = ~0U; 371 if (nsecs == 1 || nheads == 1) 372 return; 373 374 sec = lba % nsecs + 1; 375 lba /= nsecs; 376 hd = lba % nheads; 377 lba /= nheads; 378 if (lba > maxcyl) 379 return; 380 381 *cylp = lba; 382 *hdp = hd; 383 *secp = sec; 384 } 385 386 static int 387 capacity_resize(lba_t end) 388 { 389 lba_t min_capsz, max_capsz; 390 391 min_capsz = (min_capacity + secsz - 1) / secsz; 392 max_capsz = (max_capacity + secsz - 1) / secsz; 393 394 if (max_capsz != 0 && end > max_capsz) 395 return (ENOSPC); 396 if (end >= min_capsz) 397 return (0); 398 399 return (image_set_size(min_capsz)); 400 } 401 402 static void 403 mkimg_validate(void) 404 { 405 struct part *part, *part2; 406 lba_t start, end, start2, end2; 407 int i, j; 408 409 i = 0; 410 411 TAILQ_FOREACH(part, &partlist, link) { 412 start = part->block; 413 end = part->block + part->size; 414 j = i + 1; 415 part2 = TAILQ_NEXT(part, link); 416 if (part2 == NULL) 417 break; 418 419 TAILQ_FOREACH_FROM(part2, &partlist, link) { 420 start2 = part2->block; 421 end2 = part2->block + part2->size; 422 423 if ((start >= start2 && start < end2) || 424 (end > start2 && end <= end2)) { 425 errx(1, "partition %d overlaps partition %d", 426 i, j); 427 } 428 429 j++; 430 } 431 432 i++; 433 } 434 } 435 436 static void 437 mkimg(void) 438 { 439 FILE *fp; 440 struct part *part; 441 lba_t block, blkoffset; 442 uint64_t bytesize, byteoffset; 443 char *size, *offset; 444 bool abs_offset; 445 int error, fd; 446 447 /* First check partition information */ 448 TAILQ_FOREACH(part, &partlist, link) { 449 error = scheme_check_part(part); 450 if (error) 451 errc(EX_DATAERR, error, "partition %d", part->index+1); 452 } 453 454 block = scheme_metadata(SCHEME_META_IMG_START, 0); 455 abs_offset = false; 456 TAILQ_FOREACH(part, &partlist, link) { 457 byteoffset = blkoffset = 0; 458 abs_offset = false; 459 460 /* Look for an offset. Set size too if we can. */ 461 switch (part->kind) { 462 case PART_KIND_SIZE: 463 case PART_KIND_FILE: 464 offset = part->contents; 465 size = strsep(&offset, ":"); 466 if (part->kind == PART_KIND_SIZE && 467 expand_number(size, &bytesize) == -1) 468 error = errno; 469 if (offset != NULL) { 470 if (*offset != '+') 471 abs_offset = true; 472 else 473 offset++; 474 if (expand_number(offset, &byteoffset) == -1) 475 error = errno; 476 } 477 break; 478 } 479 480 /* Work out exactly where the partition starts. */ 481 blkoffset = (byteoffset + secsz - 1) / secsz; 482 if (abs_offset) 483 block = scheme_metadata(SCHEME_META_PART_ABSOLUTE, 484 blkoffset); 485 else 486 block = scheme_metadata(SCHEME_META_PART_BEFORE, 487 block + blkoffset); 488 part->block = block; 489 490 if (verbose) 491 fprintf(stderr, "partition %d: starting block %llu " 492 "... ", part->index + 1, (long long)part->block); 493 494 /* Pull in partition contents, set size if we haven't yet. */ 495 switch (part->kind) { 496 case PART_KIND_FILE: 497 fd = open(part->contents, O_RDONLY, 0); 498 if (fd != -1) { 499 error = image_copyin(block, fd, &bytesize); 500 close(fd); 501 } else 502 error = errno; 503 break; 504 case PART_KIND_PIPE: 505 fp = popen(part->contents, "r"); 506 if (fp != NULL) { 507 fd = fileno(fp); 508 error = image_copyin(block, fd, &bytesize); 509 pclose(fp); 510 } else 511 error = errno; 512 break; 513 } 514 if (error) 515 errc(EX_IOERR, error, "partition %d", part->index + 1); 516 part->size = (bytesize + secsz - 1) / secsz; 517 if (verbose) { 518 bytesize = part->size * secsz; 519 fprintf(stderr, "size %llu bytes (%llu blocks)\n", 520 (long long)bytesize, (long long)part->size); 521 if (abs_offset) { 522 fprintf(stderr, 523 " location %llu bytes (%llu blocks)\n", 524 (long long)byteoffset, 525 (long long)blkoffset); 526 } else if (blkoffset > 0) { 527 fprintf(stderr, 528 " offset %llu bytes (%llu blocks)\n", 529 (long long)byteoffset, 530 (long long)blkoffset); 531 } 532 } 533 block = scheme_metadata(SCHEME_META_PART_AFTER, 534 part->block + part->size); 535 } 536 537 mkimg_validate(); 538 539 block = scheme_metadata(SCHEME_META_IMG_END, block); 540 error = image_set_size(block); 541 if (!error) { 542 error = capacity_resize(block); 543 block = image_get_size(); 544 } 545 if (!error) { 546 error = format_resize(block); 547 block = image_get_size(); 548 } 549 if (error) 550 errc(EX_IOERR, error, "image sizing"); 551 ncyls = block / (nsecs * nheads); 552 error = scheme_write(block); 553 if (error) 554 errc(EX_IOERR, error, "writing metadata"); 555 } 556 557 int 558 main(int argc, char *argv[]) 559 { 560 const char *format_name; 561 int bcfd, outfd; 562 int c, error; 563 564 bcfd = -1; 565 outfd = 1; /* Write to stdout by default */ 566 while ((c = getopt_long(argc, argv, "a:b:c:C:f:o:p:s:vyH:P:RS:T:", 567 longopts, NULL)) != -1) { 568 switch (c) { 569 case 'a': /* ACTIVE PARTITION, if supported */ 570 error = parse_uint32(&active_partition, 1, 100, optarg); 571 if (error) 572 errc(EX_DATAERR, error, "Partition ordinal"); 573 break; 574 case 'b': /* BOOT CODE */ 575 if (bcfd != -1) 576 usage("multiple bootcode given"); 577 bcfd = open(optarg, O_RDONLY, 0); 578 if (bcfd == -1) 579 err(EX_UNAVAILABLE, "%s", optarg); 580 break; 581 case 'c': /* MINIMUM CAPACITY */ 582 error = parse_uint64(&min_capacity, 1, INT64_MAX, optarg); 583 if (error) 584 errc(EX_DATAERR, error, "minimum capacity in bytes"); 585 break; 586 case 'C': /* MAXIMUM CAPACITY */ 587 error = parse_uint64(&max_capacity, 1, INT64_MAX, optarg); 588 if (error) 589 errc(EX_DATAERR, error, "maximum capacity in bytes"); 590 break; 591 case 'f': /* OUTPUT FORMAT */ 592 if (format_selected() != NULL) 593 usage("multiple formats given"); 594 error = format_select(optarg); 595 if (error) 596 errc(EX_DATAERR, error, "format"); 597 break; 598 case 'o': /* OUTPUT FILE */ 599 if (outfd != 1) 600 usage("multiple output files given"); 601 outfd = open(optarg, O_WRONLY | O_CREAT | O_TRUNC, 602 S_IWUSR | S_IRUSR | S_IRGRP | S_IROTH); 603 if (outfd == -1) 604 err(EX_CANTCREAT, "%s", optarg); 605 break; 606 case 'p': /* PARTITION */ 607 error = parse_part(optarg); 608 if (error) 609 errc(EX_DATAERR, error, "partition"); 610 break; 611 case 'R': 612 reproducible = true; 613 break; 614 case 's': /* SCHEME */ 615 if (scheme_selected() != NULL) 616 usage("multiple schemes given"); 617 error = scheme_select(optarg); 618 if (error) 619 errc(EX_DATAERR, error, "scheme"); 620 break; 621 case 'y': 622 unit_testing++; 623 break; 624 case 'v': 625 verbose++; 626 break; 627 case 'H': /* GEOMETRY: HEADS */ 628 error = parse_uint32(&nheads, 1, 255, optarg); 629 if (error) 630 errc(EX_DATAERR, error, "number of heads"); 631 break; 632 case 'P': /* GEOMETRY: PHYSICAL SECTOR SIZE */ 633 error = parse_uint32(&blksz, 512, INT_MAX+1U, optarg); 634 if (error == 0 && !pwr_of_two(blksz)) 635 error = EINVAL; 636 if (error) 637 errc(EX_DATAERR, error, "physical sector size"); 638 break; 639 case 'S': /* GEOMETRY: LOGICAL SECTOR SIZE */ 640 error = parse_uint32(&secsz, 512, INT_MAX+1U, optarg); 641 if (error == 0 && !pwr_of_two(secsz)) 642 error = EINVAL; 643 if (error) 644 errc(EX_DATAERR, error, "logical sector size"); 645 break; 646 case 'T': /* GEOMETRY: TRACK SIZE */ 647 error = parse_uint32(&nsecs, 1, 63, optarg); 648 if (error) 649 errc(EX_DATAERR, error, "track size"); 650 break; 651 case LONGOPT_FORMATS: 652 print_formats(0); 653 exit(EX_OK); 654 /*NOTREACHED*/ 655 case LONGOPT_SCHEMES: 656 print_schemes(0); 657 exit(EX_OK); 658 /*NOTREACHED*/ 659 case LONGOPT_VERSION: 660 print_version(); 661 exit(EX_OK); 662 /*NOTREACHED*/ 663 case LONGOPT_CAPACITY: 664 error = parse_uint64(&min_capacity, 1, INT64_MAX, optarg); 665 if (error) 666 errc(EX_DATAERR, error, "capacity in bytes"); 667 max_capacity = min_capacity; 668 break; 669 default: 670 usage("unknown option"); 671 } 672 } 673 674 if (argc > optind) 675 usage("trailing arguments"); 676 if (scheme_selected() == NULL && nparts > 0) 677 usage("no scheme"); 678 if (nparts == 0 && min_capacity == 0) 679 usage("no partitions"); 680 if (max_capacity != 0 && min_capacity > max_capacity) 681 usage("minimum capacity cannot be larger than the maximum one"); 682 683 if (reproducible) 684 srandom(42); 685 686 if (secsz > blksz) { 687 if (blksz != 0) 688 errx(EX_DATAERR, "the physical block size cannot " 689 "be smaller than the sector size"); 690 blksz = secsz; 691 } 692 693 if (secsz > scheme_max_secsz()) 694 errx(EX_DATAERR, "maximum sector size supported is %u; " 695 "size specified is %u", scheme_max_secsz(), secsz); 696 697 if (nparts > scheme_max_parts()) 698 errx(EX_DATAERR, "%d partitions supported; %d given", 699 scheme_max_parts(), nparts); 700 701 if (format_selected() == NULL) 702 format_select("raw"); 703 704 if (bcfd != -1) { 705 error = scheme_bootcode(bcfd); 706 close(bcfd); 707 if (error) 708 errc(EX_DATAERR, error, "boot code"); 709 } 710 711 format_name = format_selected()->name; 712 if (verbose) { 713 fprintf(stderr, "Logical sector size: %u\n", secsz); 714 fprintf(stderr, "Physical block size: %u\n", blksz); 715 fprintf(stderr, "Sectors per track: %u\n", nsecs); 716 fprintf(stderr, "Number of heads: %u\n", nheads); 717 fputc('\n', stderr); 718 if (scheme_selected()) 719 fprintf(stderr, "Partitioning scheme: %s\n", 720 scheme_selected()->name); 721 fprintf(stderr, "Output file format: %s\n", 722 format_name); 723 fputc('\n', stderr); 724 } 725 726 #if defined(SPARSE_WRITE) 727 /* 728 * sparse_write() fails if output is not seekable so fail early 729 * not wasting some load unless output format is raw 730 */ 731 if (strcmp("raw", format_name) && 732 lseek(outfd, (off_t)0, SEEK_CUR) == -1 && errno == ESPIPE) 733 errx(EX_USAGE, "%s: output must be seekable", format_name); 734 #endif 735 736 error = image_init(); 737 if (error) 738 errc(EX_OSERR, error, "cannot initialize"); 739 740 mkimg(); 741 742 if (verbose) { 743 fputc('\n', stderr); 744 fprintf(stderr, "Number of cylinders: %u\n", ncyls); 745 } 746 747 error = format_write(outfd); 748 if (error) 749 errc(EX_IOERR, error, "writing image"); 750 751 return (0); 752 } 753