jcs's openbsd hax
openbsd
1/* $OpenBSD: printf.c,v 1.28 2024/08/26 03:49:06 deraadt Exp $ */
2
3/*
4 * Copyright (c) 1989 The Regents of the University of California.
5 * 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 <ctype.h>
33#include <err.h>
34#include <errno.h>
35#include <limits.h>
36#include <stdio.h>
37#include <stdlib.h>
38#include <string.h>
39#include <unistd.h>
40
41static int print_escape_str(const char *);
42static int print_escape(const char *);
43
44static int getchr(void);
45static double getdouble(void);
46static int getint(void);
47static long getlong(void);
48static unsigned long getulong(void);
49static char *getstr(void);
50static char *mklong(const char *, int);
51static void check_conversion(const char *, const char *);
52static void __dead usage(void);
53
54static int rval;
55static char **gargv;
56
57#define isodigit(c) ((c) >= '0' && (c) <= '7')
58#define octtobin(c) ((c) - '0')
59#define hextobin(c) ((c) >= 'A' && (c) <= 'F' ? c - 'A' + 10 : (c) >= 'a' && (c) <= 'f' ? c - 'a' + 10 : c - '0')
60
61#define PF(f, func) { \
62 if (havefieldwidth) \
63 if (haveprecision) \
64 (void)printf(f, fieldwidth, precision, func); \
65 else \
66 (void)printf(f, fieldwidth, func); \
67 else if (haveprecision) \
68 (void)printf(f, precision, func); \
69 else \
70 (void)printf(f, func); \
71}
72
73int
74main(int argc, char *argv[])
75{
76 char *fmt, *start;
77 int havefieldwidth, haveprecision;
78 int fieldwidth, precision;
79 char convch, nextch;
80 char *format;
81
82 if (pledge("stdio", NULL) == -1)
83 err(1, "pledge");
84
85 /* Need to accept/ignore "--" option. */
86 if (argc > 1 && strcmp(argv[1], "--") == 0) {
87 argc--;
88 argv++;
89 }
90
91 if (argc < 2)
92 usage();
93
94 format = *++argv;
95 gargv = ++argv;
96
97#define SKIP1 "#-+ 0"
98#define SKIP2 "0123456789"
99 do {
100 /*
101 * Basic algorithm is to scan the format string for conversion
102 * specifications -- once one is found, find out if the field
103 * width or precision is a '*'; if it is, gather up value.
104 * Note, format strings are reused as necessary to use up the
105 * provided arguments, arguments of zero/null string are
106 * provided to use up the format string.
107 */
108
109 /* find next format specification */
110 for (fmt = format; *fmt; fmt++) {
111 switch (*fmt) {
112 case '%':
113 start = fmt++;
114
115 if (*fmt == '%') {
116 putchar('%');
117 break;
118 } else if (*fmt == 'b') {
119 char *p = getstr();
120 if (print_escape_str(p)) {
121 return (rval);
122 }
123 break;
124 }
125
126 /* skip to field width */
127 for (; strchr(SKIP1, *fmt); ++fmt)
128 ;
129 if (*fmt == '*') {
130 ++fmt;
131 havefieldwidth = 1;
132 fieldwidth = getint();
133 } else
134 havefieldwidth = 0;
135
136 /* skip to field precision */
137 for (; strchr(SKIP2, *fmt); ++fmt)
138 ;
139 haveprecision = 0;
140 if (*fmt == '.') {
141 ++fmt;
142 if (*fmt == '*') {
143 ++fmt;
144 haveprecision = 1;
145 precision = getint();
146 }
147 for (; strchr(SKIP2, *fmt); ++fmt)
148 ;
149 }
150
151 if (!*fmt) {
152 warnx("missing format character");
153 return(1);
154 }
155
156 convch = *fmt;
157 nextch = *(fmt + 1);
158 *(fmt + 1) = '\0';
159 switch(convch) {
160 case 'c': {
161 char p = getchr();
162 PF(start, p);
163 break;
164 }
165 case 's': {
166 char *p = getstr();
167 PF(start, p);
168 break;
169 }
170 case 'd':
171 case 'i': {
172 long p;
173 char *f = mklong(start, convch);
174 if (!f) {
175 warnx("out of memory");
176 return (1);
177 }
178 p = getlong();
179 PF(f, p);
180 break;
181 }
182 case 'o':
183 case 'u':
184 case 'x':
185 case 'X': {
186 unsigned long p;
187 char *f = mklong(start, convch);
188 if (!f) {
189 warnx("out of memory");
190 return (1);
191 }
192 p = getulong();
193 PF(f, p);
194 break;
195 }
196 case 'a':
197 case 'A':
198 case 'e':
199 case 'E':
200 case 'f':
201 case 'F':
202 case 'g':
203 case 'G': {
204 double p = getdouble();
205 PF(start, p);
206 break;
207 }
208 default:
209 warnx("%s: invalid directive", start);
210 return(1);
211 }
212 *(fmt + 1) = nextch;
213 break;
214
215 case '\\':
216 fmt += print_escape(fmt);
217 break;
218
219 default:
220 putchar(*fmt);
221 break;
222 }
223 }
224 } while (gargv > argv && *gargv);
225
226 return (rval);
227}
228
229
230/*
231 * Print SysV echo(1) style escape string
232 * Halts processing string and returns 1 if a \c escape is encountered.
233 */
234static int
235print_escape_str(const char *str)
236{
237 int value;
238 int c;
239
240 while (*str) {
241 if (*str == '\\') {
242 str++;
243 /*
244 * %b string octal constants are not like those in C.
245 * They start with a \0, and are followed by 0, 1, 2,
246 * or 3 octal digits.
247 */
248 if (*str == '0') {
249 str++;
250 for (c = 3, value = 0; c-- && isodigit(*str); str++) {
251 value <<= 3;
252 value += octtobin(*str);
253 }
254 putchar(value);
255 str--;
256 } else if (*str == 'c') {
257 return 1;
258 } else {
259 str--;
260 str += print_escape(str);
261 }
262 } else {
263 putchar(*str);
264 }
265 str++;
266 }
267
268 return 0;
269}
270
271/*
272 * Print "standard" escape characters
273 */
274static int
275print_escape(const char *str)
276{
277 const char *start = str;
278 int value = 0;
279 int c;
280
281 str++;
282
283 switch (*str) {
284 case '0': case '1': case '2': case '3':
285 case '4': case '5': case '6': case '7':
286 for (c = 3; c-- && isodigit(*str); str++) {
287 value <<= 3;
288 value += octtobin(*str);
289 }
290 putchar(value);
291 return str - start - 1;
292 /* NOTREACHED */
293
294 case 'x':
295 str++;
296 for (c = 2; c-- && isxdigit((unsigned char)*str); str++) {
297 value <<= 4;
298 value += hextobin(*str);
299 }
300 putchar(value);
301 return str - start - 1;
302 /* NOTREACHED */
303
304 case '\\': /* backslash */
305 putchar('\\');
306 break;
307
308 case '\'': /* single quote */
309 putchar('\'');
310 break;
311
312 case '"': /* double quote */
313 putchar('"');
314 break;
315
316 case 'a': /* alert */
317 putchar('\a');
318 break;
319
320 case 'b': /* backspace */
321 putchar('\b');
322 break;
323
324 case 'e': /* escape */
325#ifdef __GNUC__
326 putchar('\e');
327#else
328 putchar(033);
329#endif
330 break;
331
332 case 'f': /* form-feed */
333 putchar('\f');
334 break;
335
336 case 'n': /* newline */
337 putchar('\n');
338 break;
339
340 case 'r': /* carriage-return */
341 putchar('\r');
342 break;
343
344 case 't': /* tab */
345 putchar('\t');
346 break;
347
348 case 'v': /* vertical-tab */
349 putchar('\v');
350 break;
351
352 case '\0':
353 warnx("null escape sequence");
354 rval = 1;
355 return 0;
356
357 default:
358 putchar(*str);
359 warnx("unknown escape sequence `\\%c'", *str);
360 rval = 1;
361 }
362
363 return 1;
364}
365
366static char *
367mklong(const char *str, int ch)
368{
369 static char *copy;
370 static int copysize;
371 int len;
372
373 len = strlen(str) + 2;
374 if (copysize < len) {
375 char *newcopy;
376 copysize = len + 256;
377
378 newcopy = realloc(copy, copysize);
379 if (newcopy == NULL) {
380 copysize = 0;
381 free(copy);
382 copy = NULL;
383 return (NULL);
384 }
385 copy = newcopy;
386 }
387 (void) memmove(copy, str, len - 3);
388 copy[len - 3] = 'l';
389 copy[len - 2] = ch;
390 copy[len - 1] = '\0';
391 return (copy);
392}
393
394static int
395getchr(void)
396{
397 if (!*gargv)
398 return((int)'\0');
399 return((int)**gargv++);
400}
401
402static char *
403getstr(void)
404{
405 if (!*gargv)
406 return("");
407 return(*gargv++);
408}
409
410static char *number = "+-.0123456789";
411static int
412getint(void)
413{
414 if (!*gargv)
415 return(0);
416
417 if (strchr(number, **gargv))
418 return(atoi(*gargv++));
419
420 return 0;
421}
422
423static long
424getlong(void)
425{
426 long val;
427 char *ep;
428
429 if (!*gargv)
430 return(0L);
431
432 if (**gargv == '\"' || **gargv == '\'')
433 return (unsigned char) *((*gargv++)+1);
434
435 errno = 0;
436 val = strtol(*gargv, &ep, 0);
437 check_conversion(*gargv++, ep);
438 return val;
439}
440
441static unsigned long
442getulong(void)
443{
444 unsigned long val;
445 char *ep;
446
447 if (!*gargv)
448 return(0UL);
449
450 if (**gargv == '\"' || **gargv == '\'')
451 return (unsigned char) *((*gargv++)+1);
452
453 errno = 0;
454 val = strtoul(*gargv, &ep, 0);
455 check_conversion(*gargv++, ep);
456 return val;
457}
458
459static double
460getdouble(void)
461{
462 double val;
463 char *ep;
464
465 if (!*gargv)
466 return(0.0);
467
468 if (**gargv == '\"' || **gargv == '\'')
469 return (unsigned char) *((*gargv++)+1);
470
471 errno = 0;
472 val = strtod(*gargv, &ep);
473 check_conversion(*gargv++, ep);
474 return val;
475}
476
477static void
478check_conversion(const char *s, const char *ep)
479{
480 if (*ep) {
481 if (ep == s)
482 warnx("%s: expected numeric value", s);
483 else
484 warnx("%s: not completely converted", s);
485 rval = 1;
486 } else if (errno == ERANGE) {
487 warnc(ERANGE, "%s", s);
488 rval = 1;
489 }
490}
491
492static void __dead
493usage(void)
494{
495 (void)fprintf(stderr, "usage: printf format [argument ...]\n");
496 exit(1);
497}