From 85cd02cf3c1ded45ef8664ae3be7728ba1f05af6 Mon Sep 17 00:00:00 2001 From: Philippe Proulx Date: Thu, 22 Feb 2018 22:01:15 -0500 Subject: [PATCH] Common: add internal bt_common_custom_vsnprintf() This new utility function (and its corresponding bt_common_custom_snprintf()) is like vsnprintf(), but it also allows to use custom conversion specifiers with the help of a user callback which consumes one or more variadic arguments and writes to the current output buffer position according to the custom conversion specifier. For example, it allows the following: bt_common_custom_snprintf(buf, buf_size, '@', handle_specifier, NULL, "hello %d world %@s (%02x-%@+d)", -23, my_object, 17.5, "meow", my_fd); Here, the `%d` and `%02x` conversion specifiers are used to consume the `-23` and `17.5` arguments as you would expect, but the custom `%@s` and `%@+d` specifiers consume the `my_object`, `"meow"`, and `my_fd` parameters thanks to the user-provided handle_specifier() callback. See the block comment above the bt_common_custom_vsnprintf() declaration for more details and for limitations regarding the format string. Signed-off-by: Philippe Proulx --- common/common.c | 321 +++++++++++++++++++++++++++ include/babeltrace/common-internal.h | 70 +++++- 2 files changed, 390 insertions(+), 1 deletion(-) diff --git a/common/common.c b/common/common.c index 3020b60f..cf31aecc 100644 --- a/common/common.c +++ b/common/common.c @@ -27,13 +27,17 @@ #include #include +#include #include #include #include #include +#include #include #include #include +#include +#include #include #include #include @@ -1246,3 +1250,320 @@ size_t bt_common_get_page_size(void) return page_size; } + +#define BUF_STD_APPEND(...) \ + do { \ + char _tmp_fmt[64]; \ + int _count; \ + size_t _size = buf_size - (size_t) (*buf_ch - buf); \ + size_t _tmp_fmt_size = (size_t) (fmt_ch - *out_fmt_ch); \ + strncpy(_tmp_fmt, *out_fmt_ch, _tmp_fmt_size); \ + _tmp_fmt[_tmp_fmt_size] = '\0'; \ + _count = snprintf(*buf_ch, _size, _tmp_fmt, __VA_ARGS__); \ + assert(_count >= 0); \ + *buf_ch += MIN(_count, _size); \ + } while (0) + +#define BUF_STD_APPEND_SINGLE_ARG(_type) \ + do { \ + _type _arg = va_arg(*args, _type); \ + BUF_STD_APPEND(_arg); \ + } while (0) + +static inline void handle_conversion_specifier_std(char *buf, char **buf_ch, + size_t buf_size, const char **out_fmt_ch, va_list *args) +{ + const char *fmt_ch = *out_fmt_ch; + enum LENGTH_MODIFIER { + LENGTH_MOD_H, + LENGTH_MOD_HH, + LENGTH_MOD_NONE, + LENGTH_MOD_LOW_L, + LENGTH_MOD_LOW_LL, + LENGTH_MOD_UP_L, + LENGTH_MOD_Z, + } length_mod = LENGTH_MOD_NONE; + + /* skip '%' */ + fmt_ch++; + + if (*fmt_ch == '%') { + fmt_ch++; + **buf_ch = '%'; + (*buf_ch)++; + goto update_rw_fmt; + } + + /* flags */ + for (;;) { + switch (*fmt_ch) { + case '-': + case '+': + case ' ': + case '#': + case '0': + case '\'': + fmt_ch++; + continue; + default: + break; + } + break; + } + + /* width */ + for (;;) { + if (*fmt_ch < '0' || *fmt_ch > '9') { + break; + } + + fmt_ch++; + } + + /* precision */ + if (*fmt_ch == '.') { + fmt_ch++; + + for (;;) { + if (*fmt_ch < '0' || *fmt_ch > '9') { + break; + } + + fmt_ch++; + } + } + + /* format (PRI*64) */ + if (strncmp(fmt_ch, PRId64, sizeof(PRId64)) == 0) { + fmt_ch += sizeof(PRId64); + BUF_STD_APPEND_SINGLE_ARG(int64_t); + goto update_rw_fmt; + } else if (strncmp(fmt_ch, PRIu64, sizeof(PRIu64)) == 0) { + fmt_ch += sizeof(PRIu64); + BUF_STD_APPEND_SINGLE_ARG(uint64_t); + goto update_rw_fmt; + } else if (strncmp(fmt_ch, PRIx64, sizeof(PRIx64)) == 0) { + fmt_ch += sizeof(PRIx64); + BUF_STD_APPEND_SINGLE_ARG(uint64_t); + goto update_rw_fmt; + } else if (strncmp(fmt_ch, PRIX64, sizeof(PRIX64)) == 0) { + fmt_ch += sizeof(PRIX64); + BUF_STD_APPEND_SINGLE_ARG(uint64_t); + goto update_rw_fmt; + } else if (strncmp(fmt_ch, PRIo64, sizeof(PRIo64)) == 0) { + fmt_ch += sizeof(PRIo64); + BUF_STD_APPEND_SINGLE_ARG(uint64_t); + goto update_rw_fmt; + } else if (strncmp(fmt_ch, PRIi64, sizeof(PRIi64)) == 0) { + fmt_ch += sizeof(PRIi64); + BUF_STD_APPEND_SINGLE_ARG(int64_t); + goto update_rw_fmt; + } + + // length modifier + switch (*fmt_ch) { + case 'h': + length_mod = LENGTH_MOD_H; + fmt_ch++; + + if (*fmt_ch == 'h') { + length_mod = LENGTH_MOD_HH; + fmt_ch++; + break; + } + break; + case 'l': + length_mod = LENGTH_MOD_LOW_L; + fmt_ch++; + + if (*fmt_ch == 'l') { + length_mod = LENGTH_MOD_LOW_LL; + fmt_ch++; + break; + } + break; + case 'L': + length_mod = LENGTH_MOD_UP_L; + fmt_ch++; + break; + case 'z': + length_mod = LENGTH_MOD_Z; + fmt_ch++; + break; + default: + break; + } + + // format + switch (*fmt_ch) { + case 'c': + { + fmt_ch++; + + switch (length_mod) { + case LENGTH_MOD_NONE: + BUF_STD_APPEND_SINGLE_ARG(int); + break; + case LENGTH_MOD_LOW_L: + BUF_STD_APPEND_SINGLE_ARG(wint_t); + break; + default: + abort(); + } + break; + } + case 's': + fmt_ch++; + + switch (length_mod) { + case LENGTH_MOD_NONE: + BUF_STD_APPEND_SINGLE_ARG(char *); + break; + case LENGTH_MOD_LOW_L: + BUF_STD_APPEND_SINGLE_ARG(wchar_t *); + break; + default: + abort(); + } + break; + case 'd': + case 'i': + fmt_ch++; + + switch (length_mod) { + case LENGTH_MOD_NONE: + case LENGTH_MOD_H: + case LENGTH_MOD_HH: + BUF_STD_APPEND_SINGLE_ARG(int); + break; + case LENGTH_MOD_LOW_L: + BUF_STD_APPEND_SINGLE_ARG(long); + break; + case LENGTH_MOD_LOW_LL: + BUF_STD_APPEND_SINGLE_ARG(long long); + break; + case LENGTH_MOD_Z: + BUF_STD_APPEND_SINGLE_ARG(size_t); + break; + default: + abort(); + } + break; + case 'o': + case 'x': + case 'X': + case 'u': + fmt_ch++; + + switch (length_mod) { + case LENGTH_MOD_NONE: + case LENGTH_MOD_H: + case LENGTH_MOD_HH: + BUF_STD_APPEND_SINGLE_ARG(unsigned int); + break; + case LENGTH_MOD_LOW_L: + BUF_STD_APPEND_SINGLE_ARG(unsigned long); + break; + case LENGTH_MOD_LOW_LL: + BUF_STD_APPEND_SINGLE_ARG(unsigned long long); + break; + case LENGTH_MOD_Z: + BUF_STD_APPEND_SINGLE_ARG(size_t); + break; + default: + abort(); + } + break; + case 'f': + case 'F': + case 'e': + case 'E': + case 'g': + case 'G': + fmt_ch++; + + switch (length_mod) { + case LENGTH_MOD_NONE: + BUF_STD_APPEND_SINGLE_ARG(double); + break; + case LENGTH_MOD_UP_L: + BUF_STD_APPEND_SINGLE_ARG(long double); + break; + default: + abort(); + } + break; + case 'p': + fmt_ch++; + + if (length_mod == LENGTH_MOD_NONE) { + BUF_STD_APPEND_SINGLE_ARG(void *); + } else { + abort(); + } + break; + default: + abort(); + } + +update_rw_fmt: + *out_fmt_ch = fmt_ch; +} + +BT_HIDDEN +void bt_common_custom_vsnprintf(char *buf, size_t buf_size, + char intro, + bt_common_handle_custom_specifier_func handle_specifier, + void *priv_data, const char *fmt, va_list *args) +{ + const char *fmt_ch = fmt; + char *buf_ch = buf; + + assert(buf); + assert(fmt); + assert(*args); + + while (*fmt_ch != '\0') { + switch (*fmt_ch) { + case '%': + assert(fmt_ch[1] != '\0'); + + if (fmt_ch[1] == intro) { + handle_specifier(priv_data, &buf_ch, + buf_size - (size_t) (buf_ch - buf), + &fmt_ch, args); + } else { + handle_conversion_specifier_std(buf, &buf_ch, + buf_size, &fmt_ch, args); + } + + if (buf_ch >= buf + buf_size - 1) { + fmt_ch = ""; + } + break; + default: + *buf_ch = *fmt_ch; + buf_ch++; + if (buf_ch >= buf + buf_size - 1) { + fmt_ch = ""; + } + + fmt_ch++; + } + } + + *buf_ch = '\0'; +} + +BT_HIDDEN +void bt_common_custom_snprintf(char *buf, size_t buf_size, + char intro, + bt_common_handle_custom_specifier_func handle_specifier, + void *priv_data, const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + bt_common_custom_vsnprintf(buf, buf_size, intro, handle_specifier, + priv_data, fmt, &args); + va_end(args); +} diff --git a/include/babeltrace/common-internal.h b/include/babeltrace/common-internal.h index 880b2e47..b6981b27 100644 --- a/include/babeltrace/common-internal.h +++ b/include/babeltrace/common-internal.h @@ -3,6 +3,7 @@ #include #include +#include #define BT_COMMON_COLOR_RESET "\033[0m" #define BT_COMMON_COLOR_BOLD "\033[1m" @@ -199,8 +200,75 @@ bool bt_common_star_glob_match(const char *pattern, size_t pattern_len, BT_HIDDEN GString *bt_common_normalize_path(const char *path, const char *wd); +typedef void (* bt_common_handle_custom_specifier_func)(void *priv_data, + char **buf, size_t avail_size, const char **fmt, va_list *args); + +/* + * This is a custom vsnprintf() which handles the standard conversion + * specifier as well as custom ones. + * + * `fmt` is a typical printf()-style format string, with the following + * limitations: + * + * * The `*` width specifier is not accepted. + * * The `*` precision specifier is not accepted. + * * The `j` and `t` length modifiers are not accepted. + * * The `n` format specifier is not accepted. + * * The format specifiers defined in are not accepted + * except for `PRId64`, `PRIu64`, `PRIx64`, `PRIX64`, `PRIo64`, and + * `PRIi64`. + * + * `intro` specifies which special character immediately following an + * introductory `%` character in `fmt` is used to indicate a custom + * conversion specifier. For example, if `intro` is '@', then any `%@` + * sequence in `fmt` is the beginning of a custom conversion specifier. + * + * When a custom conversion specifier is encountered in `fmt`, + * the function calls `handle_specifier`. This callback receives: + * + * `priv_data`: + * Custom, private data. + * + * `buf`: + * Address of the current buffer pointer. `*buf` is the position to + * append new data. The callback must update `*buf` when appending + * new data. The callback must ensure not to write passed the whole + * buffer passed to bt_common_custom_vsnprintf(). + * + * `avail_size`: + * Number of bytes left in whole buffer from the `*buf` point. + * + * `fmt`: + * Address of the current format string pointer. `*fmt` points to + * the introductory `%` character, which is followed by the + * character `intro`. The callback must update `*fmt` so that it + * points after the whole custom conversion specifier. + * + * `args`: + * Variable argument list. Use va_arg() to get new arguments from + * this list and update it at the same time. + * + * Because this is an internal utility, this function and its callback + * do not return error codes: they abort when there's any error (bad + * format string, for example). + */ +BT_HIDDEN +void bt_common_custom_vsnprintf(char *buf, size_t buf_size, + char intro, + bt_common_handle_custom_specifier_func handle_specifier, + void *priv_data, const char *fmt, va_list *args); + +/* + * Variadic form of bt_common_custom_vsnprintf(). + */ +BT_HIDDEN +void bt_common_custom_snprintf(char *buf, size_t buf_size, + char intro, + bt_common_handle_custom_specifier_func handle_specifier, + void *priv_data, const char *fmt, ...); + /* - * Return the system page size. + * Returns the system page size. */ BT_HIDDEN size_t bt_common_get_page_size(void); -- 2.34.1