GCC Code Coverage Report


Directory: ./
File: common/src/version.c
Date: 2026-11-08 20:01:15
Exec Total Coverage
Lines: 140 188 74.5%
Functions: 7 9 77.8%
Branches: 42 70 60.0%

Line Branch Exec Source
1 /*
2 * Version checking functions (non-application specific).
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 <stddef.h>
25 #include <fcntl.h>
26
27 #include <errno.h>
28
29 #include <ctype.h>
30 #include <string.h>
31 #include <stdbool.h>
32 #include <inttypes.h>
33
34 #include <gcrypt.h> // figure out how to get the gcrypt info without including the header
35 #include <curl/curl.h>
36 #include <pthread.h>
37
38 #if defined __FreeBSD__ || defined __sun
39 #include <libgen.h>
40 #endif
41
42 #ifndef _WIN32
43 #include <sys/stat.h>
44 #include <sys/wait.h>
45
46 #include <sys/utsname.h>
47 #include <sys/ioctl.h>
48 #ifdef __sun
49 #include <sys/tty.h>
50 #endif
51 #else
52 #include <windows.h>
53 #include <shellapi.h>
54 #include <Shlobj.h>
55 extern char *program_invocation_short_name;
56
57 #include "non-gnu.h"
58 #endif
59
60 #include "common.h"
61 #include "mem.h"
62 #include "version.h"
63 #include "error.h"
64 #include "cli.h"
65 #ifdef USE_GCRYPT
66 #include "ccrypt.h"
67 #endif
68
69 #if __has_include("misc.h")
70 #include "misc.h"
71 #else
72 #define ALL_CFLAGS "(unknown)"
73 #define ALL_CPPFLAGS "(unknown)"
74 #endif
75
76 #define TIMEOUT 10
77
78 static void version_format(int indent, char *id, char *value) __attribute__((nonnull(2, 3)));
79 static void version_format_line(char **buffer, int max_width, int indent, char *id, char *value) __attribute__((nonnull(1, 4, 5)));
80 static void version_download_latest(char *) __attribute__((nonnull(1)));
81 static void version_install_latest(char *) __attribute__((nonnull(1)));
82 static void *version_check(void *) __attribute__((nonnull(1)));
83 static size_t version_verify(void *, size_t, size_t, void *) __attribute__((nonnull(1, 4)));
84
85 bool version_new_available = false;
86 bool version_is_checking = false;
87 char version_available[0x10] = { 0x0 };
88 char new_version_url[0xFF] = { 0x0 };
89
90 static char *update = NULL;
91
92 typedef struct
93 {
94 char *current;
95 char *check_url;
96 char *update_url;
97 }
98 version_check_s;
99
100 3 extern void version_print(char *name, char *version, char *url)
101 {
102 3 int indent = strlen(name) + 8;
103 3 char *av = m_strdupf(_("%s version"), name);
104 3 char *runtime = NULL;
105 #ifndef _WIN32
106 struct utsname un;
107 3 uname(&un);
108 3 runtime = m_strdupf("%s %s %s %s", un.sysname, un.release, un.version, un.machine);
109 #else
110 runtime = m_strdupf("%s", windows_version());
111 #endif
112 3 version_format(indent, av, version);
113 3 version_format(indent, _("built on"), __DATE__ " " __TIME__);
114 #ifdef __DEBUG__
115 3 version_format(indent, _("debug"), _("yes"));
116 #else
117 ✗ version_format(indent, _("debug"), _("no"));
118 #endif
119 3 version_format(indent, _("git commit"), GIT_COMMIT);
120 3 version_format(indent, _("build os"), BUILD_OS);
121 3 version_format(indent, _("compiler"), COMPILER);
122 3 version_format(indent, _("cflags"), ALL_CFLAGS);
123 3 version_format(indent, _("cppflags"), ALL_CPPFLAGS);
124 3 version_format(indent, _("runtime"), runtime);
125 #ifdef USE_GCRYPT
126 ✗ char *gcv = m_strdupf("%s (compiled) %s (runtime) %s (required)", GCRYPT_VERSION, gcry_check_version(NULL), NEED_LIBGCRYPT_VERSION);
127 ✗ version_format(indent, _("libgcrypt"), gcv);
128 ✗ free(gcv);
129 #endif
130 3 free(av);
131 3 free(runtime);
132 3 struct timespec vc = { 0, MILLION }; /* 1ms == 1,000,000ns*/
133
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 while (version_is_checking)
134 ✗ nanosleep(&vc, NULL);
135
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 2 times.
3 if (version_new_available)
136 {
137 1 cli_fprintf(stderr, "\n");
138
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 cli_fprintf(stderr, _(NEW_VERSION_URL), version_available, program_invocation_short_name, strlen(new_version_url) ? new_version_url : url);
139 }
140 3 return;
141 }
142
143 1 extern char *version_build_info(void)
144 {
145 1 char *info = NULL;
146
147 1 char *runtime = NULL;
148 #ifndef _WIN32
149 struct utsname un;
150 1 uname(&un);
151 1 runtime = m_strdupf("%s %s %s %s", un.sysname, un.release, un.version, un.machine);
152 #else
153 runtime = m_strdupf("%s", windows_version());
154 #endif
155
156 #define AA_GW 80
157 #define AA_GI 10
158
159 1 version_format_line(&info, AA_GW, AA_GI, _("built on"), __DATE__ " " __TIME__);
160 #ifdef __DEBUG__
161 1 version_format_line(&info, AA_GW, AA_GI, _("debug"), _("yes"));
162 #else
163 ✗ version_format_line(&info, AA_GW, AA_GI, _("debug"), _("no"));
164 #endif
165 1 version_format_line(&info, AA_GW, AA_GI, _("git commit"), GIT_COMMIT);
166 1 version_format_line(&info, AA_GW, AA_GI, _("build os"), BUILD_OS);
167 1 version_format_line(&info, AA_GW, AA_GI, _("compiler"), COMPILER);
168 1 version_format_line(&info, AA_GW, AA_GI, _("cflags"), ALL_CFLAGS);
169 1 version_format_line(&info, AA_GW, AA_GI, _("cppflags"), ALL_CPPFLAGS);
170 1 version_format_line(&info, AA_GW, AA_GI, _("runtime"), runtime);
171
172 #ifdef USE_GCRYPT
173 ✗ char *gcv = m_strdupf("%s (compiled) %s (runtime)", GCRYPT_VERSION, gcry_check_version(NULL));
174 ✗ version_format_line(&info, AA_GW, AA_GI, _("libgcrypt"), gcv);
175 ✗ free(gcv);
176 #endif
177
178 1 free(runtime);
179
180 1 return info;
181 }
182
183 3 extern void version_check_for_update(char *current_version, char *check_url, char *download_url)
184 {
185
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 2 times.
3 if (version_is_checking)
186 1 return;
187 2 version_is_checking = true;
188
189 2 version_check_s *info = m_malloc(sizeof( version_check_s ));
190 2 info->current = strdup(current_version);
191 2 info->check_url = strdup(check_url);
192
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 info->update_url = download_url ? strdup(download_url) : NULL;
193 //#ifndef __DEBUG__
194 pthread_t vt;
195 pthread_attr_t a;
196 2 pthread_attr_init(&a);
197 2 pthread_attr_setdetachstate(&a, PTHREAD_CREATE_DETACHED);
198
199 2 pthread_create(&vt, &a, version_check, info);
200 2 pthread_attr_destroy(&a);
201 //#else
202 //version_check(info);
203 //#endif
204 2 return;
205 }
206
207 2 static void *version_check(void *n)
208 {
209 2 struct timespec vc = { 1, 0 };
210 2 nanosleep(&vc, NULL);
211
212 2 version_check_s *info = n;
213 2 curl_global_init(CURL_GLOBAL_ALL);
214 2 CURL *ccheck = curl_easy_init();
215 2 curl_easy_setopt(ccheck, CURLOPT_URL, info->check_url);
216 #ifdef WIN32
217 curl_easy_setopt(ccheck, CURLOPT_SSL_VERIFYPEER, 0L);
218 #endif
219 2 curl_easy_setopt(ccheck, CURLOPT_NOPROGRESS, 1L);
220 2 curl_easy_setopt(ccheck, CURLOPT_CONNECTTIMEOUT, TIMEOUT);
221 //curl_easy_setopt(ccheck, CURLOPT_TIMEOUT, TIMEOUT);
222 2 curl_easy_setopt(ccheck, CURLOPT_WRITEDATA, info->current);
223 2 curl_easy_setopt(ccheck, CURLOPT_WRITEFUNCTION, version_verify);
224 2 curl_easy_perform(ccheck);
225 2 curl_easy_cleanup(ccheck);
226
3/4
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
2 if (version_new_available && info->update_url)
227 ✗ version_download_latest(info->update_url);
228
229
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (info->update_url)
230 ✗ free(info->update_url);
231 2 free(info->check_url);
232 2 free(info->current);
233 2 free(info);
234 2 version_is_checking = false;
235 //#ifndef __DEBUG__
236 2 pthread_exit(NULL);
237 #ifdef _WIN32
238 return NULL;
239 #endif
240 //#else
241 //return NULL;
242 //#endif
243 }
244
245 ✗ static void version_download_latest(char *update_url)
246 {
247 /* download new version */
248 ✗ CURL *cupdate = curl_easy_init();
249 /*
250 * default template for our projects download url is /downloads/project/version/project-version
251 * and as the project knows and can set everything except the new version number this is sufficient
252 */
253 #pragma GCC diagnostic push
254 #pragma GCC diagnostic ignored "-Wformat-nonliteral"
255 ✗ snprintf(new_version_url, sizeof new_version_url - 1, update_url, version_available, version_available);
256 #pragma GCC diagnostic pop
257 ✗ curl_easy_setopt(cupdate, CURLOPT_URL, new_version_url);
258 #ifdef WIN32
259 curl_easy_setopt(cupdate, CURLOPT_SSL_VERIFYPEER, 0L);
260 #endif
261 ✗ curl_easy_setopt(cupdate, CURLOPT_NOPROGRESS, 1L);
262 #ifndef _WIN32
263 ✗ m_asprintf(&update, "%s/update-%s-XXXXXX", P_tmpdir ,version_available);
264 ✗ int64_t fd = mkstemp(update);
265 ✗ fchmod(fd, S_IRUSR | S_IWUSR | S_IXUSR);
266 #else
267 char p[MAX_PATH] = { 0x0 };
268 GetTempPath(sizeof p, p);
269 m_asprintf(&update, "%supdate-%s.exe", p, version_available);
270 int64_t fd = open(update, O_CREAT | O_WRONLY | O_BINARY);
271 #endif
272 ✗ if (fd > 0)
273 {
274 ✗ FILE *fh = fdopen(fd, "wb");
275 ✗ curl_easy_setopt(cupdate, CURLOPT_WRITEDATA, fh);
276 ✗ curl_easy_perform(cupdate);
277 ✗ curl_easy_cleanup(cupdate);
278 ✗ fclose(fh);
279 ✗ close(fd);
280
281 ✗ version_install_latest(update);
282 }
283 ✗ free(update);
284 ✗ }
285
286 ✗ static void version_install_latest(char *u)
287 {
288 #if !defined VERSION_NO_INSTALL
289 ✗ if (!version_new_available)
290 ✗ return;
291 #if !defined __APPLE__ && !defined _WIN32
292 ✗ char *u2 = m_strdup(u);
293
294 ✗ pid_t pid = fork();
295 ✗ if (pid == 0)
296 {
297 ✗ execl(u2, basename(u2), NULL);
298 ✗ unlink(u2);
299 ✗ free(u2);
300 ✗ _exit(EXIT_FAILURE);
301 }
302 ✗ else if (pid > 0)
303 ✗ waitpid(pid, NULL, 0);
304 ✗ unlink(u2);
305 ✗ free(u2);
306 #elif defined __APPLE__
307 char *dmg = m_strdupf("%s.dmg", u);
308 rename(u, dmg);
309 execl("/usr/bin/open", dmg, NULL);
310 unlink(dmg);
311 free(dmg);
312 #elif defined _WIN32
313 ShellExecute(NULL, "open", u, NULL, NULL, SW_SHOWNORMAL);
314 #endif
315 #else
316 (void)u;
317 #endif
318 ✗ return;
319 }
320
321 2 static size_t version_verify(void *p, size_t s, size_t n, void *v)
322 {
323 2 char *b = m_calloc(s + 1, n);
324 2 memcpy(b, p, s * n);
325 2 char *l = strrchr(b, '\n');
326
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if (l)
327 2 *l = '\0';
328
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
2 if (strcmp(b, (char *)v) > 0)
329 {
330 1 version_new_available = true;
331 1 snprintf(version_available, sizeof version_available - 1, "%s", b);
332 }
333 2 free(b);
334 2 return s * n;
335 }
336
337 27 static void version_format(int indent, char *id, char *value)
338 {
339 27 cli_fprintf(stderr, ANSI_COLOUR_GREEN "%*s" ANSI_COLOUR_RESET ": " ANSI_COLOUR_MAGENTA, indent, id);
340 #ifndef _WIN32
341 struct winsize ws;
342 27 ioctl(STDERR_FILENO, TIOCGWINSZ, &ws);
343 27 int max_width = ws.ws_col - 2;
344 #else
345 CONSOLE_SCREEN_BUFFER_INFO csbi;
346 GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi);
347 int max_width = (csbi.srWindow.Right - csbi.srWindow.Left + 1) - 2;
348 #endif
349
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 27 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
27 if (max_width <= 0 || !isatty(STDERR_FILENO))
350 27 max_width = 77; // needed for MSYS2, but also sensible default if output is bein redirected
351 27 int width = max_width - indent;
352
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 27 times.
27 for (; isspace(*value); value++)
353 ;
354 27 int l = strlen(value);
355
2/2
✓ Branch 0 taken 24 times.
✓ Branch 1 taken 3 times.
27 if (l < width)
356 24 cli_fprintf(stderr, "%s", value);
357 else
358 {
359 3 int s = 0;
360 do
361 {
362 6 bool too_long = false;
363 6 int e = s + width;
364
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 3 times.
6 if (e > l)
365 3 e = l;
366 else
367
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
6 for (too_long = true; e > s; e--)
368
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 3 times.
6 if (isspace(value[e]))
369 {
370 3 too_long = false;
371 3 break;
372 }
373
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
6 if (too_long)
374 {
375 ✗ for (int e2 = s; e2 < s + max_width; e2++)
376 ✗ if (isspace(value[e2]) || value[e2] == 0x00)
377 ✗ e = e2;
378 ✗ cli_fprintf(stderr, "\n ");
379 }
380
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 3 times.
6 else if (s)
381 3 cli_fprintf(stderr, "\n%*s ", indent, " ");
382 6 cli_fprintf(stderr, "%.*s", e - s, value + s);
383 6 s = e + 1;
384 }
385
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 3 times.
6 while (s < l);
386 }
387
388 27 cli_fprintf(stderr, ANSI_COLOUR_RESET "\n");
389 27 return;
390 }
391
392 8 static void version_format_line(char **buffer, int max_width, int indent, char *id, char *value)
393 {
394
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 1 times.
8 char *new = m_strdupf("%s%*s: ", *buffer ? *buffer : "", indent, id);
395 8 free(*buffer);
396 8 *buffer = new;
397
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
8 for (; isspace(*value); value++)
398 ;
399 8 int l = strlen(value);
400
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 1 times.
8 if (l < max_width)
401 {
402 7 new = m_strdupf("%s%s", *buffer, value);
403 7 free(*buffer);
404 7 *buffer = new;
405 }
406 else
407 {
408 1 int s = 0;
409 do
410 {
411 2 int e = s + max_width;
412
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
2 if (e > l)
413 1 e = l;
414 else
415
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 for (; e > s; e--)
416
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 3 times.
4 if (isspace(value[e]))
417 1 break;
418
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
2 if (s)
419 {
420 1 new = m_strdupf("%s\n%*s ", *buffer, indent, " ");
421 1 free(*buffer);
422 1 *buffer = new;
423 }
424 2 new = m_strdupf("%s%.*s", *buffer, e - s, value + s);
425 2 free(*buffer);
426 2 *buffer = new;
427 2 s = e + 1;
428 }
429
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
2 while (s < l);
430 }
431 8 new = m_strdupf("%s\n", *buffer);
432 8 free(*buffer);
433 8 *buffer = new;
434 8 return;
435 }
436