/* snprintf.c - Formatted, length-limited print to a string */ #include #if defined(AFS_OSF20_ENV) && !defined(AFS_DUX50_ENV) || defined(AFS_AIX32_ENV) || (defined(AFS_SUN55_ENV) && !defined(AFS_SUN56_ENV)) #include #include #include #include #include #include #if defined(AFS_AIX32_ENV) || defined(AFS_SUN55_ENV) #include #endif #define MAXPREC 100 /* Generate an ASCII representation of an integer , as follows: * indicates the base to be used (2-36) * is nonzero if letter digits should be uppercase * is the minimum number of digits * The resulting number is stored in , which must be long enough * to receive it. The minimum length is or ceil(log{base}(val)), * whichever is larger, plus room for a trailing NUL. */ static void mkint(char *buf, unsigned long val, int base, int uc, int prec) { int len = 0, dig, i; while (val) { dig = val % base; val = (val - dig) / base; if (dig < 10) dig = dig + '0'; else if (uc) dig = dig + 'A' - 10; else dig = dig + 'a' - 10; buf[len++] = dig; } while (len < prec) buf[len++] = '0'; for (i = 0; i < (len + 1) / 2; i++) { dig = buf[i]; buf[i] = buf[len - i - 1]; buf[len - i - 1] = dig; } buf[len] = 0; } /* This function is a mostly-complete implementation of snprintf, * with the following features: * * - Actually obeys the length limit, which (unfortunately) many * implementations of snprintf do not. * * - Supports all the standard format specifiers for integers * (d, i, o, u, x, X), floating-point values (f, e, E, g, G), * and strings and characters (c, s, %), plus a few unusual * but useful ones described below. * * - Supports all the standard flags (-, 0, +, space, #). These * flags are ignored if used when they are not appropriate. * * - Supports the standard size modifiers for short (h), long (h), * and double (L) arguments. These modifiers are ignored if used * when they are not appropriate. * * - Supports minimum field width and precision, where appropriate, * including the use of '*' to specify a value given as an argument * instead of in the format string. There is a maximum precision * of 100 digits. * * - At present, the 'p' specifier for printing pointers is not * implemented, because it is inherently non-portable and thus * can be implemented correctly only by the compiler's run-time * library. * * - Floating-point specifier (%e, %f, %g) are implemented by * calling the standard sprintf, and thus may be unsafe. * * - The '%...$' notation is used primarily when the format string * is specified by the user, who knows but cannot change the order * of the arguments. Such usage is inherently dangerous and * insecure; thus, it is not supported. * * The custom format specifier '%I' is supported. This specifier * takes as its argument an unsigned long integer containing an * IPv4 address in network byte order. The address is rendered * either as a hostname or as a dotted quad, as follows: * * - If precision is nonzero or unspecified, a hostname lookup * is attempted; if it is successful, the hostname is printed. * If the hostname lookup fails, the address is printed in * dotted-quad notation. * * - If precision is explicitly specified as 0, then the hostname * lookup is skipped, and dotted-quad notation is always used. * * - If a hostname is to be printed: * + The precision controls the maximum number of characters * printed, as with %s. * + If the '#' flag is specified, any letters in the hostname * will be forced to lower case before printing. * + If the '+' flag is specified, any letters in the hostname * will be forced to upper case before printing. If both * '#' and '+' are given, the '+' flag will be ignored. * + The '0' and ' ' flags have no effect. * * - If a dotted quad is to be printed: * + The precision has no effect; dotted quads are always * 7 to 12 characters in length, depending on the value * to be printed and the format flags used. * + If the '0' flag is given, each field (byte) of the address * will be padded with '0' on the left to three digits. * + If the ' ' flag is given, each field (byte) of the address * will be padded with spaces on the left to three digits. If * both '0' and ' ' are given, the ' ' flag will be ignored. * + The '#' and '+' flags have no effect. */ static void vsnprintf(char *p, unsigned int avail, char *fmt, va_list ap) { unsigned int width, precision, haveprec, len; int ljust, plsign, spsign, altform, zfill; int hflag, lflag, count, *countp, j; char *x, *y, xbuf[MAXPREC + 21], fbuf[20]; struct hostent *he; struct in_addr ia; unsigned long UVAL; long SVAL, *lcountp; double FVAL; short *hcountp; count = 0; avail--; while (*fmt && avail) { if (*fmt != '%') { *p++ = *fmt++; avail--; count++; continue; } /** Found a format specifier **/ ljust = plsign = spsign = altform = zfill = 0; width = precision = haveprec = 0; hflag = lflag = 0; fmt++; /* parse format flags */ while (*fmt) { switch (*fmt) { case '-': ljust = 1; fmt++; continue; /* left justify */ case '+': plsign = 1; fmt++; continue; /* use + or - */ case ' ': spsign = 1; fmt++; continue; /* use space or - */ case '#': altform = 1; fmt++; continue; /* alternate form */ case '0': zfill = 1; fmt++; continue; /* pad with 0 */ default: break; } break; } /* parse minimum width */ if (*fmt == '*') { width = va_arg(ap, int); fmt++; } else while (isdigit(*fmt)) { width = (width * 10) + (*fmt - '0'); fmt++; } /* parse precision */ if (*fmt == '.') { fmt++; haveprec = 1; if (*fmt == '*') { precision = va_arg(ap, int); fmt++; } else while (isdigit(*fmt)) { precision = (precision * 10) + (*fmt - '0'); fmt++; } } /* parse size flags */ while (*fmt) { switch (*fmt) { case 'h': hflag = 1; fmt++; continue; /* short argument */ case 'l': lflag = 1; fmt++; continue; /* long argument */ default: break; } break; } /* parse format specifier */ if (!*fmt) break; switch (*fmt++) { case 'e': case 'E': case 'f': case 'g': case 'G': FVAL = va_arg(ap, double); sprintf(fbuf, "%%%s%s.*L%c", plsign ? "+" : (spsign ? " " : ""), altform ? "#" : "", fmt[-1]); if (!haveprec) precision = 6; if (precision > MAXPREC) precision = MAXPREC; sprintf(xbuf, fbuf, precision, FVAL); x = xbuf; len = strlen(x); break; case 'i': case 'd': /* signed decimal integer */ if (lflag) SVAL = va_arg(ap, long); else if (hflag) SVAL = va_arg(ap, short); else SVAL = va_arg(ap, int); UVAL = (SVAL < 0) ? -SVAL : SVAL; if (SVAL < 0) xbuf[0] = '-'; else if (plsign) xbuf[0] = '+'; else if (spsign) xbuf[0] = ' '; else xbuf[0] = 0; if (!haveprec) { if (zfill && !ljust) precision = width - !!xbuf[0]; else precision = 1; if (precision < 1 + !!xbuf[0]) precision = 1 + !!xbuf[0]; } if (precision > MAXPREC) precision = MAXPREC; mkint(xbuf + 1, UVAL, 10, 0, precision); x = xbuf + !xbuf[0]; len = strlen(x); break; case 'o': /* unsigned octal integer */ if (lflag) UVAL = va_arg(ap, unsigned long); else if (hflag) UVAL = va_arg(ap, unsigned short); else UVAL = va_arg(ap, unsigned int); xbuf[0] = '0'; if (!haveprec) { if (zfill && !ljust) precision = width; else precision = 1; } if (precision > MAXPREC) precision = MAXPREC; mkint(xbuf + 1, UVAL, 8, 0, precision); x = xbuf + (xbuf[1] == '0' || !altform); len = strlen(x); break; case 'u': /* unsigned decimal integer */ if (lflag) UVAL = va_arg(ap, unsigned long); else if (hflag) UVAL = va_arg(ap, unsigned short); else UVAL = va_arg(ap, unsigned int); if (!haveprec) { if (zfill && !ljust) precision = width; else precision = 1; } if (precision > MAXPREC) precision = MAXPREC; mkint(xbuf, UVAL, 10, 0, precision); x = xbuf; len = strlen(x); break; case 'x': case 'X': /* unsigned hexadecimal integer */ if (lflag) UVAL = va_arg(ap, unsigned long); else if (hflag) UVAL = va_arg(ap, unsigned short); else UVAL = va_arg(ap, unsigned int); xbuf[0] = '0'; xbuf[1] = 'x'; if (!haveprec) { if (zfill && !ljust) precision = width; else precision = 1; } if (precision > MAXPREC) precision = MAXPREC; mkint(xbuf + 2, UVAL, 16, 0, precision); x = xbuf + ((altform && UVAL) ? 0 : 2); len = strlen(x); break; case '%': /* literal % */ xbuf[0] = '%'; xbuf[1] = 0; x = xbuf; len = 1; break; case 'c': /* character */ xbuf[0] = va_arg(ap, int); xbuf[1] = 0; x = xbuf; len = 1; break; case 's': /* string */ x = va_arg(ap, char *); if (!x) x = ""; len = strlen(x); if (haveprec && precision < len) len = precision; break; case 'I': /* IP address: * value is provided as a network-order unsigned long integer * precision specifies max hostname length, as for %s * if precision is explicitly 0, no hostname lookup is done * if 0fill specified, IPaddr fields are 0-filled to 3 digits * if spsign specified, IPaddr fields are space-filled to 3 digits */ UVAL = va_arg(ap, unsigned long); ia.s_addr = UVAL; if (haveprec && !precision) he = 0; else he = gethostbyaddr((char *)&ia, 4, AF_INET); if (he) { x = he->h_name; len = strlen(x); if (haveprec && precision < len) len = precision; if (altform) for (y = x; *y; y++) if (isupper(*y)) *y = tolower(*y); else if (plsign) for (y = x; *y; y++) if (islower(*y)) *y = toupper(*y); } else { UVAL = ntohl(UVAL); if (zfill) x = "%03u.%03u.%03u.%03u"; else if (spsign) x = "%3u.%3u.%3u.%3u"; else x = "%u.%u.%u.%u"; sprintf(xbuf, x, (UVAL & 0xff000000) >> 24, (UVAL & 0x00ff0000) >> 16, (UVAL & 0x0000ff00) >> 8, (UVAL & 0x000000ff)); x = xbuf; len = strlen(xbuf); } break; case 'n': /* report count so far */ if (lflag) { lcountp = va_arg(ap, long *); *lcountp = count; } else if (hflag) { hcountp = va_arg(ap, short *); *hcountp = count; } else { countp = va_arg(ap, int *); *countp = count; } continue; default: /* unknown specifier */ continue; } /* render the results */ if (len > avail) len = avail; if (!width) width = len; if (width > avail) width = avail; j = width - len; if (j > 0) { avail -= j; count += j; } if (!ljust) while (j-- > 0) *p++ = ' '; strncpy(p, x, len); avail -= len; count += len; p += len; if (ljust) while (j-- > 0) *p++ = ' '; } *p = 0; } void snprintf(char *p, unsigned int avail, char *fmt, ...) { va_list ap; va_start(ap, fmt); vsnprintf(p, avail, fmt, ap); va_end(ap); } #endif /* AFS_OSF20_ENV || AFS_AIX32_ENV */