1 /* $OpenBSD: scsi.c,v 1.27 2012/09/03 20:46:44 okan Exp $ */ 2 /* $FreeBSD: scsi.c,v 1.11 1996/04/06 11:00:28 joerg Exp $ */ 3 4 /* 5 * Written By Julian ELischer 6 * Copyright julian Elischer 1993. 7 * Permission is granted to use or redistribute this file in any way as long 8 * as this notice remains. Julian Elischer does not guarantee that this file 9 * is totally correct for any given task and users of this file must 10 * accept responsibility for any damage that occurs from the application of this 11 * file. 12 * 13 * (julian@tfs.com julian@dialix.oz.au) 14 * 15 * User SCSI hooks added by Peter Dufault: 16 * 17 * Copyright (c) 1994 HD Associates 18 * (contact: dufault@hda.com) 19 * All rights reserved. 20 * 21 * Redistribution and use in source and binary forms, with or without 22 * modification, are permitted provided that the following conditions 23 * are met: 24 * 1. Redistributions of source code must retain the above copyright 25 * notice, this list of conditions and the following disclaimer. 26 * 2. Redistributions in binary form must reproduce the above copyright 27 * notice, this list of conditions and the following disclaimer in the 28 * documentation and/or other materials provided with the distribution. 29 * 3. The name of HD Associates 30 * may not be used to endorse or promote products derived from this software 31 * without specific prior written permission. 32 * 33 * THIS SOFTWARE IS PROVIDED BY HD ASSOCIATES ``AS IS'' AND 34 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 35 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 36 * ARE DISCLAIMED. IN NO EVENT SHALL HD ASSOCIATES BE LIABLE 37 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 38 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 39 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 40 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 41 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 42 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 43 * SUCH DAMAGE. 44 */ 45 46 #include <sys/types.h> 47 #include <sys/wait.h> 48 49 #include <stdio.h> 50 #include <string.h> 51 #include <stdlib.h> 52 #include <unistd.h> 53 #include <errno.h> 54 #include <sys/scsiio.h> 55 #include <sys/file.h> 56 #include <ctype.h> 57 #include <signal.h> 58 #include <err.h> 59 #include <paths.h> 60 61 #include "libscsi.h" 62 63 int fd; 64 int debuglevel; 65 int debugflag; 66 int commandflag; 67 int verbose = 0; 68 69 int modeflag; 70 int editflag; 71 int modepage = 0; /* Read this mode page */ 72 int pagectl = 0; /* Mode sense page control */ 73 int seconds = 2; 74 75 void procargs(int *argc_p, char ***argv_p); 76 int iget(void *hook, char *name); 77 char *cget(void *hook, char *name); 78 void arg_put(void *hook, int letter, void *arg, int count, char *name); 79 void mode_sense(int fd, u_char *data, int len, int pc, int page); 80 void mode_select(int fd, u_char *data, int len, int perm); 81 82 static void 83 usage(void) 84 { 85 fprintf(stderr, 86 "Usage:\n" 87 "\n" 88 " scsi -f device -d debug_level # To set debug level\n" 89 " scsi -f device -m page [-P pc] # To read mode pages\n" 90 " scsi -f device [-v] [-s seconds] -c cmd_fmt [arg0 ... argn] # A command...\n" 91 " -o count out_fmt [arg0 ... argn] # EITHER (data out)\n" 92 " -i count in_fmt # OR (data in)\n" 93 "\n" 94 "\"out_fmt\" can be \"-\" to read output data from stdin;\n" 95 "\"in_fmt\" can be \"-\" to write input data to stdout;\n" 96 "\n" 97 "If debugging is not compiled in the kernel, \"-d\" will have no effect\n" 98 99 ); 100 101 exit (1); 102 } 103 104 void 105 procargs(int *argc_p, char ***argv_p) 106 { 107 int argc = *argc_p; 108 char **argv = *argv_p; 109 int fflag, ch; 110 111 fflag = 0; 112 commandflag = 0; 113 debugflag = 0; 114 while ((ch = getopt(argc, argv, "cef:d:m:P:s:v")) != -1) { 115 switch (ch) { 116 case 'c': 117 commandflag = 1; 118 break; 119 case 'e': 120 editflag = 1; 121 break; 122 case 'f': 123 if ((fd = scsi_open(optarg, O_RDWR)) < 0) 124 err(1, "unable to open device %s", optarg); 125 fflag = 1; 126 break; 127 case 'd': 128 debuglevel = strtol(optarg, 0, 0); 129 debugflag = 1; 130 break; 131 case 'm': 132 modeflag = 1; 133 modepage = strtol(optarg, 0, 0); 134 break; 135 case 'P': 136 pagectl = strtol(optarg, 0, 0); 137 break; 138 case 's': 139 seconds = strtol(optarg, 0, 0); 140 break; 141 case 'v': 142 verbose = 1; 143 break; 144 case '?': 145 default: 146 usage(); 147 } 148 } 149 *argc_p = argc - optind; 150 *argv_p = argv + optind; 151 152 if (!fflag) usage(); 153 } 154 155 /* get_hook: Structure for evaluating args in a callback. 156 */ 157 struct get_hook 158 { 159 int argc; 160 char **argv; 161 int got; 162 }; 163 164 /* iget: Integer argument callback 165 */ 166 int 167 iget(void *hook, char *name) 168 { 169 struct get_hook *h = (struct get_hook *)hook; 170 int arg; 171 172 if (h->got >= h->argc) 173 { 174 fprintf(stderr, "Expecting an integer argument.\n"); 175 usage(); 176 } 177 arg = strtol(h->argv[h->got], 0, 0); 178 h->got++; 179 180 if (verbose && name && *name) 181 printf("%s: %d\n", name, arg); 182 183 return arg; 184 } 185 186 /* cget: char * argument callback 187 */ 188 char * 189 cget(void *hook, char *name) 190 { 191 struct get_hook *h = (struct get_hook *)hook; 192 char *arg; 193 194 if (h->got >= h->argc) 195 { 196 fprintf(stderr, "Expecting a character pointer argument.\n"); 197 usage(); 198 } 199 arg = h->argv[h->got]; 200 h->got++; 201 202 if (verbose && name) 203 printf("cget: %s: %s", name, arg); 204 205 return arg; 206 } 207 208 /* arg_put: "put argument" callback 209 */ 210 void arg_put(void *hook, int letter, void *arg, int count, char *name) 211 { 212 if (verbose && name && *name) 213 printf("%s: ", name); 214 215 switch(letter) 216 { 217 case 'i': 218 case 'b': 219 printf("%ld ", (long)arg); 220 break; 221 222 case 'c': 223 case 'z': 224 { 225 char *p = malloc(count + 1); 226 if (p == NULL) 227 err(1, NULL); 228 229 p[count] = 0; 230 strncpy(p, (char *)arg, count); 231 if (letter == 'z') 232 { 233 int i; 234 for (i = count - 1; i >= 0; i--) 235 if (p[i] == ' ') 236 p[i] = 0; 237 else 238 break; 239 } 240 printf("%s ", p); 241 free(p); 242 } 243 244 break; 245 246 default: 247 printf("Unknown format letter: '%c'\n", letter); 248 } 249 if (verbose) 250 putchar('\n'); 251 } 252 253 /* data_phase: SCSI bus data phase: DATA IN, DATA OUT, or no data transfer. 254 */ 255 enum data_phase {none = 0, in, out}; 256 257 /* do_cmd: Send a command to a SCSI device 258 */ 259 static void 260 do_cmd(int fd, char *fmt, int argc, char **argv) 261 { 262 struct get_hook h; 263 scsireq_t *scsireq = scsireq_new(); 264 enum data_phase data_phase; 265 int count, amount; 266 char *data_fmt, *bp; 267 268 h.argc = argc; 269 h.argv = argv; 270 h.got = 0; 271 272 scsireq_reset(scsireq); 273 274 scsireq_build_visit(scsireq, 0, 0, 0, fmt, iget, (void *)&h); 275 276 /* Three choices here: 277 * 1. We've used up all the args and have no data phase. 278 * 2. We have input data ("-i") 279 * 3. We have output data ("-o") 280 */ 281 282 if (h.got >= h.argc) 283 { 284 data_phase = none; 285 count = scsireq->datalen = 0; 286 } 287 else 288 { 289 char *flag = cget(&h, 0); 290 291 if (strcmp(flag, "-o") == 0) 292 { 293 data_phase = out; 294 scsireq->flags = SCCMD_WRITE; 295 } 296 else if (strcmp(flag, "-i") == 0) 297 { 298 data_phase = in; 299 scsireq->flags = SCCMD_READ; 300 } 301 else 302 { 303 fprintf(stderr, 304 "Need either \"-i\" or \"-o\" for data phase; not \"%s\".\n", flag); 305 usage(); 306 } 307 308 count = scsireq->datalen = iget(&h, 0); 309 if (count) { 310 data_fmt = cget(&h, 0); 311 312 scsireq->databuf = malloc(count); 313 if (scsireq->databuf == NULL) 314 err(1, NULL); 315 316 if (data_phase == out) { 317 if (strcmp(data_fmt, "-") == 0) { 318 bp = (char *)scsireq->databuf; 319 while (count > 0 && 320 (amount = read(STDIN_FILENO, 321 bp, count)) > 0) { 322 count -= amount; 323 bp += amount; 324 } 325 if (amount == -1) 326 err(1, "read"); 327 else if (amount == 0) { 328 /* early EOF */ 329 fprintf(stderr, 330 "Warning: only read %lu bytes out of %lu.\n", 331 scsireq->datalen - (u_long)count, 332 scsireq->datalen); 333 scsireq->datalen -= (u_long)count; 334 } 335 } 336 else 337 { 338 bzero(scsireq->databuf, count); 339 scsireq_encode_visit(scsireq, data_fmt, iget, (void *)&h); 340 } 341 } 342 } 343 } 344 345 346 scsireq->timeout = seconds * 1000; 347 348 if (scsireq_enter(fd, scsireq) == -1) 349 { 350 scsi_debug(stderr, -1, scsireq); 351 exit(1); 352 } 353 354 if (SCSIREQ_ERROR(scsireq)) 355 scsi_debug(stderr, 0, scsireq); 356 357 if (count && data_phase == in) 358 { 359 if (strcmp(data_fmt, "-") == 0) /* stdout */ 360 { 361 bp = (char *)scsireq->databuf; 362 while (count > 0 && (amount = write(STDOUT_FILENO, bp, count)) > 0) 363 { 364 count -= amount; 365 bp += amount; 366 } 367 if (amount < 0) 368 err(1, "write"); 369 else if (amount == 0) 370 fprintf(stderr, "Warning: wrote only %lu bytes out of %lu.\n", 371 scsireq->datalen - count, 372 scsireq->datalen); 373 374 } 375 else 376 { 377 scsireq_decode_visit(scsireq, data_fmt, arg_put, 0); 378 putchar('\n'); 379 } 380 } 381 } 382 383 void mode_sense(int fd, u_char *data, int len, int pc, int page) 384 { 385 scsireq_t *scsireq; 386 387 bzero(data, len); 388 389 scsireq = scsireq_new(); 390 391 if (scsireq_enter(fd, scsireq_build(scsireq, 392 len, data, SCCMD_READ, 393 "1A 0 v:2 {Page Control} v:6 {Page Code} 0 v:i1 {Allocation Length} 0", 394 pc, page, len)) == -1) /* Mode sense */ 395 { 396 scsi_debug(stderr, -1, scsireq); 397 exit(1); 398 } 399 400 if (SCSIREQ_ERROR(scsireq)) 401 { 402 scsi_debug(stderr, 0, scsireq); 403 exit(1); 404 } 405 406 free(scsireq); 407 } 408 409 void mode_select(int fd, u_char *data, int len, int perm) 410 { 411 scsireq_t *scsireq; 412 413 scsireq = scsireq_new(); 414 415 if (scsireq_enter(fd, scsireq_build(scsireq, 416 len, data, SCCMD_WRITE, 417 "15 0:7 v:1 {SP} 0 0 v:i1 {Allocation Length} 0", perm, len)) == -1) /* Mode select */ 418 { 419 scsi_debug(stderr, -1, scsireq); 420 exit(1); 421 } 422 423 if (SCSIREQ_ERROR(scsireq)) 424 { 425 scsi_debug(stderr, 0, scsireq); 426 exit(1); 427 } 428 429 free(scsireq); 430 } 431 432 433 #define START_ENTRY '{' 434 #define END_ENTRY '}' 435 436 static void 437 skipwhite(FILE *f) 438 { 439 int c; 440 441 skip_again: 442 443 while (isspace(c = getc(f))) 444 ; 445 446 if (c == '#') { 447 while ((c = getc(f)) != '\n' && c != EOF) 448 ; 449 goto skip_again; 450 } 451 452 ungetc(c, f); 453 } 454 455 /* mode_lookup: Lookup a format description for a given page. 456 */ 457 char *mode_db = "/usr/share/misc/scsi_modes"; 458 static char *mode_lookup(int page) 459 { 460 char *new_db; 461 FILE *modes; 462 int match, next, found, c; 463 static char fmt[1024]; /* XXX This should be with strealloc */ 464 int page_desc; 465 new_db = getenv("SCSI_MODES"); 466 467 if (new_db) 468 mode_db = new_db; 469 470 modes = fopen(mode_db, "r"); 471 if (modes == NULL) 472 return 0; 473 474 next = 0; 475 found = 0; 476 477 while (!found) { 478 479 skipwhite(modes); 480 481 if (fscanf(modes, "%i", &page_desc) != 1) 482 break; 483 484 if (page_desc == page) 485 found = 1; 486 487 skipwhite(modes); 488 if (getc(modes) != START_ENTRY) { 489 errx(1, "Expected %c", START_ENTRY); 490 } 491 492 match = 1; 493 while (match != 0) { 494 c = getc(modes); 495 if (c == EOF) 496 fprintf(stderr, "Expected %c.\n", END_ENTRY); 497 498 if (c == START_ENTRY) { 499 match++; 500 } 501 if (c == END_ENTRY) { 502 match--; 503 if (match == 0) 504 break; 505 } 506 if (found && c != '\n') { 507 if (next >= sizeof(fmt)) { 508 errx(1, "Stupid program: Buffer overflow.\n"); 509 } 510 511 fmt[next++] = (u_char)c; 512 } 513 } 514 } 515 fclose(modes); 516 fmt[next] = 0; 517 518 return (found) ? fmt : 0; 519 } 520 521 /* -------- edit: Mode Select Editor --------- 522 */ 523 struct editinfo 524 { 525 long can_edit; 526 long default_value; 527 } editinfo[64]; /* XXX Bogus fixed size */ 528 529 static int editind; 530 volatile int edit_opened; 531 static FILE *edit_file; 532 static char edit_name[L_tmpnam]; 533 534 static void 535 edit_rewind(void) 536 { 537 editind = 0; 538 } 539 540 static void 541 edit_done(void) 542 { 543 int opened; 544 545 sigset_t all, prev; 546 sigfillset(&all); 547 548 (void)sigprocmask(SIG_SETMASK, &all, &prev); 549 550 opened = (int)edit_opened; 551 edit_opened = 0; 552 553 (void)sigprocmask(SIG_SETMASK, &prev, 0); 554 555 if (opened) 556 { 557 if (fclose(edit_file)) 558 perror(edit_name); 559 if (unlink(edit_name)) 560 perror(edit_name); 561 } 562 } 563 564 static void 565 edit_init(void) 566 { 567 int fd; 568 569 edit_rewind(); 570 strlcpy(edit_name, "/var/tmp/scXXXXXXXX", sizeof edit_name); 571 if ((fd = mkstemp(edit_name)) == -1) 572 err(1, "mkstemp"); 573 if ( (edit_file = fdopen(fd, "w+")) == 0) 574 err(1, "fdopen"); 575 edit_opened = 1; 576 577 atexit(edit_done); 578 } 579 580 static void 581 edit_check(void *hook, int letter, void *arg, int count, char *name) 582 { 583 if (letter != 'i' && letter != 'b') { 584 errx(1, "Can't edit format %c.\n", letter); 585 } 586 587 if (editind >= sizeof(editinfo) / sizeof(editinfo[0])) { 588 errx(1, "edit table overflow"); 589 } 590 editinfo[editind].can_edit = ((long)arg != 0); 591 editind++; 592 } 593 594 static void 595 edit_defaults(void *hook, int letter, void *arg, int count, char *name) 596 { 597 if (letter != 'i' && letter != 'b') { 598 errx(1, "Can't edit format %c.\n", letter); 599 } 600 601 editinfo[editind].default_value = ((long)arg); 602 editind++; 603 } 604 605 static void 606 edit_report(void *hook, int letter, void *arg, int count, char *name) 607 { 608 if (editinfo[editind].can_edit) { 609 if (letter != 'i' && letter != 'b') { 610 errx(1, "Can't report format %c.\n", letter); 611 } 612 613 fprintf(edit_file, "%s: %ld\n", name, (long)arg); 614 } 615 616 editind++; 617 } 618 619 static int 620 edit_get(void *hook, char *name) 621 { 622 int arg = editinfo[editind].default_value; 623 624 if (editinfo[editind].can_edit) { 625 char line[80]; 626 size_t len; 627 if (fgets(line, sizeof(line), edit_file) == NULL) 628 err(1, "fgets"); 629 630 len = strlen(line); 631 if (len && line[len - 1] == '\n') 632 line[len - 1] = '\0'; 633 634 if (strncmp(name, line, strlen(name)) != 0) { 635 errx(1, "Expected \"%s\" and read \"%s\"\n", 636 name, line); 637 } 638 639 arg = strtoul(line + strlen(name) + 2, 0, 0); 640 } 641 642 editind++; 643 return arg; 644 } 645 646 int 647 editit(const char *pathname) 648 { 649 char *argp[] = {"sh", "-c", NULL, NULL}, *ed, *p; 650 sig_t sighup, sigint, sigquit; 651 pid_t pid; 652 int st; 653 654 ed = getenv("VISUAL"); 655 if (ed == NULL || ed[0] == '\0') 656 ed = getenv("EDITOR"); 657 if (ed == NULL || ed[0] == '\0') 658 ed = _PATH_VI; 659 if (asprintf(&p, "%s %s", ed, pathname) == -1) 660 return (-1); 661 argp[2] = p; 662 663 top: 664 sighup = signal(SIGHUP, SIG_IGN); 665 sigint = signal(SIGINT, SIG_IGN); 666 sigquit = signal(SIGQUIT, SIG_IGN); 667 if ((pid = fork()) == -1) { 668 int saved_errno = errno; 669 670 (void)signal(SIGHUP, sighup); 671 (void)signal(SIGINT, sigint); 672 (void)signal(SIGQUIT, sigquit); 673 if (saved_errno == EAGAIN) { 674 sleep(1); 675 goto top; 676 } 677 free(p); 678 errno = saved_errno; 679 return (-1); 680 } 681 if (pid == 0) { 682 execv(_PATH_BSHELL, argp); 683 _exit(127); 684 } 685 free(p); 686 for (;;) { 687 if (waitpid(pid, &st, 0) == -1) { 688 if (errno != EINTR) 689 return (-1); 690 } else 691 break; 692 } 693 (void)signal(SIGHUP, sighup); 694 (void)signal(SIGINT, sigint); 695 (void)signal(SIGQUIT, sigquit); 696 if (!WIFEXITED(st) || WEXITSTATUS(st) != 0) { 697 errno = ECHILD; 698 return (-1); 699 } 700 return (0); 701 } 702 703 static void 704 mode_edit(int fd, int page, int edit, int argc, char *argv[]) 705 { 706 int i; 707 u_char data[255]; 708 u_char *mode_pars; 709 struct mode_header 710 { 711 u_char mdl; /* Mode data length */ 712 u_char medium_type; 713 u_char dev_spec_par; 714 u_char bdl; /* Block descriptor length */ 715 }; 716 717 struct mode_page_header 718 { 719 u_char page_code; 720 u_char page_length; 721 }; 722 723 struct mode_header *mh; 724 struct mode_page_header *mph; 725 726 char *fmt = mode_lookup(page); 727 if (!fmt && verbose) { 728 fprintf(stderr, 729 "No mode data base entry in \"%s\" for page %d; binary %s only.\n", 730 mode_db, page, (edit ? "edit" : "display")); 731 } 732 733 if (edit) { 734 if (!fmt) { 735 errx(1, "Sorry: can't edit without a format.\n"); 736 } 737 738 if (pagectl != 0 && pagectl != 3) { 739 errx(1, 740 "It only makes sense to edit page 0 (current) or page 3 (saved values)\n"); 741 } 742 743 verbose = 1; 744 745 mode_sense(fd, data, sizeof(data), 1, page); 746 747 mh = (struct mode_header *)data; 748 mph = (struct mode_page_header *) 749 (((char *)mh) + sizeof(*mh) + mh->bdl); 750 751 mode_pars = (char *)mph + sizeof(*mph); 752 753 edit_init(); 754 scsireq_buff_decode_visit(mode_pars, mh->mdl, 755 fmt, edit_check, 0); 756 757 mode_sense(fd, data, sizeof(data), 0, page); 758 759 edit_rewind(); 760 scsireq_buff_decode_visit(mode_pars, mh->mdl, 761 fmt, edit_defaults, 0); 762 763 edit_rewind(); 764 scsireq_buff_decode_visit(mode_pars, mh->mdl, 765 fmt, edit_report, 0); 766 767 fclose(edit_file); 768 if (editit(edit_name) == -1 && errno != ECHILD) 769 err(1, "edit %s", edit_name); 770 if ((edit_file = fopen(edit_name, "r")) == NULL) 771 err(1, "open %s", edit_name); 772 773 edit_rewind(); 774 scsireq_buff_encode_visit(mode_pars, mh->mdl, 775 fmt, edit_get, 0); 776 777 /* Eliminate block descriptors: 778 */ 779 bcopy((char *)mph, ((char *)mh) + sizeof(*mh), 780 sizeof(*mph) + mph->page_length); 781 782 mh->bdl = 0; 783 mph = (struct mode_page_header *) (((char *)mh) + sizeof(*mh)); 784 mode_pars = ((char *)mph) + 2; 785 786 #if 0 787 /* Turn this on to see what you're sending to the 788 * device: 789 */ 790 edit_rewind(); 791 scsireq_buff_decode_visit(mode_pars, 792 mh->mdl, fmt, arg_put, 0); 793 #endif 794 795 edit_done(); 796 797 /* Make it permanent if pageselect is three. 798 */ 799 800 mph->page_code &= ~0xC0; /* Clear PS and RESERVED */ 801 mh->mdl = 0; /* Reserved for mode select */ 802 803 mode_select(fd, (char *)mh, 804 sizeof(*mh) + mh->bdl + sizeof(*mph) + mph->page_length, 805 (pagectl == 3)); 806 807 exit(0); 808 } 809 810 mode_sense(fd, data, sizeof(data), pagectl, page); 811 812 /* Skip over the block descriptors. 813 */ 814 mh = (struct mode_header *)data; 815 mph = (struct mode_page_header *)(((char *)mh) + sizeof(*mh) + mh->bdl); 816 mode_pars = (char *)mph + sizeof(*mph); 817 818 if (!fmt) { 819 for (i = 0; i < mh->mdl; i++) { 820 printf("%02x%c",mode_pars[i], 821 (((i + 1) % 8) == 0) ? '\n' : ' '); 822 } 823 putc('\n', stdout); 824 } else { 825 verbose = 1; 826 scsireq_buff_decode_visit(mode_pars, 827 mh->mdl, fmt, arg_put, 0); 828 } 829 } 830 831 int 832 main(int argc, char **argv) 833 { 834 procargs(&argc,&argv); 835 836 /* XXX This has grown to the point that it should be cleaned up. 837 */ 838 if (debugflag) { 839 if (ioctl(fd,SCIOCDEBUG,&debuglevel) == -1) 840 err(1, "SCIOCDEBUG"); 841 } else if (commandflag) { 842 char *fmt; 843 844 if (argc < 1) { 845 fprintf(stderr, "Need the command format string.\n"); 846 usage(); 847 } 848 849 850 fmt = argv[0]; 851 852 argc -= 1; 853 argv += 1; 854 855 do_cmd(fd, fmt, argc, argv); 856 } else if (modeflag) 857 mode_edit(fd, modepage, editflag, argc, argv); 858 859 exit(0); 860 } 861