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