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