2 * Babeltrace common functions
4 * Copyright 2016 Philippe Proulx <pproulx@efficios.com>
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to deal
8 * in the Software without restriction, including without limitation the rights
9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 * copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25 #define BT_LOG_OUTPUT_LEVEL log_level
26 #define BT_LOG_TAG "COMMON"
27 #include "logging/log.h"
32 #include <sys/types.h>
35 #include "common/assert.h"
43 #include "common/macros.h"
44 #include "common/common.h"
45 #include "compat/unistd.h"
49 #include <sys/ioctl.h>
52 #define SYSTEM_PLUGIN_PATH BABELTRACE_PLUGINS_DIR
53 #define HOME_ENV_VAR "HOME"
54 #define HOME_PLUGIN_SUBPATH "/.local/lib/babeltrace2/plugins"
56 static const char *bt_common_color_code_reset
= "";
57 static const char *bt_common_color_code_bold
= "";
58 static const char *bt_common_color_code_fg_default
= "";
59 static const char *bt_common_color_code_fg_red
= "";
60 static const char *bt_common_color_code_fg_green
= "";
61 static const char *bt_common_color_code_fg_yellow
= "";
62 static const char *bt_common_color_code_fg_blue
= "";
63 static const char *bt_common_color_code_fg_magenta
= "";
64 static const char *bt_common_color_code_fg_cyan
= "";
65 static const char *bt_common_color_code_fg_light_gray
= "";
66 static const char *bt_common_color_code_bg_default
= "";
67 static const char *bt_common_color_code_bg_red
= "";
68 static const char *bt_common_color_code_bg_green
= "";
69 static const char *bt_common_color_code_bg_yellow
= "";
70 static const char *bt_common_color_code_bg_blue
= "";
71 static const char *bt_common_color_code_bg_magenta
= "";
72 static const char *bt_common_color_code_bg_cyan
= "";
73 static const char *bt_common_color_code_bg_light_gray
= "";
76 void __attribute__((constructor
)) bt_common_color_ctor(void)
78 if (bt_common_colors_supported()) {
79 bt_common_color_code_reset
= BT_COMMON_COLOR_RESET
;
80 bt_common_color_code_bold
= BT_COMMON_COLOR_BOLD
;
81 bt_common_color_code_fg_default
= BT_COMMON_COLOR_FG_DEFAULT
;
82 bt_common_color_code_fg_red
= BT_COMMON_COLOR_FG_RED
;
83 bt_common_color_code_fg_green
= BT_COMMON_COLOR_FG_GREEN
;
84 bt_common_color_code_fg_yellow
= BT_COMMON_COLOR_FG_YELLOW
;
85 bt_common_color_code_fg_blue
= BT_COMMON_COLOR_FG_BLUE
;
86 bt_common_color_code_fg_magenta
= BT_COMMON_COLOR_FG_MAGENTA
;
87 bt_common_color_code_fg_cyan
= BT_COMMON_COLOR_FG_CYAN
;
88 bt_common_color_code_fg_light_gray
= BT_COMMON_COLOR_FG_LIGHT_GRAY
;
89 bt_common_color_code_bg_default
= BT_COMMON_COLOR_BG_DEFAULT
;
90 bt_common_color_code_bg_red
= BT_COMMON_COLOR_BG_RED
;
91 bt_common_color_code_bg_green
= BT_COMMON_COLOR_BG_GREEN
;
92 bt_common_color_code_bg_yellow
= BT_COMMON_COLOR_BG_YELLOW
;
93 bt_common_color_code_bg_blue
= BT_COMMON_COLOR_BG_BLUE
;
94 bt_common_color_code_bg_magenta
= BT_COMMON_COLOR_BG_MAGENTA
;
95 bt_common_color_code_bg_cyan
= BT_COMMON_COLOR_BG_CYAN
;
96 bt_common_color_code_bg_light_gray
= BT_COMMON_COLOR_BG_LIGHT_GRAY
;
101 const char *bt_common_get_system_plugin_path(void)
103 return SYSTEM_PLUGIN_PATH
;
108 bool bt_common_is_setuid_setgid(void)
112 #else /* __MINGW32__ */
114 bool bt_common_is_setuid_setgid(void)
116 return (geteuid() != getuid() || getegid() != getgid());
118 #endif /* __MINGW32__ */
122 const char *bt_get_home_dir(int log_level
)
124 return g_get_home_dir();
126 #else /* __MINGW32__ */
128 char *bt_secure_getenv(const char *name
, int log_level
)
130 if (bt_common_is_setuid_setgid()) {
131 BT_LOGD("Disregarding environment variable for setuid/setgid binary: "
132 "name=\"%s\"", name
);
139 const char *bt_get_home_dir(int log_level
)
144 val
= bt_secure_getenv(HOME_ENV_VAR
, log_level
);
148 /* Fallback on password file. */
149 pwd
= getpwuid(getuid());
157 #endif /* __MINGW32__ */
160 char *bt_common_get_home_plugin_path(int log_level
)
163 const char *home_dir
;
166 home_dir
= bt_get_home_dir(log_level
);
171 length
= strlen(home_dir
) + strlen(HOME_PLUGIN_SUBPATH
) + 1;
173 if (length
>= PATH_MAX
) {
174 BT_LOGW("Home directory path is too long: "
175 "length=%zu, max-length=%u", length
, PATH_MAX
);
179 path
= malloc(PATH_MAX
);
184 strcpy(path
, home_dir
);
185 strcat(path
, HOME_PLUGIN_SUBPATH
);
192 int bt_common_append_plugin_path_dirs(const char *paths
, GPtrArray
*dirs
)
197 size_t init_dirs_len
;
200 init_dirs_len
= dirs
->len
;
203 /* Nothing to append */
208 end
= paths
+ strlen(paths
);
212 const char *next_sep
;
214 next_sep
= strchr(at
, G_SEARCHPATH_SEPARATOR
);
215 if (next_sep
== at
) {
217 * Empty path: try next character (supported
218 * to conform to the typical parsing of $PATH).
222 } else if (!next_sep
) {
223 /* No more separator: use the remaining */
224 next_sep
= paths
+ strlen(paths
);
227 path
= g_string_new(NULL
);
232 g_string_append_len(path
, at
, next_sep
- at
);
234 g_ptr_array_add(dirs
, path
);
242 /* Remove the new entries in dirs */
243 while (dirs
->len
> init_dirs_len
) {
244 g_ptr_array_remove_index(dirs
, init_dirs_len
);
252 bool isarealtty(int fd
)
255 struct stat tty_stats
;
262 if (fstat(fd
, &tty_stats
) == 0) {
263 if (!S_ISCHR(tty_stats
.st_mode
)) {
264 /* Not a character device: not a TTY */
276 bool bt_common_colors_supported(void)
278 static bool supports_colors
= false;
279 static bool supports_colors_set
= false;
280 const char *term_env_var
;
281 const char *term_color_env_var
;
283 if (supports_colors_set
) {
287 supports_colors_set
= true;
290 * `BABELTRACE_TERM_COLOR` environment variable always overrides
291 * the automatic color support detection.
293 term_color_env_var
= getenv("BABELTRACE_TERM_COLOR");
294 if (term_color_env_var
) {
295 if (g_ascii_strcasecmp(term_color_env_var
, "always") == 0) {
297 supports_colors
= true;
298 } else if (g_ascii_strcasecmp(term_color_env_var
, "never") == 0) {
299 /* Force no colors */
304 /* We need a compatible, known terminal */
305 term_env_var
= getenv("TERM");
310 if (strncmp(term_env_var
, "xterm", 5) != 0 &&
311 strncmp(term_env_var
, "rxvt", 4) != 0 &&
312 strncmp(term_env_var
, "konsole", 7) != 0 &&
313 strncmp(term_env_var
, "gnome", 5) != 0 &&
314 strncmp(term_env_var
, "screen", 5) != 0 &&
315 strncmp(term_env_var
, "tmux", 4) != 0 &&
316 strncmp(term_env_var
, "putty", 5) != 0) {
320 /* Both standard output and error streams need to be TTYs */
321 if (!isarealtty(STDOUT_FILENO
) || !isarealtty(STDERR_FILENO
)) {
325 supports_colors
= true;
328 return supports_colors
;
332 const char *bt_common_color_reset(void)
334 return bt_common_color_code_reset
;
338 const char *bt_common_color_bold(void)
340 return bt_common_color_code_bold
;
344 const char *bt_common_color_fg_default(void)
346 return bt_common_color_code_fg_default
;
350 const char *bt_common_color_fg_red(void)
352 return bt_common_color_code_fg_red
;
356 const char *bt_common_color_fg_green(void)
358 return bt_common_color_code_fg_green
;
362 const char *bt_common_color_fg_yellow(void)
364 return bt_common_color_code_fg_yellow
;
368 const char *bt_common_color_fg_blue(void)
370 return bt_common_color_code_fg_blue
;
374 const char *bt_common_color_fg_magenta(void)
376 return bt_common_color_code_fg_magenta
;
380 const char *bt_common_color_fg_cyan(void)
382 return bt_common_color_code_fg_cyan
;
386 const char *bt_common_color_fg_light_gray(void)
388 return bt_common_color_code_fg_light_gray
;
392 const char *bt_common_color_bg_default(void)
394 return bt_common_color_code_bg_default
;
398 const char *bt_common_color_bg_red(void)
400 return bt_common_color_code_bg_red
;
404 const char *bt_common_color_bg_green(void)
406 return bt_common_color_code_bg_green
;
410 const char *bt_common_color_bg_yellow(void)
412 return bt_common_color_code_bg_yellow
;
416 const char *bt_common_color_bg_blue(void)
418 return bt_common_color_code_bg_blue
;
422 const char *bt_common_color_bg_magenta(void)
424 return bt_common_color_code_bg_magenta
;
428 const char *bt_common_color_bg_cyan(void)
430 return bt_common_color_code_bg_cyan
;
434 const char *bt_common_color_bg_light_gray(void)
436 return bt_common_color_code_bg_light_gray
;
440 GString
*bt_common_string_until(const char *input
, const char *escapable_chars
,
441 const char *end_chars
, size_t *end_pos
)
443 GString
*output
= g_string_new(NULL
);
446 const char *end_char
;
452 for (ch
= input
; *ch
!= '\0'; ch
++) {
454 bool continue_loop
= false;
457 /* `\` at the end of the string: append `\` */
458 g_string_append_c(output
, *ch
);
463 for (es_char
= escapable_chars
; *es_char
!= '\0'; es_char
++) {
464 if (ch
[1] == *es_char
) {
466 * `\` followed by an escapable
467 * character: append the escaped
470 g_string_append_c(output
, ch
[1]);
472 continue_loop
= true;
482 * `\` followed by a non-escapable character:
483 * append `\` and the character.
485 g_string_append_c(output
, *ch
);
486 g_string_append_c(output
, ch
[1]);
490 for (end_char
= end_chars
; *end_char
!= '\0'; end_char
++) {
491 if (*ch
== *end_char
) {
493 * End character found:
494 * terminate this loop.
500 /* Normal character: append */
501 g_string_append_c(output
, *ch
);
507 *end_pos
= ch
- input
;
514 g_string_free(output
, TRUE
);
523 GString
*bt_common_shell_quote(const char *input
, bool with_single_quotes
)
525 GString
*output
= g_string_new(NULL
);
527 bool no_quote
= true;
533 if (strlen(input
) == 0) {
534 if (with_single_quotes
) {
535 g_string_assign(output
, "''");
541 for (ch
= input
; *ch
!= '\0'; ch
++) {
544 if (!g_ascii_isalpha(c
) && !g_ascii_isdigit(c
) && c
!= '_' &&
545 c
!= '@' && c
!= '%' && c
!= '+' && c
!= '=' &&
546 c
!= ':' && c
!= ',' && c
!= '.' && c
!= '/' &&
554 g_string_assign(output
, input
);
558 if (with_single_quotes
) {
559 g_string_assign(output
, "'");
562 for (ch
= input
; *ch
!= '\0'; ch
++) {
564 g_string_append(output
, "'\"'\"'");
566 g_string_append_c(output
, *ch
);
570 if (with_single_quotes
) {
571 g_string_append_c(output
, '\'');
579 bool bt_common_string_is_printable(const char *input
)
582 bool printable
= true;
583 BT_ASSERT_DBG(input
);
585 for (ch
= input
; *ch
!= '\0'; ch
++) {
586 if (!isprint(*ch
) && *ch
!= '\n' && *ch
!= '\r' &&
587 *ch
!= '\t' && *ch
!= '\v') {
598 void bt_common_destroy_lttng_live_url_parts(
599 struct bt_common_lttng_live_url_parts
*parts
)
606 g_string_free(parts
->proto
, TRUE
);
610 if (parts
->hostname
) {
611 g_string_free(parts
->hostname
, TRUE
);
612 parts
->hostname
= NULL
;
615 if (parts
->target_hostname
) {
616 g_string_free(parts
->target_hostname
, TRUE
);
617 parts
->target_hostname
= NULL
;
620 if (parts
->session_name
) {
621 g_string_free(parts
->session_name
, TRUE
);
622 parts
->session_name
= NULL
;
630 struct bt_common_lttng_live_url_parts
bt_common_parse_lttng_live_url(
631 const char *url
, char *error_buf
, size_t error_buf_size
)
633 struct bt_common_lttng_live_url_parts parts
;
634 const char *at
= url
;
638 memset(&parts
, 0, sizeof(parts
));
642 parts
.proto
= bt_common_string_until(at
, "", ":", &end_pos
);
643 if (!parts
.proto
|| parts
.proto
->len
== 0) {
645 snprintf(error_buf
, error_buf_size
, "Missing protocol");
651 if (strcmp(parts
.proto
->str
, "net") == 0) {
652 g_string_assign(parts
.proto
, "net4");
655 if (strcmp(parts
.proto
->str
, "net4") != 0 &&
656 strcmp(parts
.proto
->str
, "net6") != 0) {
658 snprintf(error_buf
, error_buf_size
,
659 "Unknown protocol: `%s`", parts
.proto
->str
);
665 if (at
[end_pos
] != ':') {
667 snprintf(error_buf
, error_buf_size
,
668 "Expecting `:` after `%s`", parts
.proto
->str
);
677 if (strncmp(at
, "://", 3) != 0) {
679 snprintf(error_buf
, error_buf_size
,
680 "Expecting `://` after protocol");
690 parts
.hostname
= bt_common_string_until(at
, "", ":/", &end_pos
);
691 if (!parts
.hostname
|| parts
.hostname
->len
== 0) {
693 snprintf(error_buf
, error_buf_size
, "Missing hostname");
699 if (at
[end_pos
] == ':') {
704 port
= bt_common_string_until(at
, "", "/", &end_pos
);
705 if (!port
|| port
->len
== 0) {
707 snprintf(error_buf
, error_buf_size
, "Missing port");
713 if (sscanf(port
->str
, "%d", &parts
.port
) != 1) {
715 snprintf(error_buf
, error_buf_size
,
716 "Invalid port: `%s`", port
->str
);
719 g_string_free(port
, TRUE
);
723 g_string_free(port
, TRUE
);
725 if (parts
.port
< 0 || parts
.port
>= 65536) {
727 snprintf(error_buf
, error_buf_size
,
728 "Invalid port: %d", parts
.port
);
735 if (at
[end_pos
] == '\0') {
736 /* Relay daemon hostname and ports provided only */
743 if (strncmp(at
, "/host/", 6) != 0) {
745 snprintf(error_buf
, error_buf_size
,
746 "Expecting `/host/` after hostname or port");
754 /* Target hostname */
755 parts
.target_hostname
= bt_common_string_until(at
, "", "/", &end_pos
);
756 if (!parts
.target_hostname
|| parts
.target_hostname
->len
== 0) {
758 snprintf(error_buf
, error_buf_size
,
759 "Missing target hostname");
765 if (at
[end_pos
] == '\0') {
767 snprintf(error_buf
, error_buf_size
,
768 "Missing `/` after target hostname (`%s`)",
769 parts
.target_hostname
->str
);
779 parts
.session_name
= bt_common_string_until(at
, "", "/", &end_pos
);
780 if (!parts
.session_name
|| parts
.session_name
->len
== 0) {
782 snprintf(error_buf
, error_buf_size
,
783 "Missing session name");
789 if (at
[end_pos
] == '/') {
791 snprintf(error_buf
, error_buf_size
,
792 "Unexpected `/` after session name (`%s`)",
793 parts
.session_name
->str
);
802 bt_common_destroy_lttng_live_url_parts(&parts
);
809 void bt_common_normalize_star_glob_pattern(char *pattern
)
813 bool got_star
= false;
817 for (p
= pattern
, np
= pattern
; *p
!= '\0'; p
++) {
821 /* Avoid consecutive stars. */
828 /* Copy backslash character. */
843 /* Copy single character. */
853 bool at_end_of_pattern(const char *p
, const char *pattern
, size_t pattern_len
)
855 return (p
- pattern
) == pattern_len
|| *p
== '\0';
859 * Globbing matching function with the star feature only (`?` and
860 * character sets are not supported). This matches `candidate` (plain
861 * string) against `pattern`. A literal star can be escaped with `\` in
864 * `pattern_len` or `candidate_len` can be greater than the actual
865 * string length of `pattern` or `candidate` if the string is
869 bool bt_common_star_glob_match(const char *pattern
, size_t pattern_len
,
870 const char *candidate
, size_t candidate_len
) {
871 const char *retry_c
= candidate
, *retry_p
= pattern
, *c
, *p
;
872 bool got_a_star
= false;
879 * The concept here is to retry a match in the specific case
880 * where we already got a star. The retry position for the
881 * pattern is just after the most recent star, and the retry
882 * position for the candidate is the character following the
883 * last try's first character.
887 * candidate: hi ev every onyx one
889 * pattern: hi*every*one
892 * candidate: hi ev every onyx one
894 * pattern: hi*every*one
897 * candidate: hi ev every onyx one
899 * pattern: hi*every*one
902 * candidate: hi ev every onyx one
904 * pattern: hi*every*one
907 * candidate: hi ev every onyx one
909 * pattern: hi*every*one
912 * candidate: hi ev every onyx one
914 * pattern: hi*every*one
917 * candidate: hi ev every onyx one
919 * pattern: hi*every*one
922 * candidate: hi ev every onyx one
924 * pattern: hi*every*one
927 * candidate: hi ev every onyx one
929 * pattern: hi*every*one
932 * candidate: hi ev every onyx one
934 * pattern: hi*every*one
937 * candidate: hi ev every onyx one
939 * pattern: hi*every*one
942 * candidate: hi ev every onyx one
944 * pattern: hi*every*one
947 * candidate: hi ev every onyx one
949 * pattern: hi*every*one
952 * candidate: hi ev every onyx one
954 * pattern: hi*every*one
957 * candidate: hi ev every onyx one
959 * pattern: hi*every*one
962 * candidate: hi ev every onyx one
964 * pattern: hi*every*one
967 * candidate: hi ev every onyx one
969 * pattern: hi*every*one
972 * candidate: hi ev every onyx one
974 * pattern: hi*every*one
977 * candidate: hi ev every onyx one
979 * pattern: hi*every*one
982 * candidate: hi ev every onyx one
984 * pattern: hi*every*one
987 * candidate: hi ev every onyx one
989 * pattern: hi*every*one
992 * candidate: hi ev every onyx one
994 * pattern: hi*every*one
997 * candidate: hi ev every onyx one
999 * pattern: hi*every*one
1002 * candidate: hi ev every onyx one
1004 * pattern: hi*every*one
1007 * candidate: hi ev every onyx one
1009 * pattern: hi*every*one
1012 * candidate: hi ev every onyx one
1014 * pattern: hi*every*one
1017 * candidate: hi ev every onyx one
1019 * pattern: hi*every*one
1022 while ((c
- candidate
) < candidate_len
&& *c
!= '\0') {
1025 if (at_end_of_pattern(p
, pattern
, pattern_len
)) {
1026 goto end_of_pattern
;
1034 * Our first try starts at the current candidate
1035 * character and after the star in the pattern.
1040 if (at_end_of_pattern(retry_p
, pattern
, pattern_len
)) {
1042 * Star at the end of the pattern at
1043 * this point: automatic match.
1050 /* Go to escaped character. */
1054 * Fall through the default case which compares
1055 * the escaped character now.
1059 if (at_end_of_pattern(p
, pattern
, pattern_len
) ||
1062 /* Character mismatch OR end of pattern. */
1065 * We didn't get any star yet,
1066 * so this first mismatch
1067 * automatically makes the whole
1074 * Next try: next candidate character,
1075 * original pattern character (following
1076 * the most recent star).
1084 /* Next pattern and candidate characters. */
1090 * We checked every candidate character and we're still in a
1091 * success state: the only pattern character allowed to remain
1094 if (at_end_of_pattern(p
, pattern
, pattern_len
)) {
1099 return p
[-1] == '*' && at_end_of_pattern(p
, pattern
, pattern_len
);
1104 GString
*bt_common_normalize_path(const char *path
, const char *wd
)
1107 GString
*norm_path
= NULL
;
1111 tmp
= _fullpath(NULL
, path
, PATH_MAX
);
1116 norm_path
= g_string_new(tmp
);
1124 g_string_free(norm_path
, TRUE
);
1133 void append_path_parts(const char *path
, GPtrArray
*parts
)
1135 const char *ch
= path
;
1136 const char *last
= path
;
1139 if (*ch
== G_DIR_SEPARATOR
|| *ch
== '\0') {
1140 if (ch
- last
> 0) {
1141 GString
*part
= g_string_new(NULL
);
1144 g_string_append_len(part
, last
, ch
- last
);
1145 g_ptr_array_add(parts
, part
);
1160 void destroy_gstring(void *gstring
)
1162 (void) g_string_free(gstring
, TRUE
);
1166 GString
*bt_common_normalize_path(const char *path
, const char *wd
)
1170 GPtrArray
*parts
= NULL
;
1173 norm_path
= g_string_new(G_DIR_SEPARATOR_S
);
1178 parts
= g_ptr_array_new_with_free_func(destroy_gstring
);
1183 if (path
[0] != G_DIR_SEPARATOR
) {
1184 /* Relative path: start with working directory */
1186 append_path_parts(wd
, parts
);
1188 gchar
*cd
= g_get_current_dir();
1190 append_path_parts(cd
, parts
);
1195 /* Append parts of the path parameter */
1196 append_path_parts(path
, parts
);
1198 /* Resolve special `..` and `.` parts */
1199 for (i
= 0; i
< parts
->len
; i
++) {
1200 GString
*part
= g_ptr_array_index(parts
, i
);
1202 if (strcmp(part
->str
, "..") == 0) {
1205 * First part of absolute path is `..`:
1211 /* Remove `..` and previous part */
1212 g_ptr_array_remove_index(parts
, i
- 1);
1213 g_ptr_array_remove_index(parts
, i
- 1);
1215 } else if (strcmp(part
->str
, ".") == 0) {
1217 g_ptr_array_remove_index(parts
, i
);
1222 /* Create normalized path with what's left */
1223 for (i
= 0; i
< parts
->len
; i
++) {
1224 GString
*part
= g_ptr_array_index(parts
, i
);
1226 g_string_append(norm_path
, part
->str
);
1228 if (i
< parts
->len
- 1) {
1229 g_string_append_c(norm_path
, G_DIR_SEPARATOR
);
1237 g_string_free(norm_path
, TRUE
);
1243 g_ptr_array_free(parts
, TRUE
);
1251 size_t bt_common_get_page_size(int log_level
)
1255 page_size
= bt_sysconf(_SC_PAGESIZE
);
1256 if (page_size
< 0) {
1257 BT_LOGF("Cannot get system's page size: ret=%d",
1265 #define BUF_STD_APPEND(...) \
1267 char _tmp_fmt[64]; \
1269 size_t _size = buf_size - (size_t) (*buf_ch - buf); \
1270 size_t _tmp_fmt_size = (size_t) (fmt_ch - *out_fmt_ch); \
1271 strncpy(_tmp_fmt, *out_fmt_ch, _tmp_fmt_size); \
1272 _tmp_fmt[_tmp_fmt_size] = '\0'; \
1273 _count = snprintf(*buf_ch, _size, _tmp_fmt, __VA_ARGS__); \
1274 BT_ASSERT_DBG(_count >= 0); \
1275 *buf_ch += MIN(_count, _size); \
1278 #define BUF_STD_APPEND_SINGLE_ARG(_type) \
1280 _type _arg = va_arg(*args, _type); \
1281 BUF_STD_APPEND(_arg); \
1284 static inline void handle_conversion_specifier_std(char *buf
, char **buf_ch
,
1285 size_t buf_size
, const char **out_fmt_ch
, va_list *args
)
1287 const char *fmt_ch
= *out_fmt_ch
;
1288 enum LENGTH_MODIFIER
{
1296 } length_mod
= LENGTH_MOD_NONE
;
1301 if (*fmt_ch
== '%') {
1327 if (*fmt_ch
< '0' || *fmt_ch
> '9') {
1335 if (*fmt_ch
== '.') {
1339 if (*fmt_ch
< '0' || *fmt_ch
> '9') {
1347 /* format (PRI*64) */
1348 if (strncmp(fmt_ch
, PRId64
, sizeof(PRId64
) - 1) == 0) {
1349 fmt_ch
+= sizeof(PRId64
) - 1;
1350 BUF_STD_APPEND_SINGLE_ARG(int64_t);
1352 } else if (strncmp(fmt_ch
, PRIu64
, sizeof(PRIu64
) - 1) == 0) {
1353 fmt_ch
+= sizeof(PRIu64
) - 1;
1354 BUF_STD_APPEND_SINGLE_ARG(uint64_t);
1356 } else if (strncmp(fmt_ch
, PRIx64
, sizeof(PRIx64
) - 1) == 0) {
1357 fmt_ch
+= sizeof(PRIx64
) - 1;
1358 BUF_STD_APPEND_SINGLE_ARG(uint64_t);
1360 } else if (strncmp(fmt_ch
, PRIX64
, sizeof(PRIX64
) - 1) == 0) {
1361 fmt_ch
+= sizeof(PRIX64
) - 1;
1362 BUF_STD_APPEND_SINGLE_ARG(uint64_t);
1364 } else if (strncmp(fmt_ch
, PRIo64
, sizeof(PRIo64
) - 1) == 0) {
1365 fmt_ch
+= sizeof(PRIo64
) - 1;
1366 BUF_STD_APPEND_SINGLE_ARG(uint64_t);
1368 } else if (strncmp(fmt_ch
, PRIi64
, sizeof(PRIi64
) - 1) == 0) {
1369 fmt_ch
+= sizeof(PRIi64
) - 1;
1370 BUF_STD_APPEND_SINGLE_ARG(int64_t);
1377 length_mod
= LENGTH_MOD_H
;
1380 if (*fmt_ch
== 'h') {
1381 length_mod
= LENGTH_MOD_HH
;
1387 length_mod
= LENGTH_MOD_LOW_L
;
1390 if (*fmt_ch
== 'l') {
1391 length_mod
= LENGTH_MOD_LOW_LL
;
1397 length_mod
= LENGTH_MOD_UP_L
;
1401 length_mod
= LENGTH_MOD_Z
;
1414 switch (length_mod
) {
1415 case LENGTH_MOD_NONE
:
1416 case LENGTH_MOD_LOW_L
:
1417 BUF_STD_APPEND_SINGLE_ARG(int);
1427 switch (length_mod
) {
1428 case LENGTH_MOD_NONE
:
1429 BUF_STD_APPEND_SINGLE_ARG(char *);
1431 case LENGTH_MOD_LOW_L
:
1432 BUF_STD_APPEND_SINGLE_ARG(wchar_t *);
1442 switch (length_mod
) {
1443 case LENGTH_MOD_NONE
:
1446 BUF_STD_APPEND_SINGLE_ARG(int);
1448 case LENGTH_MOD_LOW_L
:
1449 BUF_STD_APPEND_SINGLE_ARG(long);
1451 case LENGTH_MOD_LOW_LL
:
1452 BUF_STD_APPEND_SINGLE_ARG(long long);
1455 BUF_STD_APPEND_SINGLE_ARG(size_t);
1467 switch (length_mod
) {
1468 case LENGTH_MOD_NONE
:
1471 BUF_STD_APPEND_SINGLE_ARG(unsigned int);
1473 case LENGTH_MOD_LOW_L
:
1474 BUF_STD_APPEND_SINGLE_ARG(unsigned long);
1476 case LENGTH_MOD_LOW_LL
:
1477 BUF_STD_APPEND_SINGLE_ARG(unsigned long long);
1480 BUF_STD_APPEND_SINGLE_ARG(size_t);
1494 switch (length_mod
) {
1495 case LENGTH_MOD_NONE
:
1496 BUF_STD_APPEND_SINGLE_ARG(double);
1498 case LENGTH_MOD_UP_L
:
1499 BUF_STD_APPEND_SINGLE_ARG(long double);
1508 if (length_mod
== LENGTH_MOD_NONE
) {
1509 BUF_STD_APPEND_SINGLE_ARG(void *);
1519 *out_fmt_ch
= fmt_ch
;
1523 void bt_common_custom_vsnprintf(char *buf
, size_t buf_size
,
1525 bt_common_handle_custom_specifier_func handle_specifier
,
1526 void *priv_data
, const char *fmt
, va_list *args
)
1528 const char *fmt_ch
= fmt
;
1534 while (*fmt_ch
!= '\0') {
1537 BT_ASSERT_DBG(fmt_ch
[1] != '\0');
1539 if (fmt_ch
[1] == intro
) {
1540 handle_specifier(priv_data
, &buf_ch
,
1541 buf_size
- (size_t) (buf_ch
- buf
),
1544 handle_conversion_specifier_std(buf
, &buf_ch
,
1545 buf_size
, &fmt_ch
, args
);
1548 if (buf_ch
>= buf
+ buf_size
- 1) {
1555 if (buf_ch
>= buf
+ buf_size
- 1) {
1567 void bt_common_custom_snprintf(char *buf
, size_t buf_size
,
1569 bt_common_handle_custom_specifier_func handle_specifier
,
1570 void *priv_data
, const char *fmt
, ...)
1573 va_start(args
, fmt
);
1574 bt_common_custom_vsnprintf(buf
, buf_size
, intro
, handle_specifier
,
1575 priv_data
, fmt
, &args
);
1580 void bt_common_sep_digits(char *str
, unsigned int digits_per_group
, char sep
)
1589 BT_ASSERT_DBG(digits_per_group
> 0);
1590 BT_ASSERT_DBG(sep
!= '\0');
1592 /* Compute new length of `str` */
1593 orig_len
= strlen(str
);
1594 BT_ASSERT_DBG(orig_len
> 0);
1595 sep_count
= (orig_len
- 1) / digits_per_group
;
1596 new_len
= strlen(str
) + sep_count
;
1599 * Do the work in place. Have the reading pointer `rd` start at
1600 * the end of the original string, and the writing pointer `wr`
1601 * start at the end of the new string, making sure to also put a
1602 * null character there.
1604 rd
= str
+ orig_len
- 1;
1610 * Here's what the process looks like (3 digits per group):
1614 * Destination: 12345678#8
1619 * Destination: 1234567878
1624 * Destination: 1234567678
1629 * Destination: 123456,678
1634 * Destination: 123455,678
1639 * Destination: 123445,678
1644 * Destination: 123345,678
1649 * Destination: 12,345,678
1654 * Destination: 12,345,678
1659 * Destination: 12,345,678
1662 while (rd
!= str
- 1) {
1663 if (i
== digits_per_group
) {
1665 * Time to append the separator: decrement `wr`,
1666 * but keep `rd` as is.
1674 /* Copy read-side character to write-side character */
1683 GString
*bt_common_fold(const char *str
, unsigned int total_length
,
1684 unsigned int indent
)
1686 const unsigned int content_length
= total_length
- indent
;
1687 GString
*folded
= g_string_new(NULL
);
1688 GString
*tmp_line
= g_string_new(NULL
);
1689 gchar
**lines
= NULL
;
1690 gchar
**line_words
= NULL
;
1691 gchar
* const *line
;
1695 BT_ASSERT_DBG(indent
< total_length
);
1696 BT_ASSERT_DBG(tmp_line
);
1697 BT_ASSERT_DBG(folded
);
1699 if (strlen(str
) == 0) {
1700 /* Empty input string: empty output string */
1705 lines
= g_strsplit(str
, "\n", 0);
1706 BT_ASSERT_DBG(lines
);
1708 /* For each source line */
1709 for (line
= lines
; *line
; line
++) {
1710 gchar
* const *word
;
1713 * Append empty line without indenting if source line is
1716 if (strlen(*line
) == 0) {
1717 g_string_append_c(folded
, '\n');
1722 line_words
= g_strsplit(*line
, " ", 0);
1723 BT_ASSERT_DBG(line_words
);
1726 * Indent for first line (we know there's at least one
1727 * word at this point).
1729 for (i
= 0; i
< indent
; i
++) {
1730 g_string_append_c(folded
, ' ');
1733 /* Append words, folding when necessary */
1734 g_string_assign(tmp_line
, "");
1736 for (word
= line_words
; *word
; word
++) {
1738 * `tmp_line->len > 0` is in the condition so
1739 * that words that are larger than
1740 * `content_length` are at least written on
1743 * `tmp_line->len - 1` because the temporary
1744 * line always contains a trailing space which
1745 * won't be part of the line if we fold.
1747 if (tmp_line
->len
> 0 &&
1748 tmp_line
->len
- 1 + strlen(*word
) >= content_length
) {
1749 /* Fold (without trailing space) */
1750 g_string_append_len(folded
,
1751 tmp_line
->str
, tmp_line
->len
- 1);
1752 g_string_append_c(folded
, '\n');
1754 /* Indent new line */
1755 for (i
= 0; i
< indent
; i
++) {
1756 g_string_append_c(folded
, ' ');
1759 g_string_assign(tmp_line
, "");
1762 /* Append current word and space to temporary line */
1763 g_string_append(tmp_line
, *word
);
1764 g_string_append_c(tmp_line
, ' ');
1767 /* Append last line if any, without trailing space */
1768 if (tmp_line
->len
> 0) {
1769 g_string_append_len(folded
, tmp_line
->str
,
1773 /* Append source newline */
1774 g_string_append_c(folded
, '\n');
1776 /* Free array of this line's words */
1777 g_strfreev(line_words
);
1781 /* Remove trailing newline if any */
1782 if (folded
->str
[folded
->len
- 1] == '\n') {
1783 g_string_truncate(folded
, folded
->len
- 1);
1791 BT_ASSERT_DBG(!line_words
);
1794 g_string_free(tmp_line
, TRUE
);
1802 int bt_common_get_term_size(unsigned int *width
, unsigned int *height
)
1804 /* Not supported on Windows yet */
1807 #else /* __MINGW32__ */
1809 int bt_common_get_term_size(unsigned int *width
, unsigned int *height
)
1812 struct winsize winsize
;
1814 if (ioctl(STDOUT_FILENO
, TIOCGWINSZ
, &winsize
) < 0) {
1820 *width
= (unsigned int) winsize
.ws_col
;
1824 *height
= (unsigned int) winsize
.ws_row
;
1830 #endif /* __MINGW32__ */
1833 int bt_common_g_string_append_printf(GString
*str
, const char *fmt
, ...)
1836 gsize len
, allocated_len
, available_len
;
1839 /* str->len excludes \0. */
1841 /* Explicitly exclude \0. */
1842 allocated_len
= str
->allocated_len
- 1;
1843 available_len
= allocated_len
- len
;
1845 str
->len
= allocated_len
;
1847 print_len
= vsnprintf(str
->str
+ len
, available_len
+ 1, fmt
, ap
);
1849 if (print_len
< 0) {
1852 if (G_UNLIKELY(available_len
< print_len
)) {
1854 g_string_set_size(str
, len
+ print_len
);
1856 print_len
= vsprintf(str
->str
+ len
, fmt
, ap
);
1859 str
->len
= len
+ print_len
;
1865 int bt_common_append_file_content_to_g_string(GString
*str
, FILE *fp
)
1867 const size_t chunk_size
= 4096;
1871 gsize orig_len
= str
->len
;
1875 buf
= g_malloc(chunk_size
);
1891 read_len
= fread(buf
, 1, chunk_size
, fp
);
1892 g_string_append_len(str
, buf
, read_len
);
1897 /* Remove what was appended */
1898 g_string_truncate(str
, orig_len
);