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
44static const char *powerstring = "^";
45static const char *numfmt = "%.8g";
46
47static struct {
48	char *uname;
49	char *uval;
50}      unittable[MAXUNITS];
51
52struct unittype {
53	char *numerator[MAXSUBUNITS];
54	char *denominator[MAXSUBUNITS];
55	double factor;
56	double offset;
57	int quantity;
58};
59
60static struct {
61	char *prefixname;
62	char *prefixval;
63}      prefixtable[MAXPREFIXES];
64
65
66static char NULLUNIT[] = "";
67
68#define SEPARATOR      ":"
69
70static int unitcount;
71static int prefixcount;
72static bool verbose = false;
73static bool terse = false;
74static const char * outputformat;
75static const char * havestr;
76static const char * wantstr;
77
78static int	 addsubunit(char *product[], char *toadd);
79static int	 addunit(struct unittype *theunit, const char *toadd, int flip, int quantity);
80static void	 cancelunit(struct unittype * theunit);
81static int	 compare(const void *item1, const void *item2);
82static int	 compareproducts(char **one, char **two);
83static int	 compareunits(struct unittype * first, struct unittype * second);
84static int	 completereduce(struct unittype * unit);
85static char	*dupstr(const char *str);
86static void	 initializeunit(struct unittype * theunit);
87static char	*lookupunit(const char *unit);
88static void	 readunits(const char *userfile);
89static int	 reduceproduct(struct unittype * theunit, int flip);
90static int	 reduceunit(struct unittype * theunit);
91static void	 showanswer(struct unittype * have, struct unittype * want);
92static void	 showunit(struct unittype * theunit);
93static void	 sortunit(struct unittype * theunit);
94static void	 usage(void);
95static void	 zeroerror(void);
96
97static const char* promptstr = "";
98
99static const char * prompt(EditLine *e __unused) {
100	return promptstr;
101}
102
103static char *
104dupstr(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
115static void
116readunits(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
219static void
220initializeunit(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
229static int
230addsubunit(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
246static void
247showunit(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
294void
295zeroerror(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
308static int
309addunit(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
422static int
423compare(const void *item1, const void *item2)
424{
425	return strcmp(*(const char * const *)item1, *(const char * const *)item2);
426}
427
428
429static void
430sortunit(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
442void
443cancelunit(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
475static char buffer[100];	/* buffer for lookupunit answers with
476				   prefixes */
477
478char *
479lookupunit(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	} else 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
549static int
550reduceproduct(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
592static int
593reduceunit(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
607static int
608compareproducts(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++;
623			two++;
624		}
625	}
626	return 0;
627}
628
629
630/* Return zero if units are compatible, nonzero otherwise */
631
632static int
633compareunits(struct unittype * first, struct unittype * second)
634{
635	return
636	compareproducts(first->numerator, second->numerator) ||
637	compareproducts(first->denominator, second->denominator);
638}
639
640
641static int
642completereduce(struct unittype * unit)
643{
644	if (reduceunit(unit))
645		return 1;
646	sortunit(unit);
647	cancelunit(unit);
648	return 0;
649}
650
651static void
652showanswer(struct unittype * have, struct unittype * want)
653{
654	double ans;
655	char* oformat;
656
657	if (compareunits(have, want)) {
658		printf("conformability error\n");
659		if (verbose)
660			printf("\t%s = ", havestr);
661		else if (!terse)
662			printf("\t");
663		showunit(have);
664		if (!terse) {
665			if (verbose)
666				printf("\t%s = ", wantstr);
667			else
668				printf("\t");
669			showunit(want);
670		}
671	}
672	else if (have->offset != want->offset) {
673		if (want->quantity)
674			printf("WARNING: conversion of non-proportional quantities.\n");
675		if (have->quantity) {
676			asprintf(&oformat, "\t%s\n", outputformat);
677			printf(oformat,
678			    (have->factor + have->offset-want->offset)/want->factor);
679			free(oformat);
680		}
681		else {
682			asprintf(&oformat, "\t (-> x*%sg %sg)\n\t (<- y*%sg %sg)\n",
683			    outputformat, outputformat, outputformat, outputformat);
684			printf(oformat,
685			    have->factor / want->factor,
686			    (have->offset-want->offset)/want->factor,
687			    want->factor / have->factor,
688			    (want->offset - have->offset)/have->factor);
689		}
690	}
691	else {
692		ans = have->factor / want->factor;
693
694		if (verbose) {
695			printf("\t%s = ", havestr);
696			printf(outputformat, ans);
697			printf(" * %s", wantstr);
698			printf("\n");
699		}
700		else if (terse) {
701			printf(outputformat, ans);
702			printf("\n");
703		}
704		else {
705			printf("\t* ");
706			printf(outputformat, ans);
707			printf("\n");
708		}
709
710		if (verbose) {
711			printf("\t%s = (1 / ", havestr);
712			printf(outputformat, 1/ans);
713			printf(") * %s\n", wantstr);
714		}
715		else if (!terse) {
716			printf("\t/ ");
717			printf(outputformat, 1/ans);
718			printf("\n");
719		}
720	}
721}
722
723
724static void __dead2
725usage(void)
726{
727	fprintf(stderr,
728	    "usage: units [-ehqtUVv] [-f unitsfile] [-o format] [from to]\n");
729	exit(3);
730}
731
732static struct option longopts[] = {
733	{"exponential", no_argument, NULL, 'e'},
734	{"file", required_argument, NULL, 'f'},
735	{"history", required_argument, NULL, 'H'},
736	{"help", no_argument, NULL, 'h'},
737	{"output-format", required_argument, NULL, 'o'},
738	{"quiet", no_argument, NULL, 'q'},
739	{"terse", no_argument, NULL, 't'},
740	{"unitsfile", no_argument, NULL, 'U'},
741	{"version", no_argument, NULL, 'V'},
742	{"verbose", no_argument, NULL, 'v'},
743	{ 0, 0, 0, 0 }
744};
745
746
747int
748main(int argc, char **argv)
749{
750
751	struct unittype have, want;
752	int optchar;
753	bool quiet;
754	bool readfile;
755	bool quit;
756	History *inhistory;
757	EditLine *el;
758	HistEvent ev;
759	int inputsz;
760
761	quiet = false;
762	readfile = false;
763	outputformat = numfmt;
764	quit = false;
765	while ((optchar = getopt_long(argc, argv, "+ehf:o:qtvH:UV", longopts, NULL)) != -1) {
766		switch (optchar) {
767		case 'e':
768			outputformat = "%6e";
769			break;
770		case 'f':
771			readfile = true;
772			if (strlen(optarg) == 0)
773				readunits(NULL);
774			else
775				readunits(optarg);
776			break;
777		case 'H':
778			/* Ignored, for compatibility with GNU units. */
779			break;
780		case 'q':
781			quiet = true;
782			break;
783		case 't':
784			terse = true;
785			break;
786		case 'o':
787			outputformat = optarg;
788			break;
789		case 'v':
790			verbose = true;
791			break;
792		case 'V':
793			fprintf(stderr, "FreeBSD units\n");
794			/* FALLTHROUGH */
795		case 'U':
796			if (access(UNITSFILE, F_OK) == 0)
797				printf("%s\n", UNITSFILE);
798			else
799				printf("Units data file not found");
800			exit(0);
801		case 'h':
802			/* FALLTHROUGH */
803
804		default:
805			usage();
806		}
807	}
808
809	if (!readfile)
810		readunits(NULL);
811
812	if (optind == argc - 2) {
813		if (caph_enter() < 0)
814			err(1, "unable to enter capability mode");
815
816		havestr = argv[optind];
817		wantstr = argv[optind + 1];
818		initializeunit(&have);
819		addunit(&have, havestr, 0, 1);
820		completereduce(&have);
821		initializeunit(&want);
822		addunit(&want, wantstr, 0, 1);
823		completereduce(&want);
824		showanswer(&have, &want);
825	} else {
826		inhistory = history_init();
827		el = el_init(argv[0], stdin, stdout, stderr);
828		el_set(el, EL_PROMPT, &prompt);
829		el_set(el, EL_EDITOR, "emacs");
830		el_set(el, EL_SIGNAL, 1);
831		el_set(el, EL_HIST, history, inhistory);
832		el_source(el, NULL);
833		history(inhistory, &ev, H_SETSIZE, 800);
834		if (inhistory == 0)
835			err(1, "Could not initialize history");
836
837		if (caph_enter() < 0)
838			err(1, "unable to enter capability mode");
839
840		if (!quiet)
841			printf("%d units, %d prefixes\n", unitcount,
842			    prefixcount);
843		while (!quit) {
844			do {
845				initializeunit(&have);
846				if (!quiet)
847					promptstr = "You have: ";
848				havestr = el_gets(el, &inputsz);
849				if (havestr == NULL) {
850					quit = true;
851					break;
852				}
853				if (inputsz > 0)
854					history(inhistory, &ev, H_ENTER,
855					havestr);
856			} while (addunit(&have, havestr, 0, 1) ||
857			    completereduce(&have));
858			if (quit) {
859				break;
860			}
861			do {
862				initializeunit(&want);
863				if (!quiet)
864					promptstr = "You want: ";
865				wantstr = el_gets(el, &inputsz);
866				if (wantstr == NULL) {
867					quit = true;
868					break;
869				}
870				if (inputsz > 0)
871					history(inhistory, &ev, H_ENTER,
872					wantstr);
873			} while (addunit(&want, wantstr, 0, 1) ||
874			    completereduce(&want));
875			if (quit) {
876				break;
877			}
878			showanswer(&have, &want);
879		}
880
881		history_end(inhistory);
882		el_end(el);
883	}
884
885	return (0);
886}
887