Add bt_common_get_term_size()
[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 INSTALL_LIBDIR "/babeltrace2/plugins"
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 }
516
517 end:
518 return output;
519 }
520
521 BT_HIDDEN
522 GString *bt_common_shell_quote(const char *input, bool with_single_quotes)
523 {
524 GString *output = g_string_new(NULL);
525 const char *ch;
526 bool no_quote = true;
527
528 if (!output) {
529 goto end;
530 }
531
532 if (strlen(input) == 0) {
533 if (with_single_quotes) {
534 g_string_assign(output, "''");
535 }
536
537 goto end;
538 }
539
540 for (ch = input; *ch != '\0'; ch++) {
541 const char c = *ch;
542
543 if (!g_ascii_isalpha(c) && !g_ascii_isdigit(c) && c != '_' &&
544 c != '@' && c != '%' && c != '+' && c != '=' &&
545 c != ':' && c != ',' && c != '.' && c != '/' &&
546 c != '-') {
547 no_quote = false;
548 break;
549 }
550 }
551
552 if (no_quote) {
553 g_string_assign(output, input);
554 goto end;
555 }
556
557 if (with_single_quotes) {
558 g_string_assign(output, "'");
559 }
560
561 for (ch = input; *ch != '\0'; ch++) {
562 if (*ch == '\'') {
563 g_string_append(output, "'\"'\"'");
564 } else {
565 g_string_append_c(output, *ch);
566 }
567 }
568
569 if (with_single_quotes) {
570 g_string_append_c(output, '\'');
571 }
572
573 end:
574 return output;
575 }
576
577 BT_HIDDEN
578 bool bt_common_string_is_printable(const char *input)
579 {
580 const char *ch;
581 bool printable = true;
582 BT_ASSERT(input);
583
584 for (ch = input; *ch != '\0'; ch++) {
585 if (!isprint(*ch) && *ch != '\n' && *ch != '\r' &&
586 *ch != '\t' && *ch != '\v') {
587 printable = false;
588 goto end;
589 }
590 }
591
592 end:
593 return printable;
594 }
595
596 BT_HIDDEN
597 void bt_common_destroy_lttng_live_url_parts(
598 struct bt_common_lttng_live_url_parts *parts)
599 {
600 if (!parts) {
601 goto end;
602 }
603
604 if (parts->proto) {
605 g_string_free(parts->proto, TRUE);
606 parts->proto = NULL;
607 }
608
609 if (parts->hostname) {
610 g_string_free(parts->hostname, TRUE);
611 parts->hostname = NULL;
612 }
613
614 if (parts->target_hostname) {
615 g_string_free(parts->target_hostname, TRUE);
616 parts->target_hostname = NULL;
617 }
618
619 if (parts->session_name) {
620 g_string_free(parts->session_name, TRUE);
621 parts->session_name = NULL;
622 }
623
624 end:
625 return;
626 }
627
628 BT_HIDDEN
629 struct bt_common_lttng_live_url_parts bt_common_parse_lttng_live_url(
630 const char *url, char *error_buf, size_t error_buf_size)
631 {
632 struct bt_common_lttng_live_url_parts parts;
633 const char *at = url;
634 size_t end_pos;
635
636 BT_ASSERT(url);
637 memset(&parts, 0, sizeof(parts));
638 parts.port = -1;
639
640 /* Protocol */
641 parts.proto = bt_common_string_until(at, "", ":", &end_pos);
642 if (!parts.proto || parts.proto->len == 0) {
643 if (error_buf) {
644 snprintf(error_buf, error_buf_size, "Missing protocol");
645 }
646
647 goto error;
648 }
649
650 if (strcmp(parts.proto->str, "net") == 0) {
651 g_string_assign(parts.proto, "net4");
652 }
653
654 if (strcmp(parts.proto->str, "net4") != 0 &&
655 strcmp(parts.proto->str, "net6") != 0) {
656 if (error_buf) {
657 snprintf(error_buf, error_buf_size,
658 "Unknown protocol: `%s`", parts.proto->str);
659 }
660
661 goto error;
662 }
663
664 if (at[end_pos] != ':') {
665 if (error_buf) {
666 snprintf(error_buf, error_buf_size,
667 "Expecting `:` after `%s`", parts.proto->str);
668 }
669
670 goto error;
671 }
672
673 at += end_pos;
674
675 /* :// */
676 if (strncmp(at, "://", 3) != 0) {
677 if (error_buf) {
678 snprintf(error_buf, error_buf_size,
679 "Expecting `://` after protocol");
680 }
681
682 goto error;
683 }
684
685 at += 3;
686
687 /* Hostname */
688 parts.hostname = bt_common_string_until(at, "", ":/", &end_pos);
689 if (!parts.hostname || parts.hostname->len == 0) {
690 if (error_buf) {
691 snprintf(error_buf, error_buf_size, "Missing hostname");
692 }
693
694 goto error;
695 }
696
697 if (at[end_pos] == ':') {
698 /* Port */
699 GString *port;
700
701 at += end_pos + 1;
702 port = bt_common_string_until(at, "", "/", &end_pos);
703 if (!port || port->len == 0) {
704 if (error_buf) {
705 snprintf(error_buf, error_buf_size, "Missing port");
706 }
707
708 goto error;
709 }
710
711 if (sscanf(port->str, "%d", &parts.port) != 1) {
712 if (error_buf) {
713 snprintf(error_buf, error_buf_size,
714 "Invalid port: `%s`", port->str);
715 }
716
717 g_string_free(port, TRUE);
718 goto error;
719 }
720
721 g_string_free(port, TRUE);
722
723 if (parts.port < 0 || parts.port >= 65536) {
724 if (error_buf) {
725 snprintf(error_buf, error_buf_size,
726 "Invalid port: %d", parts.port);
727 }
728
729 goto error;
730 }
731 }
732
733 if (at[end_pos] == '\0') {
734 goto end;
735 }
736
737 at += end_pos;
738
739 /* /host/ */
740 if (strncmp(at, "/host/", 6) != 0) {
741 if (error_buf) {
742 snprintf(error_buf, error_buf_size,
743 "Expecting `/host/` after hostname or port");
744 }
745
746 goto error;
747 }
748
749 at += 6;
750
751 /* Target hostname */
752 parts.target_hostname = bt_common_string_until(at, "", "/", &end_pos);
753 if (!parts.target_hostname || parts.target_hostname->len == 0) {
754 if (error_buf) {
755 snprintf(error_buf, error_buf_size,
756 "Missing target hostname");
757 }
758
759 goto error;
760 }
761
762 if (at[end_pos] == '\0') {
763 goto end;
764 }
765
766 at += end_pos + 1;
767
768 /* Session name */
769 parts.session_name = bt_common_string_until(at, "", "/", &end_pos);
770 if (!parts.session_name || parts.session_name->len == 0) {
771 if (error_buf) {
772 snprintf(error_buf, error_buf_size,
773 "Missing session name");
774 }
775
776 goto error;
777 }
778
779 if (at[end_pos] == '/') {
780 if (error_buf) {
781 snprintf(error_buf, error_buf_size,
782 "Unexpected `/` after session name (`%s`)",
783 parts.session_name->str);
784 }
785
786 goto error;
787 }
788
789 goto end;
790
791 error:
792 bt_common_destroy_lttng_live_url_parts(&parts);
793
794 end:
795 return parts;
796 }
797
798 BT_HIDDEN
799 void bt_common_normalize_star_glob_pattern(char *pattern)
800 {
801 const char *p;
802 char *np;
803 bool got_star = false;
804
805 BT_ASSERT(pattern);
806
807 for (p = pattern, np = pattern; *p != '\0'; p++) {
808 switch (*p) {
809 case '*':
810 if (got_star) {
811 /* Avoid consecutive stars. */
812 continue;
813 }
814
815 got_star = true;
816 break;
817 case '\\':
818 /* Copy backslash character. */
819 *np = *p;
820 np++;
821 p++;
822
823 if (*p == '\0') {
824 goto end;
825 }
826
827 /* fall-through */
828 default:
829 got_star = false;
830 break;
831 }
832
833 /* Copy single character. */
834 *np = *p;
835 np++;
836 }
837
838 end:
839 *np = '\0';
840 }
841
842 static inline
843 bool at_end_of_pattern(const char *p, const char *pattern, size_t pattern_len)
844 {
845 return (p - pattern) == pattern_len || *p == '\0';
846 }
847
848 /*
849 * Globbing matching function with the star feature only (`?` and
850 * character sets are not supported). This matches `candidate` (plain
851 * string) against `pattern`. A literal star can be escaped with `\` in
852 * `pattern`.
853 *
854 * `pattern_len` or `candidate_len` can be greater than the actual
855 * string length of `pattern` or `candidate` if the string is
856 * null-terminated.
857 */
858 BT_HIDDEN
859 bool bt_common_star_glob_match(const char *pattern, size_t pattern_len,
860 const char *candidate, size_t candidate_len) {
861 const char *retry_c = candidate, *retry_p = pattern, *c, *p;
862 bool got_a_star = false;
863
864 retry:
865 c = retry_c;
866 p = retry_p;
867
868 /*
869 * The concept here is to retry a match in the specific case
870 * where we already got a star. The retry position for the
871 * pattern is just after the most recent star, and the retry
872 * position for the candidate is the character following the
873 * last try's first character.
874 *
875 * Example:
876 *
877 * candidate: hi ev every onyx one
878 * ^
879 * pattern: hi*every*one
880 * ^
881 *
882 * candidate: hi ev every onyx one
883 * ^
884 * pattern: hi*every*one
885 * ^
886 *
887 * candidate: hi ev every onyx one
888 * ^
889 * pattern: hi*every*one
890 * ^
891 *
892 * candidate: hi ev every onyx one
893 * ^
894 * pattern: hi*every*one
895 * ^ MISMATCH
896 *
897 * candidate: hi ev every onyx one
898 * ^
899 * pattern: hi*every*one
900 * ^
901 *
902 * candidate: hi ev every onyx one
903 * ^^
904 * pattern: hi*every*one
905 * ^^
906 *
907 * candidate: hi ev every onyx one
908 * ^ ^
909 * pattern: hi*every*one
910 * ^ ^ MISMATCH
911 *
912 * candidate: hi ev every onyx one
913 * ^
914 * pattern: hi*every*one
915 * ^ MISMATCH
916 *
917 * candidate: hi ev every onyx one
918 * ^
919 * pattern: hi*every*one
920 * ^ MISMATCH
921 *
922 * candidate: hi ev every onyx one
923 * ^
924 * pattern: hi*every*one
925 * ^
926 *
927 * candidate: hi ev every onyx one
928 * ^^
929 * pattern: hi*every*one
930 * ^^
931 *
932 * candidate: hi ev every onyx one
933 * ^ ^
934 * pattern: hi*every*one
935 * ^ ^
936 *
937 * candidate: hi ev every onyx one
938 * ^ ^
939 * pattern: hi*every*one
940 * ^ ^
941 *
942 * candidate: hi ev every onyx one
943 * ^ ^
944 * pattern: hi*every*one
945 * ^ ^
946 *
947 * candidate: hi ev every onyx one
948 * ^
949 * pattern: hi*every*one
950 * ^
951 *
952 * candidate: hi ev every onyx one
953 * ^
954 * pattern: hi*every*one
955 * ^ MISMATCH
956 *
957 * candidate: hi ev every onyx one
958 * ^
959 * pattern: hi*every*one
960 * ^
961 *
962 * candidate: hi ev every onyx one
963 * ^^
964 * pattern: hi*every*one
965 * ^^
966 *
967 * candidate: hi ev every onyx one
968 * ^ ^
969 * pattern: hi*every*one
970 * ^ ^ MISMATCH
971 *
972 * candidate: hi ev every onyx one
973 * ^
974 * pattern: hi*every*one
975 * ^ MISMATCH
976 *
977 * candidate: hi ev every onyx one
978 * ^
979 * pattern: hi*every*one
980 * ^ MISMATCH
981 *
982 * candidate: hi ev every onyx one
983 * ^
984 * pattern: hi*every*one
985 * ^ MISMATCH
986 *
987 * candidate: hi ev every onyx one
988 * ^
989 * pattern: hi*every*one
990 * ^ MISMATCH
991 *
992 * candidate: hi ev every onyx one
993 * ^
994 * pattern: hi*every*one
995 * ^
996 *
997 * candidate: hi ev every onyx one
998 * ^^
999 * pattern: hi*every*one
1000 * ^^
1001 *
1002 * candidate: hi ev every onyx one
1003 * ^ ^
1004 * pattern: hi*every*one
1005 * ^ ^
1006 *
1007 * candidate: hi ev every onyx one
1008 * ^ ^
1009 * pattern: hi*every*one
1010 * ^ ^ SUCCESS
1011 */
1012 while ((c - candidate) < candidate_len && *c != '\0') {
1013 BT_ASSERT(*c);
1014
1015 if (at_end_of_pattern(p, pattern, pattern_len)) {
1016 goto end_of_pattern;
1017 }
1018
1019 switch (*p) {
1020 case '*':
1021 got_a_star = true;
1022
1023 /*
1024 * Our first try starts at the current candidate
1025 * character and after the star in the pattern.
1026 */
1027 retry_c = c;
1028 retry_p = p + 1;
1029
1030 if (at_end_of_pattern(retry_p, pattern, pattern_len)) {
1031 /*
1032 * Star at the end of the pattern at
1033 * this point: automatic match.
1034 */
1035 return true;
1036 }
1037
1038 goto retry;
1039 case '\\':
1040 /* Go to escaped character. */
1041 p++;
1042
1043 /*
1044 * Fall through the default case which compares
1045 * the escaped character now.
1046 */
1047 /* fall-through */
1048 default:
1049 if (at_end_of_pattern(p, pattern, pattern_len) ||
1050 *c != *p) {
1051 end_of_pattern:
1052 /* Character mismatch OR end of pattern. */
1053 if (!got_a_star) {
1054 /*
1055 * We didn't get any star yet,
1056 * so this first mismatch
1057 * automatically makes the whole
1058 * test fail.
1059 */
1060 return false;
1061 }
1062
1063 /*
1064 * Next try: next candidate character,
1065 * original pattern character (following
1066 * the most recent star).
1067 */
1068 retry_c++;
1069 goto retry;
1070 }
1071 break;
1072 }
1073
1074 /* Next pattern and candidate characters. */
1075 c++;
1076 p++;
1077 }
1078
1079 /*
1080 * We checked every candidate character and we're still in a
1081 * success state: the only pattern character allowed to remain
1082 * is a star.
1083 */
1084 if (at_end_of_pattern(p, pattern, pattern_len)) {
1085 return true;
1086 }
1087
1088 p++;
1089 return p[-1] == '*' && at_end_of_pattern(p, pattern, pattern_len);
1090 }
1091
1092 #ifdef __MINGW32__
1093 BT_HIDDEN
1094 GString *bt_common_normalize_path(const char *path, const char *wd)
1095 {
1096 char *tmp;
1097 GString *norm_path = NULL;
1098
1099 BT_ASSERT(path);
1100
1101 tmp = _fullpath(NULL, path, PATH_MAX);
1102 if (!tmp) {
1103 goto error;
1104 }
1105
1106 norm_path = g_string_new(tmp);
1107 if (!norm_path) {
1108 goto error;
1109 }
1110
1111 goto end;
1112 error:
1113 if (norm_path) {
1114 g_string_free(norm_path, TRUE);
1115 norm_path = NULL;
1116 }
1117 end:
1118 if (tmp) {
1119 free(tmp);
1120 }
1121 return norm_path;
1122 }
1123 #else
1124 static
1125 void append_path_parts(const char *path, GPtrArray *parts)
1126 {
1127 const char *ch = path;
1128 const char *last = path;
1129
1130 while (true) {
1131 if (*ch == G_DIR_SEPARATOR || *ch == '\0') {
1132 if (ch - last > 0) {
1133 GString *part = g_string_new(NULL);
1134
1135 BT_ASSERT(part);
1136 g_string_append_len(part, last, ch - last);
1137 g_ptr_array_add(parts, part);
1138 }
1139
1140 if (*ch == '\0') {
1141 break;
1142 }
1143
1144 last = ch + 1;
1145 }
1146
1147 ch++;
1148 }
1149 }
1150
1151 static
1152 void destroy_gstring(void *gstring)
1153 {
1154 (void) g_string_free(gstring, TRUE);
1155 }
1156
1157 BT_HIDDEN
1158 GString *bt_common_normalize_path(const char *path, const char *wd)
1159 {
1160 size_t i;
1161 GString *norm_path;
1162 GPtrArray *parts = NULL;
1163
1164 BT_ASSERT(path);
1165 norm_path = g_string_new(G_DIR_SEPARATOR_S);
1166 if (!norm_path) {
1167 goto error;
1168 }
1169
1170 parts = g_ptr_array_new_with_free_func(destroy_gstring);
1171 if (!parts) {
1172 goto error;
1173 }
1174
1175 if (path[0] != G_DIR_SEPARATOR) {
1176 /* Relative path: start with working directory */
1177 if (wd) {
1178 append_path_parts(wd, parts);
1179 } else {
1180 gchar *cd = g_get_current_dir();
1181
1182 append_path_parts(cd, parts);
1183 g_free(cd);
1184 }
1185 }
1186
1187 /* Append parts of the path parameter */
1188 append_path_parts(path, parts);
1189
1190 /* Resolve special `..` and `.` parts */
1191 for (i = 0; i < parts->len; i++) {
1192 GString *part = g_ptr_array_index(parts, i);
1193
1194 if (strcmp(part->str, "..") == 0) {
1195 if (i == 0) {
1196 /*
1197 * First part of absolute path is `..`:
1198 * this is invalid.
1199 */
1200 goto error;
1201 }
1202
1203 /* Remove `..` and previous part */
1204 g_ptr_array_remove_index(parts, i - 1);
1205 g_ptr_array_remove_index(parts, i - 1);
1206 i -= 2;
1207 } else if (strcmp(part->str, ".") == 0) {
1208 /* Remove `.` */
1209 g_ptr_array_remove_index(parts, i);
1210 i -= 1;
1211 }
1212 }
1213
1214 /* Create normalized path with what's left */
1215 for (i = 0; i < parts->len; i++) {
1216 GString *part = g_ptr_array_index(parts, i);
1217
1218 g_string_append(norm_path, part->str);
1219
1220 if (i < parts->len - 1) {
1221 g_string_append_c(norm_path, G_DIR_SEPARATOR);
1222 }
1223 }
1224
1225 goto end;
1226
1227 error:
1228 if (norm_path) {
1229 g_string_free(norm_path, TRUE);
1230 norm_path = NULL;
1231 }
1232
1233 end:
1234 if (parts) {
1235 g_ptr_array_free(parts, TRUE);
1236 }
1237
1238 return norm_path;
1239 }
1240 #endif
1241
1242 BT_HIDDEN
1243 size_t bt_common_get_page_size(int log_level)
1244 {
1245 int page_size;
1246
1247 page_size = bt_sysconf(_SC_PAGESIZE);
1248 if (page_size < 0) {
1249 BT_LOGF("Cannot get system's page size: ret=%d",
1250 page_size);
1251 abort();
1252 }
1253
1254 return page_size;
1255 }
1256
1257 #define BUF_STD_APPEND(...) \
1258 do { \
1259 char _tmp_fmt[64]; \
1260 int _count; \
1261 size_t _size = buf_size - (size_t) (*buf_ch - buf); \
1262 size_t _tmp_fmt_size = (size_t) (fmt_ch - *out_fmt_ch); \
1263 strncpy(_tmp_fmt, *out_fmt_ch, _tmp_fmt_size); \
1264 _tmp_fmt[_tmp_fmt_size] = '\0'; \
1265 _count = snprintf(*buf_ch, _size, _tmp_fmt, __VA_ARGS__); \
1266 BT_ASSERT(_count >= 0); \
1267 *buf_ch += MIN(_count, _size); \
1268 } while (0)
1269
1270 #define BUF_STD_APPEND_SINGLE_ARG(_type) \
1271 do { \
1272 _type _arg = va_arg(*args, _type); \
1273 BUF_STD_APPEND(_arg); \
1274 } while (0)
1275
1276 static inline void handle_conversion_specifier_std(char *buf, char **buf_ch,
1277 size_t buf_size, const char **out_fmt_ch, va_list *args)
1278 {
1279 const char *fmt_ch = *out_fmt_ch;
1280 enum LENGTH_MODIFIER {
1281 LENGTH_MOD_H,
1282 LENGTH_MOD_HH,
1283 LENGTH_MOD_NONE,
1284 LENGTH_MOD_LOW_L,
1285 LENGTH_MOD_LOW_LL,
1286 LENGTH_MOD_UP_L,
1287 LENGTH_MOD_Z,
1288 } length_mod = LENGTH_MOD_NONE;
1289
1290 /* skip '%' */
1291 fmt_ch++;
1292
1293 if (*fmt_ch == '%') {
1294 fmt_ch++;
1295 **buf_ch = '%';
1296 (*buf_ch)++;
1297 goto update_rw_fmt;
1298 }
1299
1300 /* flags */
1301 for (;;) {
1302 switch (*fmt_ch) {
1303 case '-':
1304 case '+':
1305 case ' ':
1306 case '#':
1307 case '0':
1308 case '\'':
1309 fmt_ch++;
1310 continue;
1311 default:
1312 break;
1313 }
1314 break;
1315 }
1316
1317 /* width */
1318 for (;;) {
1319 if (*fmt_ch < '0' || *fmt_ch > '9') {
1320 break;
1321 }
1322
1323 fmt_ch++;
1324 }
1325
1326 /* precision */
1327 if (*fmt_ch == '.') {
1328 fmt_ch++;
1329
1330 for (;;) {
1331 if (*fmt_ch < '0' || *fmt_ch > '9') {
1332 break;
1333 }
1334
1335 fmt_ch++;
1336 }
1337 }
1338
1339 /* format (PRI*64) */
1340 if (strncmp(fmt_ch, PRId64, sizeof(PRId64) - 1) == 0) {
1341 fmt_ch += sizeof(PRId64) - 1;
1342 BUF_STD_APPEND_SINGLE_ARG(int64_t);
1343 goto update_rw_fmt;
1344 } else if (strncmp(fmt_ch, PRIu64, sizeof(PRIu64) - 1) == 0) {
1345 fmt_ch += sizeof(PRIu64) - 1;
1346 BUF_STD_APPEND_SINGLE_ARG(uint64_t);
1347 goto update_rw_fmt;
1348 } else if (strncmp(fmt_ch, PRIx64, sizeof(PRIx64) - 1) == 0) {
1349 fmt_ch += sizeof(PRIx64) - 1;
1350 BUF_STD_APPEND_SINGLE_ARG(uint64_t);
1351 goto update_rw_fmt;
1352 } else if (strncmp(fmt_ch, PRIX64, sizeof(PRIX64) - 1) == 0) {
1353 fmt_ch += sizeof(PRIX64) - 1;
1354 BUF_STD_APPEND_SINGLE_ARG(uint64_t);
1355 goto update_rw_fmt;
1356 } else if (strncmp(fmt_ch, PRIo64, sizeof(PRIo64) - 1) == 0) {
1357 fmt_ch += sizeof(PRIo64) - 1;
1358 BUF_STD_APPEND_SINGLE_ARG(uint64_t);
1359 goto update_rw_fmt;
1360 } else if (strncmp(fmt_ch, PRIi64, sizeof(PRIi64) - 1) == 0) {
1361 fmt_ch += sizeof(PRIi64) - 1;
1362 BUF_STD_APPEND_SINGLE_ARG(int64_t);
1363 goto update_rw_fmt;
1364 }
1365
1366 // length modifier
1367 switch (*fmt_ch) {
1368 case 'h':
1369 length_mod = LENGTH_MOD_H;
1370 fmt_ch++;
1371
1372 if (*fmt_ch == 'h') {
1373 length_mod = LENGTH_MOD_HH;
1374 fmt_ch++;
1375 break;
1376 }
1377 break;
1378 case 'l':
1379 length_mod = LENGTH_MOD_LOW_L;
1380 fmt_ch++;
1381
1382 if (*fmt_ch == 'l') {
1383 length_mod = LENGTH_MOD_LOW_LL;
1384 fmt_ch++;
1385 break;
1386 }
1387 break;
1388 case 'L':
1389 length_mod = LENGTH_MOD_UP_L;
1390 fmt_ch++;
1391 break;
1392 case 'z':
1393 length_mod = LENGTH_MOD_Z;
1394 fmt_ch++;
1395 break;
1396 default:
1397 break;
1398 }
1399
1400 // format
1401 switch (*fmt_ch) {
1402 case 'c':
1403 {
1404 fmt_ch++;
1405
1406 switch (length_mod) {
1407 case LENGTH_MOD_NONE:
1408 BUF_STD_APPEND_SINGLE_ARG(int);
1409 break;
1410 case LENGTH_MOD_LOW_L:
1411 BUF_STD_APPEND_SINGLE_ARG(wint_t);
1412 break;
1413 default:
1414 abort();
1415 }
1416 break;
1417 }
1418 case 's':
1419 fmt_ch++;
1420
1421 switch (length_mod) {
1422 case LENGTH_MOD_NONE:
1423 BUF_STD_APPEND_SINGLE_ARG(char *);
1424 break;
1425 case LENGTH_MOD_LOW_L:
1426 BUF_STD_APPEND_SINGLE_ARG(wchar_t *);
1427 break;
1428 default:
1429 abort();
1430 }
1431 break;
1432 case 'd':
1433 case 'i':
1434 fmt_ch++;
1435
1436 switch (length_mod) {
1437 case LENGTH_MOD_NONE:
1438 case LENGTH_MOD_H:
1439 case LENGTH_MOD_HH:
1440 BUF_STD_APPEND_SINGLE_ARG(int);
1441 break;
1442 case LENGTH_MOD_LOW_L:
1443 BUF_STD_APPEND_SINGLE_ARG(long);
1444 break;
1445 case LENGTH_MOD_LOW_LL:
1446 BUF_STD_APPEND_SINGLE_ARG(long long);
1447 break;
1448 case LENGTH_MOD_Z:
1449 BUF_STD_APPEND_SINGLE_ARG(size_t);
1450 break;
1451 default:
1452 abort();
1453 }
1454 break;
1455 case 'o':
1456 case 'x':
1457 case 'X':
1458 case 'u':
1459 fmt_ch++;
1460
1461 switch (length_mod) {
1462 case LENGTH_MOD_NONE:
1463 case LENGTH_MOD_H:
1464 case LENGTH_MOD_HH:
1465 BUF_STD_APPEND_SINGLE_ARG(unsigned int);
1466 break;
1467 case LENGTH_MOD_LOW_L:
1468 BUF_STD_APPEND_SINGLE_ARG(unsigned long);
1469 break;
1470 case LENGTH_MOD_LOW_LL:
1471 BUF_STD_APPEND_SINGLE_ARG(unsigned long long);
1472 break;
1473 case LENGTH_MOD_Z:
1474 BUF_STD_APPEND_SINGLE_ARG(size_t);
1475 break;
1476 default:
1477 abort();
1478 }
1479 break;
1480 case 'f':
1481 case 'F':
1482 case 'e':
1483 case 'E':
1484 case 'g':
1485 case 'G':
1486 fmt_ch++;
1487
1488 switch (length_mod) {
1489 case LENGTH_MOD_NONE:
1490 BUF_STD_APPEND_SINGLE_ARG(double);
1491 break;
1492 case LENGTH_MOD_UP_L:
1493 BUF_STD_APPEND_SINGLE_ARG(long double);
1494 break;
1495 default:
1496 abort();
1497 }
1498 break;
1499 case 'p':
1500 fmt_ch++;
1501
1502 if (length_mod == LENGTH_MOD_NONE) {
1503 BUF_STD_APPEND_SINGLE_ARG(void *);
1504 } else {
1505 abort();
1506 }
1507 break;
1508 default:
1509 abort();
1510 }
1511
1512 update_rw_fmt:
1513 *out_fmt_ch = fmt_ch;
1514 }
1515
1516 BT_HIDDEN
1517 void bt_common_custom_vsnprintf(char *buf, size_t buf_size,
1518 char intro,
1519 bt_common_handle_custom_specifier_func handle_specifier,
1520 void *priv_data, const char *fmt, va_list *args)
1521 {
1522 const char *fmt_ch = fmt;
1523 char *buf_ch = buf;
1524
1525 BT_ASSERT(buf);
1526 BT_ASSERT(fmt);
1527
1528 while (*fmt_ch != '\0') {
1529 switch (*fmt_ch) {
1530 case '%':
1531 BT_ASSERT(fmt_ch[1] != '\0');
1532
1533 if (fmt_ch[1] == intro) {
1534 handle_specifier(priv_data, &buf_ch,
1535 buf_size - (size_t) (buf_ch - buf),
1536 &fmt_ch, args);
1537 } else {
1538 handle_conversion_specifier_std(buf, &buf_ch,
1539 buf_size, &fmt_ch, args);
1540 }
1541
1542 if (buf_ch >= buf + buf_size - 1) {
1543 fmt_ch = "";
1544 }
1545 break;
1546 default:
1547 *buf_ch = *fmt_ch;
1548 buf_ch++;
1549 if (buf_ch >= buf + buf_size - 1) {
1550 fmt_ch = "";
1551 }
1552
1553 fmt_ch++;
1554 }
1555 }
1556
1557 *buf_ch = '\0';
1558 }
1559
1560 BT_HIDDEN
1561 void bt_common_custom_snprintf(char *buf, size_t buf_size,
1562 char intro,
1563 bt_common_handle_custom_specifier_func handle_specifier,
1564 void *priv_data, const char *fmt, ...)
1565 {
1566 va_list args;
1567 va_start(args, fmt);
1568 bt_common_custom_vsnprintf(buf, buf_size, intro, handle_specifier,
1569 priv_data, fmt, &args);
1570 va_end(args);
1571 }
1572
1573 BT_HIDDEN
1574 void bt_common_sep_digits(char *str, unsigned int digits_per_group, char sep)
1575 {
1576 const char *rd;
1577 char *wr;
1578 uint64_t i = 0;
1579 uint64_t orig_len;
1580 uint64_t sep_count;
1581 uint64_t new_len;
1582
1583 BT_ASSERT(digits_per_group > 0);
1584 BT_ASSERT(sep != '\0');
1585
1586 /* Compute new length of `str` */
1587 orig_len = strlen(str);
1588 BT_ASSERT(orig_len > 0);
1589 sep_count = (orig_len - 1) / digits_per_group;
1590 new_len = strlen(str) + sep_count;
1591
1592 /*
1593 * Do the work in place. Have the reading pointer `rd` start at
1594 * the end of the original string, and the writing pointer `wr`
1595 * start at the end of the new string, making sure to also put a
1596 * null character there.
1597 */
1598 rd = str + orig_len - 1;
1599 wr = str + new_len;
1600 *wr = '\0';
1601 wr--;
1602
1603 /*
1604 * Here's what the process looks like (3 digits per group):
1605 *
1606 * Source: 12345678
1607 * ^
1608 * Destination: 12345678#8
1609 * ^
1610 *
1611 * Source: 12345678
1612 * ^
1613 * Destination: 1234567878
1614 * ^
1615 *
1616 * Source: 12345678
1617 * ^
1618 * Destination: 1234567678
1619 * ^
1620 *
1621 * Source: 12345678
1622 * ^
1623 * Destination: 123456,678
1624 * ^
1625 *
1626 * Source: 12345678
1627 * ^
1628 * Destination: 123455,678
1629 * ^
1630 *
1631 * Source: 12345678
1632 * ^
1633 * Destination: 123445,678
1634 * ^
1635 *
1636 * Source: 12345678
1637 * ^
1638 * Destination: 123345,678
1639 * ^
1640 *
1641 * Source: 12345678
1642 * ^
1643 * Destination: 12,345,678
1644 * ^
1645 *
1646 * Source: 12345678
1647 * ^
1648 * Destination: 12,345,678
1649 * ^
1650 *
1651 * Source: 12345678
1652 * ^
1653 * Destination: 12,345,678
1654 * ^
1655 */
1656 while (rd != str - 1) {
1657 if (i == digits_per_group) {
1658 /*
1659 * Time to append the separator: decrement `wr`,
1660 * but keep `rd` as is.
1661 */
1662 i = 0;
1663 *wr = sep;
1664 wr--;
1665 continue;
1666 }
1667
1668 /* Copy read-side character to write-side character */
1669 *wr = *rd;
1670 wr--;
1671 rd--;
1672 i++;
1673 }
1674 }
1675
1676 BT_HIDDEN
1677 GString *bt_common_fold(const char *str, unsigned int total_length,
1678 unsigned int indent)
1679 {
1680 const unsigned int content_length = total_length - indent;
1681 GString *folded = g_string_new(NULL);
1682 GString *tmp_line = g_string_new(NULL);
1683 gchar **lines = NULL;
1684 gchar **line_words = NULL;
1685 gchar * const *line;
1686 unsigned int i;
1687
1688 BT_ASSERT(str);
1689 BT_ASSERT(indent < total_length);
1690 BT_ASSERT(tmp_line);
1691 BT_ASSERT(folded);
1692
1693 if (strlen(str) == 0) {
1694 /* Empty input string: empty output string */
1695 goto end;
1696 }
1697
1698 /* Split lines */
1699 lines = g_strsplit(str, "\n", 0);
1700 BT_ASSERT(lines);
1701
1702 /* For each source line */
1703 for (line = lines; *line; line++) {
1704 gchar * const *word;
1705
1706 /*
1707 * Append empty line without indenting if source line is
1708 * empty.
1709 */
1710 if (strlen(*line) == 0) {
1711 g_string_append_c(folded, '\n');
1712 continue;
1713 }
1714
1715 /* Split words */
1716 line_words = g_strsplit(*line, " ", 0);
1717 BT_ASSERT(line_words);
1718
1719 /*
1720 * Indent for first line (we know there's at least one
1721 * word at this point).
1722 */
1723 for (i = 0; i < indent; i++) {
1724 g_string_append_c(folded, ' ');
1725 }
1726
1727 /* Append words, folding when necessary */
1728 g_string_assign(tmp_line, "");
1729
1730 for (word = line_words; *word; word++) {
1731 /*
1732 * `tmp_line->len > 0` is in the condition so
1733 * that words that are larger than
1734 * `content_length` are at least written on
1735 * their own line.
1736 *
1737 * `tmp_line->len - 1` because the temporary
1738 * line always contains a trailing space which
1739 * won't be part of the line if we fold.
1740 */
1741 if (tmp_line->len > 0 &&
1742 tmp_line->len - 1 + strlen(*word) >= content_length) {
1743 /* Fold (without trailing space) */
1744 g_string_append_len(folded,
1745 tmp_line->str, tmp_line->len - 1);
1746 g_string_append_c(folded, '\n');
1747
1748 /* Indent new line */
1749 for (i = 0; i < indent; i++) {
1750 g_string_append_c(folded, ' ');
1751 }
1752
1753 g_string_assign(tmp_line, "");
1754 }
1755
1756 /* Append current word and space to temporary line */
1757 g_string_append(tmp_line, *word);
1758 g_string_append_c(tmp_line, ' ');
1759 }
1760
1761 /* Append last line if any, without trailing space */
1762 if (tmp_line->len > 0) {
1763 g_string_append_len(folded, tmp_line->str,
1764 tmp_line->len - 1);
1765 }
1766
1767 /* Append source newline */
1768 g_string_append_c(folded, '\n');
1769
1770 /* Free array of this line's words */
1771 g_strfreev(line_words);
1772 line_words = NULL;
1773 }
1774
1775 /* Remove trailing newline if any */
1776 if (folded->str[folded->len - 1] == '\n') {
1777 g_string_truncate(folded, folded->len - 1);
1778 }
1779
1780 end:
1781 if (lines) {
1782 g_strfreev(lines);
1783 }
1784
1785 if (line_words) {
1786 g_strfreev(line_words);
1787 }
1788
1789 if (tmp_line) {
1790 g_string_free(tmp_line, TRUE);
1791 }
1792
1793 return folded;
1794 }
1795
1796 #ifdef __MINGW32__
1797 BT_HIDDEN
1798 int bt_common_get_term_size(unsigned int *width, unsigned int *height)
1799 {
1800 /* Not supported on Windows yet */
1801 return -1;
1802 }
1803 #else /* __MINGW32__ */
1804 BT_HIDDEN
1805 int bt_common_get_term_size(unsigned int *width, unsigned int *height)
1806 {
1807 int ret = 0;
1808 struct winsize winsize;
1809
1810 if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &winsize) < 0) {
1811 ret = -1;
1812 goto end;
1813 }
1814
1815 if (width) {
1816 *width = (unsigned int) winsize.ws_col;
1817 }
1818
1819 if (height) {
1820 *height = (unsigned int) winsize.ws_row;
1821 }
1822
1823 end:
1824 return ret;
1825 }
1826 #endif /* __MINGW32__ */
This page took 0.065127 seconds and 5 git commands to generate.