1 /*
2  * Copyright (c) 1989, 1993
3  *	The Regents of the University of California.  All rights reserved.
4  * Copyright (c) 1997-2005
5  *	Herbert Xu <herbert@gondor.apana.org.au>.  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/types.h>
33 
34 #include <ctype.h>
35 #include <errno.h>
36 #include <inttypes.h>
37 #include <limits.h>
38 #include <stdarg.h>
39 #include <stdlib.h>
40 #include <string.h>
41 #include <unistd.h>
42 
43 static int	 conv_escape_str(char *, char **);
44 static char	*conv_escape(char *, int *);
45 static int	 getchr(void);
46 static double	 getdouble(void);
47 static uintmax_t getuintmax(int);
48 static char	*getstr(void);
49 static char	*mklong(const char *, const char *);
50 static void      check_conversion(const char *, const char *);
51 
52 static int	rval;
53 static char  **gargv;
54 
55 #define isodigit(c)	((c) >= '0' && (c) <= '7')
56 #define octtobin(c)	((c) - '0')
57 
58 #include "bltin.h"
59 #include "system.h"
60 
61 #define PF(f, func) { \
62 	switch ((char *)param - (char *)array) { \
63 	default: \
64 		(void)printf(f, array[0], array[1], func); \
65 		break; \
66 	case sizeof(*param): \
67 		(void)printf(f, array[0], func); \
68 		break; \
69 	case 0: \
70 		(void)printf(f, func); \
71 		break; \
72 	} \
73 }
74 
75 #define ASPF(sp, f, func) ({ \
76 	int ret; \
77 	switch ((char *)param - (char *)array) { \
78 	default: \
79 		ret = xasprintf(sp, f, array[0], array[1], func); \
80 		break; \
81 	case sizeof(*param): \
82 		ret = xasprintf(sp, f, array[0], func); \
83 		break; \
84 	case 0: \
85 		ret = xasprintf(sp, f, func); \
86 		break; \
87 	} \
88 	ret; \
89 })
90 
91 
print_escape_str(const char * f,int * param,int * array,char * s)92 static int print_escape_str(const char *f, int *param, int *array, char *s)
93 {
94 	struct stackmark smark;
95 	char *p, *q;
96 	int done;
97 	int len;
98 	int total;
99 
100 	setstackmark(&smark);
101 	done = conv_escape_str(s, &p);
102 	q = stackblock();
103 	len = p - q;
104 
105 	p = makestrspace(len, p);
106 	memset(p, 'X', len - 1);
107 	p[len - 1] = 0;
108 
109 	q = stackblock();
110 	total = ASPF(&p, f, p);
111 
112 	len = strchrnul(p, 'X') - p;
113 	memcpy(p + len, q, strchrnul(p + len, ' ') - (p + len));
114 
115 	out1mem(p, total);
116 
117 	popstackmark(&smark);
118 	return done;
119 }
120 
121 
printfcmd(int argc,char * argv[])122 int printfcmd(int argc, char *argv[])
123 {
124 	char *fmt;
125 	char *format;
126 	int ch;
127 
128 	rval = 0;
129 
130 	nextopt(nullstr);
131 
132 	argv = argptr;
133 	format = *argv;
134 
135 	if (!format)
136 		error("usage: printf format [arg ...]");
137 
138 	gargv = ++argv;
139 
140 #define SKIP1	"#-+ 0"
141 #define SKIP2	"*0123456789"
142 	do {
143 		/*
144 		 * Basic algorithm is to scan the format string for conversion
145 		 * specifications -- once one is found, find out if the field
146 		 * width or precision is a '*'; if it is, gather up value.
147 		 * Note, format strings are reused as necessary to use up the
148 		 * provided arguments, arguments of zero/null string are
149 		 * provided to use up the format string.
150 		 */
151 
152 		/* find next format specification */
153 		for (fmt = format; (ch = *fmt++) ;) {
154 			char *start;
155 			char nextch;
156 			int array[2];
157 			int *param;
158 
159 			if (ch == '\\') {
160 				int c_ch;
161 				fmt = conv_escape(fmt, &c_ch);
162 				ch = c_ch;
163 				goto pc;
164 			}
165 			if (ch != '%' || (*fmt == '%' && (++fmt || 1))) {
166 pc:
167 				putchar(ch);
168 				continue;
169 			}
170 
171 			/* Ok - we've found a format specification,
172 			   Save its address for a later printf(). */
173 			start = fmt - 1;
174 			param = array;
175 
176 			/* skip to field width */
177 			fmt += strspn(fmt, SKIP1);
178 			if (*fmt == '*') {
179 				++fmt;
180 				*param++ = getuintmax(1);
181 			} else {
182 				/* skip to possible '.',
183 				 * get following precision
184 				 */
185 				fmt += strspn(fmt, SKIP2);
186 			}
187 
188 			if (*fmt == '.') {
189 				++fmt;
190 				if (*fmt == '*') {
191 					++fmt;
192 					*param++ = getuintmax(1);
193 				} else
194 					fmt += strspn(fmt, SKIP2);
195 			}
196 
197 			ch = *fmt;
198 			if (!ch)
199 				error("missing format character");
200 			/* null terminate format string to we can use it
201 			   as an argument to printf. */
202 			nextch = fmt[1];
203 			fmt[1] = 0;
204 			switch (ch) {
205 
206 			case 'b':
207 				*fmt = 's';
208 				/* escape if a \c was encountered */
209 				if (print_escape_str(start, param, array,
210 						     getstr()))
211 					goto out;
212 				break;
213 			case 'c': {
214 				int p = getchr();
215 				PF(start, p);
216 				break;
217 			}
218 			case 's': {
219 				char *p = getstr();
220 				PF(start, p);
221 				break;
222 			}
223 			case 'd':
224 			case 'i': {
225 				uintmax_t p = getuintmax(1);
226 				start = mklong(start, fmt);
227 				PF(start, p);
228 				break;
229 			}
230 			case 'o':
231 			case 'u':
232 			case 'x':
233 			case 'X': {
234 				uintmax_t p = getuintmax(0);
235 				start = mklong(start, fmt);
236 				PF(start, p);
237 				break;
238 			}
239 			case 'a':
240 			case 'A':
241 			case 'e':
242 			case 'E':
243 			case 'f':
244 			case 'F':
245 			case 'g':
246 			case 'G': {
247 				double p = getdouble();
248 				PF(start, p);
249 				break;
250 			}
251 			default:
252 				error("%s: invalid directive", start);
253 			}
254 			*++fmt = nextch;
255 		}
256 	} while (gargv != argv && *gargv);
257 
258 out:
259 	return rval;
260 }
261 
262 
263 /*
264  * Print SysV echo(1) style escape string
265  *	Halts processing string if a \c escape is encountered.
266  */
267 static int
conv_escape_str(char * str,char ** sp)268 conv_escape_str(char *str, char **sp)
269 {
270 	int c;
271 	int ch;
272 	char *cp;
273 
274 	/* convert string into a temporary buffer... */
275 	STARTSTACKSTR(cp);
276 
277 	do {
278 		c = ch = *str++;
279 		if (ch != '\\')
280 			continue;
281 
282 		c = *str++;
283 		if (c == 'c') {
284 			/* \c as in SYSV echo - abort all processing.... */
285 			c = ch = 0x100;
286 			continue;
287 		}
288 
289 		/*
290 		 * %b string octal constants are not like those in C.
291 		 * They start with a \0, and are followed by 0, 1, 2,
292 		 * or 3 octal digits.
293 		 */
294 		if (c == '0' && isodigit(*str))
295 			str++;
296 
297 		/* Finally test for sequences valid in the format string */
298 		str = conv_escape(str - 1, &c);
299 	} while (STPUTC(c, cp), (char)ch);
300 
301 	*sp = cp;
302 
303 	return ch;
304 }
305 
306 /*
307  * Print "standard" escape characters
308  */
309 static char *
conv_escape(char * str,int * conv_ch)310 conv_escape(char *str, int *conv_ch)
311 {
312 	int value;
313 	int ch;
314 
315 	ch = *str;
316 
317 	switch (ch) {
318 	default:
319 		if (!isodigit(*str)) {
320 			value = '\\';
321 			goto out;
322 		}
323 
324 		ch = 3;
325 		value = 0;
326 		do {
327 			value <<= 3;
328 			value += octtobin(*str++);
329 		} while (isodigit(*str) && --ch);
330 		goto out;
331 
332 	case '\\':	value = '\\';	break;	/* backslash */
333 	case 'a':	value = '\a';	break;	/* alert */
334 	case 'b':	value = '\b';	break;	/* backspace */
335 	case 'f':	value = '\f';	break;	/* form-feed */
336 	case 'n':	value = '\n';	break;	/* newline */
337 	case 'r':	value = '\r';	break;	/* carriage-return */
338 	case 't':	value = '\t';	break;	/* tab */
339 	case 'v':	value = '\v';	break;	/* vertical-tab */
340 	}
341 
342 	str++;
343 out:
344 	*conv_ch = value;
345 	return str;
346 }
347 
348 static char *
mklong(const char * str,const char * ch)349 mklong(const char *str, const char *ch)
350 {
351 	/*
352 	 * Replace a string like "%92.3u" with "%92.3"PRIuMAX.
353 	 *
354 	 * Although C99 does not guarantee it, we assume PRIiMAX,
355 	 * PRIoMAX, PRIuMAX, PRIxMAX, and PRIXMAX are all the same
356 	 * as PRIdMAX with the final 'd' replaced by the corresponding
357 	 * character.
358 	 */
359 
360 	char *copy;
361 	size_t len;
362 
363 	len = ch - str + sizeof(PRIdMAX);
364 	STARTSTACKSTR(copy);
365 	copy = makestrspace(len, copy);
366 	memcpy(copy, str, len - sizeof(PRIdMAX));
367 	memcpy(copy + len - sizeof(PRIdMAX), PRIdMAX, sizeof(PRIdMAX));
368 	copy[len - 2] = *ch;
369 	return (copy);
370 }
371 
372 static int
getchr(void)373 getchr(void)
374 {
375 	int val = 0;
376 
377 	if (*gargv)
378 		val = **gargv++;
379 	return val;
380 }
381 
382 static char *
getstr(void)383 getstr(void)
384 {
385 	char *val = nullstr;
386 
387 	if (*gargv)
388 		val = *gargv++;
389 	return val;
390 }
391 
392 static uintmax_t
getuintmax(int sign)393 getuintmax(int sign)
394 {
395 	uintmax_t val = 0;
396 	char *cp, *ep;
397 
398 	cp = *gargv;
399 	if (cp == NULL)
400 		goto out;
401 	gargv++;
402 
403 	val = (unsigned char) cp[1];
404 	if (*cp == '\"' || *cp == '\'')
405 		goto out;
406 
407 	errno = 0;
408 	val = sign ? strtoimax(cp, &ep, 0) : strtoumax(cp, &ep, 0);
409 	check_conversion(cp, ep);
410 out:
411 	return val;
412 }
413 
414 static double
getdouble(void)415 getdouble(void)
416 {
417 	double val;
418 	char *cp, *ep;
419 
420 	cp = *gargv;
421 	if (cp == NULL)
422 		return 0;
423 	gargv++;
424 
425 	if (*cp == '\"' || *cp == '\'')
426 		return (unsigned char) cp[1];
427 
428 	errno = 0;
429 	val = strtod(cp, &ep);
430 	check_conversion(cp, ep);
431 	return val;
432 }
433 
434 static void
check_conversion(const char * s,const char * ep)435 check_conversion(const char *s, const char *ep)
436 {
437 	if (*ep) {
438 		if (ep == s)
439 			warnx("%s: expected numeric value", s);
440 		else
441 			warnx("%s: not completely converted", s);
442 		rval = 1;
443 	} else if (errno == ERANGE) {
444 		warnx("%s: %s", s, strerror(ERANGE));
445 		rval = 1;
446 	}
447 }
448 
449 int
echocmd(int argc,char ** argv)450 echocmd(int argc, char **argv)
451 {
452 	int nonl;
453 
454 	nonl = *++argv ? equal(*argv, "-n") : 0;
455 	argv += nonl;
456 
457 	do {
458 		int c;
459 
460 		if (likely(*argv))
461 			nonl += print_escape_str("%s", NULL, NULL, *argv++);
462 		if (likely((nonl + !*argv) > 1))
463 			break;
464 
465 		c = *argv ? ' ' : '\n';
466 		out1c(c);
467 	} while (*argv);
468 	return 0;
469 }
470