1 /* $NetBSD: interact.c,v 1.39 2016/01/31 18:57:29 christos Exp $ */ 2 3 /* 4 * Copyright (c) 1997 Christos Zoulas. All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions 8 * are met: 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 2. Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * 15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 16 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 17 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 18 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 19 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 20 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 21 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 22 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 24 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 */ 26 27 #if !defined(NO_INTERACT) 28 29 #if HAVE_NBTOOL_CONFIG_H 30 #include "nbtool_config.h" 31 #endif 32 33 #include <sys/cdefs.h> 34 #ifndef lint 35 __RCSID("$NetBSD: interact.c,v 1.39 2016/01/31 18:57:29 christos Exp $"); 36 #endif /* lint */ 37 38 #include <sys/param.h> 39 #define FSTYPENAMES 40 #define DKTYPENAMES 41 42 #include <err.h> 43 #include <stdio.h> 44 #include <string.h> 45 #include <stdarg.h> 46 #include <stdlib.h> 47 #include <sys/ioctl.h> 48 49 #if HAVE_NBTOOL_CONFIG_H 50 #define getmaxpartitions() maxpartitions 51 #include <nbinclude/sys/disklabel.h> 52 #else 53 #include <util.h> 54 #include <sys/disklabel.h> 55 #endif /* HAVE_NBTOOL_CONFIG_H */ 56 57 #include "extern.h" 58 59 static void cmd_help(struct disklabel *, char *, int); 60 static void cmd_adjust(struct disklabel *, char *, int); 61 static void cmd_chain(struct disklabel *, char *, int); 62 static void cmd_print(struct disklabel *, char *, int); 63 static void cmd_printall(struct disklabel *, char *, int); 64 static void cmd_info(struct disklabel *, char *, int); 65 static void cmd_part(struct disklabel *, char *, int); 66 static void cmd_label(struct disklabel *, char *, int); 67 static void cmd_round(struct disklabel *, char *, int); 68 static void cmd_name(struct disklabel *, char *, int); 69 static void cmd_listfstypes(struct disklabel *, char *, int); 70 static int runcmd(struct disklabel *, char *, int); 71 static int getinput(char *, const char *, ...) __printflike(2, 3); 72 static int alphacmp(const void *, const void *); 73 static void defnum(struct disklabel *, char *, uint32_t); 74 static void dumpnames(const char *, const char * const *, size_t); 75 static intmax_t getnum(struct disklabel *, char *, intmax_t); 76 77 static int rounding = 0; /* sector rounding */ 78 static int chaining = 0; /* make partitions contiguous */ 79 80 static struct cmds { 81 const char *name; 82 void (*func)(struct disklabel *, char *, int); 83 const char *help; 84 } cmds[] = { 85 { "?", cmd_help, "print this menu" }, 86 { "A", cmd_adjust, "adjust the label size to the max disk size" }, 87 { "C", cmd_chain, "make partitions contiguous" }, 88 { "E", cmd_printall, "print disk label and current partition table"}, 89 { "I", cmd_info, "change label information" }, 90 { "L", cmd_listfstypes,"list all known file system types" }, 91 { "N", cmd_name, "name the label" }, 92 { "P", cmd_print, "print current partition table" }, 93 { "Q", NULL, "quit" }, 94 { "R", cmd_round, "rounding (c)ylinders (s)ectors" }, 95 { "W", cmd_label, "write the current partition table" }, 96 { NULL, NULL, NULL } 97 }; 98 99 100 static void 101 cmd_help(struct disklabel *lp, char *s, int fd) 102 { 103 struct cmds *cmd; 104 105 for (cmd = cmds; cmd->name != NULL; cmd++) 106 printf("%s\t%s\n", cmd->name, cmd->help); 107 printf("[a-%c]\tdefine named partition\n", 108 'a' + getmaxpartitions() - 1); 109 } 110 111 112 static void 113 cmd_adjust(struct disklabel *lp, char *s, int fd) 114 { 115 struct disklabel dl; 116 117 if (dk_ioctl(fd, DIOCGDEFLABEL, &dl) == -1) { 118 warn("Cannot get default label"); 119 return; 120 } 121 122 if (dl.d_secperunit != lp->d_secperunit) { 123 char line[BUFSIZ]; 124 int i = getinput(line, "Adjust disklabel sector from %" PRIu32 125 " to %" PRIu32 " [n]? ", lp->d_secperunit, dl.d_secperunit); 126 if (i <= 0) 127 return; 128 if (line[0] != 'Y' && line[0] != 'y') 129 return; 130 lp->d_secperunit = dl.d_secperunit; 131 return; 132 } 133 134 printf("Already at %" PRIu32 " sectors\n", dl.d_secperunit); 135 return; 136 } 137 138 static void 139 cmd_chain(struct disklabel *lp, char *s, int fd) 140 { 141 int i; 142 char line[BUFSIZ]; 143 144 i = getinput(line, "Automatically adjust partitions [%s]? ", 145 chaining ? "yes" : "no"); 146 if (i <= 0) 147 return; 148 149 switch (line[0]) { 150 case 'y': 151 chaining = 1; 152 return; 153 case 'n': 154 chaining = 0; 155 return; 156 default: 157 printf("Invalid answer\n"); 158 return; 159 } 160 } 161 162 163 static void 164 cmd_printall(struct disklabel *lp, char *s, int fd) 165 { 166 167 showinfo(stdout, lp, specname); 168 showpartitions(stdout, lp, Cflag); 169 } 170 171 172 static void 173 cmd_print(struct disklabel *lp, char *s, int fd) 174 { 175 176 showpartitions(stdout, lp, Cflag); 177 } 178 179 180 static void 181 cmd_info(struct disklabel *lp, char *s, int fd) 182 { 183 char line[BUFSIZ]; 184 int v, i; 185 u_int32_t u; 186 187 printf("# Current values:\n"); 188 showinfo(stdout, lp, specname); 189 190 /* d_type */ 191 for (;;) { 192 i = lp->d_type; 193 if (i < 0 || i >= DKMAXTYPES) 194 i = 0; 195 i = getinput(line, "Disk type [%s]: ", dktypenames[i]); 196 if (i == -1) 197 return; 198 else if (i == 0) 199 break; 200 if (!strcmp(line, "?")) { 201 dumpnames("Supported disk types", dktypenames, 202 DKMAXTYPES); 203 continue; 204 } 205 for (i = 0; i < DKMAXTYPES; i++) { 206 if (!strcasecmp(dktypenames[i], line)) { 207 lp->d_type = i; 208 goto done_typename; 209 } 210 } 211 v = atoi(line); 212 if ((unsigned)v >= DKMAXTYPES) { 213 warnx("Unknown disk type: %s", line); 214 continue; 215 } 216 lp->d_type = v; 217 done_typename: 218 break; 219 } 220 221 /* d_typename */ 222 i = getinput(line, "Disk name [%.*s]: ", 223 (int) sizeof(lp->d_typename), lp->d_typename); 224 if (i == -1) 225 return; 226 else if (i == 1) 227 (void) strncpy(lp->d_typename, line, sizeof(lp->d_typename)); 228 229 /* d_packname */ 230 cmd_name(lp, s, fd); 231 232 /* d_npartitions */ 233 for (;;) { 234 i = getinput(line, "Number of partitions [%" PRIu16 "]: ", 235 lp->d_npartitions); 236 if (i == -1) 237 return; 238 else if (i == 0) 239 break; 240 if (sscanf(line, "%" SCNu32, &u) != 1) { 241 printf("Invalid number of partitions `%s'\n", line); 242 continue; 243 } 244 lp->d_npartitions = u; 245 break; 246 } 247 248 /* d_secsize */ 249 for (;;) { 250 i = getinput(line, "Sector size (bytes) [%" PRIu32 "]: ", 251 lp->d_secsize); 252 if (i == -1) 253 return; 254 else if (i == 0) 255 break; 256 if (sscanf(line, "%" SCNu32, &u) != 1) { 257 printf("Invalid sector size `%s'\n", line); 258 continue; 259 } 260 lp->d_secsize = u; 261 break; 262 } 263 264 /* d_nsectors */ 265 for (;;) { 266 i = getinput(line, "Number of sectors per track [%" PRIu32 267 "]: ", lp->d_nsectors); 268 if (i == -1) 269 return; 270 else if (i == 0) 271 break; 272 if (sscanf(line, "%" SCNu32, &u) != 1) { 273 printf("Invalid number of sectors `%s'\n", line); 274 continue; 275 } 276 lp->d_nsectors = u; 277 break; 278 } 279 280 /* d_ntracks */ 281 for (;;) { 282 i = getinput(line, "Number of tracks per cylinder [%" PRIu32 283 "]: ", lp->d_ntracks); 284 if (i == -1) 285 return; 286 else if (i == 0) 287 break; 288 if (sscanf(line, "%" SCNu32, &u) != 1) { 289 printf("Invalid number of tracks `%s'\n", line); 290 continue; 291 } 292 lp->d_ntracks = u; 293 break; 294 } 295 296 /* d_secpercyl */ 297 for (;;) { 298 i = getinput(line, "Number of sectors/cylinder [%" PRIu32 "]: ", 299 lp->d_secpercyl); 300 if (i == -1) 301 return; 302 else if (i == 0) 303 break; 304 if (sscanf(line, "%" SCNu32, &u) != 1) { 305 printf("Invalid number of sector/cylinder `%s'\n", 306 line); 307 continue; 308 } 309 lp->d_secpercyl = u; 310 break; 311 } 312 313 /* d_ncylinders */ 314 for (;;) { 315 i = getinput(line, "Total number of cylinders [%" PRIu32 "]: ", 316 lp->d_ncylinders); 317 if (i == -1) 318 return; 319 else if (i == 0) 320 break; 321 if (sscanf(line, "%" SCNu32, &u) != 1) { 322 printf("Invalid sector size `%s'\n", line); 323 continue; 324 } 325 lp->d_ncylinders = u; 326 break; 327 } 328 329 /* d_secperunit */ 330 for (;;) { 331 i = getinput(line, "Total number of sectors [%" PRIu32 "]: ", 332 lp->d_secperunit); 333 if (i == -1) 334 return; 335 else if (i == 0) 336 break; 337 if (sscanf(line, "%" SCNu32, &u) != 1) { 338 printf("Invalid number of sectors `%s'\n", line); 339 continue; 340 } 341 lp->d_secperunit = u; 342 break; 343 } 344 345 /* d_rpm */ 346 347 /* d_interleave */ 348 for (;;) { 349 i = getinput(line, "Hardware sectors interleave [%" PRIu16 350 "]: ", lp->d_interleave); 351 if (i == -1) 352 return; 353 else if (i == 0) 354 break; 355 if (sscanf(line, "%" SCNu32, &u) != 1) { 356 printf("Invalid sector interleave `%s'\n", line); 357 continue; 358 } 359 lp->d_interleave = u; 360 break; 361 } 362 363 /* d_trackskew */ 364 for (;;) { 365 i = getinput(line, "Sector 0 skew, per track [%" PRIu16 "]: ", 366 lp->d_trackskew); 367 if (i == -1) 368 return; 369 else if (i == 0) 370 break; 371 if (sscanf(line, "%" SCNu32, &u) != 1) { 372 printf("Invalid track sector skew `%s'\n", line); 373 continue; 374 } 375 lp->d_trackskew = u; 376 break; 377 } 378 379 /* d_cylskew */ 380 for (;;) { 381 i = getinput(line, "Sector 0 skew, per cylinder [%" PRIu16 382 "]: ", lp->d_cylskew); 383 if (i == -1) 384 return; 385 else if (i == 0) 386 break; 387 if (sscanf(line, "%" SCNu32, &u) != 1) { 388 printf("Invalid cylinder sector `%s'\n", line); 389 continue; 390 } 391 lp->d_cylskew = u; 392 break; 393 } 394 395 /* d_headswitch */ 396 for (;;) { 397 i = getinput(line, "Head switch time (usec) [%" PRIu32 "]: ", 398 lp->d_headswitch); 399 if (i == -1) 400 return; 401 else if (i == 0) 402 break; 403 if (sscanf(line, "%" SCNu32, &u) != 1) { 404 printf("Invalid head switch time `%s'\n", line); 405 continue; 406 } 407 lp->d_headswitch = u; 408 break; 409 } 410 411 /* d_trkseek */ 412 for (;;) { 413 i = getinput(line, "Track seek time (usec) [%" PRIu32 "]:", 414 lp->d_trkseek); 415 if (i == -1) 416 return; 417 else if (i == 0) 418 break; 419 if (sscanf(line, "%" SCNu32, &u) != 1) { 420 printf("Invalid track seek time `%s'\n", line); 421 continue; 422 } 423 lp->d_trkseek = u; 424 break; 425 } 426 } 427 428 429 static void 430 cmd_name(struct disklabel *lp, char *s, int fd) 431 { 432 char line[BUFSIZ]; 433 int i; 434 435 i = getinput(line, "Label name [%.*s]: ", 436 (int) sizeof(lp->d_packname), lp->d_packname); 437 if (i <= 0) 438 return; 439 (void) strncpy(lp->d_packname, line, sizeof(lp->d_packname)); 440 } 441 442 443 static void 444 cmd_round(struct disklabel *lp, char *s, int fd) 445 { 446 int i; 447 char line[BUFSIZ]; 448 449 i = getinput(line, "Rounding [%s]: ", rounding ? "cylinders" : 450 "sectors"); 451 if (i <= 0) 452 return; 453 454 switch (line[0]) { 455 case 'c': 456 case 'C': 457 rounding = 1; 458 return; 459 case 's': 460 case 'S': 461 rounding = 0; 462 return; 463 default: 464 printf("Rounding can be (c)ylinders or (s)ectors\n"); 465 return; 466 } 467 } 468 469 470 static void 471 cmd_part(struct disklabel *lp, char *s, int fd) 472 { 473 int i; 474 intmax_t im; 475 char line[BUFSIZ]; 476 char def[BUFSIZ]; 477 int part; 478 struct partition *p, ps; 479 480 part = s[0] - 'a'; 481 p = &lp->d_partitions[part]; 482 if (part >= lp->d_npartitions) 483 lp->d_npartitions = part + 1; 484 485 (void)memcpy(&ps, p, sizeof(ps)); 486 487 for (;;) { 488 i = p->p_fstype; 489 if (i < 0 || i >= FSMAXTYPES) 490 i = 0; 491 i = getinput(line, "Filesystem type [%s]: ", fstypenames[i]); 492 if (i == -1) 493 return; 494 else if (i == 0) 495 break; 496 if (!strcmp(line, "?")) { 497 dumpnames("Supported file system types", 498 fstypenames, FSMAXTYPES); 499 continue; 500 } 501 for (i = 0; i < FSMAXTYPES; i++) 502 if (!strcasecmp(line, fstypenames[i])) { 503 p->p_fstype = i; 504 goto done_typename; 505 } 506 printf("Invalid file system typename `%s'\n", line); 507 continue; 508 done_typename: 509 break; 510 } 511 for (;;) { 512 defnum(lp, def, p->p_offset); 513 i = getinput(line, "Start offset ('x' to start after partition" 514 " 'x') [%s]: ", def); 515 if (i == -1) 516 return; 517 else if (i == 0) 518 break; 519 if (line[1] == '\0' && 520 line[0] >= 'a' && line[0] < 'a' + getmaxpartitions()) { 521 struct partition *cp = lp->d_partitions; 522 523 if ((cp[line[0] - 'a'].p_offset + 524 cp[line[0] - 'a'].p_size) >= lp->d_secperunit) { 525 printf("Bad offset `%s'\n", line); 526 continue; 527 } else { 528 p->p_offset = cp[line[0] - 'a'].p_offset + 529 cp[line[0] - 'a'].p_size; 530 } 531 } else { 532 if ((im = getnum(lp, line, 0)) == -1 || im < 0) { 533 printf("Bad offset `%s'\n", line); 534 continue; 535 } else if (im > 0xffffffffLL || 536 (uint32_t)im > lp->d_secperunit) { 537 printf("Offset `%s' out of range\n", line); 538 continue; 539 } 540 p->p_offset = (uint32_t)im; 541 } 542 break; 543 } 544 for (;;) { 545 defnum(lp, def, p->p_size); 546 i = getinput(line, "Partition size ('$' for all remaining) " 547 "[%s]: ", def); 548 if (i == -1) 549 return; 550 else if (i == 0) 551 break; 552 if ((im = getnum(lp, line, lp->d_secperunit - p->p_offset)) 553 == -1) { 554 printf("Bad size `%s'\n", line); 555 continue; 556 } else if (im > 0xffffffffLL || 557 (im + p->p_offset) > lp->d_secperunit) { 558 printf("Size `%s' out of range\n", line); 559 continue; 560 } 561 p->p_size = im; 562 break; 563 } 564 565 if (memcmp(&ps, p, sizeof(ps))) 566 showpartition(stdout, lp, part, Cflag); 567 if (chaining) { 568 int offs = -1; 569 struct partition *cp = lp->d_partitions; 570 for (i = 0; i < lp->d_npartitions; i++) { 571 if (cp[i].p_fstype != FS_UNUSED) { 572 if (offs != -1 && cp[i].p_offset != (uint32_t)offs) { 573 cp[i].p_offset = offs; 574 showpartition(stdout, lp, i, Cflag); 575 } 576 offs = cp[i].p_offset + cp[i].p_size; 577 } 578 } 579 } 580 } 581 582 583 static void 584 cmd_label(struct disklabel *lp, char *s, int fd) 585 { 586 char line[BUFSIZ]; 587 int i; 588 589 i = getinput(line, "Label disk [n]?"); 590 if (i <= 0 || (*line != 'y' && *line != 'Y') ) 591 return; 592 593 if (checklabel(lp) != 0) { 594 printf("Label not written\n"); 595 return; 596 } 597 598 if (writelabel(fd, lp) != 0) { 599 printf("Label not written\n"); 600 return; 601 } 602 printf("Label written\n"); 603 } 604 605 606 static void 607 cmd_listfstypes(struct disklabel *lp, char *s, int fd) 608 { 609 610 (void)list_fs_types(); 611 } 612 613 614 static int 615 runcmd(struct disklabel *lp, char *line, int fd) 616 { 617 struct cmds *cmd; 618 619 for (cmd = cmds; cmd->name != NULL; cmd++) 620 if (strncmp(line, cmd->name, strlen(cmd->name)) == 0) { 621 if (cmd->func == NULL) 622 return -1; 623 (*cmd->func)(lp, line, fd); 624 return 0; 625 } 626 627 if (line[1] == '\0' && 628 line[0] >= 'a' && line[0] < 'a' + getmaxpartitions()) { 629 cmd_part(lp, line, fd); 630 return 0; 631 } 632 633 printf("Unknown command %s\n", line); 634 return 1; 635 } 636 637 638 static int 639 getinput(char *line, const char *prompt, ...) 640 { 641 642 for (;;) { 643 va_list ap; 644 va_start(ap, prompt); 645 vprintf(prompt, ap); 646 va_end(ap); 647 648 if (fgets(line, BUFSIZ, stdin) == NULL) 649 return -1; 650 if (line[0] == '\n' || line[0] == '\0') 651 return 0; 652 else { 653 char *p; 654 655 if ((p = strrchr(line, '\n')) != NULL) 656 *p = '\0'; 657 return 1; 658 } 659 } 660 } 661 662 static int 663 alphacmp(const void *a, const void *b) 664 { 665 666 return (strcasecmp(*(const char * const*)a, *(const char * const*)b)); 667 } 668 669 670 static void 671 dumpnames(const char *prompt, const char * const *olist, size_t numentries) 672 { 673 int w; 674 size_t i, entry, lines; 675 int columns, width; 676 const char *p; 677 const char **list; 678 679 if ((list = (const char **)malloc(sizeof(char *) * numentries)) == NULL) 680 err(1, "malloc"); 681 width = 0; 682 printf("%s:\n", prompt); 683 for (i = 0; i < numentries; i++) { 684 list[i] = olist[i]; 685 w = strlen(list[i]); 686 if (w > width) 687 width = w; 688 } 689 #if 0 690 for (i = 0; i < numentries; i++) 691 printf("%s%s", i == 0 ? "" : ", ", list[i]); 692 puts(""); 693 #endif 694 (void)qsort(list, numentries, sizeof(char *), alphacmp); 695 width++; /* want two spaces between items */ 696 width = (width + 8) &~ 7; 697 698 #define ttywidth 72 699 columns = ttywidth / width; 700 #undef ttywidth 701 if (columns == 0) 702 columns = 1; 703 lines = (numentries + columns - 1) / columns; 704 /* Output sorted by columns */ 705 for (i = 0; i < lines; i++) { 706 putc('\t', stdout); 707 entry = i; 708 for (;;) { 709 p = list[entry]; 710 fputs(p, stdout); 711 entry += lines; 712 if (entry >= numentries) 713 break; 714 w = strlen(p); 715 while (w < width) { 716 w = (w + 8) & ~7; 717 putc('\t', stdout); 718 } 719 } 720 putc('\n', stdout); 721 } 722 free(list); 723 } 724 725 726 static void 727 defnum(struct disklabel *lp, char *buf, uint32_t size) 728 { 729 730 (void) snprintf(buf, BUFSIZ, "%.40gc, %" PRIu32 "s, %.40gM", 731 size / (float) lp->d_secpercyl, 732 size, size * (lp->d_secsize / (float) (1024 * 1024))); 733 } 734 735 736 static intmax_t 737 getnum(struct disklabel *lp, char *buf, intmax_t defaultval) 738 { 739 char *ep; 740 double d; 741 intmax_t rv; 742 743 if (defaultval && buf[0] == '$' && buf[1] == 0) 744 return defaultval; 745 746 d = strtod(buf, &ep); 747 if (buf == ep) 748 return -1; 749 750 #define ROUND(a) ((((a) / lp->d_secpercyl) + \ 751 (((a) % lp->d_secpercyl) ? 1 : 0)) * lp->d_secpercyl) 752 753 switch (*ep) { 754 case '\0': 755 case 's': 756 case 'S': 757 rv = (intmax_t) d; 758 break; 759 760 case 'c': 761 case 'C': 762 rv = (intmax_t) (d * lp->d_secpercyl); 763 break; 764 765 case 'k': 766 case 'K': 767 rv = (intmax_t) (d * 1024 / lp->d_secsize); 768 break; 769 770 case 'm': 771 case 'M': 772 rv = (intmax_t) (d * 1024 * 1024 / lp->d_secsize); 773 break; 774 775 case 'g': 776 case 'G': 777 rv = (intmax_t) (d * 1024 * 1024 * 1024 / lp->d_secsize); 778 break; 779 780 case 't': 781 case 'T': 782 rv = (intmax_t) (d * 1024 * 1024 * 1024 * 1024 / lp->d_secsize); 783 break; 784 785 default: 786 printf("Unit error %c\n", *ep); 787 printf("Valid units: (S)ectors, (C)ylinders, (K)ilo, (M)ega, " 788 "(G)iga, (T)era"); 789 return -1; 790 } 791 792 if (rounding) 793 return ROUND(rv); 794 else 795 return rv; 796 } 797 798 799 void 800 interact(struct disklabel *lp, int fd) 801 { 802 char line[BUFSIZ]; 803 804 puts("Enter '?' for help"); 805 for (;;) { 806 int i = getinput(line, "partition>"); 807 if (i == -1) 808 return; 809 if (i == 0) 810 continue; 811 if (runcmd(lp, line, fd) == -1) 812 return; 813 } 814 } 815 816 #endif /* !NO_INTERACT */ 817