Fix: bt_common_string_until(): reset returned variable on error
[babeltrace.git] / src / common / common.c
1 /*
2 * Babeltrace common functions
3 *
4 * Copyright 2016 Philippe Proulx <pproulx@efficios.com>
5 *
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:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
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
22 * SOFTWARE.
23 */
24
25 #define BT_LOG_OUTPUT_LEVEL log_level
26 #define BT_LOG_TAG "COMMON"
27 #include "logging/log.h"
28
29 #include <unistd.h>
30 #include <string.h>
31 #include <inttypes.h>
32 #include <sys/types.h>
33 #include <sys/stat.h>
34 #include <unistd.h>
35 #include "common/assert.h"
36 #include <stdarg.h>
37 #include <ctype.h>
38 #include <glib.h>
39 #include <stdlib.h>
40 #include <stdio.h>
41 #include <wchar.h>
42 #include <stdbool.h>
43 #include "common/macros.h"
44 #include "common/common.h"
45 #include "compat/unistd.h"
46
47 #ifndef __MINGW32__
48 #include <pwd.h>
49 #include <sys/ioctl.h>
50 #endif
51
52 #define SYSTEM_PLUGIN_PATH BABELTRACE_PLUGINS_DIR
53 #define HOME_ENV_VAR "HOME"
54 #define HOME_PLUGIN_SUBPATH "/.local/lib/babeltrace2/plugins"
55
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 = "";
74
75 static
76 void __attribute__((constructor)) bt_common_color_ctor(void)
77 {
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;
97 }
98 }
99
100 BT_HIDDEN
101 const char *bt_common_get_system_plugin_path(void)
102 {
103 return SYSTEM_PLUGIN_PATH;
104 }
105
106 #ifdef __MINGW32__
107 BT_HIDDEN
108 bool bt_common_is_setuid_setgid(void)
109 {
110 return false;
111 }
112 #else /* __MINGW32__ */
113 BT_HIDDEN
114 bool bt_common_is_setuid_setgid(void)
115 {
116 return (geteuid() != getuid() || getegid() != getgid());
117 }
118 #endif /* __MINGW32__ */
119
120 #ifdef __MINGW32__
121 static
122 const char *bt_get_home_dir(int log_level)
123 {
124 return g_get_home_dir();
125 }
126 #else /* __MINGW32__ */
127 static
128 char *bt_secure_getenv(const char *name, int log_level)
129 {
130 if (bt_common_is_setuid_setgid()) {
131 BT_LOGD("Disregarding environment variable for setuid/setgid binary: "
132 "name=\"%s\"", name);
133 return NULL;
134 }
135 return getenv(name);
136 }
137
138 static
139 const char *bt_get_home_dir(int log_level)
140 {
141 char *val = NULL;
142 struct passwd *pwd;
143
144 val = bt_secure_getenv(HOME_ENV_VAR, log_level);
145 if (val) {
146 goto end;
147 }
148 /* Fallback on password file. */
149 pwd = getpwuid(getuid());
150 if (!pwd) {
151 goto end;
152 }
153 val = pwd->pw_dir;
154 end:
155 return val;
156 }
157 #endif /* __MINGW32__ */
158
159 BT_HIDDEN
160 char *bt_common_get_home_plugin_path(int log_level)
161 {
162 char *path = NULL;
163 const char *home_dir;
164 size_t length;
165
166 home_dir = bt_get_home_dir(log_level);
167 if (!home_dir) {
168 goto end;
169 }
170
171 length = strlen(home_dir) + strlen(HOME_PLUGIN_SUBPATH) + 1;
172
173 if (length >= PATH_MAX) {
174 BT_LOGW("Home directory path is too long: "
175 "length=%zu, max-length=%u", length, PATH_MAX);
176 goto end;
177 }
178
179 path = malloc(PATH_MAX);
180 if (!path) {
181 goto end;
182 }
183
184 strcpy(path, home_dir);
185 strcat(path, HOME_PLUGIN_SUBPATH);
186
187 end:
188 return path;
189 }
190
191 BT_HIDDEN
192 int bt_common_append_plugin_path_dirs(const char *paths, GPtrArray *dirs)
193 {
194 int ret = 0;
195 const char *at;
196 const char *end;
197 size_t init_dirs_len;
198
199 BT_ASSERT(dirs);
200 init_dirs_len = dirs->len;
201
202 if (!paths) {
203 /* Nothing to append */
204 goto end;
205 }
206
207 at = paths;
208 end = paths + strlen(paths);
209
210 while (at < end) {
211 GString *path;
212 const char *next_sep;
213
214 next_sep = strchr(at, G_SEARCHPATH_SEPARATOR);
215 if (next_sep == at) {
216 /*
217 * Empty path: try next character (supported
218 * to conform to the typical parsing of $PATH).
219 */
220 at++;
221 continue;
222 } else if (!next_sep) {
223 /* No more separator: use the remaining */
224 next_sep = paths + strlen(paths);
225 }
226
227 path = g_string_new(NULL);
228 if (!path) {
229 goto error;
230 }
231
232 g_string_append_len(path, at, next_sep - at);
233 at = next_sep + 1;
234 g_ptr_array_add(dirs, path);
235 }
236
237 goto end;
238
239 error:
240 ret = -1;
241
242 /* Remove the new entries in dirs */
243 while (dirs->len > init_dirs_len) {
244 g_ptr_array_remove_index(dirs, init_dirs_len);
245 }
246
247 end:
248 return ret;
249 }
250
251 static
252 bool isarealtty(int fd)
253 {
254 bool istty = false;
255 struct stat tty_stats;
256
257 if (!isatty(fd)) {
258 /* Not a TTY */
259 goto end;
260 }
261
262 if (fstat(fd, &tty_stats) == 0) {
263 if (!S_ISCHR(tty_stats.st_mode)) {
264 /* Not a character device: not a TTY */
265 goto end;
266 }
267 }
268
269 istty = true;
270
271 end:
272 return istty;
273 }
274
275 BT_HIDDEN
276 bool bt_common_colors_supported(void)
277 {
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;
282
283 if (supports_colors_set) {
284 goto end;
285 }
286
287 supports_colors_set = true;
288
289 /*
290 * `BABELTRACE_TERM_COLOR` environment variable always overrides
291 * the automatic color support detection.
292 */
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) {
296 /* Force colors */
297 supports_colors = true;
298 } else if (g_ascii_strcasecmp(term_color_env_var, "never") == 0) {
299 /* Force no colors */
300 goto end;
301 }
302 }
303
304 /* We need a compatible, known terminal */
305 term_env_var = getenv("TERM");
306 if (!term_env_var) {
307 goto end;
308 }
309
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) {
317 goto end;
318 }
319
320 /* Both standard output and error streams need to be TTYs */
321 if (!isarealtty(STDOUT_FILENO) || !isarealtty(STDERR_FILENO)) {
322 goto end;
323 }
324
325 supports_colors = true;
326
327 end:
328 return supports_colors;
329 }
330
331 BT_HIDDEN
332 const char *bt_common_color_reset(void)
333 {
334 return bt_common_color_code_reset;
335 }
336
337 BT_HIDDEN
338 const char *bt_common_color_bold(void)
339 {
340 return bt_common_color_code_bold;
341 }
342
343 BT_HIDDEN
344 const char *bt_common_color_fg_default(void)
345 {
346 return bt_common_color_code_fg_default;
347 }
348
349 BT_HIDDEN
350 const char *bt_common_color_fg_red(void)
351 {
352 return bt_common_color_code_fg_red;
353 }
354
355 BT_HIDDEN
356 const char *bt_common_color_fg_green(void)
357 {
358 return bt_common_color_code_fg_green;
359 }
360
361 BT_HIDDEN
362 const char *bt_common_color_fg_yellow(void)
363 {
364 return bt_common_color_code_fg_yellow;
365 }
366
367 BT_HIDDEN
368 const char *bt_common_color_fg_blue(void)
369 {
370 return bt_common_color_code_fg_blue;
371 }
372
373 BT_HIDDEN
374 const char *bt_common_color_fg_magenta(void)
375 {
376 return bt_common_color_code_fg_magenta;
377 }
378
379 BT_HIDDEN
380 const char *bt_common_color_fg_cyan(void)
381 {
382 return bt_common_color_code_fg_cyan;
383 }
384
385 BT_HIDDEN
386 const char *bt_common_color_fg_light_gray(void)
387 {
388 return bt_common_color_code_fg_light_gray;
389 }
390
391 BT_HIDDEN
392 const char *bt_common_color_bg_default(void)
393 {
394 return bt_common_color_code_bg_default;
395 }
396
397 BT_HIDDEN
398 const char *bt_common_color_bg_red(void)
399 {
400 return bt_common_color_code_bg_red;
401 }
402
403 BT_HIDDEN
404 const char *bt_common_color_bg_green(void)
405 {
406 return bt_common_color_code_bg_green;
407 }
408
409 BT_HIDDEN
410 const char *bt_common_color_bg_yellow(void)
411 {
412 return bt_common_color_code_bg_yellow;
413 }
414
415 BT_HIDDEN
416 const char *bt_common_color_bg_blue(void)
417 {
418 return bt_common_color_code_bg_blue;
419 }
420
421 BT_HIDDEN
422 const char *bt_common_color_bg_magenta(void)
423 {
424 return bt_common_color_code_bg_magenta;
425 }
426
427 BT_HIDDEN
428 const char *bt_common_color_bg_cyan(void)
429 {
430 return bt_common_color_code_bg_cyan;
431 }
432
433 BT_HIDDEN
434 const char *bt_common_color_bg_light_gray(void)
435 {
436 return bt_common_color_code_bg_light_gray;
437 }
438
439 BT_HIDDEN
440 GString *bt_common_string_until(const char *input, const char *escapable_chars,
441 const char *end_chars, size_t *end_pos)
442 {
443 GString *output = g_string_new(NULL);
444 const char *ch;
445 const char *es_char;
446 const char *end_char;
447
448 if (!output) {
449 goto error;
450 }
451
452 for (ch = input; *ch != '\0'; ch++) {
453 if (*ch == '\\') {
454 bool continue_loop = false;
455
456 if (ch[1] == '\0') {
457 /* `\` at the end of the string: append `\` */
458 g_string_append_c(output, *ch);
459 ch++;
460 goto set_end_pos;
461 }
462
463 for (es_char = escapable_chars; *es_char != '\0'; es_char++) {
464 if (ch[1] == *es_char) {
465 /*
466 * `\` followed by an escapable
467 * character: append the escaped
468 * character only.
469 */
470 g_string_append_c(output, ch[1]);
471 ch++;
472 continue_loop = true;
473 break;
474 }
475 }
476
477 if (continue_loop) {
478 continue;
479 }
480
481 /*
482 * `\` followed by a non-escapable character:
483 * append `\` and the character.
484 */
485 g_string_append_c(output, *ch);
486 g_string_append_c(output, ch[1]);
487 ch++;
488 continue;
489 } else {
490 for (end_char = end_chars; *end_char != '\0'; end_char++) {
491 if (*ch == *end_char) {
492 /*
493 * End character found:
494 * terminate this loop.
495 */
496 goto set_end_pos;
497 }
498 }
499
500 /* Normal character: append */
501 g_string_append_c(output, *ch);
502 }
503 }
504
505 set_end_pos:
506 if (end_pos) {
507 *end_pos = ch - input;
508 }
509
510 goto end;
511
512 error:
513 if (output) {
514 g_string_free(output, TRUE);
515 output = NULL;
516 }
517
518 end:
519 return output;
520 }
521
522 BT_HIDDEN
523 GString *bt_common_shell_quote(const char *input, bool with_single_quotes)
524 {
525 GString *output = g_string_new(NULL);
526 const char *ch;
527 bool no_quote = true;
528
529 if (!output) {
530 goto end;
531 }
532
533 if (strlen(input) == 0) {
534 if (with_single_quotes) {
535 g_string_assign(output, "''");
536 }
537
538 goto end;
539 }
540
541 for (ch = input; *ch != '\0'; ch++) {
542 const char c = *ch;
543
544 if (!g_ascii_isalpha(c) && !g_ascii_isdigit(c) && c != '_' &&
545 c != '@' && c != '%' && c != '+' && c != '=' &&
546 c != ':' && c != ',' && c != '.' && c != '/' &&
547 c != '-') {
548 no_quote = false;
549 break;
550 }
551 }
552
553 if (no_quote) {
554 g_string_assign(output, input);
555 goto end;
556 }
557
558 if (with_single_quotes) {
559 g_string_assign(output, "'");
560 }
561
562 for (ch = input; *ch != '\0'; ch++) {
563 if (*ch == '\'') {
564 g_string_append(output, "'\"'\"'");
565 } else {
566 g_string_append_c(output, *ch);
567 }
568 }
569
570 if (with_single_quotes) {
571 g_string_append_c(output, '\'');
572 }
573
574 end:
575 return output;
576 }
577
578 BT_HIDDEN
579 bool bt_common_string_is_printable(const char *input)
580 {
581 const char *ch;
582 bool printable = true;
583 BT_ASSERT(input);
584
585 for (ch = input; *ch != '\0'; ch++) {
586 if (!isprint(*ch) && *ch != '\n' && *ch != '\r' &&
587 *ch != '\t' && *ch != '\v') {
588 printable = false;
589 goto end;
590 }
591 }
592
593 end:
594 return printable;
595 }
596
597 BT_HIDDEN
598 void bt_common_destroy_lttng_live_url_parts(
599 struct bt_common_lttng_live_url_parts *parts)
600 {
601 if (!parts) {
602 goto end;
603 }
604
605 if (parts->proto) {
606 g_string_free(parts->proto, TRUE);
607 parts->proto = NULL;
608 }
609
610 if (parts->hostname) {
611 g_string_free(parts->hostname, TRUE);
612 parts->hostname = NULL;
613 }
614
615 if (parts->target_hostname) {
616 g_string_free(parts->target_hostname, TRUE);
617 parts->target_hostname = NULL;
618 }
619
620 if (parts->session_name) {
621 g_string_free(parts->session_name, TRUE);
622 parts->session_name = NULL;
623 }
624
625 end:
626 return;
627 }
628
629 BT_HIDDEN
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)
632 {
633 struct bt_common_lttng_live_url_parts parts;
634 const char *at = url;
635 size_t end_pos;
636
637 BT_ASSERT(url);
638 memset(&parts, 0, sizeof(parts));
639 parts.port = -1;
640
641 /* Protocol */
642 parts.proto = bt_common_string_until(at, "", ":", &end_pos);
643 if (!parts.proto || parts.proto->len == 0) {
644 if (error_buf) {
645 snprintf(error_buf, error_buf_size, "Missing protocol");
646 }
647
648 goto error;
649 }
650
651 if (strcmp(parts.proto->str, "net") == 0) {
652 g_string_assign(parts.proto, "net4");
653 }
654
655 if (strcmp(parts.proto->str, "net4") != 0 &&
656 strcmp(parts.proto->str, "net6") != 0) {
657 if (error_buf) {
658 snprintf(error_buf, error_buf_size,
659 "Unknown protocol: `%s`", parts.proto->str);
660 }
661
662 goto error;
663 }
664
665 if (at[end_pos] != ':') {
666 if (error_buf) {
667 snprintf(error_buf, error_buf_size,
668 "Expecting `:` after `%s`", parts.proto->str);
669 }
670
671 goto error;
672 }
673
674 at += end_pos;
675
676 /* :// */
677 if (strncmp(at, "://", 3) != 0) {
678 if (error_buf) {
679 snprintf(error_buf, error_buf_size,
680 "Expecting `://` after protocol");
681 }
682
683 goto error;
684 }
685
686 at += 3;
687
688 /* Hostname */
689 parts.hostname = bt_common_string_until(at, "", ":/", &end_pos);
690 if (!parts.hostname || parts.hostname->len == 0) {
691 if (error_buf) {
692 snprintf(error_buf, error_buf_size, "Missing hostname");
693 }
694
695 goto error;
696 }
697
698 if (at[end_pos] == ':') {
699 /* Port */
700 GString *port;
701
702 at += end_pos + 1;
703 port = bt_common_string_until(at, "", "/", &end_pos);
704 if (!port || port->len == 0) {
705 if (error_buf) {
706 snprintf(error_buf, error_buf_size, "Missing port");
707 }
708
709 goto error;
710 }
711
712 if (sscanf(port->str, "%d", &parts.port) != 1) {
713 if (error_buf) {
714 snprintf(error_buf, error_buf_size,
715 "Invalid port: `%s`", port->str);
716 }
717
718 g_string_free(port, TRUE);
719 goto error;
720 }
721
722 g_string_free(port, TRUE);
723
724 if (parts.port < 0 || parts.port >= 65536) {
725 if (error_buf) {
726 snprintf(error_buf, error_buf_size,
727 "Invalid port: %d", parts.port);
728 }
729
730 goto error;
731 }
732 }
733
734 if (at[end_pos] == '\0') {
735 goto end;
736 }
737
738 at += end_pos;
739
740 /* /host/ */
741 if (strncmp(at, "/host/", 6) != 0) {
742 if (error_buf) {
743 snprintf(error_buf, error_buf_size,
744 "Expecting `/host/` after hostname or port");
745 }
746
747 goto error;
748 }
749
750 at += 6;
751
752 /* Target hostname */
753 parts.target_hostname = bt_common_string_until(at, "", "/", &end_pos);
754 if (!parts.target_hostname || parts.target_hostname->len == 0) {
755 if (error_buf) {
756 snprintf(error_buf, error_buf_size,
757 "Missing target hostname");
758 }
759
760 goto error;
761 }
762
763 if (at[end_pos] == '\0') {
764 goto end;
765 }
766
767 at += end_pos + 1;
768
769 /* Session name */
770 parts.session_name = bt_common_string_until(at, "", "/", &end_pos);
771 if (!parts.session_name || parts.session_name->len == 0) {
772 if (error_buf) {
773 snprintf(error_buf, error_buf_size,
774 "Missing session name");
775 }
776
777 goto error;
778 }
779
780 if (at[end_pos] == '/') {
781 if (error_buf) {
782 snprintf(error_buf, error_buf_size,
783 "Unexpected `/` after session name (`%s`)",
784 parts.session_name->str);
785 }
786
787 goto error;
788 }
789
790 goto end;
791
792 error:
793 bt_common_destroy_lttng_live_url_parts(&parts);
794
795 end:
796 return parts;
797 }
798
799 BT_HIDDEN
800 void bt_common_normalize_star_glob_pattern(char *pattern)
801 {
802 const char *p;
803 char *np;
804 bool got_star = false;
805
806 BT_ASSERT(pattern);
807
808 for (p = pattern, np = pattern; *p != '\0'; p++) {
809 switch (*p) {
810 case '*':
811 if (got_star) {
812 /* Avoid consecutive stars. */
813 continue;
814 }
815
816 got_star = true;
817 break;
818 case '\\':
819 /* Copy backslash character. */
820 *np = *p;
821 np++;
822 p++;
823
824 if (*p == '\0') {
825 goto end;
826 }
827
828 /* fall-through */
829 default:
830 got_star = false;
831 break;
832 }
833
834 /* Copy single character. */
835 *np = *p;
836 np++;
837 }
838
839 end:
840 *np = '\0';
841 }
842
843 static inline
844 bool at_end_of_pattern(const char *p, const char *pattern, size_t pattern_len)
845 {
846 return (p - pattern) == pattern_len || *p == '\0';
847 }
848
849 /*
850 * Globbing matching function with the star feature only (`?` and
851 * character sets are not supported). This matches `candidate` (plain
852 * string) against `pattern`. A literal star can be escaped with `\` in
853 * `pattern`.
854 *
855 * `pattern_len` or `candidate_len` can be greater than the actual
856 * string length of `pattern` or `candidate` if the string is
857 * null-terminated.
858 */
859 BT_HIDDEN
860 bool bt_common_star_glob_match(const char *pattern, size_t pattern_len,
861 const char *candidate, size_t candidate_len) {
862 const char *retry_c = candidate, *retry_p = pattern, *c, *p;
863 bool got_a_star = false;
864
865 retry:
866 c = retry_c;
867 p = retry_p;
868
869 /*
870 * The concept here is to retry a match in the specific case
871 * where we already got a star. The retry position for the
872 * pattern is just after the most recent star, and the retry
873 * position for the candidate is the character following the
874 * last try's first character.
875 *
876 * Example:
877 *
878 * candidate: hi ev every onyx one
879 * ^
880 * pattern: hi*every*one
881 * ^
882 *
883 * candidate: hi ev every onyx one
884 * ^
885 * pattern: hi*every*one
886 * ^
887 *
888 * candidate: hi ev every onyx one
889 * ^
890 * pattern: hi*every*one
891 * ^
892 *
893 * candidate: hi ev every onyx one
894 * ^
895 * pattern: hi*every*one
896 * ^ MISMATCH
897 *
898 * candidate: hi ev every onyx one
899 * ^
900 * pattern: hi*every*one
901 * ^
902 *
903 * candidate: hi ev every onyx one
904 * ^^
905 * pattern: hi*every*one
906 * ^^
907 *
908 * candidate: hi ev every onyx one
909 * ^ ^
910 * pattern: hi*every*one
911 * ^ ^ MISMATCH
912 *
913 * candidate: hi ev every onyx one
914 * ^
915 * pattern: hi*every*one
916 * ^ MISMATCH
917 *
918 * candidate: hi ev every onyx one
919 * ^
920 * pattern: hi*every*one
921 * ^ MISMATCH
922 *
923 * candidate: hi ev every onyx one
924 * ^
925 * pattern: hi*every*one
926 * ^
927 *
928 * candidate: hi ev every onyx one
929 * ^^
930 * pattern: hi*every*one
931 * ^^
932 *
933 * candidate: hi ev every onyx one
934 * ^ ^
935 * pattern: hi*every*one
936 * ^ ^
937 *
938 * candidate: hi ev every onyx one
939 * ^ ^
940 * pattern: hi*every*one
941 * ^ ^
942 *
943 * candidate: hi ev every onyx one
944 * ^ ^
945 * pattern: hi*every*one
946 * ^ ^
947 *
948 * candidate: hi ev every onyx one
949 * ^
950 * pattern: hi*every*one
951 * ^
952 *
953 * candidate: hi ev every onyx one
954 * ^
955 * pattern: hi*every*one
956 * ^ MISMATCH
957 *
958 * candidate: hi ev every onyx one
959 * ^
960 * pattern: hi*every*one
961 * ^
962 *
963 * candidate: hi ev every onyx one
964 * ^^
965 * pattern: hi*every*one
966 * ^^
967 *
968 * candidate: hi ev every onyx one
969 * ^ ^
970 * pattern: hi*every*one
971 * ^ ^ MISMATCH
972 *
973 * candidate: hi ev every onyx one
974 * ^
975 * pattern: hi*every*one
976 * ^ MISMATCH
977 *
978 * candidate: hi ev every onyx one
979 * ^
980 * pattern: hi*every*one
981 * ^ MISMATCH
982 *
983 * candidate: hi ev every onyx one
984 * ^
985 * pattern: hi*every*one
986 * ^ MISMATCH
987 *
988 * candidate: hi ev every onyx one
989 * ^
990 * pattern: hi*every*one
991 * ^ MISMATCH
992 *
993 * candidate: hi ev every onyx one
994 * ^
995 * pattern: hi*every*one
996 * ^
997 *
998 * candidate: hi ev every onyx one
999 * ^^
1000 * pattern: hi*every*one
1001 * ^^
1002 *
1003 * candidate: hi ev every onyx one
1004 * ^ ^
1005 * pattern: hi*every*one
1006 * ^ ^
1007 *
1008 * candidate: hi ev every onyx one
1009 * ^ ^
1010 * pattern: hi*every*one
1011 * ^ ^ SUCCESS
1012 */
1013 while ((c - candidate) < candidate_len && *c != '\0') {
1014 BT_ASSERT(*c);
1015
1016 if (at_end_of_pattern(p, pattern, pattern_len)) {
1017 goto end_of_pattern;
1018 }
1019
1020 switch (*p) {
1021 case '*':
1022 got_a_star = true;
1023
1024 /*
1025 * Our first try starts at the current candidate
1026 * character and after the star in the pattern.
1027 */
1028 retry_c = c;
1029 retry_p = p + 1;
1030
1031 if (at_end_of_pattern(retry_p, pattern, pattern_len)) {
1032 /*
1033 * Star at the end of the pattern at
1034 * this point: automatic match.
1035 */
1036 return true;
1037 }
1038
1039 goto retry;
1040 case '\\':
1041 /* Go to escaped character. */
1042 p++;
1043
1044 /*
1045 * Fall through the default case which compares
1046 * the escaped character now.
1047 */
1048 /* fall-through */
1049 default:
1050 if (at_end_of_pattern(p, pattern, pattern_len) ||
1051 *c != *p) {
1052 end_of_pattern:
1053 /* Character mismatch OR end of pattern. */
1054 if (!got_a_star) {
1055 /*
1056 * We didn't get any star yet,
1057 * so this first mismatch
1058 * automatically makes the whole
1059 * test fail.
1060 */
1061 return false;
1062 }
1063
1064 /*
1065 * Next try: next candidate character,
1066 * original pattern character (following
1067 * the most recent star).
1068 */
1069 retry_c++;
1070 goto retry;
1071 }
1072 break;
1073 }
1074
1075 /* Next pattern and candidate characters. */
1076 c++;
1077 p++;
1078 }
1079
1080 /*
1081 * We checked every candidate character and we're still in a
1082 * success state: the only pattern character allowed to remain
1083 * is a star.
1084 */
1085 if (at_end_of_pattern(p, pattern, pattern_len)) {
1086 return true;
1087 }
1088
1089 p++;
1090 return p[-1] == '*' && at_end_of_pattern(p, pattern, pattern_len);
1091 }
1092
1093 #ifdef __MINGW32__
1094 BT_HIDDEN
1095 GString *bt_common_normalize_path(const char *path, const char *wd)
1096 {
1097 char *tmp;
1098 GString *norm_path = NULL;
1099
1100 BT_ASSERT(path);
1101
1102 tmp = _fullpath(NULL, path, PATH_MAX);
1103 if (!tmp) {
1104 goto error;
1105 }
1106
1107 norm_path = g_string_new(tmp);
1108 if (!norm_path) {
1109 goto error;
1110 }
1111
1112 goto end;
1113 error:
1114 if (norm_path) {
1115 g_string_free(norm_path, TRUE);
1116 norm_path = NULL;
1117 }
1118 end:
1119 free(tmp);
1120 return norm_path;
1121 }
1122 #else
1123 static
1124 void append_path_parts(const char *path, GPtrArray *parts)
1125 {
1126 const char *ch = path;
1127 const char *last = path;
1128
1129 while (true) {
1130 if (*ch == G_DIR_SEPARATOR || *ch == '\0') {
1131 if (ch - last > 0) {
1132 GString *part = g_string_new(NULL);
1133
1134 BT_ASSERT(part);
1135 g_string_append_len(part, last, ch - last);
1136 g_ptr_array_add(parts, part);
1137 }
1138
1139 if (*ch == '\0') {
1140 break;
1141 }
1142
1143 last = ch + 1;
1144 }
1145
1146 ch++;
1147 }
1148 }
1149
1150 static
1151 void destroy_gstring(void *gstring)
1152 {
1153 (void) g_string_free(gstring, TRUE);
1154 }
1155
1156 BT_HIDDEN
1157 GString *bt_common_normalize_path(const char *path, const char *wd)
1158 {
1159 size_t i;
1160 GString *norm_path;
1161 GPtrArray *parts = NULL;
1162
1163 BT_ASSERT(path);
1164 norm_path = g_string_new(G_DIR_SEPARATOR_S);
1165 if (!norm_path) {
1166 goto error;
1167 }
1168
1169 parts = g_ptr_array_new_with_free_func(destroy_gstring);
1170 if (!parts) {
1171 goto error;
1172 }
1173
1174 if (path[0] != G_DIR_SEPARATOR) {
1175 /* Relative path: start with working directory */
1176 if (wd) {
1177 append_path_parts(wd, parts);
1178 } else {
1179 gchar *cd = g_get_current_dir();
1180
1181 append_path_parts(cd, parts);
1182 g_free(cd);
1183 }
1184 }
1185
1186 /* Append parts of the path parameter */
1187 append_path_parts(path, parts);
1188
1189 /* Resolve special `..` and `.` parts */
1190 for (i = 0; i < parts->len; i++) {
1191 GString *part = g_ptr_array_index(parts, i);
1192
1193 if (strcmp(part->str, "..") == 0) {
1194 if (i == 0) {
1195 /*
1196 * First part of absolute path is `..`:
1197 * this is invalid.
1198 */
1199 goto error;
1200 }
1201
1202 /* Remove `..` and previous part */
1203 g_ptr_array_remove_index(parts, i - 1);
1204 g_ptr_array_remove_index(parts, i - 1);
1205 i -= 2;
1206 } else if (strcmp(part->str, ".") == 0) {
1207 /* Remove `.` */
1208 g_ptr_array_remove_index(parts, i);
1209 i -= 1;
1210 }
1211 }
1212
1213 /* Create normalized path with what's left */
1214 for (i = 0; i < parts->len; i++) {
1215 GString *part = g_ptr_array_index(parts, i);
1216
1217 g_string_append(norm_path, part->str);
1218
1219 if (i < parts->len - 1) {
1220 g_string_append_c(norm_path, G_DIR_SEPARATOR);
1221 }
1222 }
1223
1224 goto end;
1225
1226 error:
1227 if (norm_path) {
1228 g_string_free(norm_path, TRUE);
1229 norm_path = NULL;
1230 }
1231
1232 end:
1233 if (parts) {
1234 g_ptr_array_free(parts, TRUE);
1235 }
1236
1237 return norm_path;
1238 }
1239 #endif
1240
1241 BT_HIDDEN
1242 size_t bt_common_get_page_size(int log_level)
1243 {
1244 int page_size;
1245
1246 page_size = bt_sysconf(_SC_PAGESIZE);
1247 if (page_size < 0) {
1248 BT_LOGF("Cannot get system's page size: ret=%d",
1249 page_size);
1250 abort();
1251 }
1252
1253 return page_size;
1254 }
1255
1256 #define BUF_STD_APPEND(...) \
1257 do { \
1258 char _tmp_fmt[64]; \
1259 int _count; \
1260 size_t _size = buf_size - (size_t) (*buf_ch - buf); \
1261 size_t _tmp_fmt_size = (size_t) (fmt_ch - *out_fmt_ch); \
1262 strncpy(_tmp_fmt, *out_fmt_ch, _tmp_fmt_size); \
1263 _tmp_fmt[_tmp_fmt_size] = '\0'; \
1264 _count = snprintf(*buf_ch, _size, _tmp_fmt, __VA_ARGS__); \
1265 BT_ASSERT(_count >= 0); \
1266 *buf_ch += MIN(_count, _size); \
1267 } while (0)
1268
1269 #define BUF_STD_APPEND_SINGLE_ARG(_type) \
1270 do { \
1271 _type _arg = va_arg(*args, _type); \
1272 BUF_STD_APPEND(_arg); \
1273 } while (0)
1274
1275 static inline void handle_conversion_specifier_std(char *buf, char **buf_ch,
1276 size_t buf_size, const char **out_fmt_ch, va_list *args)
1277 {
1278 const char *fmt_ch = *out_fmt_ch;
1279 enum LENGTH_MODIFIER {
1280 LENGTH_MOD_H,
1281 LENGTH_MOD_HH,
1282 LENGTH_MOD_NONE,
1283 LENGTH_MOD_LOW_L,
1284 LENGTH_MOD_LOW_LL,
1285 LENGTH_MOD_UP_L,
1286 LENGTH_MOD_Z,
1287 } length_mod = LENGTH_MOD_NONE;
1288
1289 /* skip '%' */
1290 fmt_ch++;
1291
1292 if (*fmt_ch == '%') {
1293 fmt_ch++;
1294 **buf_ch = '%';
1295 (*buf_ch)++;
1296 goto update_rw_fmt;
1297 }
1298
1299 /* flags */
1300 for (;;) {
1301 switch (*fmt_ch) {
1302 case '-':
1303 case '+':
1304 case ' ':
1305 case '#':
1306 case '0':
1307 case '\'':
1308 fmt_ch++;
1309 continue;
1310 default:
1311 break;
1312 }
1313 break;
1314 }
1315
1316 /* width */
1317 for (;;) {
1318 if (*fmt_ch < '0' || *fmt_ch > '9') {
1319 break;
1320 }
1321
1322 fmt_ch++;
1323 }
1324
1325 /* precision */
1326 if (*fmt_ch == '.') {
1327 fmt_ch++;
1328
1329 for (;;) {
1330 if (*fmt_ch < '0' || *fmt_ch > '9') {
1331 break;
1332 }
1333
1334 fmt_ch++;
1335 }
1336 }
1337
1338 /* format (PRI*64) */
1339 if (strncmp(fmt_ch, PRId64, sizeof(PRId64) - 1) == 0) {
1340 fmt_ch += sizeof(PRId64) - 1;
1341 BUF_STD_APPEND_SINGLE_ARG(int64_t);
1342 goto update_rw_fmt;
1343 } else if (strncmp(fmt_ch, PRIu64, sizeof(PRIu64) - 1) == 0) {
1344 fmt_ch += sizeof(PRIu64) - 1;
1345 BUF_STD_APPEND_SINGLE_ARG(uint64_t);
1346 goto update_rw_fmt;
1347 } else if (strncmp(fmt_ch, PRIx64, sizeof(PRIx64) - 1) == 0) {
1348 fmt_ch += sizeof(PRIx64) - 1;
1349 BUF_STD_APPEND_SINGLE_ARG(uint64_t);
1350 goto update_rw_fmt;
1351 } else if (strncmp(fmt_ch, PRIX64, sizeof(PRIX64) - 1) == 0) {
1352 fmt_ch += sizeof(PRIX64) - 1;
1353 BUF_STD_APPEND_SINGLE_ARG(uint64_t);
1354 goto update_rw_fmt;
1355 } else if (strncmp(fmt_ch, PRIo64, sizeof(PRIo64) - 1) == 0) {
1356 fmt_ch += sizeof(PRIo64) - 1;
1357 BUF_STD_APPEND_SINGLE_ARG(uint64_t);
1358 goto update_rw_fmt;
1359 } else if (strncmp(fmt_ch, PRIi64, sizeof(PRIi64) - 1) == 0) {
1360 fmt_ch += sizeof(PRIi64) - 1;
1361 BUF_STD_APPEND_SINGLE_ARG(int64_t);
1362 goto update_rw_fmt;
1363 }
1364
1365 // length modifier
1366 switch (*fmt_ch) {
1367 case 'h':
1368 length_mod = LENGTH_MOD_H;
1369 fmt_ch++;
1370
1371 if (*fmt_ch == 'h') {
1372 length_mod = LENGTH_MOD_HH;
1373 fmt_ch++;
1374 break;
1375 }
1376 break;
1377 case 'l':
1378 length_mod = LENGTH_MOD_LOW_L;
1379 fmt_ch++;
1380
1381 if (*fmt_ch == 'l') {
1382 length_mod = LENGTH_MOD_LOW_LL;
1383 fmt_ch++;
1384 break;
1385 }
1386 break;
1387 case 'L':
1388 length_mod = LENGTH_MOD_UP_L;
1389 fmt_ch++;
1390 break;
1391 case 'z':
1392 length_mod = LENGTH_MOD_Z;
1393 fmt_ch++;
1394 break;
1395 default:
1396 break;
1397 }
1398
1399 // format
1400 switch (*fmt_ch) {
1401 case 'c':
1402 {
1403 fmt_ch++;
1404
1405 switch (length_mod) {
1406 case LENGTH_MOD_NONE:
1407 BUF_STD_APPEND_SINGLE_ARG(int);
1408 break;
1409 case LENGTH_MOD_LOW_L:
1410 BUF_STD_APPEND_SINGLE_ARG(wint_t);
1411 break;
1412 default:
1413 abort();
1414 }
1415 break;
1416 }
1417 case 's':
1418 fmt_ch++;
1419
1420 switch (length_mod) {
1421 case LENGTH_MOD_NONE:
1422 BUF_STD_APPEND_SINGLE_ARG(char *);
1423 break;
1424 case LENGTH_MOD_LOW_L:
1425 BUF_STD_APPEND_SINGLE_ARG(wchar_t *);
1426 break;
1427 default:
1428 abort();
1429 }
1430 break;
1431 case 'd':
1432 case 'i':
1433 fmt_ch++;
1434
1435 switch (length_mod) {
1436 case LENGTH_MOD_NONE:
1437 case LENGTH_MOD_H:
1438 case LENGTH_MOD_HH:
1439 BUF_STD_APPEND_SINGLE_ARG(int);
1440 break;
1441 case LENGTH_MOD_LOW_L:
1442 BUF_STD_APPEND_SINGLE_ARG(long);
1443 break;
1444 case LENGTH_MOD_LOW_LL:
1445 BUF_STD_APPEND_SINGLE_ARG(long long);
1446 break;
1447 case LENGTH_MOD_Z:
1448 BUF_STD_APPEND_SINGLE_ARG(size_t);
1449 break;
1450 default:
1451 abort();
1452 }
1453 break;
1454 case 'o':
1455 case 'x':
1456 case 'X':
1457 case 'u':
1458 fmt_ch++;
1459
1460 switch (length_mod) {
1461 case LENGTH_MOD_NONE:
1462 case LENGTH_MOD_H:
1463 case LENGTH_MOD_HH:
1464 BUF_STD_APPEND_SINGLE_ARG(unsigned int);
1465 break;
1466 case LENGTH_MOD_LOW_L:
1467 BUF_STD_APPEND_SINGLE_ARG(unsigned long);
1468 break;
1469 case LENGTH_MOD_LOW_LL:
1470 BUF_STD_APPEND_SINGLE_ARG(unsigned long long);
1471 break;
1472 case LENGTH_MOD_Z:
1473 BUF_STD_APPEND_SINGLE_ARG(size_t);
1474 break;
1475 default:
1476 abort();
1477 }
1478 break;
1479 case 'f':
1480 case 'F':
1481 case 'e':
1482 case 'E':
1483 case 'g':
1484 case 'G':
1485 fmt_ch++;
1486
1487 switch (length_mod) {
1488 case LENGTH_MOD_NONE:
1489 BUF_STD_APPEND_SINGLE_ARG(double);
1490 break;
1491 case LENGTH_MOD_UP_L:
1492 BUF_STD_APPEND_SINGLE_ARG(long double);
1493 break;
1494 default:
1495 abort();
1496 }
1497 break;
1498 case 'p':
1499 fmt_ch++;
1500
1501 if (length_mod == LENGTH_MOD_NONE) {
1502 BUF_STD_APPEND_SINGLE_ARG(void *);
1503 } else {
1504 abort();
1505 }
1506 break;
1507 default:
1508 abort();
1509 }
1510
1511 update_rw_fmt:
1512 *out_fmt_ch = fmt_ch;
1513 }
1514
1515 BT_HIDDEN
1516 void bt_common_custom_vsnprintf(char *buf, size_t buf_size,
1517 char intro,
1518 bt_common_handle_custom_specifier_func handle_specifier,
1519 void *priv_data, const char *fmt, va_list *args)
1520 {
1521 const char *fmt_ch = fmt;
1522 char *buf_ch = buf;
1523
1524 BT_ASSERT(buf);
1525 BT_ASSERT(fmt);
1526
1527 while (*fmt_ch != '\0') {
1528 switch (*fmt_ch) {
1529 case '%':
1530 BT_ASSERT(fmt_ch[1] != '\0');
1531
1532 if (fmt_ch[1] == intro) {
1533 handle_specifier(priv_data, &buf_ch,
1534 buf_size - (size_t) (buf_ch - buf),
1535 &fmt_ch, args);
1536 } else {
1537 handle_conversion_specifier_std(buf, &buf_ch,
1538 buf_size, &fmt_ch, args);
1539 }
1540
1541 if (buf_ch >= buf + buf_size - 1) {
1542 fmt_ch = "";
1543 }
1544 break;
1545 default:
1546 *buf_ch = *fmt_ch;
1547 buf_ch++;
1548 if (buf_ch >= buf + buf_size - 1) {
1549 fmt_ch = "";
1550 }
1551
1552 fmt_ch++;
1553 }
1554 }
1555
1556 *buf_ch = '\0';
1557 }
1558
1559 BT_HIDDEN
1560 void bt_common_custom_snprintf(char *buf, size_t buf_size,
1561 char intro,
1562 bt_common_handle_custom_specifier_func handle_specifier,
1563 void *priv_data, const char *fmt, ...)
1564 {
1565 va_list args;
1566 va_start(args, fmt);
1567 bt_common_custom_vsnprintf(buf, buf_size, intro, handle_specifier,
1568 priv_data, fmt, &args);
1569 va_end(args);
1570 }
1571
1572 BT_HIDDEN
1573 void bt_common_sep_digits(char *str, unsigned int digits_per_group, char sep)
1574 {
1575 const char *rd;
1576 char *wr;
1577 uint64_t i = 0;
1578 uint64_t orig_len;
1579 uint64_t sep_count;
1580 uint64_t new_len;
1581
1582 BT_ASSERT(digits_per_group > 0);
1583 BT_ASSERT(sep != '\0');
1584
1585 /* Compute new length of `str` */
1586 orig_len = strlen(str);
1587 BT_ASSERT(orig_len > 0);
1588 sep_count = (orig_len - 1) / digits_per_group;
1589 new_len = strlen(str) + sep_count;
1590
1591 /*
1592 * Do the work in place. Have the reading pointer `rd` start at
1593 * the end of the original string, and the writing pointer `wr`
1594 * start at the end of the new string, making sure to also put a
1595 * null character there.
1596 */
1597 rd = str + orig_len - 1;
1598 wr = str + new_len;
1599 *wr = '\0';
1600 wr--;
1601
1602 /*
1603 * Here's what the process looks like (3 digits per group):
1604 *
1605 * Source: 12345678
1606 * ^
1607 * Destination: 12345678#8
1608 * ^
1609 *
1610 * Source: 12345678
1611 * ^
1612 * Destination: 1234567878
1613 * ^
1614 *
1615 * Source: 12345678
1616 * ^
1617 * Destination: 1234567678
1618 * ^
1619 *
1620 * Source: 12345678
1621 * ^
1622 * Destination: 123456,678
1623 * ^
1624 *
1625 * Source: 12345678
1626 * ^
1627 * Destination: 123455,678
1628 * ^
1629 *
1630 * Source: 12345678
1631 * ^
1632 * Destination: 123445,678
1633 * ^
1634 *
1635 * Source: 12345678
1636 * ^
1637 * Destination: 123345,678
1638 * ^
1639 *
1640 * Source: 12345678
1641 * ^
1642 * Destination: 12,345,678
1643 * ^
1644 *
1645 * Source: 12345678
1646 * ^
1647 * Destination: 12,345,678
1648 * ^
1649 *
1650 * Source: 12345678
1651 * ^
1652 * Destination: 12,345,678
1653 * ^
1654 */
1655 while (rd != str - 1) {
1656 if (i == digits_per_group) {
1657 /*
1658 * Time to append the separator: decrement `wr`,
1659 * but keep `rd` as is.
1660 */
1661 i = 0;
1662 *wr = sep;
1663 wr--;
1664 continue;
1665 }
1666
1667 /* Copy read-side character to write-side character */
1668 *wr = *rd;
1669 wr--;
1670 rd--;
1671 i++;
1672 }
1673 }
1674
1675 BT_HIDDEN
1676 GString *bt_common_fold(const char *str, unsigned int total_length,
1677 unsigned int indent)
1678 {
1679 const unsigned int content_length = total_length - indent;
1680 GString *folded = g_string_new(NULL);
1681 GString *tmp_line = g_string_new(NULL);
1682 gchar **lines = NULL;
1683 gchar **line_words = NULL;
1684 gchar * const *line;
1685 unsigned int i;
1686
1687 BT_ASSERT(str);
1688 BT_ASSERT(indent < total_length);
1689 BT_ASSERT(tmp_line);
1690 BT_ASSERT(folded);
1691
1692 if (strlen(str) == 0) {
1693 /* Empty input string: empty output string */
1694 goto end;
1695 }
1696
1697 /* Split lines */
1698 lines = g_strsplit(str, "\n", 0);
1699 BT_ASSERT(lines);
1700
1701 /* For each source line */
1702 for (line = lines; *line; line++) {
1703 gchar * const *word;
1704
1705 /*
1706 * Append empty line without indenting if source line is
1707 * empty.
1708 */
1709 if (strlen(*line) == 0) {
1710 g_string_append_c(folded, '\n');
1711 continue;
1712 }
1713
1714 /* Split words */
1715 line_words = g_strsplit(*line, " ", 0);
1716 BT_ASSERT(line_words);
1717
1718 /*
1719 * Indent for first line (we know there's at least one
1720 * word at this point).
1721 */
1722 for (i = 0; i < indent; i++) {
1723 g_string_append_c(folded, ' ');
1724 }
1725
1726 /* Append words, folding when necessary */
1727 g_string_assign(tmp_line, "");
1728
1729 for (word = line_words; *word; word++) {
1730 /*
1731 * `tmp_line->len > 0` is in the condition so
1732 * that words that are larger than
1733 * `content_length` are at least written on
1734 * their own line.
1735 *
1736 * `tmp_line->len - 1` because the temporary
1737 * line always contains a trailing space which
1738 * won't be part of the line if we fold.
1739 */
1740 if (tmp_line->len > 0 &&
1741 tmp_line->len - 1 + strlen(*word) >= content_length) {
1742 /* Fold (without trailing space) */
1743 g_string_append_len(folded,
1744 tmp_line->str, tmp_line->len - 1);
1745 g_string_append_c(folded, '\n');
1746
1747 /* Indent new line */
1748 for (i = 0; i < indent; i++) {
1749 g_string_append_c(folded, ' ');
1750 }
1751
1752 g_string_assign(tmp_line, "");
1753 }
1754
1755 /* Append current word and space to temporary line */
1756 g_string_append(tmp_line, *word);
1757 g_string_append_c(tmp_line, ' ');
1758 }
1759
1760 /* Append last line if any, without trailing space */
1761 if (tmp_line->len > 0) {
1762 g_string_append_len(folded, tmp_line->str,
1763 tmp_line->len - 1);
1764 }
1765
1766 /* Append source newline */
1767 g_string_append_c(folded, '\n');
1768
1769 /* Free array of this line's words */
1770 g_strfreev(line_words);
1771 line_words = NULL;
1772 }
1773
1774 /* Remove trailing newline if any */
1775 if (folded->str[folded->len - 1] == '\n') {
1776 g_string_truncate(folded, folded->len - 1);
1777 }
1778
1779 end:
1780 if (lines) {
1781 g_strfreev(lines);
1782 }
1783
1784 BT_ASSERT(!line_words);
1785
1786 if (tmp_line) {
1787 g_string_free(tmp_line, TRUE);
1788 }
1789
1790 return folded;
1791 }
1792
1793 #ifdef __MINGW32__
1794 BT_HIDDEN
1795 int bt_common_get_term_size(unsigned int *width, unsigned int *height)
1796 {
1797 /* Not supported on Windows yet */
1798 return -1;
1799 }
1800 #else /* __MINGW32__ */
1801 BT_HIDDEN
1802 int bt_common_get_term_size(unsigned int *width, unsigned int *height)
1803 {
1804 int ret = 0;
1805 struct winsize winsize;
1806
1807 if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &winsize) < 0) {
1808 ret = -1;
1809 goto end;
1810 }
1811
1812 if (width) {
1813 *width = (unsigned int) winsize.ws_col;
1814 }
1815
1816 if (height) {
1817 *height = (unsigned int) winsize.ws_row;
1818 }
1819
1820 end:
1821 return ret;
1822 }
1823 #endif /* __MINGW32__ */
1824
1825 BT_HIDDEN
1826 int bt_common_g_string_append_printf(GString *str, const char *fmt, ...)
1827 {
1828 va_list ap;
1829 gsize len, allocated_len, available_len;
1830 int print_len;
1831
1832 /* str->len excludes \0. */
1833 len = str->len;
1834 /* Explicitly exclude \0. */
1835 allocated_len = str->allocated_len - 1;
1836 available_len = allocated_len - len;
1837
1838 str->len = allocated_len;
1839 va_start(ap, fmt);
1840 print_len = vsnprintf(str->str + len, available_len + 1, fmt, ap);
1841 va_end(ap);
1842 if (print_len < 0) {
1843 return print_len;
1844 }
1845 if (G_UNLIKELY(available_len < print_len)) {
1846 /* Resize. */
1847 g_string_set_size(str, len + print_len);
1848 va_start(ap, fmt);
1849 print_len = vsprintf(str->str + len, fmt, ap);
1850 va_end(ap);
1851 } else {
1852 str->len = len + print_len;
1853 }
1854 return print_len;
1855 }
This page took 0.101358 seconds and 5 git commands to generate.