+
+BT_HIDDEN
+bool bt_common_colors_supported(void)
+{
+ static bool supports_colors = false;
+ static bool supports_colors_set = false;
+ const char *term;
+
+ if (supports_colors_set) {
+ goto end;
+ }
+
+ supports_colors_set = true;
+
+ term = getenv("TERM");
+ if (!term) {
+ goto end;
+ }
+
+ if (strncmp(term, "xterm", 5) != 0 &&
+ strncmp(term, "rxvt", 4) != 0 &&
+ strncmp(term, "konsole", 7) != 0 &&
+ strncmp(term, "gnome", 5) != 0 &&
+ strncmp(term, "screen", 5) != 0 &&
+ strncmp(term, "tmux", 4) != 0 &&
+ strncmp(term, "putty", 5) != 0) {
+ goto end;
+ }
+
+ if (!isatty(1)) {
+ goto end;
+ }
+
+ supports_colors = true;
+
+end:
+ return supports_colors;
+}
+
+BT_HIDDEN
+const char *bt_common_color_reset(void)
+{
+ return bt_common_colors_supported() ? BT_COMMON_COLOR_RESET : "";
+}
+
+BT_HIDDEN
+const char *bt_common_color_bold(void)
+{
+ return bt_common_colors_supported() ? BT_COMMON_COLOR_BOLD : "";
+}
+
+BT_HIDDEN
+const char *bt_common_color_fg_default(void)
+{
+ return bt_common_colors_supported() ? BT_COMMON_COLOR_FG_DEFAULT : "";
+}
+
+BT_HIDDEN
+const char *bt_common_color_fg_red(void)
+{
+ return bt_common_colors_supported() ? BT_COMMON_COLOR_FG_RED : "";
+}
+
+BT_HIDDEN
+const char *bt_common_color_fg_green(void)
+{
+ return bt_common_colors_supported() ? BT_COMMON_COLOR_FG_GREEN : "";
+}
+
+BT_HIDDEN
+const char *bt_common_color_fg_yellow(void)
+{
+ return bt_common_colors_supported() ? BT_COMMON_COLOR_FG_YELLOW : "";
+}
+
+BT_HIDDEN
+const char *bt_common_color_fg_blue(void)
+{
+ return bt_common_colors_supported() ? BT_COMMON_COLOR_FG_BLUE : "";
+}
+
+BT_HIDDEN
+const char *bt_common_color_fg_magenta(void)
+{
+ return bt_common_colors_supported() ? BT_COMMON_COLOR_FG_MAGENTA : "";
+}
+
+BT_HIDDEN
+const char *bt_common_color_fg_cyan(void)
+{
+ return bt_common_colors_supported() ? BT_COMMON_COLOR_FG_CYAN : "";
+}
+
+BT_HIDDEN
+const char *bt_common_color_fg_light_gray(void)
+{
+ return bt_common_colors_supported() ?
+ BT_COMMON_COLOR_FG_LIGHT_GRAY : "";
+}
+
+BT_HIDDEN
+const char *bt_common_color_bg_default(void)
+{
+ return bt_common_colors_supported() ? BT_COMMON_COLOR_BG_DEFAULT : "";
+}
+
+BT_HIDDEN
+const char *bt_common_color_bg_red(void)
+{
+ return bt_common_colors_supported() ? BT_COMMON_COLOR_BG_RED : "";
+}
+
+BT_HIDDEN
+const char *bt_common_color_bg_green(void)
+{
+ return bt_common_colors_supported() ? BT_COMMON_COLOR_BG_GREEN : "";
+}
+
+BT_HIDDEN
+const char *bt_common_color_bg_yellow(void)
+{
+ return bt_common_colors_supported() ? BT_COMMON_COLOR_BG_YELLOW : "";
+}
+
+BT_HIDDEN
+const char *bt_common_color_bg_blue(void)
+{
+ return bt_common_colors_supported() ? BT_COMMON_COLOR_BG_BLUE : "";
+}
+
+BT_HIDDEN
+const char *bt_common_color_bg_magenta(void)
+{
+ return bt_common_colors_supported() ? BT_COMMON_COLOR_BG_MAGENTA : "";
+}
+
+BT_HIDDEN
+const char *bt_common_color_bg_cyan(void)
+{
+ return bt_common_colors_supported() ? BT_COMMON_COLOR_BG_CYAN : "";
+}
+
+BT_HIDDEN
+const char *bt_common_color_bg_light_gray(void)
+{
+ return bt_common_colors_supported() ?
+ BT_COMMON_COLOR_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)
+{
+ GString *output = g_string_new(NULL);
+ const char *ch;
+ bool no_quote = true;
+
+ if (!output) {
+ goto end;
+ }
+
+ if (strlen(input) == 0) {
+ 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;
+ }
+
+ g_string_assign(output, "'");
+
+ for (ch = input; *ch != '\0'; ch++) {
+ if (*ch == '\'') {
+ g_string_append(output, "'\"'\"'");
+ } else {
+ g_string_append_c(output, *ch);
+ }
+ }
+
+ 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;
+ 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;
+
+ 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;
+}