-
Notifications
You must be signed in to change notification settings - Fork 3
/
verge.c
468 lines (423 loc) · 12.2 KB
/
verge.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
/*
* verge - determine if first version is greater or equal to the second version
*
* "Because too much minimalism can be subminimal." :-)
*
* This JSON parser was co-developed in 2022 by:
*
* @xexyl
* https://xexyl.net Cody Boone Ferguson
* https://ioccc.xexyl.net
* and:
* chongo (Landon Curt Noll, http://www.isthe.com/chongo/index.html) /\oo/\
*
* "Because sometimes even the IOCCC Judges need some help." :-)
*
* "Share and Enjoy!"
* -- Sirius Cybernetics Corporation Complaints Division, JSON spec department. :-)
*/
/* special comments for the seqcexit tool */
/* exit code out of numerical order - ignore in sequencing - ooo */
/* exit code change of order - use new value in sequencing - coo */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>
/*
* verge - determine if first version is greater or equal to the second version
*/
#include "verge.h"
/*
* definitions
*/
#define REQUIRED_ARGS (2) /* number of required arguments on the command line */
/*
* usage message
*
* Use the usage() function to print the usage_msg([0-9]?)+ strings.
*/
static const char * const usage_msg =
"usage: %s [-h] [-v level] [-V] major.minor.patch-1 major.minor.patch-2\n"
"\n"
"\t-h\t\t\tprint help message and exit\n"
"\t-v level\t\tset verbosity level (def level: %d)\n"
"\t-V\t\t\tprint version string and exit\n"
"\n"
"\tmajor.minor.patch-1\tfirst version (example: 0.1.1)\n"
"\tmajor.minor.patch-2\tsecond version (example: 1.3.2)\n"
"\n"
"Exit codes:\n"
" 0 first version >= second version\n"
" 1 first version < second version\n"
" 2 -h and help string printed or -V and version string printed\n"
" 3 command line error\n"
" 4 first or second version string is an invalid version\n"
" >=10 internal error\n"
"\n"
"%s version: %s\n"
"jparse UTF-8 version: %s\n"
"jparse library version: %s";
/*
* forward declarations
*/
static void usage(int exitcode, char const *prog, char const *str) __attribute__((noreturn));
int
main(int argc, char *argv[])
{
char const *program = NULL; /* our name */
extern char *optarg; /* option argument */
extern int optind; /* argv index of the next arg */
int arg_count = 0; /* number of args to process */
char *ver1 = NULL; /* first version string */
char *ver2 = NULL; /* second version string */
int ver1_levels = 0; /* number of version levels for first version string */
int ver2_levels = 0; /* number of version levels for second version string */
intmax_t *vlevel1 = NULL; /* allocated version levels from first version string */
intmax_t *vlevel2 = NULL; /* allocated version levels from second version string */
int i;
/*
* parse args
*/
program = argv[0];
while ((i = getopt(argc, argv, ":hv:V")) != -1) {
switch (i) {
case 'h': /* -h - print help to stderr and exit 0 */
usage(2, program, ""); /*ooo*/
not_reached();
break;
case 'v': /* -v verbosity */
/*
* parse verbosity
*/
verbosity_level = parse_verbosity(optarg);
if (verbosity_level < 0) {
usage(3, program, "invalid -v verbosity"); /*ooo*/
not_reached();
}
break;
case 'V': /* -V - print version and exit */
print("%s version: %s\n", VERGE_BASENAME, VERGE_VERSION);
print("jparse UTF-8 version: %s\n", JPARSE_UTF8_VERSION);
print("jparse library version: %s\n", JPARSE_LIBRARY_VERSION);
exit(2); /*ooo*/
not_reached();
break;
case ':': /* option requires an argument */
case '?': /* illegal option */
default: /* anything else but should not actually happen */
check_invalid_option(program, i, optopt);
usage(3, program, ""); /*ooo*/
not_reached();
break;
}
}
arg_count = argc - optind;
if (arg_count != REQUIRED_ARGS) {
usage(3, program, "two args are required"); /*ooo*/
not_reached();
}
ver1 = argv[optind];
ver2 = argv[optind+1];
dbg(DBG_LOW, "first version: <%s>", ver1);
dbg(DBG_LOW, "second version: <%s>", ver2);
/*
* convert first version string
*/
ver1_levels = (int)allocate_vers(ver1, &vlevel1);
if (ver1_levels <= 0) {
err(4, program, "first version string is invalid"); /*ooo*/
not_reached();
}
/*
* convert second version string
*/
ver2_levels = (int)allocate_vers(ver2, &vlevel2);
if (ver2_levels <= 0) {
err(4, program, "second version string is invalid"); /*ooo*/
not_reached();
}
/*
* compare versions
*/
for (i=0; i < ver1_levels && i < ver2_levels; ++i) {
/*
* compare both versions at a given level (i)
*/
if (vlevel1[i] > vlevel2[i]) {
/* ver1 > ver2 */
dbg(DBG_MED, "version 1 level %d: %jd > version 2 level %d: %jd",
i, vlevel1[i], i, vlevel2[i]);
dbg(DBG_LOW, "%s > %s", ver1, ver2);
/* free memory */
if (vlevel1 != NULL) {
free(vlevel1);
vlevel1 = NULL;
}
if (vlevel2 != NULL) {
free(vlevel2);
vlevel2 = NULL;
}
/* report ver1 > ver2 */
exit(0); /*ooo*/
} else if (vlevel1[i] < vlevel2[i]) {
/* ver1 < ver2 */
dbg(DBG_MED, "version 1 level %d: %jd < version 2 level %d: %jd",
i, vlevel1[i], i, vlevel2[i]);
dbg(DBG_LOW, "%s < %s", ver1, ver2);
/* free memory */
if (vlevel1 != NULL) {
free(vlevel1);
vlevel1 = NULL;
}
if (vlevel2 != NULL) {
free(vlevel2);
vlevel2 = NULL;
}
/* report ver1 < ver2 */
exit(1); /*ooo*/
} else {
/* versions match down to this level */
dbg(DBG_MED, "version 1 level %d: %jd == version 2 level %d: %jd",
i, vlevel1[i], i, vlevel2[i]);
}
}
dbg(DBG_MED, "versions match down to level: %d",
(ver1_levels > ver2_levels) ? ver2_levels : ver1_levels);
/*
* free memory
*/
if (vlevel1 != NULL) {
free(vlevel1);
vlevel1 = NULL;
}
if (vlevel2 != NULL) {
free(vlevel2);
vlevel2 = NULL;
}
/*
* ver1 matches ver2 to the extent that they share the same level
*
* The presence of sub-levels will determine the final comparison
*/
if (ver1_levels < ver2_levels) {
dbg(DBG_MED, "version 1 level count: %d < version level count: %d",
ver1_levels, ver2_levels);
dbg(DBG_LOW, "%s < %s", ver1, ver2);
/* report ver1 < ver2 */
exit(1); /*ooo*/
} else if (ver1_levels > ver2_levels) {
dbg(DBG_MED, "version 1 level count: %d > version level count: %d",
ver1_levels, ver2_levels);
dbg(DBG_LOW, "%s > %s", ver1, ver2);
/* report ver1 > ver2 */
exit(0); /*ooo*/
}
/*
* versions match
*/
dbg(DBG_MED, "version 1 level count: %d == version level count: %d",
ver1_levels, ver2_levels);
dbg(DBG_LOW, "%s == %s", ver1, ver2);
/* report ver1 == ver2 */
exit(0); /*ooo*/
}
/*
* allocate_vers - convert version string into an allocated array or version numbers
*
* given:
* ver version string
* pvers pointer to allocated array of versions
*
* returns:
* > 0 ==> number of version integers in allocated array of versions
* 0 <= ==> string was not a valid version string,
* array of versions not allocated
*
* NOTE: This function does not return on allocation failures or NULL args.
*/
static size_t
allocate_vers(char *str, intmax_t **pvers)
{
char *wstr = NULL; /* working allocated copy of orig_str */
char *wstr_start = NULL; /* pointer to starting point of wstr */
size_t len; /* length of version string */
bool dot = false; /* true ==> previous character was dot */
size_t dot_count = 0; /* number of .'s in version string */
char *word = NULL; /* token */
char *brkt; /* last arg for strtok_r() */
size_t i;
/*
* firewall
*/
if (str == NULL || pvers == NULL) {
err(10, __func__, "NULL arg(s)");
not_reached();
}
len = strlen(str);
if (len <= 0) {
dbg(DBG_MED, "version string is empty");
return 0;
}
/*
* duplicate str
*/
errno = 0; /* pre-clear errno for errp() */
wstr = strdup(str);
if (wstr == NULL) {
errp(11, __func__, "cannot strdup: <%s>", str);
not_reached();
}
wstr_start = wstr;
/*
* trim leading non-digits
*/
for (i=0; i < len; ++i) {
if (isascii(wstr[i]) && isdigit(wstr[i])) {
/* stop on first digit */
break;
}
}
if (i == len) {
/* report invalid version string */
dbg(DBG_MED, "version string contained no digits: <%s>", wstr);
/* free strdup()d string if != NULL */
if (wstr_start != NULL) {
free(wstr_start);
wstr_start = NULL;
}
return 0;
}
wstr += i;
len -= i;
/*
* trim at and beyond any whitespace
*/
for (i=0; i < len; ++i) {
if (isascii(wstr[i]) && isspace(wstr[i])) {
wstr[i] = '\0';
len = i;
break;
}
}
/*
* trim trailing non-digits
*/
for (i=len-1; i > 0; --i) {
if (isascii(wstr[i]) && isdigit(wstr[i])) {
/* stop on first digit */
break;
}
}
wstr[i+1] = '\0';
len = i;
/*
* we now have a string that starts and ends with digits
*
* Inspect the remaining string for digits and '.'s only.
* Also reject string if we find more than 2 '.'s in a row.
*/
dbg(DBG_HIGH, "trimmed version string: <%s>", wstr);
for (i=0; i < len; ++i) {
if (isascii(wstr[i]) && isdigit(wstr[i])) {
dot = false;
} else if (wstr[i] == '.') {
if (dot == true) {
/* report invalid version string */
dbg(DBG_MED, "trimmed version string contains 2 dots in a row: <%s>", wstr);
/* free strdup()d string if != NULL */
if (wstr_start != NULL) {
free(wstr_start);
wstr_start = NULL;
}
return 0;
}
dot = true;
++dot_count;
} else {
/* report invalid version string */
dbg(DBG_MED, "trimmed version string contains non-version character: wstr[%ju] = <%c>: <%s>",
(uintmax_t)i, wstr[i], wstr);
/* free strdup()d string if != NULL */
if (wstr_start != NULL) {
free(wstr_start);
wstr_start = NULL;
}
return 0;
}
}
dbg(DBG_MED, "trimmed version string is valid format: <%s>", wstr);
dbg(DBG_HIGH, "trimmed version string has %ju '.'s in it: <%s>", (uintmax_t)dot_count, wstr);
/*
* we now know the version string is valid format: ([0-9]+\.)*[0-9]+
*
* Allocate the array of dot_count+1 versions.
*/
errno = 0; /* pre-clear errno for errp() */
*pvers = (intmax_t *)calloc(dot_count+1+1, sizeof(intmax_t));
if (*pvers == NULL) {
errp(12, __func__, "cannot calloc %ju intmax_ts", (uintmax_t)dot_count+1+1);
not_reached();
}
/*
* tokenize version string using '.' delimiters
*/
for (i=0, word=strtok_r(wstr, ".", &brkt);
i <= dot_count && word != NULL;
++i, word=strtok_r(NULL, ".", &brkt)) {
/* word is the next version string - convert to integer */
dbg(DBG_VVHIGH, "version level %ju word: <%s>", (uintmax_t)i, word);
(void) string_to_intmax(word, &(*pvers)[i]);
dbg(DBG_VHIGH, "version level %ju: %jd", (uintmax_t)i, (*pvers)[i]);
}
/* we no longer need the duplicated string */
if (wstr_start != NULL) {
free(wstr_start);
wstr_start = NULL;
}
/*
* return number of version levels
*/
return dot_count+1;
}
/*
* usage - print usage to stderr
*
* Example:
* usage(3, program, "wrong number of arguments");
*
* given:
* exitcode value to exit with
* prog our program name
* str top level usage message
*
* NOTE: We warn with extra newlines to help internal fault messages stand out.
* Normally one should NOT include newlines in warn messages.
*
* This function does not return.
*/
static void
usage(int exitcode, char const *prog, char const *str)
{
/*
* firewall
*/
if (str == NULL) {
str = "((NULL str))";
warn(__func__, "\nin usage(): str was NULL, forcing it to be: %s\n", str);
}
if (prog == NULL) {
prog = VERGE_BASENAME;
warn(__func__, "\nin usage(): prog was NULL, forcing it to be: %s\n", prog);
}
/*
* print the formatted usage stream
*/
if (*str != '\0') {
fprintf_usage(DO_NOT_EXIT, stderr, "%s\n", str);
}
fprintf_usage(exitcode, stderr, usage_msg, prog, DBG_DEFAULT, VERGE_BASENAME, VERGE_VERSION,
JPARSE_UTF8_VERSION, JPARSE_LIBRARY_VERSION);
exit(exitcode); /*ooo*/
not_reached();
}