Coverage Report

Created: 2026-08-30 07:13

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