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