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