1/*-
2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
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#ifndef lint
21static const char rcsid[] =
22  "$FreeBSD$";
23#endif /* not lint */
24
25#include <ctype.h>
26#include <err.h>
27#include <errno.h>
28#include <histedit.h>
29#include <getopt.h>
30#include <stdbool.h>
31#include <stdio.h>
32#include <stdlib.h>
33#include <string.h>
34#include <unistd.h>
35
36#include <capsicum_helpers.h>
37
38#ifndef UNITSFILE
39#define UNITSFILE "/usr/share/misc/definitions.units"
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 = "^";
50static const char *numfmt = "%.8g";
51
52static struct {
53	char *uname;
54	char *uval;
55}      unittable[MAXUNITS];
56
57struct unittype {
58	char *numerator[MAXSUBUNITS];
59	char *denominator[MAXSUBUNITS];
60	double factor;
61	double offset;
62	int quantity;
63};
64
65static struct {
66	char *prefixname;
67	char *prefixval;
68}      prefixtable[MAXPREFIXES];
69
70
71static char NULLUNIT[] = "";
72
73#define SEPARATOR      ":"
74
75static int unitcount;
76static int prefixcount;
77static bool verbose = false;
78static bool terse = false;
79static const char * outputformat;
80static const char * havestr;
81static const char * wantstr;
82
83static int	 addsubunit(char *product[], char *toadd);
84static int	 addunit(struct unittype *theunit, const char *toadd, int flip, int quantity);
85static void	 cancelunit(struct unittype * theunit);
86static int	 compare(const void *item1, const void *item2);
87static int	 compareproducts(char **one, char **two);
88static int	 compareunits(struct unittype * first, struct unittype * second);
89static int	 completereduce(struct unittype * unit);
90static char	*dupstr(const char *str);
91static void	 initializeunit(struct unittype * theunit);
92static char	*lookupunit(const char *unit);
93static void	 readunits(const char *userfile);
94static int	 reduceproduct(struct unittype * theunit, int flip);
95static int	 reduceunit(struct unittype * theunit);
96static void	 showanswer(struct unittype * have, struct unittype * want);
97static void	 showunit(struct unittype * theunit);
98static void	 sortunit(struct unittype * theunit);
99static void	 usage(void);
100static void	 zeroerror(void);
101
102static const char* promptstr = "";
103
104static const char * prompt(EditLine *e __unused) {
105	return promptstr;
106}
107
108static char *
109dupstr(const char *str)
110{
111	char *ret;
112
113	ret = strdup(str);
114	if (!ret)
115		err(3, "dupstr");
116	return (ret);
117}
118
119
120static void
121readunits(const char *userfile)
122{
123	FILE *unitfile;
124	char line[512], *lineptr;
125	int len, linenum, i;
126	cap_rights_t unitfilerights;
127
128	unitcount = 0;
129	linenum = 0;
130
131	if (userfile) {
132		unitfile = fopen(userfile, "r");
133		if (!unitfile)
134			errx(1, "unable to open units file '%s'", userfile);
135	}
136	else {
137		unitfile = fopen(UNITSFILE, "r");
138		if (!unitfile) {
139			char *direc, *env;
140			char filename[1000];
141
142			env = getenv("PATH");
143			if (env) {
144				direc = strtok(env, SEPARATOR);
145				while (direc) {
146					snprintf(filename, sizeof(filename),
147					    "%s/%s", direc, UNITSFILE);
148					unitfile = fopen(filename, "rt");
149					if (unitfile)
150						break;
151					direc = strtok(NULL, SEPARATOR);
152				}
153			}
154			if (!unitfile)
155				errx(1, "can't find units file '%s'", UNITSFILE);
156		}
157	}
158	cap_rights_init(&unitfilerights, CAP_READ, CAP_FSTAT);
159	if (caph_rights_limit(fileno(unitfile), &unitfilerights) < 0)
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
224static void
225initializeunit(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
234static int
235addsubunit(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
251static void
252showunit(struct unittype * theunit)
253{
254	char **ptr;
255	int printedslash;
256	int counter = 1;
257
258	printf(numfmt, 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
299void
300zeroerror(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
313static int
314addunit(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						free(savescr);
357						return 1;
358					}
359					if (doingtop ^ flip) {
360						theunit->factor *= num;
361						theunit->offset *= num;
362					} else {
363						theunit->factor /= num;
364						theunit->offset /= num;
365					}
366					num = atof(divider + 1);
367					if (!num) {
368						zeroerror();
369						free(savescr);
370						return 1;
371					}
372					if (doingtop ^ flip) {
373						theunit->factor /= num;
374						theunit->offset /= num;
375					} else {
376						theunit->factor *= num;
377						theunit->offset *= num;
378					}
379				}
380				else {
381					num = atof(item);
382					if (!num) {
383						zeroerror();
384						free(savescr);
385						return 1;
386					}
387					if (doingtop ^ flip) {
388						theunit->factor *= num;
389						theunit->offset *= num;
390					} else {
391						theunit->factor /= num;
392						theunit->offset /= num;
393					}
394				}
395				if (doingtop ^ flip)
396					theunit->offset += offsetnum;
397			}
398			else {	/* item is not a number */
399				int repeat = 1;
400
401				if (strchr("23456789",
402				    item[strlen(item) - 1])) {
403					repeat = item[strlen(item) - 1] - '0';
404					item[strlen(item) - 1] = 0;
405				}
406				for (; repeat; repeat--) {
407					if (addsubunit(doingtop ^ flip ? theunit->numerator : theunit->denominator, item)) {
408						free(savescr);
409						return 1;
410					}
411				}
412			}
413			item = strtok(NULL, " *\t/\n");
414		}
415		doingtop--;
416		if (slash) {
417			scratch = slash + 1;
418		}
419		else
420			doingtop--;
421	} while (doingtop >= 0);
422	free(savescr);
423	return 0;
424}
425
426
427static int
428compare(const void *item1, const void *item2)
429{
430	return strcmp(*(const char * const *)item1, *(const char * const *)item2);
431}
432
433
434static void
435sortunit(struct unittype * theunit)
436{
437	char **ptr;
438	unsigned int count;
439
440	for (count = 0, ptr = theunit->numerator; *ptr; ptr++, count++);
441	qsort(theunit->numerator, count, sizeof(char *), compare);
442	for (count = 0, ptr = theunit->denominator; *ptr; ptr++, count++);
443	qsort(theunit->denominator, count, sizeof(char *), compare);
444}
445
446
447void
448cancelunit(struct unittype * theunit)
449{
450	char **den, **num;
451	int comp;
452
453	den = theunit->denominator;
454	num = theunit->numerator;
455
456	while (*num && *den) {
457		comp = strcmp(*den, *num);
458		if (!comp) {
459/*      if (*den!=NULLUNIT) free(*den);
460      if (*num!=NULLUNIT) free(*num);*/
461			*den++ = NULLUNIT;
462			*num++ = NULLUNIT;
463		}
464		else if (comp < 0)
465			den++;
466		else
467			num++;
468	}
469}
470
471
472
473
474/*
475   Looks up the definition for the specified unit.
476   Returns a pointer to the definition or a null pointer
477   if the specified unit does not appear in the units table.
478*/
479
480static char buffer[100];	/* buffer for lookupunit answers with
481				   prefixes */
482
483char *
484lookupunit(const char *unit)
485{
486	int i;
487	char *copy;
488
489	for (i = 0; i < unitcount; i++) {
490		if (!strcmp(unittable[i].uname, unit))
491			return unittable[i].uval;
492	}
493
494	if (unit[strlen(unit) - 1] == '^') {
495		copy = dupstr(unit);
496		copy[strlen(copy) - 1] = 0;
497		for (i = 0; i < unitcount; i++) {
498			if (!strcmp(unittable[i].uname, copy)) {
499				strlcpy(buffer, copy, sizeof(buffer));
500				free(copy);
501				return buffer;
502			}
503		}
504		free(copy);
505	}
506	if (unit[strlen(unit) - 1] == 's') {
507		copy = dupstr(unit);
508		copy[strlen(copy) - 1] = 0;
509		for (i = 0; i < unitcount; i++) {
510			if (!strcmp(unittable[i].uname, copy)) {
511				strlcpy(buffer, copy, sizeof(buffer));
512				free(copy);
513				return buffer;
514			}
515		}
516		if (copy[strlen(copy) - 1] == 'e') {
517			copy[strlen(copy) - 1] = 0;
518			for (i = 0; i < unitcount; i++) {
519				if (!strcmp(unittable[i].uname, copy)) {
520					strlcpy(buffer, copy, sizeof(buffer));
521					free(copy);
522					return buffer;
523				}
524			}
525		}
526		free(copy);
527	}
528	for (i = 0; i < prefixcount; i++) {
529		size_t len = strlen(prefixtable[i].prefixname);
530		if (!strncmp(prefixtable[i].prefixname, unit, len)) {
531			if (!strlen(unit + len) || lookupunit(unit + len)) {
532				snprintf(buffer, sizeof(buffer), "%s %s",
533				    prefixtable[i].prefixval, unit + len);
534				return buffer;
535			}
536		}
537	}
538	return 0;
539}
540
541
542
543/*
544   reduces a product of symbolic units to primitive units.
545   The three low bits are used to return flags:
546
547     bit 0 (1) set on if reductions were performed without error.
548     bit 1 (2) set on if no reductions are performed.
549     bit 2 (4) set on if an unknown unit is discovered.
550*/
551
552
553#define ERROR 4
554
555static int
556reduceproduct(struct unittype * theunit, int flip)
557{
558
559	char *toadd;
560	char **product;
561	int didsomething = 2;
562
563	if (flip)
564		product = theunit->denominator;
565	else
566		product = theunit->numerator;
567
568	for (; *product; product++) {
569
570		for (;;) {
571			if (!strlen(*product))
572				break;
573			toadd = lookupunit(*product);
574			if (!toadd) {
575				printf("unknown unit '%s'\n", *product);
576				return ERROR;
577			}
578			if (strchr(toadd, PRIMITIVECHAR))
579				break;
580			didsomething = 1;
581			if (*product != NULLUNIT) {
582				free(*product);
583				*product = NULLUNIT;
584			}
585			if (addunit(theunit, toadd, flip, 0))
586				return ERROR;
587		}
588	}
589	return didsomething;
590}
591
592
593/*
594   Reduces numerator and denominator of the specified unit.
595   Returns 0 on success, or 1 on unknown unit error.
596*/
597
598static int
599reduceunit(struct unittype * theunit)
600{
601	int ret;
602
603	ret = 1;
604	while (ret & 1) {
605		ret = reduceproduct(theunit, 0) | reduceproduct(theunit, 1);
606		if (ret & 4)
607			return 1;
608	}
609	return 0;
610}
611
612
613static int
614compareproducts(char **one, char **two)
615{
616	while (*one || *two) {
617		if (!*one && *two != NULLUNIT)
618			return 1;
619		if (!*two && *one != NULLUNIT)
620			return 1;
621		if (*one == NULLUNIT)
622			one++;
623		else if (*two == NULLUNIT)
624			two++;
625		else if (strcmp(*one, *two))
626			return 1;
627		else {
628			one++;
629			two++;
630		}
631	}
632	return 0;
633}
634
635
636/* Return zero if units are compatible, nonzero otherwise */
637
638static int
639compareunits(struct unittype * first, struct unittype * second)
640{
641	return
642	compareproducts(first->numerator, second->numerator) ||
643	compareproducts(first->denominator, second->denominator);
644}
645
646
647static int
648completereduce(struct unittype * unit)
649{
650	if (reduceunit(unit))
651		return 1;
652	sortunit(unit);
653	cancelunit(unit);
654	return 0;
655}
656
657static void
658showanswer(struct unittype * have, struct unittype * want)
659{
660	double ans;
661	char* oformat;
662
663	if (compareunits(have, want)) {
664		printf("conformability error\n");
665		if (verbose)
666			printf("\t%s = ", havestr);
667		else if (!terse)
668			printf("\t");
669		showunit(have);
670		if (!terse) {
671			if (verbose)
672				printf("\t%s = ", wantstr);
673			else
674				printf("\t");
675			showunit(want);
676		}
677	}
678	else if (have->offset != want->offset) {
679		if (want->quantity)
680			printf("WARNING: conversion of non-proportional quantities.\n");
681		if (have->quantity) {
682			asprintf(&oformat, "\t%s\n", outputformat);
683			printf(oformat,
684			    (have->factor + have->offset-want->offset)/want->factor);
685			free(oformat);
686		}
687		else {
688			asprintf(&oformat, "\t (-> x*%sg %sg)\n\t (<- y*%sg %sg)\n",
689			    outputformat, outputformat, outputformat, outputformat);
690			printf(oformat,
691			    have->factor / want->factor,
692			    (have->offset-want->offset)/want->factor,
693			    want->factor / have->factor,
694			    (want->offset - have->offset)/have->factor);
695		}
696	}
697	else {
698		ans = have->factor / want->factor;
699
700		if (verbose) {
701			printf("\t%s = ", havestr);
702			printf(outputformat, ans);
703			printf(" * %s", wantstr);
704			printf("\n");
705		}
706		else if (terse) {
707			printf(outputformat, ans);
708			printf("\n");
709		}
710		else {
711			printf("\t* ");
712			printf(outputformat, ans);
713			printf("\n");
714		}
715
716		if (verbose) {
717			printf("\t%s = (1 / ", havestr);
718			printf(outputformat, 1/ans);
719			printf(") * %s\n", wantstr);
720		}
721		else if (!terse) {
722			printf("\t/ ");
723			printf(outputformat, 1/ans);
724			printf("\n");
725		}
726	}
727}
728
729
730static void __dead2
731usage(void)
732{
733	fprintf(stderr,
734		"usage: units [-f unitsfile] [-H historyfile] [-UVq] [from-unit to-unit]\n");
735	exit(3);
736}
737
738static struct option longopts[] = {
739	{"help", no_argument, NULL, 'h'},
740	{"exponential", no_argument, NULL, 'e'},
741	{"file", required_argument, NULL, 'f'},
742	{"history", required_argument, NULL, 'H'},
743	{"output-format", required_argument, NULL, 'o'},
744	{"quiet", no_argument, NULL, 'q'},
745	{"terse", no_argument, NULL, 't'},
746	{"unitsfile", no_argument, NULL, 'U'},
747	{"verbose", no_argument, NULL, 'v'},
748	{"version", no_argument, NULL, 'V'},
749	{ 0, 0, 0, 0 }
750};
751
752
753int
754main(int argc, char **argv)
755{
756
757	struct unittype have, want;
758	int optchar;
759	bool quiet;
760	bool readfile;
761	bool quit;
762	History *inhistory;
763	EditLine *el;
764	HistEvent ev;
765	int inputsz;
766	char const * history_file;
767
768	quiet = false;
769	readfile = false;
770	history_file = NULL;
771	outputformat = numfmt;
772	quit = false;
773	while ((optchar = getopt_long(argc, argv, "+ehf:o:qtvH:UV", longopts, NULL)) != -1) {
774		switch (optchar) {
775		case 'e':
776			outputformat = "%6e";
777			break;
778		case 'f':
779			readfile = true;
780			if (strlen(optarg) == 0)
781				readunits(NULL);
782			else
783				readunits(optarg);
784			break;
785		case 'H':
786			history_file = optarg;
787			break;
788		case 'q':
789			quiet = true;
790			break;
791		case 't':
792			terse = true;
793			break;
794		case 'o':
795			outputformat = optarg;
796			break;
797		case 'v':
798			verbose = true;
799			break;
800		case 'V':
801			fprintf(stderr, "FreeBSD units\n");
802			/* FALLTHROUGH */
803		case 'U':
804			if (access(UNITSFILE, F_OK) == 0)
805				printf("%s\n", UNITSFILE);
806			else
807				printf("Units data file not found");
808			exit(0);
809		case 'h':
810			/* FALLTHROUGH */
811
812		default:
813			usage();
814		}
815	}
816
817	if (!readfile)
818		readunits(NULL);
819
820	if (optind == argc - 2) {
821		if (caph_enter() < 0)
822			err(1, "unable to enter capability mode");
823
824		havestr = argv[optind];
825		wantstr = argv[optind + 1];
826		initializeunit(&have);
827		addunit(&have, havestr, 0, 1);
828		completereduce(&have);
829		initializeunit(&want);
830		addunit(&want, wantstr, 0, 1);
831		completereduce(&want);
832		showanswer(&have, &want);
833	} else {
834		inhistory = history_init();
835		el = el_init(argv[0], stdin, stdout, stderr);
836		el_set(el, EL_PROMPT, &prompt);
837		el_set(el, EL_EDITOR, "emacs");
838		el_set(el, EL_SIGNAL, 1);
839		el_set(el, EL_HIST, history, inhistory);
840		el_source(el, NULL);
841		history(inhistory, &ev, H_SETSIZE, 800);
842		if (inhistory == 0)
843			err(1, "Could not initialize history");
844
845		if (caph_enter() < 0)
846			err(1, "unable to enter capability mode");
847
848		if (!quiet)
849			printf("%d units, %d prefixes\n", unitcount,
850			    prefixcount);
851		while (!quit) {
852			do {
853				initializeunit(&have);
854				if (!quiet)
855					promptstr = "You have: ";
856				havestr = el_gets(el, &inputsz);
857				if (havestr == NULL) {
858					quit = true;
859					break;
860				}
861				if (inputsz > 0)
862					history(inhistory, &ev, H_ENTER,
863					havestr);
864			} while (addunit(&have, havestr, 0, 1) ||
865			    completereduce(&have));
866			if (quit) {
867				break;
868			}
869			do {
870				initializeunit(&want);
871				if (!quiet)
872					promptstr = "You want: ";
873				wantstr = el_gets(el, &inputsz);
874				if (wantstr == NULL) {
875					quit = true;
876					break;
877				}
878				if (inputsz > 0)
879					history(inhistory, &ev, H_ENTER,
880					wantstr);
881			} while (addunit(&want, wantstr, 0, 1) ||
882			    completereduce(&want));
883			if (quit) {
884				break;
885			}
886			showanswer(&have, &want);
887		}
888
889		history_end(inhistory);
890		el_end(el);
891	}
892
893	return (0);
894}
895