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