xref: /netbsd-src/usr.bin/jot/jot.c (revision f3cfa6f6ce31685c6c4a758bc430e69eb99f50a4)
1 /*	$NetBSD: jot.c,v 1.27 2019/02/03 03:19:29 mrg Exp $	*/
2 
3 /*-
4  * Copyright (c) 1993
5  *	The Regents of the University of California.  All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  * 3. Neither the name of the University nor the names of its contributors
16  *    may be used to endorse or promote products derived from this software
17  *    without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
20  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
23  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29  * SUCH DAMAGE.
30  */
31 
32 #include <sys/cdefs.h>
33 #ifndef lint
34 __COPYRIGHT("@(#) Copyright (c) 1993\
35  The Regents of the University of California.  All rights reserved.");
36 #endif /* not lint */
37 
38 #ifndef lint
39 #if 0
40 static char sccsid[] = "@(#)jot.c	8.1 (Berkeley) 6/6/93";
41 #endif
42 __RCSID("$NetBSD: jot.c,v 1.27 2019/02/03 03:19:29 mrg Exp $");
43 #endif /* not lint */
44 
45 /*
46  * jot - print sequential or random data
47  *
48  * Author:  John Kunze, Office of Comp. Affairs, UCB
49  */
50 
51 #include <ctype.h>
52 #include <err.h>
53 #include <limits.h>
54 #include <math.h>
55 #include <stdio.h>
56 #include <stdlib.h>
57 #include <string.h>
58 #include <time.h>
59 #include <unistd.h>
60 
61 #define	REPS_DEF	100
62 #define	BEGIN_DEF	1
63 #define	ENDER_DEF	100
64 #define	STEP_DEF	1
65 
66 #define	is_default(s)	(strcmp((s), "-") == 0)
67 
68 static double	begin = BEGIN_DEF;
69 static double	ender = ENDER_DEF;
70 static double	step = STEP_DEF;
71 static long	reps = REPS_DEF;
72 static int	randomize;
73 static int	boring;
74 static int	prec = -1;
75 static int	dox;
76 static int	chardata;
77 static int	nofinalnl;
78 static const char *sepstring = "\n";
79 static char	format[BUFSIZ];
80 
81 static void	getargs(int, char *[]);
82 static void	getformat(void);
83 static int	getprec(char *);
84 static void	putdata(double, long);
85 static void	usage(void) __dead;
86 
87 int
88 main(int argc, char *argv[])
89 {
90 	double	x;
91 	long	i;
92 
93 	getargs(argc, argv);
94 	if (randomize) {
95 		x = ender - begin;
96 		if (x < 0) {
97 			x = -x;
98 			begin = ender;
99 		}
100 		if (dox == 0)
101 			/*
102 			 * We are printing floating point, generate random
103 			 * number that include both supplied limits.
104 			 * Due to FP routing for display the low and high
105 			 * values are likely to occur half as often as all
106 			 * the others.
107 			 */
108 			x /= (1u << 31) - 1.0;
109 		else {
110 			/*
111 			 * We are printing integers increase the range by
112 			 * one but ensure we never generate it.
113 			 * This makes all the integer values equally likely.
114 			 */
115 			x += 1.0;
116 			x /= (1u << 31);
117 		}
118 		srandom((unsigned long) step);
119 		for (i = 1; i <= reps || reps == 0; i++)
120 			putdata(random() * x + begin, reps - i);
121 	} else {
122 		/*
123 		 * If we are going to display as integer, add 0.5 here
124 		 * and use floor(x) later to get sane rounding.
125 		 */
126 		x = begin;
127 		if (dox)
128 			x += 0.5;
129 		for (i = 1; i <= reps || reps == 0; i++, x += step)
130 			putdata(x, reps - i);
131 	}
132 	if (!nofinalnl)
133 		putchar('\n');
134 	exit(0);
135 }
136 
137 static void
138 getargs(int argc, char *argv[])
139 {
140 	unsigned int have = 0;
141 #define BEGIN	1
142 #define	STEP	2	/* seed if -r */
143 #define REPS	4
144 #define	ENDER	8
145 	int n = 0;
146 	long t;
147 	char *ep;
148 
149 	for (;;) {
150 		switch (getopt(argc, argv, "b:cnp:rs:w:")) {
151 		default:
152 			usage();
153 		case -1:
154 			break;
155 		case 'c':
156 			chardata = 1;
157 			continue;
158 		case 'n':
159 			nofinalnl = 1;
160 			continue;
161 		case 'p':
162 			prec = strtol(optarg, &ep, 0);
163 			if (*ep != 0 || prec < 0)
164 				errx(EXIT_FAILURE, "Bad precision value");
165 			continue;
166 		case 'r':
167 			randomize = 1;
168 			continue;
169 		case 's':
170 			sepstring = optarg;
171 			continue;
172 		case 'b':
173 			boring = 1;
174 			/* FALLTHROUGH */
175 		case 'w':
176 			strlcpy(format, optarg, sizeof(format));
177 			continue;
178 		}
179 		break;
180 	}
181 	argc -= optind;
182 	argv += optind;
183 
184 	switch (argc) {	/* examine args right to left, falling thru cases */
185 	case 4:
186 		if (!is_default(argv[3])) {
187 			step = strtod(argv[3], &ep);
188 			if (*ep != 0)
189 				errx(EXIT_FAILURE, "Bad step value:  %s",
190 				    argv[3]);
191 			have |= STEP;
192 		}
193 		/* FALLTHROUGH */
194 	case 3:
195 		if (!is_default(argv[2])) {
196 			if (!sscanf(argv[2], "%lf", &ender))
197 				ender = argv[2][strlen(argv[2])-1];
198 			have |= ENDER;
199 			if (prec < 0)
200 				n = getprec(argv[2]);
201 		}
202 		/* FALLTHROUGH */
203 	case 2:
204 		if (!is_default(argv[1])) {
205 			if (!sscanf(argv[1], "%lf", &begin))
206 				begin = argv[1][strlen(argv[1])-1];
207 			have |= BEGIN;
208 			if (prec < 0)
209 				prec = getprec(argv[1]);
210 			if (n > prec)		/* maximum precision */
211 				prec = n;
212 		}
213 		/* FALLTHROUGH */
214 	case 1:
215 		if (!is_default(argv[0])) {
216 			reps = strtoul(argv[0], &ep, 0);
217 			if (*ep != 0 || reps < 0)
218 				errx(EXIT_FAILURE, "Bad reps value:  %s",
219 				    argv[0]);
220 			have |= REPS;
221 		}
222 		/* FALLTHROUGH */
223 	case 0:
224 		break;
225 	default:
226 		errx(EXIT_FAILURE,
227 		    "Too many arguments.  What do you mean by %s?", argv[4]);
228 	}
229 	getformat();
230 
231 	if (prec == -1)
232 		prec = 0;
233 
234 	if (randomize) {
235 		/* 'step' is the seed here, use pseudo-random default */
236 		if (!(have & STEP))
237 			step = time(NULL) * getpid();
238 		/* Take the default values for everything else */
239 		return;
240 	}
241 
242 	/*
243 	 * The loop we run uses begin/step/reps, so if we have been
244 	 * given an end value (ender) we must use it to replace the
245 	 * default values of the others.
246 	 * We will assume a begin of 0 and step of 1 if necessary.
247 	 */
248 
249 	switch (have) {
250 
251 	case ENDER | STEP:
252 	case ENDER | STEP | BEGIN:
253 		/* Calculate reps */
254 		if (step == 0.0)
255 			reps = 0;	/* ie infinite */
256 		else {
257 			reps = (ender - begin + step) / step;
258 			if (reps <= 0)
259 				errx(EXIT_FAILURE, "Impossible stepsize");
260 		}
261 		break;
262 
263 	case REPS | ENDER:
264 	case REPS | ENDER | STEP:
265 		/* Calculate begin */
266 		if (reps == 0)
267 			errx(EXIT_FAILURE,
268 			    "Must specify begin if reps == 0");
269 		begin = ender - reps * step + step;
270 		break;
271 
272 	case REPS | BEGIN | ENDER:
273 		/* Calculate step */
274 		if (reps == 0)
275 			errx(EXIT_FAILURE,
276 			    "Infinite sequences cannot be bounded");
277 		if (reps == 1)
278 			step = 0.0;
279 		else
280 			step = (ender - begin) / (reps - 1);
281 		break;
282 
283 	case REPS | BEGIN | ENDER | STEP:
284 		/* reps given and implied - take smaller */
285 		if (step == 0.0)
286 			break;
287 		t = (ender - begin + step) / step;
288 		if (t <= 0)
289 			errx(EXIT_FAILURE,
290 			    "Impossible stepsize");
291 		if (t < reps)
292 			reps = t;
293 		break;
294 
295 	default:
296 		/* No values can be calculated, use defaults */
297 		break;
298 	}
299 }
300 
301 static void
302 putdata(double x, long notlast)
303 {
304 
305 	if (boring)				/* repeated word */
306 		printf("%s", format);
307 	else if (dox)				/* scalar */
308 		printf(format, (long)floor(x));
309 	else					/* real */
310 		printf(format, x);
311 	if (notlast != 0)
312 		fputs(sepstring, stdout);
313 }
314 
315 __dead static void
316 usage(void)
317 {
318 	(void)fprintf(stderr, "usage: %s [-cnr] [-b word] [-p precision] "
319 	    "[-s string] [-w word] [reps [begin [end [step | seed]]]]\n",
320 	    getprogname());
321 	exit(1);
322 }
323 
324 static int
325 getprec(char *num_str)
326 {
327 
328 	num_str = strchr(num_str, '.');
329 	if (num_str == NULL)
330 		return 0;
331 	return strspn(num_str + 1, "0123456789");
332 }
333 
334 static void
335 getformat(void)
336 {
337 	char	*p;
338 	size_t	sz;
339 
340 	if (boring)				/* no need to bother */
341 		return;
342 	for (p = format; *p; p++) {		/* look for '%' */
343 		if (*p == '%') {
344 			if (*(p+1) != '%')
345 				break;
346 			p++;		/* leave %% alone */
347 		}
348 	}
349 	sz = sizeof(format) - strlen(format) - 1;
350 	if (!*p) {
351 		if (chardata || prec == 0) {
352 			if ((size_t)snprintf(p, sz, "%%%s", chardata ? "c" : "ld") >= sz)
353 				errx(EXIT_FAILURE, "-w word too long");
354 			dox = 1;
355 		} else {
356 			if (snprintf(p, sz, "%%.%df", prec) >= (int)sz)
357 				errx(EXIT_FAILURE, "-w word too long");
358 		}
359 	} else if (!*(p+1)) {
360 		if (sz <= 0)
361 			errx(EXIT_FAILURE, "-w word too long");
362 		strcat(format, "%");		/* cannot end in single '%' */
363 	} else {
364 		p++;				/* skip leading % */
365 		for(; *p && !isalpha((unsigned char)*p); p++) {
366 			/* allow all valid printf(3) flags, but deny '*' */
367 			if (!strchr("0123456789#-+. ", *p))
368 				break;
369 		}
370 		/* Allow 'l' prefix, but no other. */
371 		if (*p == 'l')
372 			p++;
373 		switch (*p) {
374 		case 'f': case 'e': case 'g': case '%':
375 		case 'E': case 'G':
376 			break;
377 		case 's':
378 			errx(EXIT_FAILURE,
379 			    "cannot convert numeric data to strings");
380 			break;
381 		case 'd': case 'o': case 'x': case 'u':
382 		case 'D': case 'O': case 'X': case 'U':
383 		case 'c': case 'i':
384 			dox = 1;
385 			break;
386 		default:
387 			errx(EXIT_FAILURE, "unknown or invalid format `%s'",
388 			    format);
389 		}
390 		/* Need to check for trailing stuff to print */
391 		for (; *p; p++)		/* look for '%' */
392 			if (*p == '%') {
393 				if (*(p+1) != '%')
394 					break;
395 				p++;		/* leave %% alone */
396 			}
397 		if (*p)
398 			errx(EXIT_FAILURE, "unknown or invalid format `%s'",
399 			    format);
400 	}
401 }
402