#include #include #include #include #include #include #define FORMAT_BUF_INIT_SIZE 200 #define FORMAT_BUF_CHUNK_SIZE 50 #define BAD_SPEC "%%UNKNOWN FORMAT SPEC%%" #define BAD_SPEC_LEN 23 static char *buf; static uint32_t buf_s; static char *buf_i; static const char *const hextab = "0123456789abcdef"; static void ensure(uint32_t extra) { const uint32_t total_len = buf_i - buf + extra; if (total_len < buf_s) return; buf_s = (total_len / FORMAT_BUF_CHUNK_SIZE + 1) * FORMAT_BUF_CHUNK_SIZE; char *const new_buf = get_block(buf_s); if (!new_buf) PANIC("out of memory in knob format"); blockcpy(new_buf, buf, buf_i - buf); free_block(buf); buf_i += new_buf - buf; buf = new_buf; } struct format_spec { uint32_t len; enum { UNKNOWN, CHAR, STRING, UNSIGNED_DECIMAL, HEXADECIMAL, SIGNED_DECIMAL } kind; }; static const char *get_format(const char *from, struct format_spec *format_out) { if (*from == 'n') { ++from; format_out->len = -1; } else { uint32_t len = 0; while ((*from >= '0') && (*from <= '9')) len = len * 10 + *(from++) - '0'; format_out->len = len; } switch (*from) { case 'c': format_out->kind = CHAR; break; case 's': format_out->kind = STRING; break; case 'u': format_out->kind = UNSIGNED_DECIMAL; break; case 'h': case 'x': format_out->kind = HEXADECIMAL; break; case 'd': format_out->kind = SIGNED_DECIMAL; break; default: format_out->kind = UNKNOWN; break; } return from + 1; } static uint8_t get_digits(uint32_t k) { if (k >= 1000000000) return 10; uint8_t r = 1; uint32_t n = 10; while (k >= n) { ++r; n *= 10; } return r; } //allocates new memory char *format_v(const char *fmt, va_list args) { buf = get_block(FORMAT_BUF_INIT_SIZE); if (!buf) PANIC("out of memory in knob format"); buf_s = FORMAT_BUF_INIT_SIZE; buf_i = buf; while (*fmt) { if (*fmt != '%') { ensure(1); *(buf_i++) = *(fmt++); } else if (fmt[1] == '%') { ensure(1); *(buf_i++) = '%'; fmt += 2; } else { struct format_spec form; fmt = get_format(fmt + 1, &form); if (form.len == -1) //should passing zero still have the special meaning? form.len = va_arg(args, uint32_t); switch (form.kind) { case UNKNOWN: ensure(BAD_SPEC_LEN); blockcpy(buf_i, BAD_SPEC, BAD_SPEC_LEN); buf_i += BAD_SPEC_LEN; continue; uint32_t ch; case CHAR: ch = va_arg(args, uint32_t); ensure(1); *(buf_i++) = (char)ch; continue; const char *str; case STRING: str = va_arg(args, const char *); if (!form.len) form.len = strlen(str); ensure(form.len); blockcpy(buf_i, str, form.len); buf_i += form.len; continue; uint32_t k; case SIGNED_DECIMAL: k = va_arg(args, uint32_t); bool is_neg = k & 0x80000000; if (is_neg) { ensure(1); *(buf_i++) = '-'; k = -k; } if (!form.len) form.len = get_digits(k); else if (is_neg) --form.len; goto print_dec; case UNSIGNED_DECIMAL: k = va_arg(args, uint32_t); if (!form.len) form.len = get_digits(k); print_dec: ensure(form.len); const uint32_t len_backup = form.len; while (form.len--) { buf_i[form.len] = (k % 10) + '0'; k /= 10; } buf_i += len_backup; continue; case HEXADECIMAL: k = va_arg(args, uint32_t); if (!form.len) form.len = 8; ensure(form.len); const uint32_t hlen_backup = form.len; while (form.len--) { buf_i[form.len] = hextab[k % 16]; k >>= 4; } buf_i += hlen_backup; continue; } } } *buf_i = '\0'; return buf; } //allocates new memory char *format(const char *fmt, ...) { va_list args; va_start(args, fmt); char *const res = format_v(fmt, args); va_end(args); return res; } void syslogf_v(const char *fmt, va_list args) { char *const msg = format_v(fmt, args); _system_log(msg); free_block(msg); } void syslogf(const char *fmt, ...) { va_list args; va_start(args, fmt); syslogf_v(fmt, args); va_end(args); } //reads a unsigned decimal terminated by either null or whitespace //returns length of string plus length of whitespace //returns 0 on failure uint32_t try_swtou(const char *from, uint32_t *i_out) { const char *const old_from = from; uint32_t v = 0; while (*from && (*from != '\n') && (*from != ' ')) { if ((*from < '0') || (*from > '9')) return 0; v = v * 10 + *(from++) - '0'; } *i_out = v; while ((*from == '\n') || (*from == ' ')) ++from; return from - old_from; } //reads a hexadecimal terminated by either null or whitespace //returns length of string plus length of whitespace //returns 0 on failure uint32_t try_swtoh(const char *from, uint32_t *i_out) { const char *const old_from = from; uint32_t v = 0; while (*from && (*from != '\n') && (*from != ' ')) { if ((*from >= '0') && (*from <= '9')) v = v * 16 + *(from++) - '0'; else if ((*from >= 'a') && (*from <= 'f')) v = v * 16 + *(from++) - 'a' + 10; else if ((*from >= 'A') && (*from <= 'F')) v = v * 16 + *(from++) - 'A' + 10; else return 0; } *i_out = v; while ((*from == '\n') || (*from == ' ')) ++from; return from - old_from; }