/* * Copyright (C) Igor Sysoev * Copyright (C) NGINX, Inc. */ #include #include #include /* * Supported formats: * * %[0][width][x|X]O nxt_off_t * %[0][width][x|X]T nxt_time_t * %[0][width][u][x|X]z ssize_t/size_t * %[0][width][u][x|X]d int/u_int * %[0][width][u][x|X]l long * %[0][width|m][u][x|X]i nxt_int_t/nxt_uint_t * %[0][width][u][x|X]D int32_t/uint32_t * %[0][width][u][x|X]L int64_t/uint64_t * %[0][width|m][u][x|X]A nxt_atomic_int_t/nxt_atomic_uint_t * %[0][width][.width]f double, max valid number fits to %18.15f * * %FD nxt_fd_t, int / HANDLE * %d nxt_socket_t, int * * %PI nxt_pid_t, process id * %PT nxt_tid_t, thread id * %PF nxt_fid_t, fiber id * %PH pthread_t handle returned by pthread_self() * * %s null-terminated string * %*s length and string * %FN nxt_file_name_t * * * %M nxt_msec_t * %N nxt_nsec_t * %r rlim_t * %p void * * %b nxt_bool_t * %E nxt_err_t * %V nxt_str_t * * %Z '\0' * %n '\n' * %c char * %% % * * Reserved: * %t ptrdiff_t * %S null-terminated wchar string * %C wchar * %[0][width][u][x|X]Q int128_t/uint128_t */ u_char * nxt_cdecl nxt_sprintf(u_char *buf, u_char *end, const char *fmt, ...) { u_char *p; va_list args; va_start(args, fmt); p = nxt_vsprintf(buf, end, fmt, args); va_end(args); return p; } /* * nxt_sprintf_t is used: * to pass several parameters of nxt_integer() via single pointer * and to store little used variables of nxt_vsprintf(). */ typedef struct { u_char *end; const u_char *hex; uint32_t width; int32_t frac_width; uint8_t max_width; u_char padding; } nxt_sprintf_t; static u_char *nxt_integer(nxt_sprintf_t *spf, u_char *buf, uint64_t ui64); static u_char *nxt_number(nxt_sprintf_t *spf, u_char *buf, double n); /* A right way of "f == 0.0". */ #define nxt_double_is_zero(f) \ (fabs(f) <= FLT_EPSILON) u_char * nxt_vsprintf(u_char *buf, u_char *end, const char *fmt, va_list args) { int d; double f, i; size_t length; int64_t i64; uint64_t ui64, frac; nxt_str_t *v; nxt_err_t err; nxt_uint_t scale, n; nxt_msec_t ms; nxt_nsec_t ns; nxt_bool_t sign; const u_char *p; nxt_sprintf_t spf; nxt_file_name_t *fn; static const u_char hexadecimal[16] = "0123456789abcdef"; static const u_char HEXADECIMAL[16] = "0123456789ABCDEF"; static const u_char nan[] = "[nan]"; static const u_char null[] = "[null]"; static const u_char infinity[] = "[infinity]"; spf.end = end; while (*fmt != '\0' && buf < end) { /* * "buf < end" means that we could copy at least one character: * a plain character, "%%", "%c", or a minus without test. */ if (*fmt != '%') { *buf++ = *fmt++; continue; } fmt++; /* Test some often used text formats first. */ switch (*fmt) { case 'V': fmt++; v = va_arg(args, nxt_str_t *); if (nxt_fast_path(v != NULL)) { length = v->length; p = v->start; goto copy; } continue; case 's': fmt++; p = va_arg(args, const u_char *); if (nxt_slow_path(p == NULL)) { goto copy; } while (*p != '\0' && buf < end) { *buf++ = *p++; } continue; case '*': length = va_arg(args, size_t); fmt++; if (*fmt == 's') { fmt++; p = va_arg(args, const u_char *); goto copy; } continue; default: break; } spf.hex = NULL; spf.width = 0; spf.frac_width = -1; spf.max_width = 0; spf.padding = (*fmt == '0') ? '0' : ' '; sign = 1; i64 = 0; ui64 = 0; while (*fmt >= '0' && *fmt <= '9') { spf.width = spf.width * 10 + (*fmt++ - '0'); } for ( ;; ) { switch (*fmt) { case 'u': sign = 0; fmt++; continue; case 'm': spf.max_width = 1; fmt++; continue; case 'X': spf.hex = HEXADECIMAL; sign = 0; fmt++; continue; case 'x': spf.hex = hexadecimal; sign = 0; fmt++; continue; case '.': fmt++; spf.frac_width = 0; while (*fmt >= '0' && *fmt <= '9') { spf.frac_width = spf.frac_width * 10 + *fmt++ - '0'; } break; default: break; } break; } switch (*fmt) { case 'E': err = va_arg(args, nxt_err_t); *buf++ = '('; spf.hex = NULL; spf.width = 0; buf = nxt_integer(&spf, buf, err); if (buf < end - 1) { *buf++ = ':'; *buf++ = ' '; } buf = nxt_strerror(err, buf, end - buf); if (buf < end) { *buf++ = ')'; } fmt++; continue; case 'O': i64 = (int64_t) va_arg(args, nxt_off_t); sign = 1; goto number; case 'T': i64 = (int64_t) va_arg(args, nxt_time_t); sign = 1; goto number; case 'M': ms = (nxt_msec_t) va_arg(args, nxt_msec_t); if ((nxt_msec_int_t) ms == -1 && spf.hex == NULL) { i64 = -1; sign = 1; } else { ui64 = (uint64_t) ms; sign = 0; } goto number; case 'N': ns = (nxt_nsec_t) va_arg(args, nxt_nsec_t); if ((nxt_nsec_int_t) ns == -1) { i64 = -1; sign = 1; } else { ui64 = (uint64_t) ns; sign = 0; } goto number; case 'z': if (sign) { i64 = (int64_t) va_arg(args, ssize_t); } else { ui64 = (uint64_t) va_arg(args, size_t); } goto number; case 'i': if (sign) { i64 = (int64_t) va_arg(args, nxt_int_t); } else { ui64 = (uint64_t) va_arg(args, nxt_uint_t); } if (spf.max_width != 0) { spf.width = NXT_INT_T_LEN; } goto number; case 'd': if (sign) { i64 = (int64_t) va_arg(args, int); } else { ui64 = (uint64_t) va_arg(args, u_int); } goto number; case 'l': if (sign) { i64 = (int64_t) va_arg(args, long); } else { ui64 = (uint64_t) va_arg(args, u_long); } goto number; case 'D': if (sign) { i64 = (int64_t) va_arg(args, int32_t); } else { ui64 = (uint64_t) va_arg(args, uint32_t); } goto number; case 'L': if (sign) { i64 = va_arg(args, int64_t); } else { ui64 = va_arg(args, uint64_t); } goto number; case 'A': if (sign) { i64 = (int64_t) va_arg(args, nxt_atomic_int_t); } else { ui64 = (uint64_t) va_arg(args, nxt_atomic_uint_t); } if (spf.max_width != 0) { spf.width = NXT_ATOMIC_T_LEN; } goto number; case 'b': ui64 = (uint64_t) va_arg(args, nxt_bool_t); sign = 0; goto number; case 'f': fmt++; f = va_arg(args, double); if (f < 0) { *buf++ = '-'; f = -f; } if (nxt_slow_path(isnan(f))) { p = nan; length = nxt_length(nan); goto copy; } else if (nxt_slow_path(isinf(f))) { p = infinity; length = nxt_length(infinity); goto copy; } (void) modf(f, &i); frac = 0; if (spf.frac_width > 0) { scale = 1; for (n = spf.frac_width; n != 0; n--) { scale *= 10; } frac = (uint64_t) ((f - i) * scale + 0.5); if (frac == scale) { i += 1; frac = 0; } } buf = nxt_number(&spf, buf, i); if (spf.frac_width > 0) { if (buf < end) { *buf++ = '.'; spf.hex = NULL; spf.padding = '0'; spf.width = spf.frac_width; buf = nxt_integer(&spf, buf, frac); } } else if (spf.frac_width < 0) { f = modf(f, &i); if (!nxt_double_is_zero(f) && buf < end) { *buf++ = '.'; while (!nxt_double_is_zero(f) && buf < end) { f *= 10; f = modf(f, &i); *buf++ = (u_char) i + '0'; } } } continue; case 'r': i64 = (int64_t) va_arg(args, rlim_t); sign = 1; break; case 'p': ui64 = (uintptr_t) va_arg(args, void *); sign = 0; spf.hex = HEXADECIMAL; /* * spf.width = NXT_PTR_SIZE * 2; * spf.padding = '0'; */ goto number; case 'c': d = va_arg(args, int); *buf++ = (u_char) (d & 0xFF); fmt++; continue; case 'F': fmt++; switch (*fmt) { case 'D': i64 = (int64_t) va_arg(args, nxt_fd_t); sign = 1; goto number; case 'N': fn = va_arg(args, nxt_file_name_t *); p = fn; while (*p != '\0' && buf < end) { *buf++ = *p++; } fmt++; continue; default: continue; } case 'P': fmt++; switch (*fmt) { case 'I': i64 = (int64_t) va_arg(args, nxt_pid_t); sign = 1; goto number; case 'T': ui64 = (uint64_t) (uintptr_t) va_arg(args, nxt_tid_t); sign = 0; goto number; #if 0 case 'F': ui64 = (uint64_t) va_arg(args, nxt_fid_t); sign = 0; goto number; #endif case 'H': ui64 = (uint64_t) (uintptr_t) va_arg(args, pthread_t); spf.hex = HEXADECIMAL; sign = 0; goto number; default: continue; } case 'Z': *buf++ = '\0'; fmt++; continue; case 'n': *buf++ = '\n'; fmt++; continue; case '%': *buf++ = '%'; fmt++; continue; default: *buf++ = *fmt++; continue; } number: if (sign) { if (i64 < 0) { *buf++ = '-'; ui64 = (uint64_t) -i64; } else { ui64 = (uint64_t) i64; } } buf = nxt_integer(&spf, buf, ui64); fmt++; continue; copy: if (nxt_slow_path(p == NULL)) { p = null; length = nxt_length(null); } else { length = nxt_min((size_t) (end - buf), length); } buf = nxt_cpymem(buf, p, length); continue; } return buf; } static u_char * nxt_integer(nxt_sprintf_t *spf, u_char *buf, uint64_t ui64) { u_char *p, *end; size_t length; u_char temp[NXT_INT64_T_LEN]; p = temp + NXT_INT64_T_LEN; if (spf->hex == NULL) { #if (NXT_32BIT) for ( ;; ) { u_char *start; uint32_t ui32; /* * 32-bit platforms usually lack hardware support of 64-bit * division and remainder operations. For this reason C compiler * adds calls to the runtime library functions which provides * these operations. These functions usually have about hundred * lines of code. * * For 32-bit numbers and some constant divisors GCC, Clang and * other compilers can use inlined multiplications and shifts * which are faster than division or remainder operations. * For example, unsigned "ui32 / 10" is compiled to * * ((uint64_t) ui32 * 0xCCCCCCCD) >> 35 * * So a 64-bit number is split to parts by 10^9. The parts fit * to 32 bits and are processed separately as 32-bit numbers. A * number of 64-bit division/remainder operations is significantly * decreased depending on the 64-bit number's value, it is * 0 if the 64-bit value is less than 4294967296, * 1 if the 64-bit value is greater than 4294967295 * and less than 4294967296000000000, * 2 otherwise. */ if (ui64 <= 0xFFFFFFFF) { ui32 = (uint32_t) ui64; start = NULL; } else { ui32 = (uint32_t) (ui64 % 1000000000); start = p - 9; } do { *(--p) = (u_char) (ui32 % 10 + '0'); ui32 /= 10; } while (ui32 != 0); if (start == NULL) { break; } /* Add leading zeros of part. */ while (p > start) { *(--p) = '0'; } ui64 /= 1000000000; } #else /* NXT_64BIT */ do { *(--p) = (u_char) (ui64 % 10 + '0'); ui64 /= 10; } while (ui64 != 0); #endif } else { do { *(--p) = spf->hex[ui64 & 0xF]; ui64 >>= 4; } while (ui64 != 0); } /* Zero or space padding. */ if (spf->width != 0) { length = (temp + NXT_INT64_T_LEN) - p; end = buf + (spf->width - length); end = nxt_min(end, spf->end); while (buf < end) { *buf++ = spf->padding; } } /* Number copying. */ length = (temp + NXT_INT64_T_LEN) - p; end = buf + length; end = nxt_min(end, spf->end); while (buf < end) { *buf++ = *p++; } return buf; } static u_char * nxt_number(nxt_sprintf_t *spf, u_char *buf, double n) { u_char *p, *end; size_t length; u_char temp[NXT_DOUBLE_LEN]; p = temp + NXT_DOUBLE_LEN; do { *(--p) = (u_char) (fmod(n, 10) + '0'); n = trunc(n / 10); } while (!nxt_double_is_zero(n)); /* Zero or space padding. */ if (spf->width != 0) { length = (temp + NXT_DOUBLE_LEN) - p; end = buf + (spf->width - length); end = nxt_min(end, spf->end); while (buf < end) { *buf++ = spf->padding; } } /* Number copying. */ length = (temp + NXT_DOUBLE_LEN) - p; end = buf + length; end = nxt_min(end, spf->end); while (buf < end) { *buf++ = *p++; } return buf; }