Common: add internal bt_common_custom_vsnprintf()
authorPhilippe Proulx <eeppeliteloop@gmail.com>
Fri, 23 Feb 2018 03:01:15 +0000 (22:01 -0500)
committerPhilippe Proulx <eeppeliteloop@gmail.com>
Thu, 2 May 2019 03:32:03 +0000 (23:32 -0400)
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 <eeppeliteloop@gmail.com>
common/common.c
include/babeltrace/common-internal.h

index 3020b60ffd1a1bbd39d6fc1ba16f65736883b198..cf31aecc13f91505209d8202774bbfa423eca7af 100644 (file)
 
 #include <unistd.h>
 #include <string.h>
+#include <inttypes.h>
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <unistd.h>
 #include <assert.h>
+#include <stdarg.h>
 #include <ctype.h>
 #include <glib.h>
 #include <stdlib.h>
+#include <stdio.h>
+#include <wchar.h>
 #include <stdbool.h>
 #include <babeltrace/babeltrace-internal.h>
 #include <babeltrace/common-internal.h>
@@ -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);
+}
index 880b2e47357b33a0328686dc73a4b4e2c8b8a6f7..b6981b27a0125508fe79acd29a5ab8e575cedd6d 100644 (file)
@@ -3,6 +3,7 @@
 
 #include <stdbool.h>
 #include <babeltrace/babeltrace-internal.h>
+#include <stdarg.h>
 
 #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 <inttypes.h> 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);
This page took 0.033993 seconds and 4 git commands to generate.