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