]> git.ipfire.org Git - thirdparty/util-linux.git/blame - lib/strutils.c
Merge branch 'master' of https://github.com/breavyn/util-linux
[thirdparty/util-linux.git] / lib / strutils.c
CommitLineData
8abcf290
DB
1/*
2 * Copyright (C) 2010 Karel Zak <kzak@redhat.com>
3 * Copyright (C) 2010 Davidlohr Bueso <dave@gnu.org>
4e36662e
KZ
4 *
5 * No copyright is claimed. This code is in the public domain; do with
6 * it what you wish.
8abcf290 7 */
c4ecaf21 8#include <stdio.h>
8abcf290
DB
9#include <stdlib.h>
10#include <inttypes.h>
11#include <ctype.h>
12#include <errno.h>
ce877f2d 13#include <sys/stat.h>
c4ecaf21 14#include <string.h>
a85d6a04 15#include <strings.h>
199e939d 16#include <assert.h>
c87638ad 17
eb76ca98 18#include "c.h"
ab65d635 19#include "nls.h"
5d2a9849 20#include "strutils.h"
c87638ad 21#include "bitops.h"
b82237df 22#include "pathnames.h"
8abcf290 23
9c8b9fba
RM
24static int STRTOXX_EXIT_CODE = EXIT_FAILURE;
25
26void strutils_set_exitcode(int ex) {
27 STRTOXX_EXIT_CODE = ex;
28}
29
8abcf290
DB
30static int do_scale_by_power (uintmax_t *x, int base, int power)
31{
32 while (power--) {
33 if (UINTMAX_MAX / base < *x)
3643958f 34 return -ERANGE;
8abcf290
DB
35 *x *= base;
36 }
37 return 0;
38}
39
cf8de26a
KZ
40/*
41 * strtosize() - convert string to size (uintmax_t).
42 *
43 * Supported suffixes:
44 *
45 * XiB or X for 2^N
23106a29 46 * where X = {K,M,G,T,P,E,Z,Y}
cf8de26a
KZ
47 * or X = {k,m,g,t,p,e} (undocumented for backward compatibility only)
48 * for example:
49 * 10KiB = 10240
50 * 10K = 10240
51 *
52 * XB for 10^N
23106a29 53 * where X = {K,M,G,T,P,E,Z,Y}
cf8de26a
KZ
54 * for example:
55 * 10KB = 10000
56 *
9e930041 57 * The optional 'power' variable returns number associated with used suffix
23106a29
KZ
58 * {K,M,G,T,P,E,Z,Y} = {1,2,3,4,5,6,7,8}.
59 *
0e65dcde 60 * The function also supports decimal point, for example:
6c6750db
KZ
61 * 0.5MB = 500000
62 * 0.5MiB = 512000
63 *
cf8de26a
KZ
64 * Note that the function does not accept numbers with '-' (negative sign)
65 * prefix.
cf8de26a 66 */
23106a29 67int parse_size(const char *str, uintmax_t *res, int *power)
cf8de26a 68{
d54b8315
KZ
69 const char *p;
70 char *end;
6c6750db
KZ
71 uintmax_t x, frac = 0;
72 int base = 1024, rc = 0, pwr = 0, frac_zeros = 0;
23106a29 73
cab84ea3
KZ
74 static const char *suf = "KMGTPEZY";
75 static const char *suf2 = "kmgtpezy";
23106a29 76 const char *sp;
cf8de26a
KZ
77
78 *res = 0;
79
5296523d
KZ
80 if (!str || !*str) {
81 rc = -EINVAL;
cf8de26a 82 goto err;
5296523d 83 }
cf8de26a
KZ
84
85 /* Only positive numbers are acceptable
86 *
87 * Note that this check is not perfect, it would be better to
88 * use lconv->negative_sign. But coreutils use the same solution,
89 * so it's probably good enough...
90 */
d54b8315 91 p = str;
cf8de26a
KZ
92 while (isspace((unsigned char) *p))
93 p++;
3643958f
KZ
94 if (*p == '-') {
95 rc = -EINVAL;
cf8de26a 96 goto err;
3643958f 97 }
cf8de26a 98
d54b8315
KZ
99 errno = 0, end = NULL;
100 x = strtoumax(str, &end, 0);
cf8de26a 101
d54b8315 102 if (end == str ||
3643958f 103 (errno != 0 && (x == UINTMAX_MAX || x == 0))) {
f643d334 104 rc = errno ? -errno : -EINVAL;
cf8de26a 105 goto err;
3643958f 106 }
d54b8315 107 if (!end || !*end)
cf8de26a 108 goto done; /* without suffix */
d54b8315 109 p = end;
cf8de26a
KZ
110
111 /*
112 * Check size suffixes
113 */
6c6750db 114check_suffix:
25e3dd17 115 if (*(p + 1) == 'i' && (*(p + 2) == 'B' || *(p + 2) == 'b') && !*(p + 3))
cf8de26a 116 base = 1024; /* XiB, 2^N */
25e3dd17 117 else if ((*(p + 1) == 'B' || *(p + 1) == 'b') && !*(p + 2))
cf8de26a 118 base = 1000; /* XB, 10^N */
3643958f 119 else if (*(p + 1)) {
6c6750db 120 struct lconv const *l = localeconv();
d54b8315 121 const char *dp = l ? l->decimal_point : NULL;
6c6750db
KZ
122 size_t dpsz = dp ? strlen(dp) : 0;
123
124 if (frac == 0 && *p && dp && strncmp(dp, p, dpsz) == 0) {
d54b8315 125 const char *fstr = p + dpsz;
6c6750db 126
8deb8161 127 for (p = fstr; *p == '0'; p++)
6c6750db 128 frac_zeros++;
482e0a07
KZ
129 fstr = p;
130 if (isdigit(*fstr)) {
131 errno = 0, end = NULL;
132 frac = strtoumax(fstr, &end, 0);
133 if (end == fstr ||
134 (errno != 0 && (frac == UINTMAX_MAX || frac == 0))) {
135 rc = errno ? -errno : -EINVAL;
136 goto err;
137 }
138 } else
139 end = (char *) p;
140
d54b8315 141 if (frac && (!end || !*end)) {
6c6750db
KZ
142 rc = -EINVAL;
143 goto err; /* without suffix, but with frac */
144 }
d54b8315 145 p = end;
6c6750db
KZ
146 goto check_suffix;
147 }
3643958f 148 rc = -EINVAL;
cf8de26a 149 goto err; /* unexpected suffix */
3643958f 150 }
6c6750db 151
23106a29
KZ
152 sp = strchr(suf, *p);
153 if (sp)
154 pwr = (sp - suf) + 1;
155 else {
156 sp = strchr(suf2, *p);
157 if (sp)
158 pwr = (sp - suf2) + 1;
3643958f
KZ
159 else {
160 rc = -EINVAL;
23106a29 161 goto err;
3643958f 162 }
cf8de26a
KZ
163 }
164
23106a29
KZ
165 rc = do_scale_by_power(&x, base, pwr);
166 if (power)
167 *power = pwr;
6c6750db 168 if (frac && pwr) {
8c368dc6
KZ
169 int i;
170 uintmax_t frac_div = 10, frac_poz = 1, frac_base = 1;
171
172 /* mega, giga, ... */
173 do_scale_by_power(&frac_base, base, pwr);
174
175 /* maximal divisor for last digit (e.g. for 0.05 is
176 * frac_div=100, for 0.054 is frac_div=1000, etc.)
191836be
KZ
177 *
178 * Reduce frac if too large.
8c368dc6 179 */
191836be
KZ
180 while (frac_div < frac) {
181 if (frac_div <= UINTMAX_MAX/10)
182 frac_div *= 10;
183 else
184 frac /= 10;
185 }
8c368dc6
KZ
186
187 /* 'frac' is without zeros (5 means 0.5 as well as 0.05) */
191836be
KZ
188 for (i = 0; i < frac_zeros; i++) {
189 if (frac_div <= UINTMAX_MAX/10)
190 frac_div *= 10;
191 else
192 frac /= 10;
193 }
8c368dc6
KZ
194
195 /*
196 * Go backwardly from last digit and add to result what the
197 * digit represents in the frac_base. For example 0.25G
198 *
199 * 5 means 1GiB / (100/5)
200 * 2 means 1GiB / (10/2)
201 */
202 do {
203 unsigned int seg = frac % 10; /* last digit of the frac */
204 uintmax_t seg_div = frac_div / frac_poz; /* what represents the segment 1000, 100, .. */
205
206 frac /= 10; /* remove last digit from frac */
207 frac_poz *= 10;
208
1186cdf3 209 if (seg && seg_div / seg)
8c368dc6
KZ
210 x += frac_base / (seg_div / seg);
211 } while (frac);
6c6750db 212 }
cf8de26a
KZ
213done:
214 *res = x;
cf8de26a 215err:
f643d334
RM
216 if (rc < 0)
217 errno = -rc;
3643958f 218 return rc;
cf8de26a
KZ
219}
220
23106a29
KZ
221int strtosize(const char *str, uintmax_t *res)
222{
223 return parse_size(str, res, NULL);
224}
225
61cbc8a3 226int isdigit_strend(const char *str, const char **end)
416c43a9
KZ
227{
228 const char *p;
229
230 for (p = str; p && *p && isdigit((unsigned char) *p); p++);
231
61cbc8a3
KZ
232 if (end)
233 *end = p;
416c43a9
KZ
234 return p && p > str && !*p;
235}
236
61cbc8a3 237int isxdigit_strend(const char *str, const char **end)
40f00b4f
KZ
238{
239 const char *p;
240
241 for (p = str; p && *p && isxdigit((unsigned char) *p); p++);
242
61cbc8a3
KZ
243 if (end)
244 *end = p;
245
40f00b4f
KZ
246 return p && p > str && !*p;
247}
248
30b294c4
KZ
249/*
250 * parse_switch(argv[i], "on", "off", "yes", "no", NULL);
251 */
252int parse_switch(const char *arg, const char *errmesg, ...)
e5cf1476 253{
30b294c4
KZ
254 const char *a, *b;
255 va_list ap;
256
257 va_start(ap, errmesg);
258 do {
259 a = va_arg(ap, char *);
260 if (!a)
261 break;
262 b = va_arg(ap, char *);
263 if (!b)
264 break;
265
74819b5f
AH
266 if (strcmp(arg, a) == 0) {
267 va_end(ap);
30b294c4 268 return 1;
042f62df
RP
269 }
270
271 if (strcmp(arg, b) == 0) {
74819b5f 272 va_end(ap);
30b294c4 273 return 0;
74819b5f 274 }
30b294c4
KZ
275 } while (1);
276 va_end(ap);
277
278 errx(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, arg);
e5cf1476 279}
416c43a9 280
02887b73
DT
281#ifndef HAVE_MEMPCPY
282void *mempcpy(void *restrict dest, const void *restrict src, size_t n)
283{
284 return ((char *)memcpy(dest, src, n)) + n;
285}
286#endif
287
8abcf290
DB
288#ifndef HAVE_STRNLEN
289size_t strnlen(const char *s, size_t maxlen)
290{
64af1a29 291 size_t i;
cf8de26a 292
8abcf290
DB
293 for (i = 0; i < maxlen; i++) {
294 if (s[i] == '\0')
64af1a29 295 return i;
8abcf290
DB
296 }
297 return maxlen;
298}
299#endif
cf8de26a 300
8abcf290
DB
301#ifndef HAVE_STRNCHR
302char *strnchr(const char *s, size_t maxlen, int c)
cf8de26a 303{
8abcf290
DB
304 for (; maxlen-- && *s != '\0'; ++s)
305 if (*s == (char)c)
306 return (char *)s;
307 return NULL;
308}
309#endif
cf8de26a 310
8abcf290
DB
311#ifndef HAVE_STRNDUP
312char *strndup(const char *s, size_t n)
313{
314 size_t len = strnlen(s, n);
fea1cbf7 315 char *new = malloc((len + 1) * sizeof(char));
8abcf290
DB
316 if (!new)
317 return NULL;
318 new[len] = '\0';
319 return (char *) memcpy(new, s, len);
320}
321#endif
cf8de26a 322
84825b16
KZ
323/*
324 * convert strings to numbers; returns <0 on error, and 0 on success
325 */
326int ul_strtos64(const char *str, int64_t *num, int base)
327{
328 char *end = NULL;
54394eab 329
84825b16 330 if (str == NULL || *str == '\0')
47f851e1
KZ
331 return -(errno = EINVAL);
332
333 errno = 0;
84825b16
KZ
334 *num = (int64_t) strtoimax(str, &end, base);
335
47f851e1
KZ
336 if (errno != 0)
337 return -errno;
338 if (str == end || (end && *end))
339 return -(errno = EINVAL);
84825b16
KZ
340 return 0;
341}
342
343int ul_strtou64(const char *str, uint64_t *num, int base)
a9f97001 344{
84825b16 345 char *end = NULL;
9fc0f69c 346 int64_t tmp;
551dae40 347
84825b16 348 if (str == NULL || *str == '\0')
47f851e1 349 return -(errno = EINVAL);
9fc0f69c
KZ
350
351 /* we need to ignore negative numbers, note that for invalid negative
352 * number strtoimax() returns negative number too, so we do not
353 * need to check errno here */
47f851e1 354 errno = 0;
9fc0f69c
KZ
355 tmp = (int64_t) strtoimax(str, &end, base);
356 if (tmp < 0)
357 errno = ERANGE;
358 else {
359 errno = 0;
360 *num = strtoumax(str, &end, base);
361 }
84825b16 362
47f851e1
KZ
363 if (errno != 0)
364 return -errno;
365 if (str == end || (end && *end))
366 return -(errno = EINVAL);
84825b16 367 return 0;
551dae40
KZ
368}
369
9fc0f69c
KZ
370int ul_strtos32(const char *str, int32_t *num, int base)
371{
372 int64_t tmp;
373 int rc;
374
375 rc = ul_strtos64(str, &tmp, base);
376 if (rc == 0 && (tmp < INT32_MIN || tmp > INT32_MAX))
377 rc = -(errno = ERANGE);
378 if (rc == 0)
379 *num = (int32_t) tmp;
380 return rc;
381}
382
383int ul_strtou32(const char *str, uint32_t *num, int base)
384{
385 uint64_t tmp;
386 int rc;
387
388 rc = ul_strtou64(str, &tmp, base);
389 if (rc == 0 && tmp > UINT32_MAX)
390 rc = -(errno = ERANGE);
391 if (rc == 0)
392 *num = (uint32_t) tmp;
393 return rc;
394}
395
84825b16 396/*
0e85613e 397 * Convert strings to numbers in defined range and print message on error.
84825b16 398 *
9fc0f69c 399 * These functions are used when we read input from users (getopt() etc.). It's
b1fc5d61 400 * better to consolidate the code and keep it all based on 64-bit numbers than
9fc0f69c 401 * implement it for 32 and 16-bit numbers too.
84825b16 402 */
9fc0f69c
KZ
403int64_t str2num_or_err(const char *str, int base, const char *errmesg,
404 int64_t low, int64_t up)
551dae40 405{
84825b16 406 int64_t num = 0;
9fc0f69c 407 int rc;
551dae40 408
9fc0f69c
KZ
409 rc = ul_strtos64(str, &num, base);
410 if (rc == 0 && ((low && num < low) || (up && num > up)))
411 rc = -(errno = ERANGE);
551dae40 412
9fc0f69c 413 if (rc) {
84825b16
KZ
414 if (errno == ERANGE)
415 err(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, str);
84825b16
KZ
416 errx(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, str);
417 }
418 return num;
54394eab
HC
419}
420
9fc0f69c 421uint64_t str2unum_or_err(const char *str, int base, const char *errmesg, uint64_t up)
54394eab 422{
84825b16 423 uint64_t num = 0;
9fc0f69c
KZ
424 int rc;
425
426 rc = ul_strtou64(str, &num, base);
427 if (rc == 0 && (up && num > up))
428 rc = -(errno = ERANGE);
84825b16 429
9fc0f69c 430 if (rc) {
84825b16
KZ
431 if (errno == ERANGE)
432 err(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, str);
84825b16
KZ
433 errx(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, str);
434 }
435 return num;
54394eab
HC
436}
437
551dae40 438double strtod_or_err(const char *str, const char *errmesg)
94d32126 439{
551dae40 440 double num;
a99c9130 441 char *end = NULL;
94d32126 442
73b9e094 443 errno = 0;
a99c9130
KZ
444 if (str == NULL || *str == '\0')
445 goto err;
551dae40 446 num = strtod(str, &end);
94d32126 447
a99c9130
KZ
448 if (errno || str == end || (end && *end))
449 goto err;
94d32126 450
a99c9130 451 return num;
94d32126 452err:
73b9e094 453 if (errno == ERANGE)
a99c9130
KZ
454 err(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, str);
455
456 errx(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, str);
94d32126 457}
551dae40 458
35c84bf7
KZ
459long double strtold_or_err(const char *str, const char *errmesg)
460{
461 double num;
462 char *end = NULL;
463
464 errno = 0;
465 if (str == NULL || *str == '\0')
466 goto err;
467 num = strtold(str, &end);
468
469 if (errno || str == end || (end && *end))
470 goto err;
471
472 return num;
473err:
474 if (errno == ERANGE)
475 err(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, str);
476
477 errx(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, str);
478}
479
a99c9130
KZ
480uintmax_t strtosize_or_err(const char *str, const char *errmesg)
481{
482 uintmax_t num;
483
484 if (strtosize(str, &num) == 0)
485 return num;
486
487 if (errno)
488 err(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, str);
489
490 errx(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, str);
e53bc960 491}
ce877f2d 492
477254da
KZ
493
494void strtotimeval_or_err(const char *str, struct timeval *tv, const char *errmesg)
495{
35c84bf7 496 long double user_input;
477254da 497
35c84bf7 498 user_input = strtold_or_err(str, errmesg);
477254da 499 tv->tv_sec = (time_t) user_input;
35c84bf7
KZ
500 tv->tv_usec = (suseconds_t)((user_input - tv->tv_sec) * 1000000);
501}
502
937cd872
TW
503void strtotimespec_or_err(const char *str, struct timespec *ts, const char *errmesg)
504{
505 long double user_input;
506
507 user_input = strtold_or_err(str, errmesg);
508 ts->tv_sec = (time_t) user_input;
509 ts->tv_nsec = (long)((user_input - ts->tv_sec) * 1000000000);
510}
511
35c84bf7
KZ
512time_t strtotime_or_err(const char *str, const char *errmesg)
513{
514 int64_t user_input;
515
516 user_input = strtos64_or_err(str, errmesg);
517 return (time_t) user_input;
477254da
KZ
518}
519
ce877f2d
KZ
520/*
521 * Converts stat->st_mode to ls(1)-like mode string. The size of "str" must
d9554c97 522 * be 11 bytes.
ce877f2d 523 */
5b82289b 524char *xstrmode(mode_t mode, char *str)
ce877f2d 525{
7015df49
KZ
526 unsigned short i = 0;
527
ce877f2d 528 if (S_ISDIR(mode))
7015df49 529 str[i++] = 'd';
ce877f2d 530 else if (S_ISLNK(mode))
7015df49 531 str[i++] = 'l';
ce877f2d 532 else if (S_ISCHR(mode))
7015df49 533 str[i++] = 'c';
ce877f2d 534 else if (S_ISBLK(mode))
7015df49 535 str[i++] = 'b';
ce877f2d 536 else if (S_ISSOCK(mode))
7015df49 537 str[i++] = 's';
ce877f2d 538 else if (S_ISFIFO(mode))
7015df49 539 str[i++] = 'p';
ce877f2d 540 else if (S_ISREG(mode))
7015df49 541 str[i++] = '-';
ce877f2d 542
7015df49
KZ
543 str[i++] = mode & S_IRUSR ? 'r' : '-';
544 str[i++] = mode & S_IWUSR ? 'w' : '-';
545 str[i++] = (mode & S_ISUID
ce877f2d
KZ
546 ? (mode & S_IXUSR ? 's' : 'S')
547 : (mode & S_IXUSR ? 'x' : '-'));
7015df49
KZ
548 str[i++] = mode & S_IRGRP ? 'r' : '-';
549 str[i++] = mode & S_IWGRP ? 'w' : '-';
550 str[i++] = (mode & S_ISGID
ce877f2d
KZ
551 ? (mode & S_IXGRP ? 's' : 'S')
552 : (mode & S_IXGRP ? 'x' : '-'));
7015df49
KZ
553 str[i++] = mode & S_IROTH ? 'r' : '-';
554 str[i++] = mode & S_IWOTH ? 'w' : '-';
555 str[i++] = (mode & S_ISVTX
ce877f2d
KZ
556 ? (mode & S_IXOTH ? 't' : 'T')
557 : (mode & S_IXOTH ? 'x' : '-'));
7015df49 558 str[i] = '\0';
5b82289b
KZ
559
560 return str;
ce877f2d 561}
c4ecaf21
DB
562
563/*
cab84ea3 564 * returns exponent (2^x=n) in range KiB..EiB (2^10..2^60)
c4ecaf21
DB
565 */
566static int get_exp(uint64_t n)
567{
568 int shft;
569
570 for (shft = 10; shft <= 60; shft += 10) {
571 if (n < (1ULL << shft))
572 break;
573 }
574 return shft - 10;
575}
576
5d2a9849 577char *size_to_human_string(int options, uint64_t bytes)
c4ecaf21
DB
578{
579 char buf[32];
f9e05daf
DR
580 int dec, exp;
581 uint64_t frac;
582 const char *letters = "BKMGTPE";
5d2a9849 583 char suffix[sizeof(" KiB")], *psuf = suffix;
c4ecaf21
DB
584 char c;
585
5d2a9849
FC
586 if (options & SIZE_SUFFIX_SPACE)
587 *psuf++ = ' ';
588
07b94c9f 589
c4ecaf21
DB
590 exp = get_exp(bytes);
591 c = *(letters + (exp ? exp / 10 : 0));
592 dec = exp ? bytes / (1ULL << exp) : bytes;
593 frac = exp ? bytes % (1ULL << exp) : 0;
594
5d2a9849
FC
595 *psuf++ = c;
596
597 if ((options & SIZE_SUFFIX_3LETTER) && (c != 'B')) {
598 *psuf++ = 'i';
599 *psuf++ = 'B';
600 }
601
602 *psuf = '\0';
603
604 /* fprintf(stderr, "exp: %d, unit: %c, dec: %d, frac: %jd\n",
605 * exp, suffix[0], dec, frac);
f9e05daf
DR
606 */
607
07b94c9f 608 /* round */
c4ecaf21 609 if (frac) {
b38c20e1 610 /* get 3 digits after decimal point */
f12d5ad2
KZ
611 if (frac >= UINT64_MAX / 1000)
612 frac = ((frac / 1024) * 1000) / (1ULL << (exp - 10)) ;
613 else
614 frac = (frac * 1000) / (1ULL << (exp)) ;
b38c20e1 615
07b94c9f 616 if (options & SIZE_DECIMAL_2DIGITS) {
b38c20e1
KZ
617 /* round 4/5 and keep 2 digits after decimal point */
618 frac = (frac + 5) / 10 ;
07b94c9f 619 } else {
b38c20e1
KZ
620 /* round 4/5 and keep 1 digit after decimal point */
621 frac = ((frac + 50) / 100) * 10 ;
622 }
623
624 /* rounding could have overflowed */
625 if (frac == 100) {
626 dec++;
627 frac = 0;
07b94c9f 628 }
c4ecaf21
DB
629 }
630
631 if (frac) {
632 struct lconv const *l = localeconv();
633 char *dp = l ? l->decimal_point : NULL;
b38c20e1 634 int len;
c4ecaf21
DB
635
636 if (!dp || !*dp)
637 dp = ".";
b38c20e1
KZ
638
639 len = snprintf(buf, sizeof(buf), "%d%s%02" PRIu64, dec, dp, frac);
640 if (len > 0 && (size_t) len < sizeof(buf)) {
641 /* remove potential extraneous zero */
642 if (buf[len - 1] == '0')
643 buf[len--] = '\0';
644 /* append suffix */
645 xstrncpy(buf+len, suffix, sizeof(buf) - len);
646 } else
647 *buf = '\0'; /* snprintf error */
c4ecaf21 648 } else
5d2a9849 649 snprintf(buf, sizeof(buf), "%d%s", dec, suffix);
c4ecaf21
DB
650
651 return strdup(buf);
652}
013bff51 653
c87638ad
KZ
654/*
655 * Parses comma delimited list to array with IDs, for example:
656 *
657 * "aaa,bbb,ccc" --> ary[0] = FOO_AAA;
658 * ary[1] = FOO_BBB;
659 * ary[3] = FOO_CCC;
660 *
661 * The function name2id() provides conversion from string to ID.
662 *
663 * Returns: >= 0 : number of items added to ary[]
664 * -1 : parse error or unknown item
665 * -2 : arysz reached
666 */
667int string_to_idarray(const char *list, int ary[], size_t arysz,
668 int (name2id)(const char *, size_t))
669{
670 const char *begin = NULL, *p;
b28f2267 671 size_t n = 0;
c87638ad
KZ
672
673 if (!list || !*list || !ary || !arysz || !name2id)
674 return -1;
675
676 for (p = list; p && *p; p++) {
677 const char *end = NULL;
678 int id;
679
179b923a
CR
680 if (n >= arysz)
681 return -2;
c87638ad
KZ
682 if (!begin)
683 begin = p; /* begin of the column name */
684 if (*p == ',')
685 end = p; /* terminate the name */
686 if (*(p + 1) == '\0')
687 end = p + 1; /* end of string */
688 if (!begin || !end)
689 continue;
690 if (end <= begin)
691 return -1;
692
693 id = name2id(begin, end - begin);
694 if (id == -1)
695 return -1;
696 ary[ n++ ] = id;
c87638ad
KZ
697 begin = NULL;
698 if (end && !*end)
699 break;
700 }
701 return n;
702}
703
f5077b51
MB
704/*
705 * Parses the array like string_to_idarray but if format is "+aaa,bbb"
706 * it adds fields to array instead of replacing them.
707 */
708int string_add_to_idarray(const char *list, int ary[], size_t arysz,
40b17508 709 size_t *ary_pos, int (name2id)(const char *, size_t))
f5077b51
MB
710{
711 const char *list_add;
712 int r;
713
40b17508 714 if (!list || !*list || !ary_pos || *ary_pos > arysz)
f5077b51
MB
715 return -1;
716
717 if (list[0] == '+')
718 list_add = &list[1];
719 else {
720 list_add = list;
721 *ary_pos = 0;
722 }
723
724 r = string_to_idarray(list_add, &ary[*ary_pos], arysz - *ary_pos, name2id);
725 if (r > 0)
726 *ary_pos += r;
727 return r;
728}
729
c87638ad
KZ
730/*
731 * LIST ::= <item> [, <item>]
732 *
733 * The <item> is translated to 'id' by name2id() function and the 'id' is used
455fe9a0 734 * as a position in the 'ary' bit array. It means that the 'id' has to be in
c87638ad
KZ
735 * range <0..N> where N < sizeof(ary) * NBBY.
736 *
73a96af6
TW
737 * If allow_range is enabled:
738 * An item ending in '+' also sets all bits in <0..N>.
739 * An item beginning with '+' also sets all bits in <N..allow_minus>.
740 *
a883c634 741 * Returns: 0 on success, <0 on error.
c87638ad
KZ
742 */
743int string_to_bitarray(const char *list,
744 char *ary,
73a96af6
TW
745 int (*name2bit)(const char *, size_t),
746 size_t allow_range)
c87638ad
KZ
747{
748 const char *begin = NULL, *p;
749
750 if (!list || !name2bit || !ary)
751 return -EINVAL;
752
753 for (p = list; p && *p; p++) {
754 const char *end = NULL;
73a96af6 755 int bit, set_lower = 0, set_higher = 0;
c87638ad
KZ
756
757 if (!begin)
758 begin = p; /* begin of the level name */
759 if (*p == ',')
760 end = p; /* terminate the name */
761 if (*(p + 1) == '\0')
762 end = p + 1; /* end of string */
763 if (!begin || !end)
764 continue;
765 if (end <= begin)
766 return -1;
73a96af6
TW
767 if (allow_range) {
768 if (*(end - 1) == '+') {
769 end--;
770 set_lower = 1;
771 } else if (*begin == '+') {
772 begin++;
773 set_higher = 1;
774 }
775 }
c87638ad
KZ
776
777 bit = name2bit(begin, end - begin);
778 if (bit < 0)
779 return bit;
780 setbit(ary, bit);
73a96af6
TW
781 if (set_lower)
782 while (--bit >= 0)
783 setbit(ary, bit);
784 else if (set_higher)
785 while (++bit < (int) allow_range)
786 setbit(ary, bit);
c87638ad
KZ
787 begin = NULL;
788 if (end && !*end)
789 break;
790 }
5ef16771
KZ
791 return 0;
792}
793
794/*
795 * LIST ::= <item> [, <item>]
796 *
797 * The <item> is translated to 'id' by name2flag() function and the flags is
798 * set to the 'mask'
799*
800 * Returns: 0 on success, <0 on error.
801 */
802int string_to_bitmask(const char *list,
803 unsigned long *mask,
804 long (*name2flag)(const char *, size_t))
805{
806 const char *begin = NULL, *p;
807
808 if (!list || !name2flag || !mask)
809 return -EINVAL;
810
811 for (p = list; p && *p; p++) {
812 const char *end = NULL;
813 long flag;
814
815 if (!begin)
816 begin = p; /* begin of the level name */
817 if (*p == ',')
818 end = p; /* terminate the name */
819 if (*(p + 1) == '\0')
820 end = p + 1; /* end of string */
821 if (!begin || !end)
822 continue;
823 if (end <= begin)
824 return -1;
825
826 flag = name2flag(begin, end - begin);
827 if (flag < 0)
828 return flag; /* error */
829 *mask |= flag;
830 begin = NULL;
831 if (end && !*end)
832 break;
833 }
c87638ad
KZ
834 return 0;
835}
013bff51 836
a883c634
DB
837/*
838 * Parse the lower and higher values in a string containing
839 * "lower:higher" or "lower-higher" format. Note that either
af7df9ee
DB
840 * the lower or the higher values may be missing, and the def
841 * value will be assigned to it by default.
a883c634
DB
842 *
843 * Returns: 0 on success, <0 on error.
844 */
af7df9ee 845int parse_range(const char *str, int *lower, int *upper, int def)
a883c634
DB
846{
847 char *end = NULL;
848
849 if (!str)
850 return 0;
851
af7df9ee 852 *upper = *lower = def;
a883c634
DB
853 errno = 0;
854
855 if (*str == ':') { /* <:N> */
856 str++;
857 *upper = strtol(str, &end, 10);
858 if (errno || !end || *end || end == str)
859 return -1;
860 } else {
861 *upper = *lower = strtol(str, &end, 10);
862 if (errno || !end || end == str)
863 return -1;
864
865 if (*end == ':' && !*(end + 1)) /* <M:> */
2d47fa39 866 *upper = def;
a883c634
DB
867 else if (*end == '-' || *end == ':') { /* <M:N> <M-N> */
868 str = end + 1;
869 end = NULL;
870 errno = 0;
871 *upper = strtol(str, &end, 10);
872
873 if (errno || !end || *end || end == str)
874 return -1;
875 }
876 }
877 return 0;
878}
879
d4e89dea 880static const char *next_path_segment(const char *str, size_t *sz)
b106d052 881{
d4e89dea 882 const char *start, *p;
b106d052 883
d4e89dea
KZ
884 start = str;
885 *sz = 0;
886 while (start && *start == '/' && *(start + 1) == '/')
887 start++;
888
889 if (!start || !*start)
890 return NULL;
891
892 for (*sz = 1, p = start + 1; *p && *p != '/'; p++) {
893 (*sz)++;
894 }
895
896 return start;
897}
b106d052 898
d4e89dea
KZ
899int streq_paths(const char *a, const char *b)
900{
901 while (a && b) {
902 size_t a_sz, b_sz;
903 const char *a_seg = next_path_segment(a, &a_sz);
904 const char *b_seg = next_path_segment(b, &b_sz);
905
906 /*
907 fprintf(stderr, "A>>>(%zu) '%s'\n", a_sz, a_seg);
908 fprintf(stderr, "B>>>(%zu) '%s'\n", b_sz, b_seg);
909 */
b106d052 910
d4e89dea
KZ
911 /* end of the path */
912 if (a_sz + b_sz == 0)
913 return 1;
914
915 /* ignore tailing slash */
916 if (a_sz + b_sz == 1 &&
917 ((a_seg && *a_seg == '/') || (b_seg && *b_seg == '/')))
918 return 1;
b106d052 919
6d92f7d7
KZ
920 if (!a_seg || !b_seg)
921 break;
d4e89dea 922 if (a_sz != b_sz || strncmp(a_seg, b_seg, a_sz) != 0)
6d92f7d7 923 break;
b106d052 924
d4e89dea
KZ
925 a = a_seg + a_sz;
926 b = b_seg + b_sz;
927 };
b106d052 928
d4e89dea 929 return 0;
b106d052
PU
930}
931
8420463b
KZ
932/* concatenate two strings to a new string, the size of the second string is limited by @b */
933char *strnconcat(const char *s, const char *suffix, size_t b)
548b9714
KZ
934{
935 size_t a;
936 char *r;
937
938 if (!s && !suffix)
939 return strdup("");
940 if (!s)
941 return strndup(suffix, b);
942 if (!suffix)
943 return strdup(s);
944
945 assert(s);
946 assert(suffix);
947
948 a = strlen(s);
949 if (b > ((size_t) -1) - a)
950 return NULL;
951
952 r = malloc(a + b + 1);
953 if (!r)
954 return NULL;
955
956 memcpy(r, s, a);
957 memcpy(r + a, suffix, b);
958 r[a+b] = 0;
959
960 return r;
961}
962
8420463b
KZ
963/* concatenate two strings to a new string */
964char *strconcat(const char *s, const char *suffix)
548b9714 965{
8420463b 966 return strnconcat(s, suffix, suffix ? strlen(suffix) : 0);
548b9714
KZ
967}
968
8420463b
KZ
969/* concatenate @s and string defined by @format to a new string */
970char *strfconcat(const char *s, const char *format, ...)
3c201431
KZ
971{
972 va_list ap;
973 char *val, *res;
974 int sz;
975
976 va_start(ap, format);
977 sz = vasprintf(&val, format, ap);
978 va_end(ap);
979
980 if (sz < 0)
981 return NULL;
982
8420463b 983 res = strnconcat(s, val, sz);
3c201431
KZ
984 free(val);
985 return res;
986}
548b9714 987
2e03758d
KZ
988int strappend(char **a, const char *b)
989{
990 size_t al, bl;
991 char *tmp;
992
993 if (!a)
994 return -EINVAL;
995 if (!b || !*b)
996 return 0;
997 if (!*a) {
998 *a = strdup(b);
999 return !*a ? -ENOMEM : 0;
1000 }
1001
1002 al = strlen(*a);
1003 bl = strlen(b);
1004
1005 tmp = realloc(*a, al + bl + 1);
1006 if (!tmp)
1007 return -ENOMEM;
1008 *a = tmp;
1009 memcpy((*a) + al, b, bl + 1);
1010 return 0;
1011}
1012
548b9714
KZ
1013static size_t strcspn_escaped(const char *s, const char *reject)
1014{
1015 int escaped = 0;
1016 int n;
1017
1018 for (n=0; s[n]; n++) {
1019 if (escaped)
1020 escaped = 0;
1021 else if (s[n] == '\\')
1022 escaped = 1;
1023 else if (strchr(reject, s[n]))
1024 break;
1025 }
1026
1027 /* if s ends in \, return index of previous char */
1028 return n - escaped;
1029}
1030
63bb0450
KZ
1031/*
1032 * Like strchr() but ignores @c if escaped by '\', '\\' is interpreted like '\'.
1033 *
1034 * For example for @c='X':
1035 *
1036 * "abcdXefgXh" --> "XefgXh"
1037 * "abcd\XefgXh" --> "Xh"
1038 * "abcd\\XefgXh" --> "XefgXh"
1039 * "abcd\\\XefgXh" --> "Xh"
1040 * "abcd\Xefg\Xh" --> (null)
1041 *
1042 * "abcd\\XefgXh" --> "\XefgXh" for @c='\\'
1043 */
1044char *ul_strchr_escaped(const char *s, int c)
1045{
1046 char *p;
1047 int esc = 0;
1048
1049 for (p = (char *) s; p && *p; p++) {
1050 if (!esc && *p == '\\') {
1051 esc = 1;
1052 continue;
1053 }
1054 if (*p == c && (!esc || c == '\\'))
1055 return p;
1056 esc = 0;
1057 }
1058
1059 return NULL;
1060}
1061
548b9714
KZ
1062/* Split a string into words. */
1063const char *split(const char **state, size_t *l, const char *separator, int quoted)
1064{
1065 const char *current;
1066
1067 current = *state;
1068
1069 if (!*current) {
1070 assert(**state == '\0');
1071 return NULL;
1072 }
1073
1074 current += strspn(current, separator);
1075 if (!*current) {
1076 *state = current;
1077 return NULL;
1078 }
1079
1080 if (quoted && strchr("\'\"", *current)) {
1081 char quotechars[2] = {*current, '\0'};
1082
1083 *l = strcspn_escaped(current + 1, quotechars);
1084 if (current[*l + 1] == '\0' || current[*l + 1] != quotechars[0] ||
1085 (current[*l + 2] && !strchr(separator, current[*l + 2]))) {
1086 /* right quote missing or garbage at the end */
1087 *state = current;
1088 return NULL;
1089 }
1090 *state = current++ + *l + 2;
1091 } else if (quoted) {
1092 *l = strcspn_escaped(current, separator);
1093 if (current[*l] && !strchr(separator, current[*l])) {
1094 /* unfinished escape */
1095 *state = current;
1096 return NULL;
1097 }
1098 *state = current + *l;
1099 } else {
1100 *l = strcspn(current, separator);
1101 *state = current + *l;
1102 }
1103
1104 return current;
1105}
1106
3e5a5455
SK
1107/* Rewind file pointer forward to new line. */
1108int skip_fline(FILE *fp)
1109{
d551d668 1110 int ch;
3e5a5455
SK
1111
1112 do {
1113 if ((ch = fgetc(fp)) == EOF)
1114 return 1;
1115 if (ch == '\n')
1116 return 0;
1117 } while (1);
1118}
548b9714 1119
27e6b3a9
KZ
1120
1121/* compare two strings, but ignoring non-alnum and case of the characters, for example
1122 * "Hello (123)!" is the same as "hello123".
1123 */
1124int ul_stralnumcmp(const char *p1, const char *p2)
1125{
1126 const unsigned char *s1 = (const unsigned char *) p1;
1127 const unsigned char *s2 = (const unsigned char *) p2;
1128 unsigned char c1, c2;
1129
1130 do {
1131 do {
1132 c1 = (unsigned char) *s1++;
1133 } while (c1 != '\0' && !isalnum((unsigned int) c1));
1134
1135 do {
1136 c2 = (unsigned char) *s2++;
1137 } while (c2 != '\0' && !isalnum((unsigned int) c2));
1138
1139 if (c1 != '\0')
1140 c1 = tolower(c1);
1141 if (c2 != '\0')
1142 c2 = tolower(c2);
1143 if (c1 == '\0')
1144 return c1 - c2;
1145 } while (c1 == c2);
1146
1147 return c1 - c2;
1148}
1149
6c513f3c
KZ
1150/*
1151 * Parses the first option from @optstr. The @optstr pointer is set to the beginning
1152 * of the next option. The options string looks like 'aaa,bbb=data,foo,bar="xxx"'.
1153 *
1154 * Note this function is used by libmount to parse mount options. Be careful when modify.
1155 *
1156 * Returns -EINVAL on parse error, 1 at the end of optstr and 0 on success.
1157 */
1158int ul_optstr_next(char **optstr, char **name, size_t *namesz,
1159 char **value, size_t *valsz)
1160{
1161 int open_quote = 0;
1162 char *start = NULL, *stop = NULL, *p, *sep = NULL;
1163 char *optstr0;
1164
1165 assert(optstr);
1166 assert(*optstr);
1167
1168 optstr0 = *optstr;
1169
1170 if (name)
1171 *name = NULL;
1172 if (namesz)
1173 *namesz = 0;
1174 if (value)
1175 *value = NULL;
1176 if (valsz)
1177 *valsz = 0;
1178
1179 /* trim leading commas as to not invalidate option
1180 * strings with multiple consecutive commas */
1181 while (optstr0 && *optstr0 == ',')
1182 optstr0++;
1183
1184 for (p = optstr0; p && *p; p++) {
1185 if (!start)
1186 start = p; /* beginning of the option item */
1187 if (*p == '"')
1188 open_quote ^= 1; /* reverse the status */
1189 if (open_quote)
1190 continue; /* still in quoted block */
1191 if (!sep && p > start && *p == '=')
1192 sep = p; /* name and value separator */
1193 if (*p == ',')
1194 stop = p; /* terminate the option item */
1195 else if (*(p + 1) == '\0')
1196 stop = p + 1; /* end of optstr */
1197 if (!start || !stop)
1198 continue;
1199 if (stop <= start)
1200 return -EINVAL;
1201
1202 if (name)
1203 *name = start;
1204 if (namesz)
1205 *namesz = sep ? sep - start : stop - start;
1206 *optstr = *stop ? stop + 1 : stop;
1207
1208 if (sep) {
1209 if (value)
1210 *value = sep + 1;
1211 if (valsz)
1212 *valsz = stop - sep - 1;
1213 }
1214 return 0;
1215 }
1216
1217 return 1; /* end of optstr */
1218}
1219
e8f7acb0 1220#ifdef TEST_PROGRAM_STRUTILS
d2246a5b
KZ
1221
1222#include "cctype.h"
1223
0683647c
KZ
1224struct testS {
1225 char *name;
1226 char *value;
1227};
1228
1229static int test_strdup_to_member(int argc, char *argv[])
1230{
1231 struct testS *xx;
1232
1233 if (argc < 3)
1234 return EXIT_FAILURE;
1235
1236 xx = calloc(1, sizeof(*xx));
1237 if (!xx)
1aa0f1ab 1238 err(EXIT_FAILURE, "calloc() failed");
0683647c
KZ
1239
1240 strdup_to_struct_member(xx, name, argv[1]);
1241 strdup_to_struct_member(xx, value, argv[2]);
1242
1243 if (strcmp(xx->name, argv[1]) != 0 &&
1244 strcmp(xx->value, argv[2]) != 0)
1245 errx(EXIT_FAILURE, "strdup_to_struct_member() failed");
1246
1247 printf("1: '%s', 2: '%s'\n", xx->name, xx->value);
1248
1249 free(xx->name);
1250 free(xx->value);
1251 free(xx);
1252 return EXIT_SUCCESS;
1253}
013bff51 1254
d4e89dea 1255static int test_strutils_sizes(int argc, char *argv[])
013bff51
KZ
1256{
1257 uintmax_t size = 0;
b38c20e1 1258 char *hum1, *hum2, *hum3;
013bff51 1259
d4e89dea
KZ
1260 if (argc < 2)
1261 return EXIT_FAILURE;
013bff51
KZ
1262
1263 if (strtosize(argv[1], &size))
1264 errx(EXIT_FAILURE, "invalid size '%s' value", argv[1]);
1265
b38c20e1 1266 hum1 = size_to_human_string(SIZE_SUFFIX_1LETTER, size);
5d2a9849
FC
1267 hum2 = size_to_human_string(SIZE_SUFFIX_3LETTER |
1268 SIZE_SUFFIX_SPACE, size);
b38c20e1
KZ
1269 hum3 = size_to_human_string(SIZE_SUFFIX_3LETTER |
1270 SIZE_SUFFIX_SPACE |
1271 SIZE_DECIMAL_2DIGITS, size);
f9e05daf 1272
b38c20e1
KZ
1273 printf("%25s : %20ju : %8s : %12s : %13s\n", argv[1], size, hum1, hum2, hum3);
1274 free(hum1);
5d2a9849 1275 free(hum2);
b38c20e1 1276 free(hum3);
f9e05daf 1277
958d2b71 1278 return EXIT_SUCCESS;
013bff51 1279}
d4e89dea
KZ
1280
1281static int test_strutils_cmp_paths(int argc, char *argv[])
1282{
1283 int rc = streq_paths(argv[1], argv[2]);
1284
1285 if (argc < 3)
1286 return EXIT_FAILURE;
1287
1288 printf("%s: '%s' '%s'\n", rc == 1 ? "YES" : "NOT", argv[1], argv[2]);
1289 return EXIT_SUCCESS;
1290}
1291
5d68f974
KZ
1292static int test_strutils_normalize(int argc, char *argv[])
1293{
f3e5d34c 1294 unsigned char *src, *dst, *org;
c862d0e1 1295 size_t sz, len;
5d68f974
KZ
1296
1297 if (argc < 2)
1298 return EXIT_FAILURE;
1299
f3e5d34c
KZ
1300 org = (unsigned char *) strdup(argv[1]);
1301 src = (unsigned char *) strdup((char *) org);
c862d0e1
KZ
1302 len = strlen((char *) src);
1303 dst = malloc(len + 1);
5d68f974 1304
f3e5d34c
KZ
1305 if (!org || !src || !dst)
1306 goto done;
1307
c862d0e1
KZ
1308 /* two buffers */
1309 sz = __normalize_whitespace(src, len, dst, len + 1);
1310 printf("1: '%s' --> '%s' [sz=%zu]\n", src, dst, sz);
1311
1312 /* one buffer */
1313 sz = normalize_whitespace(src);
f3e5d34c 1314 printf("2: '%s' --> '%s' [sz=%zu]\n", org, src, sz);
c862d0e1 1315
f3e5d34c 1316done:
c862d0e1
KZ
1317 free(src);
1318 free(dst);
f3e5d34c 1319 free(org);
5d68f974
KZ
1320
1321 return EXIT_SUCCESS;
1322}
1323
d2246a5b
KZ
1324static int test_strutils_cstrcasecmp(int argc, char *argv[])
1325{
1326 char *a, *b;
1327
1328 if (argc < 3)
1329 return EXIT_FAILURE;
1330
1331 a = argv[1];
1332 b = argv[2];
1333
09aae733
TW
1334 if (!a || !b)
1335 return EXIT_FAILURE;
1336
d2246a5b
KZ
1337 printf("cmp '%s' '%s' = %d\n", a, b, strcasecmp(a, b));
1338 printf("c_cmp '%s' '%s' = %d\n", a, b, c_strcasecmp(a, b));
1339 printf("c_ncmp '%s' '%s' = %d\n", a, b, c_strncasecmp(a, b, strlen(a)));
1340
1341 return EXIT_SUCCESS;
1342}
1343
d4e89dea
KZ
1344int main(int argc, char *argv[])
1345{
84825b16 1346 if (argc == 3 && strcmp(argv[1], "--size") == 0) {
d4e89dea
KZ
1347 return test_strutils_sizes(argc - 1, argv + 1);
1348
84825b16 1349 } else if (argc == 4 && strcmp(argv[1], "--cmp-paths") == 0) {
d4e89dea
KZ
1350 return test_strutils_cmp_paths(argc - 1, argv + 1);
1351
84825b16 1352 } else if (argc == 4 && strcmp(argv[1], "--strdup-member") == 0) {
0683647c
KZ
1353 return test_strdup_to_member(argc - 1, argv + 1);
1354
84825b16 1355 } else if (argc == 4 && strcmp(argv[1], "--stralnumcmp") == 0) {
27e6b3a9
KZ
1356 printf("%s\n", ul_stralnumcmp(argv[2], argv[3]) == 0 ?
1357 "match" : "dismatch");
1358 return EXIT_SUCCESS;
d2246a5b
KZ
1359
1360 } else if (argc == 4 && strcmp(argv[1], "--cstrcasecmp") == 0) {
1361 return test_strutils_cstrcasecmp(argc - 1, argv + 1);
1362
84825b16 1363 } else if (argc == 3 && strcmp(argv[1], "--normalize") == 0) {
5d68f974
KZ
1364 return test_strutils_normalize(argc - 1, argv + 1);
1365
9fc0f69c
KZ
1366 } else if (argc == 3 && strcmp(argv[1], "--strtos64") == 0) {
1367 printf("'%s'-->%jd\n", argv[2], strtos64_or_err(argv[2], "strtos64 failed"));
1368 return EXIT_SUCCESS;
1369 } else if (argc == 3 && strcmp(argv[1], "--strtou64") == 0) {
1370 printf("'%s'-->%ju\n", argv[2], strtou64_or_err(argv[2], "strtou64 failed"));
1371 return EXIT_SUCCESS;
1372 } else if (argc == 3 && strcmp(argv[1], "--strtos32") == 0) {
1373 printf("'%s'-->%d\n", argv[2], strtos32_or_err(argv[2], "strtos32 failed"));
1374 return EXIT_SUCCESS;
1375 } else if (argc == 3 && strcmp(argv[1], "--strtou32") == 0) {
1376 printf("'%s'-->%u\n", argv[2], strtou32_or_err(argv[2], "strtou32 failed"));
1377 return EXIT_SUCCESS;
1378 } else if (argc == 3 && strcmp(argv[1], "--strtos16") == 0) {
1379 printf("'%s'-->%hd\n", argv[2], strtos16_or_err(argv[2], "strtos16 failed"));
1380 return EXIT_SUCCESS;
1381 } else if (argc == 3 && strcmp(argv[1], "--strtou16") == 0) {
1382 printf("'%s'-->%hu\n", argv[2], strtou16_or_err(argv[2], "strtou16 failed"));
1383 return EXIT_SUCCESS;
1384
63bb0450
KZ
1385 } else if (argc == 4 && strcmp(argv[1], "--strchr-escaped") == 0) {
1386 printf("\"%s\" --> \"%s\"\n", argv[2], ul_strchr_escaped(argv[2], *argv[3]));
1387 return EXIT_SUCCESS;
1388
84825b16 1389 } else {
5d68f974
KZ
1390 fprintf(stderr, "usage: %1$s --size <number>[suffix]\n"
1391 " %1$s --cmp-paths <path> <path>\n"
1392 " %1$s --strdup-member <str> <str>\n"
27e6b3a9 1393 " %1$s --stralnumcmp <str> <str>\n"
d2246a5b 1394 " %1$s --cstrcasecmp <str> <str>\n"
84825b16 1395 " %1$s --normalize <str>\n"
9fc0f69c 1396 " %1$s --strto{s,u}{16,32,64} <str>\n",
5d68f974
KZ
1397 argv[0]);
1398 exit(EXIT_FAILURE);
1399 }
d4e89dea
KZ
1400
1401 return EXIT_FAILURE;
1402}
e8f7acb0 1403#endif /* TEST_PROGRAM_STRUTILS */