xref: /netbsd-src/usr.bin/jot/jot.c (revision 5dc82c03ce99695d67af1c2ddabb6cfdc55e56f3)
1 /*	$NetBSD: jot.c,v 1.28 2020/06/14 01:26:46 kamil 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.28 2020/06/14 01:26:46 kamil 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
main(int argc,char * argv[])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
getargs(int argc,char * argv[])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 
230 	if (prec == -1)
231 		prec = 0;
232 
233 	getformat();
234 
235 	if (randomize) {
236 		/* 'step' is the seed here, use pseudo-random default */
237 		if (!(have & STEP))
238 			step = time(NULL) * getpid();
239 		/* Take the default values for everything else */
240 		return;
241 	}
242 
243 	/*
244 	 * The loop we run uses begin/step/reps, so if we have been
245 	 * given an end value (ender) we must use it to replace the
246 	 * default values of the others.
247 	 * We will assume a begin of 0 and step of 1 if necessary.
248 	 */
249 
250 	switch (have) {
251 
252 	case ENDER | STEP:
253 	case ENDER | STEP | BEGIN:
254 		/* Calculate reps */
255 		if (step == 0.0)
256 			reps = 0;	/* ie infinite */
257 		else {
258 			reps = (ender - begin + step) / step;
259 			if (reps <= 0)
260 				errx(EXIT_FAILURE, "Impossible stepsize");
261 		}
262 		break;
263 
264 	case REPS | ENDER:
265 	case REPS | ENDER | STEP:
266 		/* Calculate begin */
267 		if (reps == 0)
268 			errx(EXIT_FAILURE,
269 			    "Must specify begin if reps == 0");
270 		begin = ender - reps * step + step;
271 		break;
272 
273 	case REPS | BEGIN | ENDER:
274 		/* Calculate step */
275 		if (reps == 0)
276 			errx(EXIT_FAILURE,
277 			    "Infinite sequences cannot be bounded");
278 		if (reps == 1)
279 			step = 0.0;
280 		else
281 			step = (ender - begin) / (reps - 1);
282 		break;
283 
284 	case REPS | BEGIN | ENDER | STEP:
285 		/* reps given and implied - take smaller */
286 		if (step == 0.0)
287 			break;
288 		t = (ender - begin + step) / step;
289 		if (t <= 0)
290 			errx(EXIT_FAILURE,
291 			    "Impossible stepsize");
292 		if (t < reps)
293 			reps = t;
294 		break;
295 
296 	default:
297 		/* No values can be calculated, use defaults */
298 		break;
299 	}
300 }
301 
302 static void
putdata(double x,long notlast)303 putdata(double x, long notlast)
304 {
305 
306 	if (boring)				/* repeated word */
307 		printf("%s", format);
308 	else if (dox)				/* scalar */
309 		printf(format, (long)floor(x));
310 	else					/* real */
311 		printf(format, x);
312 	if (notlast != 0)
313 		fputs(sepstring, stdout);
314 }
315 
316 __dead static void
usage(void)317 usage(void)
318 {
319 	(void)fprintf(stderr, "usage: %s [-cnr] [-b word] [-p precision] "
320 	    "[-s string] [-w word] [reps [begin [end [step | seed]]]]\n",
321 	    getprogname());
322 	exit(1);
323 }
324 
325 static int
getprec(char * num_str)326 getprec(char *num_str)
327 {
328 
329 	num_str = strchr(num_str, '.');
330 	if (num_str == NULL)
331 		return 0;
332 	return strspn(num_str + 1, "0123456789");
333 }
334 
335 static void
getformat(void)336 getformat(void)
337 {
338 	char	*p;
339 	size_t	sz;
340 
341 	if (boring)				/* no need to bother */
342 		return;
343 	for (p = format; *p; p++) {		/* look for '%' */
344 		if (*p == '%') {
345 			if (*(p+1) != '%')
346 				break;
347 			p++;		/* leave %% alone */
348 		}
349 	}
350 	sz = sizeof(format) - strlen(format) - 1;
351 	if (!*p) {
352 		if (chardata || prec == 0) {
353 			if ((size_t)snprintf(p, sz, "%%%s", chardata ? "c" : "ld") >= sz)
354 				errx(EXIT_FAILURE, "-w word too long");
355 			dox = 1;
356 		} else {
357 			if (snprintf(p, sz, "%%.%df", prec) >= (int)sz)
358 				errx(EXIT_FAILURE, "-w word too long");
359 		}
360 	} else if (!*(p+1)) {
361 		if (sz <= 0)
362 			errx(EXIT_FAILURE, "-w word too long");
363 		strcat(format, "%");		/* cannot end in single '%' */
364 	} else {
365 		p++;				/* skip leading % */
366 		for(; *p && !isalpha((unsigned char)*p); p++) {
367 			/* allow all valid printf(3) flags, but deny '*' */
368 			if (!strchr("0123456789#-+. ", *p))
369 				break;
370 		}
371 		/* Allow 'l' prefix, but no other. */
372 		if (*p == 'l')
373 			p++;
374 		switch (*p) {
375 		case 'f': case 'e': case 'g': case '%':
376 		case 'E': case 'G':
377 			break;
378 		case 's':
379 			errx(EXIT_FAILURE,
380 			    "cannot convert numeric data to strings");
381 			break;
382 		case 'd': case 'o': case 'x': case 'u':
383 		case 'D': case 'O': case 'X': case 'U':
384 		case 'c': case 'i':
385 			dox = 1;
386 			break;
387 		default:
388 			errx(EXIT_FAILURE, "unknown or invalid format `%s'",
389 			    format);
390 		}
391 		/* Need to check for trailing stuff to print */
392 		for (; *p; p++)		/* look for '%' */
393 			if (*p == '%') {
394 				if (*(p+1) != '%')
395 					break;
396 				p++;		/* leave %% alone */
397 			}
398 		if (*p)
399 			errx(EXIT_FAILURE, "unknown or invalid format `%s'",
400 			    format);
401 	}
402 }
403