+
+BT_HIDDEN
+void bt_common_sep_digits(char *str, unsigned int digits_per_group, char sep)
+{
+ const char *rd;
+ char *wr;
+ uint64_t i = 0;
+ uint64_t orig_len;
+ uint64_t sep_count;
+ uint64_t new_len;
+
+ BT_ASSERT_DBG(digits_per_group > 0);
+ BT_ASSERT_DBG(sep != '\0');
+
+ /* Compute new length of `str` */
+ orig_len = strlen(str);
+ BT_ASSERT_DBG(orig_len > 0);
+ sep_count = (orig_len - 1) / digits_per_group;
+ new_len = strlen(str) + sep_count;
+
+ /*
+ * Do the work in place. Have the reading pointer `rd` start at
+ * the end of the original string, and the writing pointer `wr`
+ * start at the end of the new string, making sure to also put a
+ * null character there.
+ */
+ rd = str + orig_len - 1;
+ wr = str + new_len;
+ *wr = '\0';
+ wr--;
+
+ /*
+ * Here's what the process looks like (3 digits per group):
+ *
+ * Source: 12345678
+ * ^
+ * Destination: 12345678#8
+ * ^
+ *
+ * Source: 12345678
+ * ^
+ * Destination: 1234567878
+ * ^
+ *
+ * Source: 12345678
+ * ^
+ * Destination: 1234567678
+ * ^
+ *
+ * Source: 12345678
+ * ^
+ * Destination: 123456,678
+ * ^
+ *
+ * Source: 12345678
+ * ^
+ * Destination: 123455,678
+ * ^
+ *
+ * Source: 12345678
+ * ^
+ * Destination: 123445,678
+ * ^
+ *
+ * Source: 12345678
+ * ^
+ * Destination: 123345,678
+ * ^
+ *
+ * Source: 12345678
+ * ^
+ * Destination: 12,345,678
+ * ^
+ *
+ * Source: 12345678
+ * ^
+ * Destination: 12,345,678
+ * ^
+ *
+ * Source: 12345678
+ * ^
+ * Destination: 12,345,678
+ * ^
+ */
+ while (rd != str - 1) {
+ if (i == digits_per_group) {
+ /*
+ * Time to append the separator: decrement `wr`,
+ * but keep `rd` as is.
+ */
+ i = 0;
+ *wr = sep;
+ wr--;
+ continue;
+ }
+
+ /* Copy read-side character to write-side character */
+ *wr = *rd;
+ wr--;
+ rd--;
+ i++;
+ }
+}
+
+BT_HIDDEN
+GString *bt_common_fold(const char *str, unsigned int total_length,
+ unsigned int indent)
+{
+ const unsigned int content_length = total_length - indent;
+ GString *folded = g_string_new(NULL);
+ GString *tmp_line = g_string_new(NULL);
+ gchar **lines = NULL;
+ gchar **line_words = NULL;
+ gchar * const *line;
+ unsigned int i;
+
+ BT_ASSERT_DBG(str);
+ BT_ASSERT_DBG(indent < total_length);
+ BT_ASSERT_DBG(tmp_line);
+ BT_ASSERT_DBG(folded);
+
+ if (strlen(str) == 0) {
+ /* Empty input string: empty output string */
+ goto end;
+ }
+
+ /* Split lines */
+ lines = g_strsplit(str, "\n", 0);
+ BT_ASSERT_DBG(lines);
+
+ /* For each source line */
+ for (line = lines; *line; line++) {
+ gchar * const *word;
+
+ /*
+ * Append empty line without indenting if source line is
+ * empty.
+ */
+ if (strlen(*line) == 0) {
+ g_string_append_c(folded, '\n');
+ continue;
+ }
+
+ /* Split words */
+ line_words = g_strsplit(*line, " ", 0);
+ BT_ASSERT_DBG(line_words);
+
+ /*
+ * Indent for first line (we know there's at least one
+ * word at this point).
+ */
+ for (i = 0; i < indent; i++) {
+ g_string_append_c(folded, ' ');
+ }
+
+ /* Append words, folding when necessary */
+ g_string_assign(tmp_line, "");
+
+ for (word = line_words; *word; word++) {
+ /*
+ * `tmp_line->len > 0` is in the condition so
+ * that words that are larger than
+ * `content_length` are at least written on
+ * their own line.
+ *
+ * `tmp_line->len - 1` because the temporary
+ * line always contains a trailing space which
+ * won't be part of the line if we fold.
+ */
+ if (tmp_line->len > 0 &&
+ tmp_line->len - 1 + strlen(*word) >= content_length) {
+ /* Fold (without trailing space) */
+ g_string_append_len(folded,
+ tmp_line->str, tmp_line->len - 1);
+ g_string_append_c(folded, '\n');
+
+ /* Indent new line */
+ for (i = 0; i < indent; i++) {
+ g_string_append_c(folded, ' ');
+ }
+
+ g_string_assign(tmp_line, "");
+ }
+
+ /* Append current word and space to temporary line */
+ g_string_append(tmp_line, *word);
+ g_string_append_c(tmp_line, ' ');
+ }
+
+ /* Append last line if any, without trailing space */
+ if (tmp_line->len > 0) {
+ g_string_append_len(folded, tmp_line->str,
+ tmp_line->len - 1);
+ }
+
+ /* Append source newline */
+ g_string_append_c(folded, '\n');
+
+ /* Free array of this line's words */
+ g_strfreev(line_words);
+ line_words = NULL;
+ }
+
+ /* Remove trailing newline if any */
+ if (folded->str[folded->len - 1] == '\n') {
+ g_string_truncate(folded, folded->len - 1);
+ }
+
+end:
+ if (lines) {
+ g_strfreev(lines);
+ }
+
+ BT_ASSERT_DBG(!line_words);
+
+ if (tmp_line) {
+ g_string_free(tmp_line, TRUE);
+ }
+
+ return folded;
+}
+
+#ifdef __MINGW32__
+BT_HIDDEN
+int bt_common_get_term_size(unsigned int *width, unsigned int *height)
+{
+ /* Not supported on Windows yet */
+ return -1;
+}
+#else /* __MINGW32__ */
+BT_HIDDEN
+int bt_common_get_term_size(unsigned int *width, unsigned int *height)
+{
+ int ret = 0;
+ struct winsize winsize;
+
+ if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &winsize) < 0) {
+ ret = -1;
+ goto end;
+ }
+
+ if (width) {
+ *width = (unsigned int) winsize.ws_col;
+ }
+
+ if (height) {
+ *height = (unsigned int) winsize.ws_row;
+ }
+
+end:
+ return ret;
+}
+#endif /* __MINGW32__ */
+
+BT_HIDDEN
+int bt_common_g_string_append_printf(GString *str, const char *fmt, ...)
+{
+ va_list ap;
+ gsize len, allocated_len, available_len;
+ int print_len;
+
+ /* str->len excludes \0. */
+ len = str->len;
+ /* Explicitly exclude \0. */
+ allocated_len = str->allocated_len - 1;
+ available_len = allocated_len - len;
+
+ str->len = allocated_len;
+ va_start(ap, fmt);
+ print_len = vsnprintf(str->str + len, available_len + 1, fmt, ap);
+ va_end(ap);
+ if (print_len < 0) {
+ return print_len;
+ }
+ if (G_UNLIKELY(available_len < print_len)) {
+ /* Resize. */
+ g_string_set_size(str, len + print_len);
+ va_start(ap, fmt);
+ print_len = vsprintf(str->str + len, fmt, ap);
+ va_end(ap);
+ } else {
+ str->len = len + print_len;
+ }
+ return print_len;
+}
+
+BT_HIDDEN
+int bt_common_append_file_content_to_g_string(GString *str, FILE *fp)
+{
+ const size_t chunk_size = 4096;
+ int ret = 0;
+ char *buf;
+ size_t read_len;
+ gsize orig_len = str->len;
+
+ BT_ASSERT(str);
+ BT_ASSERT(fp);
+ buf = g_malloc(chunk_size);
+ if (!buf) {
+ ret = -1;
+ goto end;
+ }
+
+ while (true) {
+ if (ferror(fp)) {
+ ret = -1;
+ goto end;
+ }
+
+ if (feof(fp)) {
+ break;
+ }
+
+ read_len = fread(buf, 1, chunk_size, fp);
+ g_string_append_len(str, buf, read_len);
+ }
+
+end:
+ if (ret) {
+ /* Remove what was appended */
+ g_string_truncate(str, orig_len);
+ }
+
+ g_free(buf);
+ return ret;
+}
+
+BT_HIDDEN
+void bt_common_abort(void)
+{
+ static const char * const exec_on_abort_env_name =
+ "BABELTRACE_EXEC_ON_ABORT";
+ const char *env_exec_on_abort;
+
+ env_exec_on_abort = getenv(exec_on_abort_env_name);
+ if (env_exec_on_abort) {
+ if (bt_common_is_setuid_setgid()) {
+ goto do_abort;
+ }
+
+ (void) g_spawn_command_line_sync(env_exec_on_abort,
+ NULL, NULL, NULL, NULL);
+ }
+
+do_abort:
+ abort();
+}