* SOFTWARE.
*/
+#define BT_LOG_TAG "COMMON"
+#include "logging.h"
+
#include <unistd.h>
#include <string.h>
+#include <inttypes.h>
#include <sys/types.h>
-#include <pwd.h>
+#include <sys/stat.h>
#include <unistd.h>
-#include <assert.h>
+#include <babeltrace2/assert-internal.h>
+#include <stdarg.h>
+#include <ctype.h>
#include <glib.h>
-#include <babeltrace/babeltrace-internal.h>
-#include <babeltrace/common-internal.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <wchar.h>
+#include <stdbool.h>
+#include <babeltrace2/babeltrace-internal.h>
+#include <babeltrace2/common-internal.h>
+#include <babeltrace2/compat/unistd-internal.h>
+
+#ifndef __MINGW32__
+#include <pwd.h>
+#endif
#define SYSTEM_PLUGIN_PATH INSTALL_LIBDIR "/babeltrace/plugins"
#define HOME_ENV_VAR "HOME"
#define HOME_PLUGIN_SUBPATH "/.local/lib/babeltrace/plugins"
+static const char *bt_common_color_code_reset = "";
+static const char *bt_common_color_code_bold = "";
+static const char *bt_common_color_code_fg_default = "";
+static const char *bt_common_color_code_fg_red = "";
+static const char *bt_common_color_code_fg_green = "";
+static const char *bt_common_color_code_fg_yellow = "";
+static const char *bt_common_color_code_fg_blue = "";
+static const char *bt_common_color_code_fg_magenta = "";
+static const char *bt_common_color_code_fg_cyan = "";
+static const char *bt_common_color_code_fg_light_gray = "";
+static const char *bt_common_color_code_bg_default = "";
+static const char *bt_common_color_code_bg_red = "";
+static const char *bt_common_color_code_bg_green = "";
+static const char *bt_common_color_code_bg_yellow = "";
+static const char *bt_common_color_code_bg_blue = "";
+static const char *bt_common_color_code_bg_magenta = "";
+static const char *bt_common_color_code_bg_cyan = "";
+static const char *bt_common_color_code_bg_light_gray = "";
+
+static
+void __attribute__((constructor)) bt_common_color_ctor(void)
+{
+ if (bt_common_colors_supported()) {
+ bt_common_color_code_reset = BT_COMMON_COLOR_RESET;
+ bt_common_color_code_bold = BT_COMMON_COLOR_BOLD;
+ bt_common_color_code_fg_default = BT_COMMON_COLOR_FG_DEFAULT;
+ bt_common_color_code_fg_red = BT_COMMON_COLOR_FG_RED;
+ bt_common_color_code_fg_green = BT_COMMON_COLOR_FG_GREEN;
+ bt_common_color_code_fg_yellow = BT_COMMON_COLOR_FG_YELLOW;
+ bt_common_color_code_fg_blue = BT_COMMON_COLOR_FG_BLUE;
+ bt_common_color_code_fg_magenta = BT_COMMON_COLOR_FG_MAGENTA;
+ bt_common_color_code_fg_cyan = BT_COMMON_COLOR_FG_CYAN;
+ bt_common_color_code_fg_light_gray = BT_COMMON_COLOR_FG_LIGHT_GRAY;
+ bt_common_color_code_bg_default = BT_COMMON_COLOR_BG_DEFAULT;
+ bt_common_color_code_bg_red = BT_COMMON_COLOR_BG_RED;
+ bt_common_color_code_bg_green = BT_COMMON_COLOR_BG_GREEN;
+ bt_common_color_code_bg_yellow = BT_COMMON_COLOR_BG_YELLOW;
+ bt_common_color_code_bg_blue = BT_COMMON_COLOR_BG_BLUE;
+ bt_common_color_code_bg_magenta = BT_COMMON_COLOR_BG_MAGENTA;
+ bt_common_color_code_bg_cyan = BT_COMMON_COLOR_BG_CYAN;
+ bt_common_color_code_bg_light_gray = BT_COMMON_COLOR_BG_LIGHT_GRAY;
+ }
+}
+
BT_HIDDEN
const char *bt_common_get_system_plugin_path(void)
{
return SYSTEM_PLUGIN_PATH;
}
+#ifdef __MINGW32__
+BT_HIDDEN
+bool bt_common_is_setuid_setgid(void)
+{
+ return false;
+}
+#else /* __MINGW32__ */
BT_HIDDEN
bool bt_common_is_setuid_setgid(void)
{
return (geteuid() != getuid() || getegid() != getgid());
}
+#endif /* __MINGW32__ */
-static char *bt_secure_getenv(const char *name)
+static
+char *bt_secure_getenv(const char *name)
{
if (bt_common_is_setuid_setgid()) {
- printf_error("Disregarding %s environment variable for setuid/setgid binary",
- name);
+ BT_LOGD("Disregarding environment variable for setuid/setgid binary: "
+ "name=\"%s\"", name);
return NULL;
}
return getenv(name);
}
-static const char *get_home_dir(void)
+#ifdef __MINGW32__
+static
+const char *bt_get_home_dir(void)
+{
+ return g_get_home_dir();
+}
+#else /* __MINGW32__ */
+static
+const char *bt_get_home_dir(void)
{
char *val = NULL;
struct passwd *pwd;
end:
return val;
}
+#endif /* __MINGW32__ */
BT_HIDDEN
char *bt_common_get_home_plugin_path(void)
{
char *path = NULL;
const char *home_dir;
+ size_t length;
- home_dir = get_home_dir();
+ home_dir = bt_get_home_dir();
if (!home_dir) {
goto end;
}
- if (strlen(home_dir) + strlen(HOME_PLUGIN_SUBPATH) + 1 >= PATH_MAX) {
- printf_error("Home directory path is too long: `%s`\n",
- home_dir);
+ length = strlen(home_dir) + strlen(HOME_PLUGIN_SUBPATH) + 1;
+
+ if (length >= PATH_MAX) {
+ BT_LOGW("Home directory path is too long: length=%zu",
+ length);
goto end;
}
const char *end;
size_t init_dirs_len;
- assert(dirs);
+ BT_ASSERT(dirs);
init_dirs_len = dirs->len;
if (!paths) {
while (at < end) {
GString *path;
- const char *next_colon;
+ const char *next_sep;
- next_colon = strchr(at, ':');
- if (next_colon == at) {
+ next_sep = strchr(at, G_SEARCHPATH_SEPARATOR);
+ if (next_sep == at) {
/*
* Empty path: try next character (supported
* to conform to the typical parsing of $PATH).
*/
at++;
continue;
- } else if (!next_colon) {
- /* No more colon: use the remaining */
- next_colon = paths + strlen(paths);
+ } else if (!next_sep) {
+ /* No more separator: use the remaining */
+ next_sep = paths + strlen(paths);
}
path = g_string_new(NULL);
goto error;
}
- g_string_append_len(path, at, next_colon - at);
- at = next_colon + 1;
+ g_string_append_len(path, at, next_sep - at);
+ at = next_sep + 1;
g_ptr_array_add(dirs, path);
}
return ret;
}
+static
+bool isarealtty(int fd)
+{
+ bool istty = false;
+ struct stat tty_stats;
+
+ if (!isatty(fd)) {
+ /* Not a TTY */
+ goto end;
+ }
+
+ if (fstat(fd, &tty_stats) == 0) {
+ if (!S_ISCHR(tty_stats.st_mode)) {
+ /* Not a character device: not a TTY */
+ goto end;
+ }
+ }
+
+ istty = true;
+
+end:
+ return istty;
+}
+
BT_HIDDEN
bool bt_common_colors_supported(void)
{
static bool supports_colors = false;
static bool supports_colors_set = false;
- const char *term;
+ const char *term_env_var;
+ const char *term_color_env_var;
if (supports_colors_set) {
goto end;
supports_colors_set = true;
- term = getenv("TERM");
- if (!term) {
+ /*
+ * `BABELTRACE_TERM_COLOR` environment variable always overrides
+ * the automatic color support detection.
+ */
+ term_color_env_var = getenv("BABELTRACE_TERM_COLOR");
+ if (term_color_env_var) {
+ if (g_ascii_strcasecmp(term_color_env_var, "always") == 0) {
+ /* Force colors */
+ supports_colors = true;
+ } else if (g_ascii_strcasecmp(term_color_env_var, "never") == 0) {
+ /* Force no colors */
+ goto end;
+ }
+ }
+
+ /* We need a compatible, known terminal */
+ term_env_var = getenv("TERM");
+ if (!term_env_var) {
goto end;
}
- if (strncmp(term, "xterm", 5) != 0 &&
- strncmp(term, "rxvt", 4) != 0 &&
- strncmp(term, "konsole", 7) != 0 &&
- strncmp(term, "gnome", 5) != 0) {
+ if (strncmp(term_env_var, "xterm", 5) != 0 &&
+ strncmp(term_env_var, "rxvt", 4) != 0 &&
+ strncmp(term_env_var, "konsole", 7) != 0 &&
+ strncmp(term_env_var, "gnome", 5) != 0 &&
+ strncmp(term_env_var, "screen", 5) != 0 &&
+ strncmp(term_env_var, "tmux", 4) != 0 &&
+ strncmp(term_env_var, "putty", 5) != 0) {
goto end;
}
- if (!isatty(1)) {
+ /* Both standard output and error streams need to be TTYs */
+ if (!isarealtty(STDOUT_FILENO) || !isarealtty(STDERR_FILENO)) {
goto end;
}
BT_HIDDEN
const char *bt_common_color_reset(void)
{
- return bt_common_colors_supported() ? BT_COMMON_COLOR_RESET : "";
+ return bt_common_color_code_reset;
}
BT_HIDDEN
const char *bt_common_color_bold(void)
{
- return bt_common_colors_supported() ? BT_COMMON_COLOR_BOLD : "";
+ return bt_common_color_code_bold;
}
BT_HIDDEN
const char *bt_common_color_fg_default(void)
{
- return bt_common_colors_supported() ? BT_COMMON_COLOR_FG_DEFAULT : "";
+ return bt_common_color_code_fg_default;
}
BT_HIDDEN
const char *bt_common_color_fg_red(void)
{
- return bt_common_colors_supported() ? BT_COMMON_COLOR_FG_RED : "";
+ return bt_common_color_code_fg_red;
}
BT_HIDDEN
const char *bt_common_color_fg_green(void)
{
- return bt_common_colors_supported() ? BT_COMMON_COLOR_FG_GREEN : "";
+ return bt_common_color_code_fg_green;
}
BT_HIDDEN
const char *bt_common_color_fg_yellow(void)
{
- return bt_common_colors_supported() ? BT_COMMON_COLOR_FG_YELLOW : "";
+ return bt_common_color_code_fg_yellow;
}
BT_HIDDEN
const char *bt_common_color_fg_blue(void)
{
- return bt_common_colors_supported() ? BT_COMMON_COLOR_FG_BLUE : "";
+ return bt_common_color_code_fg_blue;
}
BT_HIDDEN
const char *bt_common_color_fg_magenta(void)
{
- return bt_common_colors_supported() ? BT_COMMON_COLOR_FG_MAGENTA : "";
+ return bt_common_color_code_fg_magenta;
}
BT_HIDDEN
const char *bt_common_color_fg_cyan(void)
{
- return bt_common_colors_supported() ? BT_COMMON_COLOR_FG_CYAN : "";
+ return bt_common_color_code_fg_cyan;
}
BT_HIDDEN
const char *bt_common_color_fg_light_gray(void)
{
- return bt_common_colors_supported() ?
- BT_COMMON_COLOR_FG_LIGHT_GRAY : "";
+ return bt_common_color_code_fg_light_gray;
}
BT_HIDDEN
const char *bt_common_color_bg_default(void)
{
- return bt_common_colors_supported() ? BT_COMMON_COLOR_BG_DEFAULT : "";
+ return bt_common_color_code_bg_default;
}
BT_HIDDEN
const char *bt_common_color_bg_red(void)
{
- return bt_common_colors_supported() ? BT_COMMON_COLOR_BG_RED : "";
+ return bt_common_color_code_bg_red;
}
BT_HIDDEN
const char *bt_common_color_bg_green(void)
{
- return bt_common_colors_supported() ? BT_COMMON_COLOR_BG_GREEN : "";
+ return bt_common_color_code_bg_green;
}
BT_HIDDEN
const char *bt_common_color_bg_yellow(void)
{
- return bt_common_colors_supported() ? BT_COMMON_COLOR_BG_YELLOW : "";
+ return bt_common_color_code_bg_yellow;
}
BT_HIDDEN
const char *bt_common_color_bg_blue(void)
{
- return bt_common_colors_supported() ? BT_COMMON_COLOR_BG_BLUE : "";
+ return bt_common_color_code_bg_blue;
}
BT_HIDDEN
const char *bt_common_color_bg_magenta(void)
{
- return bt_common_colors_supported() ? BT_COMMON_COLOR_BG_MAGENTA : "";
+ return bt_common_color_code_bg_magenta;
}
BT_HIDDEN
const char *bt_common_color_bg_cyan(void)
{
- return bt_common_colors_supported() ? BT_COMMON_COLOR_BG_CYAN : "";
+ return bt_common_color_code_bg_cyan;
}
BT_HIDDEN
const char *bt_common_color_bg_light_gray(void)
{
- return bt_common_colors_supported() ?
- BT_COMMON_COLOR_BG_LIGHT_GRAY : "";
+ return bt_common_color_code_bg_light_gray;
+}
+
+BT_HIDDEN
+GString *bt_common_string_until(const char *input, const char *escapable_chars,
+ const char *end_chars, size_t *end_pos)
+{
+ GString *output = g_string_new(NULL);
+ const char *ch;
+ const char *es_char;
+ const char *end_char;
+
+ if (!output) {
+ goto error;
+ }
+
+ for (ch = input; *ch != '\0'; ch++) {
+ if (*ch == '\\') {
+ bool continue_loop = false;
+
+ if (ch[1] == '\0') {
+ /* `\` at the end of the string: append `\` */
+ g_string_append_c(output, *ch);
+ ch++;
+ goto set_end_pos;
+ }
+
+ for (es_char = escapable_chars; *es_char != '\0'; es_char++) {
+ if (ch[1] == *es_char) {
+ /*
+ * `\` followed by an escapable
+ * character: append the escaped
+ * character only.
+ */
+ g_string_append_c(output, ch[1]);
+ ch++;
+ continue_loop = true;
+ break;
+ }
+ }
+
+ if (continue_loop) {
+ continue;
+ }
+
+ /*
+ * `\` followed by a non-escapable character:
+ * append `\` and the character.
+ */
+ g_string_append_c(output, *ch);
+ g_string_append_c(output, ch[1]);
+ ch++;
+ continue;
+ } else {
+ for (end_char = end_chars; *end_char != '\0'; end_char++) {
+ if (*ch == *end_char) {
+ /*
+ * End character found:
+ * terminate this loop.
+ */
+ goto set_end_pos;
+ }
+ }
+
+ /* Normal character: append */
+ g_string_append_c(output, *ch);
+ }
+ }
+
+set_end_pos:
+ if (end_pos) {
+ *end_pos = ch - input;
+ }
+
+ goto end;
+
+error:
+ if (output) {
+ g_string_free(output, TRUE);
+ }
+
+end:
+ return output;
+}
+
+BT_HIDDEN
+GString *bt_common_shell_quote(const char *input, bool with_single_quotes)
+{
+ GString *output = g_string_new(NULL);
+ const char *ch;
+ bool no_quote = true;
+
+ if (!output) {
+ goto end;
+ }
+
+ if (strlen(input) == 0) {
+ if (with_single_quotes) {
+ g_string_assign(output, "''");
+ }
+
+ goto end;
+ }
+
+ for (ch = input; *ch != '\0'; ch++) {
+ const char c = *ch;
+
+ if (!g_ascii_isalpha(c) && !g_ascii_isdigit(c) && c != '_' &&
+ c != '@' && c != '%' && c != '+' && c != '=' &&
+ c != ':' && c != ',' && c != '.' && c != '/' &&
+ c != '-') {
+ no_quote = false;
+ break;
+ }
+ }
+
+ if (no_quote) {
+ g_string_assign(output, input);
+ goto end;
+ }
+
+ if (with_single_quotes) {
+ g_string_assign(output, "'");
+ }
+
+ for (ch = input; *ch != '\0'; ch++) {
+ if (*ch == '\'') {
+ g_string_append(output, "'\"'\"'");
+ } else {
+ g_string_append_c(output, *ch);
+ }
+ }
+
+ if (with_single_quotes) {
+ g_string_append_c(output, '\'');
+ }
+
+end:
+ return output;
+}
+
+BT_HIDDEN
+bool bt_common_string_is_printable(const char *input)
+{
+ const char *ch;
+ bool printable = true;
+ BT_ASSERT(input);
+
+ for (ch = input; *ch != '\0'; ch++) {
+ if (!isprint(*ch) && *ch != '\n' && *ch != '\r' &&
+ *ch != '\t' && *ch != '\v') {
+ printable = false;
+ goto end;
+ }
+ }
+
+end:
+ return printable;
+}
+
+BT_HIDDEN
+void bt_common_destroy_lttng_live_url_parts(
+ struct bt_common_lttng_live_url_parts *parts)
+{
+ if (!parts) {
+ goto end;
+ }
+
+ if (parts->proto) {
+ g_string_free(parts->proto, TRUE);
+ parts->proto = NULL;
+ }
+
+ if (parts->hostname) {
+ g_string_free(parts->hostname, TRUE);
+ parts->hostname = NULL;
+ }
+
+ if (parts->target_hostname) {
+ g_string_free(parts->target_hostname, TRUE);
+ parts->target_hostname = NULL;
+ }
+
+ if (parts->session_name) {
+ g_string_free(parts->session_name, TRUE);
+ parts->session_name = NULL;
+ }
+
+end:
+ return;
+}
+
+BT_HIDDEN
+struct bt_common_lttng_live_url_parts bt_common_parse_lttng_live_url(
+ const char *url, char *error_buf, size_t error_buf_size)
+{
+ struct bt_common_lttng_live_url_parts parts;
+ const char *at = url;
+ size_t end_pos;
+
+ BT_ASSERT(url);
+ memset(&parts, 0, sizeof(parts));
+ parts.port = -1;
+
+ /* Protocol */
+ parts.proto = bt_common_string_until(at, "", ":", &end_pos);
+ if (!parts.proto || parts.proto->len == 0) {
+ if (error_buf) {
+ snprintf(error_buf, error_buf_size, "Missing protocol");
+ }
+
+ goto error;
+ }
+
+ if (strcmp(parts.proto->str, "net") == 0) {
+ g_string_assign(parts.proto, "net4");
+ }
+
+ if (strcmp(parts.proto->str, "net4") != 0 &&
+ strcmp(parts.proto->str, "net6") != 0) {
+ if (error_buf) {
+ snprintf(error_buf, error_buf_size,
+ "Unknown protocol: `%s`", parts.proto->str);
+ }
+
+ goto error;
+ }
+
+ if (at[end_pos] != ':') {
+ if (error_buf) {
+ snprintf(error_buf, error_buf_size,
+ "Expecting `:` after `%s`", parts.proto->str);
+ }
+
+ goto error;
+ }
+
+ at += end_pos;
+
+ /* :// */
+ if (strncmp(at, "://", 3) != 0) {
+ if (error_buf) {
+ snprintf(error_buf, error_buf_size,
+ "Expecting `://` after protocol");
+ }
+
+ goto error;
+ }
+
+ at += 3;
+
+ /* Hostname */
+ parts.hostname = bt_common_string_until(at, "", ":/", &end_pos);
+ if (!parts.hostname || parts.hostname->len == 0) {
+ if (error_buf) {
+ snprintf(error_buf, error_buf_size, "Missing hostname");
+ }
+
+ goto error;
+ }
+
+ if (at[end_pos] == ':') {
+ /* Port */
+ GString *port;
+
+ at += end_pos + 1;
+ port = bt_common_string_until(at, "", "/", &end_pos);
+ if (!port || port->len == 0) {
+ if (error_buf) {
+ snprintf(error_buf, error_buf_size, "Missing port");
+ }
+
+ goto error;
+ }
+
+ if (sscanf(port->str, "%d", &parts.port) != 1) {
+ if (error_buf) {
+ snprintf(error_buf, error_buf_size,
+ "Invalid port: `%s`", port->str);
+ }
+
+ g_string_free(port, TRUE);
+ goto error;
+ }
+
+ g_string_free(port, TRUE);
+
+ if (parts.port < 0 || parts.port >= 65536) {
+ if (error_buf) {
+ snprintf(error_buf, error_buf_size,
+ "Invalid port: %d", parts.port);
+ }
+
+ goto error;
+ }
+ }
+
+ if (at[end_pos] == '\0') {
+ goto end;
+ }
+
+ at += end_pos;
+
+ /* /host/ */
+ if (strncmp(at, "/host/", 6) != 0) {
+ if (error_buf) {
+ snprintf(error_buf, error_buf_size,
+ "Expecting `/host/` after hostname or port");
+ }
+
+ goto error;
+ }
+
+ at += 6;
+
+ /* Target hostname */
+ parts.target_hostname = bt_common_string_until(at, "", "/", &end_pos);
+ if (!parts.target_hostname || parts.target_hostname->len == 0) {
+ if (error_buf) {
+ snprintf(error_buf, error_buf_size,
+ "Missing target hostname");
+ }
+
+ goto error;
+ }
+
+ if (at[end_pos] == '\0') {
+ goto end;
+ }
+
+ at += end_pos + 1;
+
+ /* Session name */
+ parts.session_name = bt_common_string_until(at, "", "/", &end_pos);
+ if (!parts.session_name || parts.session_name->len == 0) {
+ if (error_buf) {
+ snprintf(error_buf, error_buf_size,
+ "Missing session name");
+ }
+
+ goto error;
+ }
+
+ if (at[end_pos] == '/') {
+ if (error_buf) {
+ snprintf(error_buf, error_buf_size,
+ "Unexpected `/` after session name (`%s`)",
+ parts.session_name->str);
+ }
+
+ goto error;
+ }
+
+ goto end;
+
+error:
+ bt_common_destroy_lttng_live_url_parts(&parts);
+
+end:
+ return parts;
+}
+
+BT_HIDDEN
+void bt_common_normalize_star_glob_pattern(char *pattern)
+{
+ const char *p;
+ char *np;
+ bool got_star = false;
+
+ BT_ASSERT(pattern);
+
+ for (p = pattern, np = pattern; *p != '\0'; p++) {
+ switch (*p) {
+ case '*':
+ if (got_star) {
+ /* Avoid consecutive stars. */
+ continue;
+ }
+
+ got_star = true;
+ break;
+ case '\\':
+ /* Copy backslash character. */
+ *np = *p;
+ np++;
+ p++;
+
+ if (*p == '\0') {
+ goto end;
+ }
+
+ /* fall-through */
+ default:
+ got_star = false;
+ break;
+ }
+
+ /* Copy single character. */
+ *np = *p;
+ np++;
+ }
+
+end:
+ *np = '\0';
+}
+
+static inline
+bool at_end_of_pattern(const char *p, const char *pattern, size_t pattern_len)
+{
+ return (p - pattern) == pattern_len || *p == '\0';
+}
+
+/*
+ * Globbing matching function with the star feature only (`?` and
+ * character sets are not supported). This matches `candidate` (plain
+ * string) against `pattern`. A literal star can be escaped with `\` in
+ * `pattern`.
+ *
+ * `pattern_len` or `candidate_len` can be greater than the actual
+ * string length of `pattern` or `candidate` if the string is
+ * null-terminated.
+ */
+BT_HIDDEN
+bool bt_common_star_glob_match(const char *pattern, size_t pattern_len,
+ const char *candidate, size_t candidate_len) {
+ const char *retry_c = candidate, *retry_p = pattern, *c, *p;
+ bool got_a_star = false;
+
+retry:
+ c = retry_c;
+ p = retry_p;
+
+ /*
+ * The concept here is to retry a match in the specific case
+ * where we already got a star. The retry position for the
+ * pattern is just after the most recent star, and the retry
+ * position for the candidate is the character following the
+ * last try's first character.
+ *
+ * Example:
+ *
+ * candidate: hi ev every onyx one
+ * ^
+ * pattern: hi*every*one
+ * ^
+ *
+ * candidate: hi ev every onyx one
+ * ^
+ * pattern: hi*every*one
+ * ^
+ *
+ * candidate: hi ev every onyx one
+ * ^
+ * pattern: hi*every*one
+ * ^
+ *
+ * candidate: hi ev every onyx one
+ * ^
+ * pattern: hi*every*one
+ * ^ MISMATCH
+ *
+ * candidate: hi ev every onyx one
+ * ^
+ * pattern: hi*every*one
+ * ^
+ *
+ * candidate: hi ev every onyx one
+ * ^^
+ * pattern: hi*every*one
+ * ^^
+ *
+ * candidate: hi ev every onyx one
+ * ^ ^
+ * pattern: hi*every*one
+ * ^ ^ MISMATCH
+ *
+ * candidate: hi ev every onyx one
+ * ^
+ * pattern: hi*every*one
+ * ^ MISMATCH
+ *
+ * candidate: hi ev every onyx one
+ * ^
+ * pattern: hi*every*one
+ * ^ MISMATCH
+ *
+ * candidate: hi ev every onyx one
+ * ^
+ * pattern: hi*every*one
+ * ^
+ *
+ * candidate: hi ev every onyx one
+ * ^^
+ * pattern: hi*every*one
+ * ^^
+ *
+ * candidate: hi ev every onyx one
+ * ^ ^
+ * pattern: hi*every*one
+ * ^ ^
+ *
+ * candidate: hi ev every onyx one
+ * ^ ^
+ * pattern: hi*every*one
+ * ^ ^
+ *
+ * candidate: hi ev every onyx one
+ * ^ ^
+ * pattern: hi*every*one
+ * ^ ^
+ *
+ * candidate: hi ev every onyx one
+ * ^
+ * pattern: hi*every*one
+ * ^
+ *
+ * candidate: hi ev every onyx one
+ * ^
+ * pattern: hi*every*one
+ * ^ MISMATCH
+ *
+ * candidate: hi ev every onyx one
+ * ^
+ * pattern: hi*every*one
+ * ^
+ *
+ * candidate: hi ev every onyx one
+ * ^^
+ * pattern: hi*every*one
+ * ^^
+ *
+ * candidate: hi ev every onyx one
+ * ^ ^
+ * pattern: hi*every*one
+ * ^ ^ MISMATCH
+ *
+ * candidate: hi ev every onyx one
+ * ^
+ * pattern: hi*every*one
+ * ^ MISMATCH
+ *
+ * candidate: hi ev every onyx one
+ * ^
+ * pattern: hi*every*one
+ * ^ MISMATCH
+ *
+ * candidate: hi ev every onyx one
+ * ^
+ * pattern: hi*every*one
+ * ^ MISMATCH
+ *
+ * candidate: hi ev every onyx one
+ * ^
+ * pattern: hi*every*one
+ * ^ MISMATCH
+ *
+ * candidate: hi ev every onyx one
+ * ^
+ * pattern: hi*every*one
+ * ^
+ *
+ * candidate: hi ev every onyx one
+ * ^^
+ * pattern: hi*every*one
+ * ^^
+ *
+ * candidate: hi ev every onyx one
+ * ^ ^
+ * pattern: hi*every*one
+ * ^ ^
+ *
+ * candidate: hi ev every onyx one
+ * ^ ^
+ * pattern: hi*every*one
+ * ^ ^ SUCCESS
+ */
+ while ((c - candidate) < candidate_len && *c != '\0') {
+ BT_ASSERT(*c);
+
+ if (at_end_of_pattern(p, pattern, pattern_len)) {
+ goto end_of_pattern;
+ }
+
+ switch (*p) {
+ case '*':
+ got_a_star = true;
+
+ /*
+ * Our first try starts at the current candidate
+ * character and after the star in the pattern.
+ */
+ retry_c = c;
+ retry_p = p + 1;
+
+ if (at_end_of_pattern(retry_p, pattern, pattern_len)) {
+ /*
+ * Star at the end of the pattern at
+ * this point: automatic match.
+ */
+ return true;
+ }
+
+ goto retry;
+ case '\\':
+ /* Go to escaped character. */
+ p++;
+
+ /*
+ * Fall through the default case which compares
+ * the escaped character now.
+ */
+ /* fall-through */
+ default:
+ if (at_end_of_pattern(p, pattern, pattern_len) ||
+ *c != *p) {
+end_of_pattern:
+ /* Character mismatch OR end of pattern. */
+ if (!got_a_star) {
+ /*
+ * We didn't get any star yet,
+ * so this first mismatch
+ * automatically makes the whole
+ * test fail.
+ */
+ return false;
+ }
+
+ /*
+ * Next try: next candidate character,
+ * original pattern character (following
+ * the most recent star).
+ */
+ retry_c++;
+ goto retry;
+ }
+ break;
+ }
+
+ /* Next pattern and candidate characters. */
+ c++;
+ p++;
+ }
+
+ /*
+ * We checked every candidate character and we're still in a
+ * success state: the only pattern character allowed to remain
+ * is a star.
+ */
+ if (at_end_of_pattern(p, pattern, pattern_len)) {
+ return true;
+ }
+
+ p++;
+ return p[-1] == '*' && at_end_of_pattern(p, pattern, pattern_len);
+}
+
+static
+void append_path_parts(const char *path, GPtrArray *parts)
+{
+ const char *ch = path;
+ const char *last = path;
+
+ while (true) {
+ if (*ch == G_DIR_SEPARATOR || *ch == '\0') {
+ if (ch - last > 0) {
+ GString *part = g_string_new(NULL);
+
+ BT_ASSERT(part);
+ g_string_append_len(part, last, ch - last);
+ g_ptr_array_add(parts, part);
+ }
+
+ if (*ch == '\0') {
+ break;
+ }
+
+ last = ch + 1;
+ }
+
+ ch++;
+ }
+}
+
+static
+void destroy_gstring(void *gstring)
+{
+ (void) g_string_free(gstring, TRUE);
+}
+
+#ifdef __MINGW32__
+BT_HIDDEN
+GString *bt_common_normalize_path(const char *path, const char *wd)
+{
+ char *tmp;
+ GString *norm_path = NULL;
+
+ BT_ASSERT(path);
+
+ tmp = _fullpath(NULL, path, PATH_MAX);
+ if (!tmp) {
+ goto error;
+ }
+
+ norm_path = g_string_new(tmp);
+ if (!norm_path) {
+ goto error;
+ }
+
+ goto end;
+error:
+ if (norm_path) {
+ g_string_free(norm_path, TRUE);
+ norm_path = NULL;
+ }
+end:
+ if (tmp) {
+ free(tmp);
+ }
+ return norm_path;
+}
+#else
+BT_HIDDEN
+GString *bt_common_normalize_path(const char *path, const char *wd)
+{
+ size_t i;
+ GString *norm_path;
+ GPtrArray *parts = NULL;
+
+ BT_ASSERT(path);
+ norm_path = g_string_new(G_DIR_SEPARATOR_S);
+ if (!norm_path) {
+ goto error;
+ }
+
+ parts = g_ptr_array_new_with_free_func(destroy_gstring);
+ if (!parts) {
+ goto error;
+ }
+
+ if (path[0] != G_DIR_SEPARATOR) {
+ /* Relative path: start with working directory */
+ if (wd) {
+ append_path_parts(wd, parts);
+ } else {
+ gchar *cd = g_get_current_dir();
+
+ append_path_parts(cd, parts);
+ g_free(cd);
+ }
+ }
+
+ /* Append parts of the path parameter */
+ append_path_parts(path, parts);
+
+ /* Resolve special `..` and `.` parts */
+ for (i = 0; i < parts->len; i++) {
+ GString *part = g_ptr_array_index(parts, i);
+
+ if (strcmp(part->str, "..") == 0) {
+ if (i == 0) {
+ /*
+ * First part of absolute path is `..`:
+ * this is invalid.
+ */
+ goto error;
+ }
+
+ /* Remove `..` and previous part */
+ g_ptr_array_remove_index(parts, i - 1);
+ g_ptr_array_remove_index(parts, i - 1);
+ i -= 2;
+ } else if (strcmp(part->str, ".") == 0) {
+ /* Remove `.` */
+ g_ptr_array_remove_index(parts, i);
+ i -= 1;
+ }
+ }
+
+ /* Create normalized path with what's left */
+ for (i = 0; i < parts->len; i++) {
+ GString *part = g_ptr_array_index(parts, i);
+
+ g_string_append(norm_path, part->str);
+
+ if (i < parts->len - 1) {
+ g_string_append_c(norm_path, G_DIR_SEPARATOR);
+ }
+ }
+
+ goto end;
+
+error:
+ if (norm_path) {
+ g_string_free(norm_path, TRUE);
+ norm_path = NULL;
+ }
+
+end:
+ if (parts) {
+ g_ptr_array_free(parts, TRUE);
+ }
+
+ return norm_path;
+}
+#endif
+
+BT_HIDDEN
+size_t bt_common_get_page_size(void)
+{
+ int page_size;
+
+ page_size = bt_sysconf(_SC_PAGESIZE);
+ if (page_size < 0) {
+ BT_LOGF("Cannot get system's page size: ret=%d",
+ page_size);
+ abort();
+ }
+
+ 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__); \
+ BT_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) - 1) == 0) {
+ fmt_ch += sizeof(PRId64) - 1;
+ BUF_STD_APPEND_SINGLE_ARG(int64_t);
+ goto update_rw_fmt;
+ } else if (strncmp(fmt_ch, PRIu64, sizeof(PRIu64) - 1) == 0) {
+ fmt_ch += sizeof(PRIu64) - 1;
+ BUF_STD_APPEND_SINGLE_ARG(uint64_t);
+ goto update_rw_fmt;
+ } else if (strncmp(fmt_ch, PRIx64, sizeof(PRIx64) - 1) == 0) {
+ fmt_ch += sizeof(PRIx64) - 1;
+ BUF_STD_APPEND_SINGLE_ARG(uint64_t);
+ goto update_rw_fmt;
+ } else if (strncmp(fmt_ch, PRIX64, sizeof(PRIX64) - 1) == 0) {
+ fmt_ch += sizeof(PRIX64) - 1;
+ BUF_STD_APPEND_SINGLE_ARG(uint64_t);
+ goto update_rw_fmt;
+ } else if (strncmp(fmt_ch, PRIo64, sizeof(PRIo64) - 1) == 0) {
+ fmt_ch += sizeof(PRIo64) - 1;
+ BUF_STD_APPEND_SINGLE_ARG(uint64_t);
+ goto update_rw_fmt;
+ } else if (strncmp(fmt_ch, PRIi64, sizeof(PRIi64) - 1) == 0) {
+ fmt_ch += sizeof(PRIi64) - 1;
+ 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;
+
+ BT_ASSERT(buf);
+ BT_ASSERT(fmt);
+
+ while (*fmt_ch != '\0') {
+ switch (*fmt_ch) {
+ case '%':
+ BT_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);
}