• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * vsnprintf.c
3  *
4  * vsnprintf(), from which the rest of the printf()
5  * family is built
6  */
7 
8 #include <stdarg.h>
9 #include <stddef.h>
10 #include <inttypes.h>
11 #include <string.h>
12 #include <limits.h>
13 #include <stdio.h>
14 
15 enum flags {
16     FL_ZERO = 0x01,		/* Zero modifier */
17     FL_MINUS = 0x02,		/* Minus modifier */
18     FL_PLUS = 0x04,		/* Plus modifier */
19     FL_TICK = 0x08,		/* ' modifier */
20     FL_SPACE = 0x10,		/* Space modifier */
21     FL_HASH = 0x20,		/* # modifier */
22     FL_SIGNED = 0x40,		/* Number is signed */
23     FL_UPPER = 0x80		/* Upper case digits */
24 };
25 
26 /* These may have to be adjusted on certain implementations */
27 enum ranks {
28     rank_char = -2,
29     rank_short = -1,
30     rank_int = 0,
31     rank_long = 1,
32     rank_longlong = 2
33 };
34 
35 #define MIN_RANK	rank_char
36 #define MAX_RANK	rank_longlong
37 
38 #define INTMAX_RANK	rank_longlong
39 #define SIZE_T_RANK	rank_long
40 #define PTRDIFF_T_RANK	rank_long
41 
42 #define EMIT(x) ({ if (o<n){*q++ = (x);} o++; })
43 
44 static size_t
format_int(char * q,size_t n,uintmax_t val,enum flags flags,int base,int width,int prec)45 format_int(char *q, size_t n, uintmax_t val, enum flags flags,
46 	   int base, int width, int prec)
47 {
48     char *qq;
49     size_t o = 0, oo;
50     static const char lcdigits[] = "0123456789abcdef";
51     static const char ucdigits[] = "0123456789ABCDEF";
52     const char *digits;
53     uintmax_t tmpval;
54     int minus = 0;
55     int ndigits = 0, nchars;
56     int tickskip, b4tick;
57 
58     /* Select type of digits */
59     digits = (flags & FL_UPPER) ? ucdigits : lcdigits;
60 
61     /* If signed, separate out the minus */
62     if (flags & FL_SIGNED && (intmax_t) val < 0) {
63 	minus = 1;
64 	val = (uintmax_t) (-(intmax_t) val);
65     }
66 
67     /* Count the number of digits needed.  This returns zero for 0. */
68     tmpval = val;
69     while (tmpval) {
70 	tmpval /= base;
71 	ndigits++;
72     }
73 
74     /* Adjust ndigits for size of output */
75 
76     if (flags & FL_HASH && base == 8) {
77 	if (prec < ndigits + 1)
78 	    prec = ndigits + 1;
79     }
80 
81     if (ndigits < prec) {
82 	ndigits = prec;		/* Mandatory number padding */
83     } else if (val == 0) {
84 	ndigits = 1;		/* Zero still requires space */
85     }
86 
87     /* For ', figure out what the skip should be */
88     if (flags & FL_TICK) {
89 	tickskip = (base == 16) ? 4 : 3;
90     } else {
91 	tickskip = ndigits;	/* No tick marks */
92     }
93 
94     /* Tick marks aren't digits, but generated by the number converter */
95     ndigits += (ndigits - 1) / tickskip;
96 
97     /* Now compute the number of nondigits */
98     nchars = ndigits;
99 
100     if (minus || (flags & (FL_PLUS | FL_SPACE)))
101 	nchars++;		/* Need space for sign */
102     if ((flags & FL_HASH) && base == 16) {
103 	nchars += 2;		/* Add 0x for hex */
104     }
105 
106     /* Emit early space padding */
107     if (!(flags & (FL_MINUS | FL_ZERO)) && width > nchars) {
108 	while (width > nchars) {
109 	    EMIT(' ');
110 	    width--;
111 	}
112     }
113 
114     /* Emit nondigits */
115     if (minus)
116 	EMIT('-');
117     else if (flags & FL_PLUS)
118 	EMIT('+');
119     else if (flags & FL_SPACE)
120 	EMIT(' ');
121 
122     if ((flags & FL_HASH) && base == 16) {
123 	EMIT('0');
124 	EMIT((flags & FL_UPPER) ? 'X' : 'x');
125     }
126 
127     /* Emit zero padding */
128     if ((flags & (FL_MINUS | FL_ZERO)) == FL_ZERO && width > ndigits) {
129 	while (width > nchars) {
130 	    EMIT('0');
131 	    width--;
132 	}
133     }
134 
135     /* Generate the number.  This is done from right to left. */
136     q += ndigits;		/* Advance the pointer to end of number */
137     o += ndigits;
138     qq = q;
139     oo = o;			/* Temporary values */
140 
141     b4tick = tickskip;
142     while (ndigits > 0) {
143 	if (!b4tick--) {
144 	    qq--;
145 	    oo--;
146 	    ndigits--;
147 	    if (oo < n)
148 		*qq = '_';
149 	    b4tick = tickskip - 1;
150 	}
151 	qq--;
152 	oo--;
153 	ndigits--;
154 	if (oo < n)
155 	    *qq = digits[val % base];
156 	val /= base;
157     }
158 
159     /* Emit late space padding */
160     while ((flags & FL_MINUS) && width > nchars) {
161 	EMIT(' ');
162 	width--;
163     }
164 
165     return o;
166 }
167 
vsnprintf(char * buffer,size_t n,const char * format,va_list ap)168 int vsnprintf(char *buffer, size_t n, const char *format, va_list ap)
169 {
170     const char *p = format;
171     char ch;
172     char *q = buffer;
173     size_t o = 0;		/* Number of characters output */
174     uintmax_t val = 0;
175     int rank = rank_int;	/* Default rank */
176     int width = 0;
177     int prec = -1;
178     int base;
179     size_t sz;
180     enum flags flags = 0;
181     enum {
182 	st_normal,		/* Ground state */
183 	st_flags,		/* Special flags */
184 	st_width,		/* Field width */
185 	st_prec,		/* Field precision */
186 	st_modifiers		/* Length or conversion modifiers */
187     } state = st_normal;
188     const char *sarg;		/* %s string argument */
189     char carg;			/* %c char argument */
190     int slen;			/* String length */
191 
192     while ((ch = *p++)) {
193 	switch (state) {
194 	case st_normal:
195 	    if (ch == '%') {
196 		state = st_flags;
197 		flags = 0;
198 		rank = rank_int;
199 		width = 0;
200 		prec = -1;
201 	    } else {
202 		EMIT(ch);
203 	    }
204 	    break;
205 
206 	case st_flags:
207 	    switch (ch) {
208 	    case '-':
209 		flags |= FL_MINUS;
210 		break;
211 	    case '+':
212 		flags |= FL_PLUS;
213 		break;
214 	    case '\'':
215 		flags |= FL_TICK;
216 		break;
217 	    case ' ':
218 		flags |= FL_SPACE;
219 		break;
220 	    case '#':
221 		flags |= FL_HASH;
222 		break;
223 	    case '0':
224 		flags |= FL_ZERO;
225 		break;
226 	    default:
227 		state = st_width;
228 		p--;		/* Process this character again */
229 		break;
230 	    }
231 	    break;
232 
233 	case st_width:
234 	    if (ch >= '0' && ch <= '9') {
235 		width = width * 10 + (ch - '0');
236 	    } else if (ch == '*') {
237 		width = va_arg(ap, int);
238 		if (width < 0) {
239 		    width = -width;
240 		    flags |= FL_MINUS;
241 		}
242 	    } else if (ch == '.') {
243 		prec = 0;	/* Precision given */
244 		state = st_prec;
245 	    } else {
246 		state = st_modifiers;
247 		p--;		/* Process this character again */
248 	    }
249 	    break;
250 
251 	case st_prec:
252 	    if (ch >= '0' && ch <= '9') {
253 		prec = prec * 10 + (ch - '0');
254 	    } else if (ch == '*') {
255 		prec = va_arg(ap, int);
256 		if (prec < 0)
257 		    prec = -1;
258 	    } else {
259 		state = st_modifiers;
260 		p--;		/* Process this character again */
261 	    }
262 	    break;
263 
264 	case st_modifiers:
265 	    switch (ch) {
266 		/* Length modifiers - nonterminal sequences */
267 	    case 'h':
268 		rank--;		/* Shorter rank */
269 		break;
270 	    case 'l':
271 		rank++;		/* Longer rank */
272 		break;
273 	    case 'j':
274 		rank = INTMAX_RANK;
275 		break;
276 	    case 'z':
277 		rank = SIZE_T_RANK;
278 		break;
279 	    case 't':
280 		rank = PTRDIFF_T_RANK;
281 		break;
282 	    case 'L':
283 	    case 'q':
284 		rank += 2;
285 		break;
286 	    default:
287 		/* Output modifiers - terminal sequences */
288 		state = st_normal;	/* Next state will be normal */
289 		if (rank < MIN_RANK)	/* Canonicalize rank */
290 		    rank = MIN_RANK;
291 		else if (rank > MAX_RANK)
292 		    rank = MAX_RANK;
293 
294 		switch (ch) {
295 		case 'P':	/* Upper case pointer */
296 		    flags |= FL_UPPER;
297 		    /* fall through */
298 		case 'p':	/* Pointer */
299 		    base = 16;
300 		    prec = (CHAR_BIT * sizeof(void *) + 3) / 4;
301 		    flags |= FL_HASH;
302 		    val = (uintmax_t) (uintptr_t) va_arg(ap, void *);
303 		    goto is_integer;
304 
305 		case 'd':	/* Signed decimal output */
306 		case 'i':
307 		    base = 10;
308 		    flags |= FL_SIGNED;
309 		    switch (rank) {
310 		    case rank_char:
311 			/* Yes, all these casts are needed... */
312 			val =
313 			    (uintmax_t) (intmax_t) (signed char)va_arg(ap,
314 								       signed
315 								       int);
316 			break;
317 		    case rank_short:
318 			val =
319 			    (uintmax_t) (intmax_t) (signed short)va_arg(ap,
320 									signed
321 									int);
322 			break;
323 		    case rank_int:
324 			val = (uintmax_t) (intmax_t) va_arg(ap, signed int);
325 			break;
326 		    case rank_long:
327 			val = (uintmax_t) (intmax_t) va_arg(ap, signed long);
328 			break;
329 		    case rank_longlong:
330 			val =
331 			    (uintmax_t) (intmax_t) va_arg(ap, signed long long);
332 			break;
333 		    }
334 		    goto is_integer;
335 		case 'o':	/* Octal */
336 		    base = 8;
337 		    goto is_unsigned;
338 		case 'u':	/* Unsigned decimal */
339 		    base = 10;
340 		    goto is_unsigned;
341 		case 'X':	/* Upper case hexadecimal */
342 		    flags |= FL_UPPER;
343 		    /* fall through */
344 		case 'x':	/* Hexadecimal */
345 		    base = 16;
346 		    goto is_unsigned;
347 
348 is_unsigned:
349 		    switch (rank) {
350 		    case rank_char:
351 			val =
352 			    (uintmax_t) (unsigned char)va_arg(ap, unsigned int);
353 			break;
354 		    case rank_short:
355 			val =
356 			    (uintmax_t) (unsigned short)va_arg(ap,
357 							       unsigned int);
358 			break;
359 		    case rank_int:
360 			val = (uintmax_t) va_arg(ap, unsigned int);
361 			break;
362 		    case rank_long:
363 			val = (uintmax_t) va_arg(ap, unsigned long);
364 			break;
365 		    case rank_longlong:
366 			val = (uintmax_t) va_arg(ap, unsigned long long);
367 			break;
368 		    }
369 		    /* fall through */
370 
371 is_integer:
372 		    sz = format_int(q, (o < n) ? n - o : 0, val, flags, base,
373 				    width, prec);
374 		    q += sz;
375 		    o += sz;
376 		    break;
377 
378 		case 'c':	/* Character */
379 		    carg = (char)va_arg(ap, int);
380 		    sarg = &carg;
381 		    slen = 1;
382 		    goto is_string;
383 		case 's':	/* String */
384 		    sarg = va_arg(ap, const char *);
385 		    sarg = sarg ? sarg : "(null)";
386 		    slen = strlen(sarg);
387 		    goto is_string;
388 
389 is_string:
390 		    {
391 			char sch;
392 			int i;
393 
394 			if (prec != -1 && slen > prec)
395 			    slen = prec;
396 
397 			if (width > slen && !(flags & FL_MINUS)) {
398 			    char pad = (flags & FL_ZERO) ? '0' : ' ';
399 			    while (width > slen) {
400 				EMIT(pad);
401 				width--;
402 			    }
403 			}
404 			for (i = slen; i; i--) {
405 			    sch = *sarg++;
406 			    EMIT(sch);
407 			}
408 			if (width > slen && (flags & FL_MINUS)) {
409 			    while (width > slen) {
410 				EMIT(' ');
411 				width--;
412 			    }
413 			}
414 		    }
415 		    break;
416 
417 		case 'n':	/* Output the number of characters written */
418 		    {
419 			switch (rank) {
420 			case rank_char:
421 			    *va_arg(ap, signed char *) = o;
422 			    break;
423 			case rank_short:
424 			    *va_arg(ap, signed short *) = o;
425 			    break;
426 			case rank_int:
427 			    *va_arg(ap, signed int *) = o;
428 			    break;
429 			case rank_long:
430 			    *va_arg(ap, signed long *) = o;
431 			    break;
432 			case rank_longlong:
433 			    *va_arg(ap, signed long long *) = o;
434 			    break;
435 			}
436 		    }
437 		    break;
438 
439 		default:	/* Anything else, including % */
440 		    EMIT(ch);
441 		    break;
442 		}
443 	    }
444 	}
445     }
446 
447     /* Null-terminate the string */
448     if (o < n)
449 	*q = '\0';		/* No overflow */
450     else if (n > 0)
451 	buffer[n - 1] = '\0';	/* Overflow - terminate at end of buffer */
452 
453     return o;
454 }
455