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