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