--- /dev/null
+/* snprintf.c - Formatted, length-limited print to a string */
+
+#include <sys/types.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <netinet/in.h>
+#include <netdb.h>
+
+#define MAXPREC 100
+
+/* Generate an ASCII representation of an integer <val>, as follows:
+ * <base> indicates the base to be used (2-36)
+ * <uc> is nonzero if letter digits should be uppercase
+ * <prec> is the minimum number of digits
+ * The resulting number is stored in <buf>, which must be long enough
+ * to receive it. The minimum length is <prec> 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 = "<null>";
+ 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);
+}