GCC Code Coverage Report


Directory: ./
File: common/src/cli.c
Date: 2026-11-08 20:01:15
Exec Total Coverage
Lines: 105 194 54.1%
Functions: 11 17 64.7%
Branches: 34 100 34.0%

Line Branch Exec Source
1 /*
2 * Common code for providing a command line progress bar.
3 * Copyright © 2005-2026, albinoloverats ~ Software Development
4 * email: webmaster@albinoloverats.net
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 *
19 */
20
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <unistd.h>
24 #include <time.h>
25 #include <stdarg.h>
26
27 #include <ctype.h>
28 #include <inttypes.h>
29 #include <string.h>
30
31 #include <time.h>
32 #include <signal.h>
33
34 #include <sys/stat.h>
35 #include <sys/time.h>
36 #include <math.h>
37 #include <locale.h>
38
39 #ifndef _WIN32
40 #include <termios.h>
41 #include <sys/ioctl.h>
42 #endif
43
44 #include "common.h"
45 #include "cli.h"
46 #include "error.h"
47 #include "mem.h"
48 #include "dir.h"
49 #include "non-gnu.h"
50
51 //#define CLI_HELP_FORMAT_RIGHT_COLUMN 37
52
53 #define CLI_SMALL 62
54 #define CLI_DEFAULT 80 /* Expected default terminal width */
55 #define CLI_LARGE 75
56
57 static int cli_width = CLI_DEFAULT;
58 static int cli_size_width = 0;
59
60 static void cli_display_bars(cli_progress_s *, cli_progress_s *, cli_bps_s *);
61 #ifndef _WIN32
62 static void cli_sigwinch(int);
63 #endif
64
65 #define CLI_DO_INIT if (cli_inited != 1) cli_init();
66
67 extern void on_quit(int) __attribute__((noreturn));
68
69 static void cli_init(void);
70 static int cli_inited = -1; // -1 (no), 0 (failed), 1 (okay)
71
72 static int cli_bps_sort(const void *, const void *);
73 static int cli_print(FILE *, const char *);
74
75 1 extern int cli_printf(const char * const restrict s, ...)
76 {
77
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 CLI_DO_INIT;
78
79 va_list ap;
80 1 va_start(ap, s);
81 1 char *d = NULL;
82 1 vasprintf(&d, s, ap);
83 1 int x = cli_print(stdout, d);
84 1 va_end(ap);
85 1 free(d);
86 1 return x;
87 }
88
89 94 extern int cli_eprintf(const char * const restrict s, ...)
90 {
91
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 94 times.
94 CLI_DO_INIT;
92
93 va_list ap;
94 94 va_start(ap, s);
95 94 char *d = NULL;
96 94 vasprintf(&d, s, ap);
97 94 int x = cli_print(stderr, d);
98 94 va_end(ap);
99 94 free(d);
100 94 return x;
101 }
102
103 1 extern int logger(const char * const restrict s, ...)
104 {
105
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 CLI_DO_INIT;
106
107 1 int x = 0;
108 #if 0
109 time_t now = time(NULL);
110 char at[24];
111 struct tm t;
112 localtime_r(&now, &t);
113 strftime(at, sizeof at, "%F %T : ", &t);
114 x += cli_print(stderr, at);
115 #endif
116 va_list ap;
117 1 va_start(ap, s);
118 1 char *d = NULL;
119 1 vasprintf(&d, s, ap);
120 1 x += cli_print(stderr, d);
121 1 va_end(ap);
122 1 free(d);
123
124 static const char nl[] = "\n";
125 1 x += cli_print(stderr, nl);
126
127 1 return x;
128 }
129
130 120 extern int cli_fprintf(FILE *f, const char * const restrict s, ...)
131 {
132
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 119 times.
120 CLI_DO_INIT;
133
134 va_list ap;
135 120 va_start(ap, s);
136 120 char *d = NULL;
137 120 vasprintf(&d, s, ap);
138 120 int x = cli_print(f, d);
139 120 va_end(ap);
140 120 free(d);
141 120 return x;
142 }
143
144 1 extern int cli_printx(const uint8_t * const restrict x, size_t z)
145 {
146
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 CLI_DO_INIT;
147
148 1 return cli_fprintx(stdout, x, z);
149 }
150
151 1 extern int cli_eprintx(const uint8_t * const restrict x, size_t z)
152 {
153
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 CLI_DO_INIT;
154
155 1 return cli_fprintx(stderr, x, z);
156 }
157
158 5 extern int cli_fprintx(FILE *f, const uint8_t * const restrict x, size_t z)
159 {
160
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
5 CLI_DO_INIT;
161
162 #define CLI_PRINTX_W 16
163 // TODO allow variable width lines
164 5 int l = 0, o = 0;
165 5 char r[CLI_PRINTX_W * 2 + CLI_PRINTX_W] = { 0x0 };
166
2/2
✓ Branch 0 taken 26 times.
✓ Branch 1 taken 5 times.
31 for (size_t i = 0; i < z; i++)
167 {
168 26 char c[4] = { 0x0 };
169 26 sprintf(c, "%02x", x[i]);
170 26 strcat(r, c);
171
2/2
✓ Branch 0 taken 11 times.
✓ Branch 1 taken 15 times.
26 if (i % 2 == 1)
172 11 strcat(r, " ");
173
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 25 times.
26 if (i % CLI_PRINTX_W == (CLI_PRINTX_W - 1))
174 {
175 1 l += cli_fprintf(f, "%08x: %s\n", o, r);
176 1 memset(r, 0x00, sizeof r);
177 1 o += CLI_PRINTX_W;
178 }
179 }
180
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 1 times.
5 if (strlen(r))
181 4 l += cli_fprintf(f, "%08x: %s\n", o, r);
182 5 return l;
183 }
184
185 1 extern double cli_calc_bps(cli_bps_s *bps)
186 {
187
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 CLI_DO_INIT;
188
189 1 cli_bps_s *copy = m_calloc(BPS, sizeof( cli_bps_s ));
190
2/2
✓ Branch 0 taken 128 times.
✓ Branch 1 taken 1 times.
129 for (int i = 0; i < BPS; i++)
191 {
192 128 copy[i].time = bps[i].time;
193 128 copy[i].bytes = bps[i].bytes;
194 }
195 1 qsort(copy, BPS, sizeof( cli_bps_s ), cli_bps_sort);
196 1 double avg[BPS - 1] = { 0.0f };
197
2/2
✓ Branch 0 taken 127 times.
✓ Branch 1 taken 1 times.
128 for (int i = 0; i < BPS - 1; i++)
198 /*
199 * requires scale factor of MILLION as time is in microseconds
200 * not seconds (millions of bytes / micros of seconds, so to
201 * speak)
202 */
203 127 avg[i] = MILLION * (double)(copy[i + 1].bytes - copy[i].bytes) / (double)(copy[i + 1].time - copy[i].time);
204 1 double val = 0.0;
205
2/2
✓ Branch 0 taken 127 times.
✓ Branch 1 taken 1 times.
128 for (int i = 0; i < BPS - 1; i++)
206 127 val += avg[i];
207 1 free(copy);
208 1 return val /= (BPS - 1);
209 }
210
211 //#ifndef _WIN32
212 ✗ extern void cli_display(cli_s *p)
213 {
214 ✗ CLI_DO_INIT;
215
216 #ifndef _WIN32
217 ✗ cli_sigwinch(SIGWINCH);
218 #endif
219 ✗ cli_size_width = 0;
220
221 cli_bps_s bps[BPS];
222 ✗ memset(bps, 0x00, BPS * sizeof( cli_bps_s ));
223 ✗ int b = 0;
224
225 ✗ fprintf(stderr, "\e[?25l\n"); /* hide cursor */
226 ✗ while (*p->status == CLI_INIT || *p->status == CLI_RUN)
227 {
228 ✗ struct timespec s = { 0, 10 * MILLION }; /* 10 milliseconds */
229 ✗ nanosleep(&s, NULL);
230 ✗ if (*p->status == CLI_INIT)
231 ✗ continue;
232 struct timeval tv;
233 ✗ gettimeofday(&tv, NULL);
234 ✗ bps[b].time = tv.tv_sec * MILLION + tv.tv_usec;
235 ✗ bps[b].bytes = p->current->offset;
236 ✗ b++;
237 ✗ if (b >= BPS)
238 ✗ b = 0;
239 ✗ cli_display_bars(p->total, p->current, bps);
240 }
241 ✗ if (*p->status == CLI_DONE)
242 {
243 ✗ p->total->offset = p->total->size;
244 ✗ p->current->offset = p->current->size;
245 ✗ cli_display_bars(p->total, p->current, bps);
246 }
247 ✗ fprintf(stderr, "\e[?25h\n"); /* restore cursor */
248
249 ✗ return;
250 }
251
252 ✗ static void cli_display_bar(char *name, cli_progress_s *p, double percent)
253 {
254 ✗ int name_width = CLI_TRUNCATED_DISPLAY_SHORT + strlen(CLI_TRUNCATED_ELLIPSE) + (cli_width > CLI_DEFAULT ? CLI_TRUNCATED_DISPLAY_SHORT : 0);
255 ✗ fprintf(stderr, "\r%-*s : ", name_width, name);
256 ✗ if (cli_width > CLI_SMALL)
257 ✗ fprintf(stderr, "%'13" PRIu64 "/%-'13" PRIu64 " (", p->offset, p->size);
258 ✗ fprintf(stderr, "%3.0f%%", isnan(percent) ? 0.0f : (percent > PERCENT ? PERCENT : percent));
259 ✗ if (cli_width > CLI_SMALL)
260 ✗ fprintf(stderr, ")");
261
262 ✗ if (cli_width > CLI_DEFAULT)
263 {
264 ✗ int progress_bar_width = cli_width - CLI_LARGE;
265 ✗ fprintf(stderr, " [");
266 /*
267 * display overall progress bar
268 */
269 ✗ for (int i = 0; i < progress_bar_width; i++)
270 ✗ fprintf(stderr, "%c", i < progress_bar_width * percent / PERCENT ? '=' : ' ');
271 ✗ fprintf(stderr, "]");
272 }
273 ✗ return;
274 }
275
276 ✗ static void cli_display_bps(cli_bps_s *bps)
277 {
278 /*
279 * calculate B/s
280 */
281 ✗ double val = cli_calc_bps(bps);
282 ✗ if (isnan(val) || val == 0.0f)
283 ✗ fprintf(stderr, " 0.0 B/s");
284 else
285 {
286 ✗ if (val < THOUSAND)
287 ✗ fprintf(stderr, " %5.1f B/s", val);
288 ✗ else if (val < MILLION)
289 ✗ fprintf(stderr, " %5.1f KB/s", val / KILOBYTE);
290 ✗ else if (val < THOUSAND_MILLION)
291 ✗ fprintf(stderr, " %5.1f MB/s", val / MEGABYTE);
292 ✗ else if (val < BILLION)
293 ✗ fprintf(stderr, " %5.1f GB/s", val / GIGABYTE);
294 #if 1 /* if you’re getting these kinds of speeds please, please can I have your machine ;-) */
295 ✗ else if (val < THOUSAND_BILLION)
296 ✗ fprintf(stderr, " %5.1f TB/s", val / TERABYTE);
297 ✗ else if (val < TRILLION)
298 ✗ fprintf(stderr, " %5.1f PB/s", val / PETABYTE);
299 // else // if (val < THOUSAND_TRILLION) // even uint64_t is too small now!
300 // fprintf(progress_bar + strlen(progress_bar), " %5.1f EB/s", val / EXABYTE);
301 // else if (val < QUADRILLION)
302 // fprintf(progress_bar + strlen(progress_bar), " %5.1f ZB/s", val / ZETTABYTE);
303 // else
304 // fprintf(progress_bar + strlen(progress_bar), " %5.1f YB/s", val / YOTTABYTE);
305 #endif
306 else
307 ✗ fprintf(stderr, " ---.- B/s");
308 }
309 ✗ return;
310 }
311
312 ✗ static void cli_display_bars(cli_progress_s *t, cli_progress_s *c, cli_bps_s *bps)
313 {
314 ✗ double total = PERCENT * (t->offset + (1.0f * c->offset / c->size)) / t->size;
315 ✗ double current = PERCENT * c->offset / c->size;
316 ✗ bool single = t->size == 1;
317
318 ✗ if (!single)
319 {
320 ✗ char name[CLI_TRUNCATED_DISPLAY_LONG] = { 0x0 };
321 ✗ char *nm = c->display ? /*dir_get_name(c->display, '.')*/ : CLI_UNKNOWN;
322 ✗ if (strlen(nm) < CLI_TRUNCATED_DISPLAY_LONG)
323 ✗ strcpy(name, nm);
324 else
325 {
326 ✗ strncpy(name, nm, CLI_TRUNCATED_DISPLAY_SHORT);
327 ✗ strcat(name, CLI_TRUNCATED_ELLIPSE);
328 ✗ if (cli_width > CLI_DEFAULT)
329 ✗ strcat(name, nm + (strlen(nm) - CLI_TRUNCATED_DISPLAY_SHORT));
330 }
331 ✗ fprintf(stderr, "\x1B[1F"); // go up 1 line
332 ✗ cli_display_bar(name, c, current);
333 ✗ cli_display_bps(bps);
334 ✗ fprintf(stderr, "\n");
335 }
336 ✗ cli_display_bar("Total", single ? c : t, total);
337
338 ✗ return;
339 }
340
341 #ifndef _WIN32
342 ✗ static void cli_sigwinch(int s)
343 {
344 struct winsize ws;
345 ✗ ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws);
346 ✗ cli_width = ws.ws_col;
347 ✗ signal(SIGWINCH, cli_sigwinch);
348 (void)s;
349 ✗ }
350 #endif
351
352 448 static int cli_bps_sort(const void *a, const void *b)
353 {
354 448 const cli_bps_s *ba = (const cli_bps_s *)a;
355 448 const cli_bps_s *bb = (const cli_bps_s *)b;
356 448 return (ba->time > bb->time) - (ba->time < bb->time);
357 }
358
359 217 static int cli_print(FILE *stream, const char *text)
360 {
361 217 size_t l = strlen(text);
362 217 char *copy = m_calloc(1, l + 1);
363 #ifndef _WIN32
364
6/8
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 215 times.
✓ Branch 3 taken 2 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 186 times.
✓ Branch 6 taken 31 times.
✓ Branch 8 taken 186 times.
✗ Branch 9 not taken.
217 bool strip = !((stream == stdout && isatty(STDOUT_FILENO)) || (stream == stderr && isatty(STDERR_FILENO)));
365 #else
366 #ifdef __DEBUG__
367 bool strip = cli_inited != 1;
368 #else
369 bool strip = false;
370 #endif
371 #endif
372
1/2
✓ Branch 0 taken 217 times.
✗ Branch 1 not taken.
217 if (strip)
373 {
374 217 char *ptr = (char *)text;
375
1/2
✓ Branch 0 taken 421 times.
✗ Branch 1 not taken.
421 for (size_t i = 0, j = 0; i < l; i++)
376 {
377 421 char *e = strstr(ptr, "\x1b[");
378
2/2
✓ Branch 0 taken 217 times.
✓ Branch 1 taken 204 times.
421 if (!e)
379 217 break;
380 204 memcpy(copy + j, ptr, e - ptr);
381 204 j += e - ptr;
382 204 ptr = strchr(e, 'm') + 1;
383 }
384 217 strcat(copy, ptr);
385 }
386 else
387 ✗ strcpy(copy, text);
388 217 int x = fprintf(stream, "%s", copy);
389 //fprintf(stream, "\n");
390 217 free(copy);
391 217 return x;
392 }
393
394 ✗ extern void on_quit(int s)
395 {
396 ✗ fprintf(stderr, "\e[?25h\n"); /* restore cursor */
397 ✗ signal(s, SIG_DFL);
398 ✗ raise(s);
399 ✗ _exit(EXIT_FAILURE); // this shouldn't happen as the raise above will handle things
400 }
401
402 1 static void cli_init(void)
403 {
404
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (cli_inited >= 0)
405 ✗ return;
406 1 signal(SIGTERM, on_quit);
407 1 signal(SIGINT, on_quit);
408 1 signal(SIGQUIT, on_quit);
409
410 1 setlocale(LC_NUMERIC, "");
411 #ifdef _WIN32
412 // Set output mode to handle virtual terminal sequences
413 HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
414 if (hOut == INVALID_HANDLE_VALUE)
415 {
416 cli_inited = 0;
417 return;
418 }
419 DWORD dwMode = 0;
420 if (!GetConsoleMode(hOut, &dwMode))
421 {
422 cli_inited = 1;
423 return; // MSYS2 ends up here; so take a chance that everything's going to be okay
424 }
425 dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
426 if (!SetConsoleMode(hOut, dwMode))
427 {
428 cli_inited = 0;
429 return; // cmd on Win7 ends up here; looks like the feature is not available until Win10
430 }
431 #endif
432 1 cli_inited = 1;
433 1 return;
434 }
435