1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause 3 * 4 * units.c Copyright (c) 1993 by Adrian Mariano (adrian@cam.cornell.edu) 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. The name of the author may not be used to endorse or promote products 12 * derived from this software without specific prior written permission. 13 * Disclaimer: This software is provided by the author "as is". The author 14 * shall not be liable for any damages caused in any way by this software. 15 * 16 * I would appreciate (though I do not require) receiving a copy of any 17 * improvements you might make to this program. 18 */ 19 20 #include <ctype.h> 21 #include <err.h> 22 #include <errno.h> 23 #include <histedit.h> 24 #include <getopt.h> 25 #include <stdbool.h> 26 #include <stdio.h> 27 #include <stdlib.h> 28 #include <string.h> 29 #include <unistd.h> 30 31 #include <capsicum_helpers.h> 32 33 #ifndef UNITSFILE 34 #define UNITSFILE "/usr/share/misc/definitions.units" 35 #endif 36 37 #define MAXUNITS 1000 38 #define MAXPREFIXES 100 39 40 #define MAXSUBUNITS 500 41 42 #define PRIMITIVECHAR '!' 43 44 static const char *powerstring = "^"; 45 static const char *numfmt = "%.8g"; 46 47 static struct { 48 char *uname; 49 char *uval; 50 } unittable[MAXUNITS]; 51 52 struct unittype { 53 char *numerator[MAXSUBUNITS]; 54 char *denominator[MAXSUBUNITS]; 55 double factor; 56 double offset; 57 int quantity; 58 }; 59 60 static struct { 61 char *prefixname; 62 char *prefixval; 63 } prefixtable[MAXPREFIXES]; 64 65 66 static char NULLUNIT[] = ""; 67 68 #define SEPARATOR ":" 69 70 static int unitcount; 71 static int prefixcount; 72 static bool verbose = false; 73 static bool terse = false; 74 static const char * outputformat; 75 static const char * havestr; 76 static const char * wantstr; 77 78 static int addsubunit(char *product[], char *toadd); 79 static int addunit(struct unittype *theunit, const char *toadd, int flip, int quantity); 80 static void cancelunit(struct unittype * theunit); 81 static int compare(const void *item1, const void *item2); 82 static int compareproducts(char **one, char **two); 83 static int compareunits(struct unittype * first, struct unittype * second); 84 static int completereduce(struct unittype * unit); 85 static char *dupstr(const char *str); 86 static void initializeunit(struct unittype * theunit); 87 static char *lookupunit(const char *unit); 88 static void readunits(const char *userfile); 89 static int reduceproduct(struct unittype * theunit, int flip); 90 static int reduceunit(struct unittype * theunit); 91 static void showanswer(struct unittype * have, struct unittype * want); 92 static void showunit(struct unittype * theunit); 93 static void sortunit(struct unittype * theunit); 94 static void usage(void); 95 static void zeroerror(void); 96 97 static const char* promptstr = ""; 98 99 static const char * prompt(EditLine *e __unused) { 100 return promptstr; 101 } 102 103 static char * 104 dupstr(const char *str) 105 { 106 char *ret; 107 108 ret = strdup(str); 109 if (!ret) 110 err(3, "dupstr"); 111 return (ret); 112 } 113 114 115 static void 116 readunits(const char *userfile) 117 { 118 FILE *unitfile; 119 char line[512], *lineptr; 120 int len, linenum, i; 121 cap_rights_t unitfilerights; 122 123 unitcount = 0; 124 linenum = 0; 125 126 if (userfile) { 127 unitfile = fopen(userfile, "r"); 128 if (!unitfile) 129 errx(1, "unable to open units file '%s'", userfile); 130 } 131 else { 132 unitfile = fopen(UNITSFILE, "r"); 133 if (!unitfile) { 134 char *direc, *env; 135 char filename[1000]; 136 137 env = getenv("PATH"); 138 if (env) { 139 direc = strtok(env, SEPARATOR); 140 while (direc) { 141 snprintf(filename, sizeof(filename), 142 "%s/%s", direc, UNITSFILE); 143 unitfile = fopen(filename, "rt"); 144 if (unitfile) 145 break; 146 direc = strtok(NULL, SEPARATOR); 147 } 148 } 149 if (!unitfile) 150 errx(1, "can't find units file '%s'", UNITSFILE); 151 } 152 } 153 cap_rights_init(&unitfilerights, CAP_READ, CAP_FSTAT); 154 if (caph_rights_limit(fileno(unitfile), &unitfilerights) < 0) 155 err(1, "cap_rights_limit() failed"); 156 while (!feof(unitfile)) { 157 if (!fgets(line, sizeof(line), unitfile)) 158 break; 159 linenum++; 160 lineptr = line; 161 if (*lineptr == '/' || *lineptr == '#') 162 continue; 163 lineptr += strspn(lineptr, " \n\t"); 164 len = strcspn(lineptr, " \n\t"); 165 lineptr[len] = 0; 166 if (!strlen(lineptr)) 167 continue; 168 if (lineptr[strlen(lineptr) - 1] == '-') { /* it's a prefix */ 169 if (prefixcount == MAXPREFIXES) { 170 warnx("memory for prefixes exceeded in line %d", linenum); 171 continue; 172 } 173 lineptr[strlen(lineptr) - 1] = 0; 174 prefixtable[prefixcount].prefixname = dupstr(lineptr); 175 for (i = 0; i < prefixcount; i++) 176 if (!strcmp(prefixtable[i].prefixname, lineptr)) { 177 warnx("redefinition of prefix '%s' on line %d ignored", 178 lineptr, linenum); 179 continue; 180 } 181 lineptr += len + 1; 182 lineptr += strspn(lineptr, " \n\t"); 183 len = strcspn(lineptr, "\n\t"); 184 if (len == 0) { 185 warnx("unexpected end of prefix on line %d", 186 linenum); 187 continue; 188 } 189 lineptr[len] = 0; 190 prefixtable[prefixcount++].prefixval = dupstr(lineptr); 191 } 192 else { /* it's not a prefix */ 193 if (unitcount == MAXUNITS) { 194 warnx("memory for units exceeded in line %d", linenum); 195 continue; 196 } 197 unittable[unitcount].uname = dupstr(lineptr); 198 for (i = 0; i < unitcount; i++) 199 if (!strcmp(unittable[i].uname, lineptr)) { 200 warnx("redefinition of unit '%s' on line %d ignored", 201 lineptr, linenum); 202 continue; 203 } 204 lineptr += len + 1; 205 lineptr += strspn(lineptr, " \n\t"); 206 if (!strlen(lineptr)) { 207 warnx("unexpected end of unit on line %d", 208 linenum); 209 continue; 210 } 211 len = strcspn(lineptr, "\n\t"); 212 lineptr[len] = 0; 213 unittable[unitcount++].uval = dupstr(lineptr); 214 } 215 } 216 fclose(unitfile); 217 } 218 219 static void 220 initializeunit(struct unittype * theunit) 221 { 222 theunit->numerator[0] = theunit->denominator[0] = NULL; 223 theunit->factor = 1.0; 224 theunit->offset = 0.0; 225 theunit->quantity = 0; 226 } 227 228 229 static int 230 addsubunit(char *product[], char *toadd) 231 { 232 char **ptr; 233 234 for (ptr = product; *ptr && *ptr != NULLUNIT; ptr++); 235 if (ptr >= product + MAXSUBUNITS) { 236 warnx("memory overflow in unit reduction"); 237 return 1; 238 } 239 if (!*ptr) 240 *(ptr + 1) = NULL; 241 *ptr = dupstr(toadd); 242 return 0; 243 } 244 245 246 static void 247 showunit(struct unittype * theunit) 248 { 249 char **ptr; 250 int printedslash; 251 int counter = 1; 252 253 printf(numfmt, theunit->factor); 254 if (theunit->offset) 255 printf("&%.8g", theunit->offset); 256 for (ptr = theunit->numerator; *ptr; ptr++) { 257 if (ptr > theunit->numerator && **ptr && 258 !strcmp(*ptr, *(ptr - 1))) 259 counter++; 260 else { 261 if (counter > 1) 262 printf("%s%d", powerstring, counter); 263 if (**ptr) 264 printf(" %s", *ptr); 265 counter = 1; 266 } 267 } 268 if (counter > 1) 269 printf("%s%d", powerstring, counter); 270 counter = 1; 271 printedslash = 0; 272 for (ptr = theunit->denominator; *ptr; ptr++) { 273 if (ptr > theunit->denominator && **ptr && 274 !strcmp(*ptr, *(ptr - 1))) 275 counter++; 276 else { 277 if (counter > 1) 278 printf("%s%d", powerstring, counter); 279 if (**ptr) { 280 if (!printedslash) 281 printf(" /"); 282 printedslash = 1; 283 printf(" %s", *ptr); 284 } 285 counter = 1; 286 } 287 } 288 if (counter > 1) 289 printf("%s%d", powerstring, counter); 290 printf("\n"); 291 } 292 293 294 void 295 zeroerror(void) 296 { 297 warnx("unit reduces to zero"); 298 } 299 300 /* 301 Adds the specified string to the unit. 302 Flip is 0 for adding normally, 1 for adding reciprocal. 303 Quantity is 1 if this is a quantity to be converted rather than a pure unit. 304 305 Returns 0 for successful addition, nonzero on error. 306 */ 307 308 static int 309 addunit(struct unittype * theunit, const char *toadd, int flip, int quantity) 310 { 311 char *scratch, *savescr; 312 char *item; 313 char *divider, *slash, *offset; 314 int doingtop; 315 316 if (!strlen(toadd)) 317 return 1; 318 319 savescr = scratch = dupstr(toadd); 320 for (slash = scratch + 1; *slash; slash++) 321 if (*slash == '-' && 322 (tolower(*(slash - 1)) != 'e' || 323 !strchr(".0123456789", *(slash + 1)))) 324 *slash = ' '; 325 slash = strchr(scratch, '/'); 326 if (slash) 327 *slash = 0; 328 doingtop = 1; 329 do { 330 item = strtok(scratch, " *\t\n/"); 331 while (item) { 332 if (strchr("0123456789.", *item)) { /* item is a number */ 333 double num, offsetnum; 334 335 if (quantity) 336 theunit->quantity = 1; 337 338 offset = strchr(item, '&'); 339 if (offset) { 340 *offset = 0; 341 offsetnum = atof(offset+1); 342 } else 343 offsetnum = 0.0; 344 345 divider = strchr(item, '|'); 346 if (divider) { 347 *divider = 0; 348 num = atof(item); 349 if (!num) { 350 zeroerror(); 351 free(savescr); 352 return 1; 353 } 354 if (doingtop ^ flip) { 355 theunit->factor *= num; 356 theunit->offset *= num; 357 } else { 358 theunit->factor /= num; 359 theunit->offset /= num; 360 } 361 num = atof(divider + 1); 362 if (!num) { 363 zeroerror(); 364 free(savescr); 365 return 1; 366 } 367 if (doingtop ^ flip) { 368 theunit->factor /= num; 369 theunit->offset /= num; 370 } else { 371 theunit->factor *= num; 372 theunit->offset *= num; 373 } 374 } 375 else { 376 num = atof(item); 377 if (!num) { 378 zeroerror(); 379 free(savescr); 380 return 1; 381 } 382 if (doingtop ^ flip) { 383 theunit->factor *= num; 384 theunit->offset *= num; 385 } else { 386 theunit->factor /= num; 387 theunit->offset /= num; 388 } 389 } 390 if (doingtop ^ flip) 391 theunit->offset += offsetnum; 392 } 393 else { /* item is not a number */ 394 int repeat = 1; 395 396 if (strchr("23456789", 397 item[strlen(item) - 1])) { 398 repeat = item[strlen(item) - 1] - '0'; 399 item[strlen(item) - 1] = 0; 400 } 401 for (; repeat; repeat--) { 402 if (addsubunit(doingtop ^ flip ? theunit->numerator : theunit->denominator, item)) { 403 free(savescr); 404 return 1; 405 } 406 } 407 } 408 item = strtok(NULL, " *\t/\n"); 409 } 410 doingtop--; 411 if (slash) { 412 scratch = slash + 1; 413 } 414 else 415 doingtop--; 416 } while (doingtop >= 0); 417 free(savescr); 418 return 0; 419 } 420 421 422 static int 423 compare(const void *item1, const void *item2) 424 { 425 return strcmp(*(const char * const *)item1, *(const char * const *)item2); 426 } 427 428 429 static void 430 sortunit(struct unittype * theunit) 431 { 432 char **ptr; 433 unsigned int count; 434 435 for (count = 0, ptr = theunit->numerator; *ptr; ptr++, count++); 436 qsort(theunit->numerator, count, sizeof(char *), compare); 437 for (count = 0, ptr = theunit->denominator; *ptr; ptr++, count++); 438 qsort(theunit->denominator, count, sizeof(char *), compare); 439 } 440 441 442 void 443 cancelunit(struct unittype * theunit) 444 { 445 char **den, **num; 446 int comp; 447 448 den = theunit->denominator; 449 num = theunit->numerator; 450 451 while (*num && *den) { 452 comp = strcmp(*den, *num); 453 if (!comp) { 454 /* if (*den!=NULLUNIT) free(*den); 455 if (*num!=NULLUNIT) free(*num);*/ 456 *den++ = NULLUNIT; 457 *num++ = NULLUNIT; 458 } 459 else if (comp < 0) 460 den++; 461 else 462 num++; 463 } 464 } 465 466 467 468 469 /* 470 Looks up the definition for the specified unit. 471 Returns a pointer to the definition or a null pointer 472 if the specified unit does not appear in the units table. 473 */ 474 475 static char buffer[100]; /* buffer for lookupunit answers with 476 prefixes */ 477 478 char * 479 lookupunit(const char *unit) 480 { 481 int i; 482 char *copy; 483 484 for (i = 0; i < unitcount; i++) { 485 if (!strcmp(unittable[i].uname, unit)) 486 return unittable[i].uval; 487 } 488 489 if (unit[strlen(unit) - 1] == '^') { 490 copy = dupstr(unit); 491 copy[strlen(copy) - 1] = 0; 492 for (i = 0; i < unitcount; i++) { 493 if (!strcmp(unittable[i].uname, copy)) { 494 strlcpy(buffer, copy, sizeof(buffer)); 495 free(copy); 496 return buffer; 497 } 498 } 499 free(copy); 500 } 501 if (unit[strlen(unit) - 1] == 's') { 502 copy = dupstr(unit); 503 copy[strlen(copy) - 1] = 0; 504 for (i = 0; i < unitcount; i++) { 505 if (!strcmp(unittable[i].uname, copy)) { 506 strlcpy(buffer, copy, sizeof(buffer)); 507 free(copy); 508 return buffer; 509 } 510 } 511 if (copy[strlen(copy) - 1] == 'e') { 512 copy[strlen(copy) - 1] = 0; 513 for (i = 0; i < unitcount; i++) { 514 if (!strcmp(unittable[i].uname, copy)) { 515 strlcpy(buffer, copy, sizeof(buffer)); 516 free(copy); 517 return buffer; 518 } 519 } 520 } 521 free(copy); 522 } 523 for (i = 0; i < prefixcount; i++) { 524 size_t len = strlen(prefixtable[i].prefixname); 525 if (!strncmp(prefixtable[i].prefixname, unit, len)) { 526 if (!strlen(unit + len) || lookupunit(unit + len)) { 527 snprintf(buffer, sizeof(buffer), "%s %s", 528 prefixtable[i].prefixval, unit + len); 529 return buffer; 530 } 531 } 532 } 533 return 0; 534 } 535 536 537 538 /* 539 reduces a product of symbolic units to primitive units. 540 The three low bits are used to return flags: 541 542 bit 0 (1) set on if reductions were performed without error. 543 bit 1 (2) set on if no reductions are performed. 544 bit 2 (4) set on if an unknown unit is discovered. 545 */ 546 547 548 #define ERROR 4 549 550 static int 551 reduceproduct(struct unittype * theunit, int flip) 552 { 553 554 char *toadd; 555 char **product; 556 int didsomething = 2; 557 558 if (flip) 559 product = theunit->denominator; 560 else 561 product = theunit->numerator; 562 563 for (; *product; product++) { 564 565 for (;;) { 566 if (!strlen(*product)) 567 break; 568 toadd = lookupunit(*product); 569 if (!toadd) { 570 printf("unknown unit '%s'\n", *product); 571 return ERROR; 572 } 573 if (strchr(toadd, PRIMITIVECHAR)) 574 break; 575 didsomething = 1; 576 if (*product != NULLUNIT) { 577 free(*product); 578 *product = NULLUNIT; 579 } 580 if (addunit(theunit, toadd, flip, 0)) 581 return ERROR; 582 } 583 } 584 return didsomething; 585 } 586 587 588 /* 589 Reduces numerator and denominator of the specified unit. 590 Returns 0 on success, or 1 on unknown unit error. 591 */ 592 593 static int 594 reduceunit(struct unittype * theunit) 595 { 596 int ret; 597 598 ret = 1; 599 while (ret & 1) { 600 ret = reduceproduct(theunit, 0) | reduceproduct(theunit, 1); 601 if (ret & 4) 602 return 1; 603 } 604 return 0; 605 } 606 607 608 static int 609 compareproducts(char **one, char **two) 610 { 611 while (*one || *two) { 612 if (!*one && *two != NULLUNIT) 613 return 1; 614 if (!*two && *one != NULLUNIT) 615 return 1; 616 if (*one == NULLUNIT) 617 one++; 618 else if (*two == NULLUNIT) 619 two++; 620 else if (strcmp(*one, *two)) 621 return 1; 622 else { 623 one++; 624 two++; 625 } 626 } 627 return 0; 628 } 629 630 631 /* Return zero if units are compatible, nonzero otherwise */ 632 633 static int 634 compareunits(struct unittype * first, struct unittype * second) 635 { 636 return 637 compareproducts(first->numerator, second->numerator) || 638 compareproducts(first->denominator, second->denominator); 639 } 640 641 642 static int 643 completereduce(struct unittype * unit) 644 { 645 if (reduceunit(unit)) 646 return 1; 647 sortunit(unit); 648 cancelunit(unit); 649 return 0; 650 } 651 652 static void 653 showanswer(struct unittype * have, struct unittype * want) 654 { 655 double ans; 656 char* oformat; 657 658 if (compareunits(have, want)) { 659 printf("conformability error\n"); 660 if (verbose) 661 printf("\t%s = ", havestr); 662 else if (!terse) 663 printf("\t"); 664 showunit(have); 665 if (!terse) { 666 if (verbose) 667 printf("\t%s = ", wantstr); 668 else 669 printf("\t"); 670 showunit(want); 671 } 672 } 673 else if (have->offset != want->offset) { 674 if (want->quantity) 675 printf("WARNING: conversion of non-proportional quantities.\n"); 676 if (have->quantity) { 677 asprintf(&oformat, "\t%s\n", outputformat); 678 printf(oformat, 679 (have->factor + have->offset-want->offset)/want->factor); 680 free(oformat); 681 } 682 else { 683 asprintf(&oformat, "\t (-> x*%sg %sg)\n\t (<- y*%sg %sg)\n", 684 outputformat, outputformat, outputformat, outputformat); 685 printf(oformat, 686 have->factor / want->factor, 687 (have->offset-want->offset)/want->factor, 688 want->factor / have->factor, 689 (want->offset - have->offset)/have->factor); 690 } 691 } 692 else { 693 ans = have->factor / want->factor; 694 695 if (verbose) { 696 printf("\t%s = ", havestr); 697 printf(outputformat, ans); 698 printf(" * %s", wantstr); 699 printf("\n"); 700 } 701 else if (terse) { 702 printf(outputformat, ans); 703 printf("\n"); 704 } 705 else { 706 printf("\t* "); 707 printf(outputformat, ans); 708 printf("\n"); 709 } 710 711 if (verbose) { 712 printf("\t%s = (1 / ", havestr); 713 printf(outputformat, 1/ans); 714 printf(") * %s\n", wantstr); 715 } 716 else if (!terse) { 717 printf("\t/ "); 718 printf(outputformat, 1/ans); 719 printf("\n"); 720 } 721 } 722 } 723 724 725 static void __dead2 726 usage(void) 727 { 728 fprintf(stderr, 729 "usage: units [-ehqtUVv] [-f unitsfile] [-o format] [from to]\n"); 730 exit(3); 731 } 732 733 static struct option longopts[] = { 734 {"exponential", no_argument, NULL, 'e'}, 735 {"file", required_argument, NULL, 'f'}, 736 {"history", required_argument, NULL, 'H'}, 737 {"help", no_argument, NULL, 'h'}, 738 {"output-format", required_argument, NULL, 'o'}, 739 {"quiet", no_argument, NULL, 'q'}, 740 {"terse", no_argument, NULL, 't'}, 741 {"unitsfile", no_argument, NULL, 'U'}, 742 {"version", no_argument, NULL, 'V'}, 743 {"verbose", no_argument, NULL, 'v'}, 744 { 0, 0, 0, 0 } 745 }; 746 747 748 int 749 main(int argc, char **argv) 750 { 751 752 struct unittype have, want; 753 int optchar; 754 bool quiet; 755 bool readfile; 756 bool quit; 757 History *inhistory; 758 EditLine *el; 759 HistEvent ev; 760 int inputsz; 761 762 quiet = false; 763 readfile = false; 764 outputformat = numfmt; 765 quit = false; 766 while ((optchar = getopt_long(argc, argv, "+ehf:o:qtvH:UV", longopts, NULL)) != -1) { 767 switch (optchar) { 768 case 'e': 769 outputformat = "%6e"; 770 break; 771 case 'f': 772 readfile = true; 773 if (strlen(optarg) == 0) 774 readunits(NULL); 775 else 776 readunits(optarg); 777 break; 778 case 'H': 779 /* Ignored, for compatibility with GNU units. */ 780 break; 781 case 'q': 782 quiet = true; 783 break; 784 case 't': 785 terse = true; 786 break; 787 case 'o': 788 outputformat = optarg; 789 break; 790 case 'v': 791 verbose = true; 792 break; 793 case 'V': 794 fprintf(stderr, "FreeBSD units\n"); 795 /* FALLTHROUGH */ 796 case 'U': 797 if (access(UNITSFILE, F_OK) == 0) 798 printf("%s\n", UNITSFILE); 799 else 800 printf("Units data file not found"); 801 exit(0); 802 case 'h': 803 /* FALLTHROUGH */ 804 805 default: 806 usage(); 807 } 808 } 809 810 if (!readfile) 811 readunits(NULL); 812 813 if (optind == argc - 2) { 814 if (caph_enter() < 0) 815 err(1, "unable to enter capability mode"); 816 817 havestr = argv[optind]; 818 wantstr = argv[optind + 1]; 819 initializeunit(&have); 820 addunit(&have, havestr, 0, 1); 821 completereduce(&have); 822 initializeunit(&want); 823 addunit(&want, wantstr, 0, 1); 824 completereduce(&want); 825 showanswer(&have, &want); 826 } else { 827 inhistory = history_init(); 828 el = el_init(argv[0], stdin, stdout, stderr); 829 el_set(el, EL_PROMPT, &prompt); 830 el_set(el, EL_EDITOR, "emacs"); 831 el_set(el, EL_SIGNAL, 1); 832 el_set(el, EL_HIST, history, inhistory); 833 el_source(el, NULL); 834 history(inhistory, &ev, H_SETSIZE, 800); 835 if (inhistory == 0) 836 err(1, "Could not initialize history"); 837 838 if (caph_enter() < 0) 839 err(1, "unable to enter capability mode"); 840 841 if (!quiet) 842 printf("%d units, %d prefixes\n", unitcount, 843 prefixcount); 844 while (!quit) { 845 do { 846 initializeunit(&have); 847 if (!quiet) 848 promptstr = "You have: "; 849 havestr = el_gets(el, &inputsz); 850 if (havestr == NULL) { 851 quit = true; 852 break; 853 } 854 if (inputsz > 0) 855 history(inhistory, &ev, H_ENTER, 856 havestr); 857 } while (addunit(&have, havestr, 0, 1) || 858 completereduce(&have)); 859 if (quit) { 860 break; 861 } 862 do { 863 initializeunit(&want); 864 if (!quiet) 865 promptstr = "You want: "; 866 wantstr = el_gets(el, &inputsz); 867 if (wantstr == NULL) { 868 quit = true; 869 break; 870 } 871 if (inputsz > 0) 872 history(inhistory, &ev, H_ENTER, 873 wantstr); 874 } while (addunit(&want, wantstr, 0, 1) || 875 completereduce(&want)); 876 if (quit) { 877 break; 878 } 879 showanswer(&have, &want); 880 } 881 882 history_end(inhistory); 883 el_end(el); 884 } 885 886 return (0); 887 } 888