]>
Commit | Line | Data |
---|---|---|
8abcf290 DB |
1 | /* |
2 | * Copyright (C) 2010 Karel Zak <kzak@redhat.com> | |
3 | * Copyright (C) 2010 Davidlohr Bueso <dave@gnu.org> | |
4 | */ | |
5 | ||
c4ecaf21 | 6 | #include <stdio.h> |
8abcf290 DB |
7 | #include <stdlib.h> |
8 | #include <inttypes.h> | |
9 | #include <ctype.h> | |
10 | #include <errno.h> | |
ce877f2d | 11 | #include <sys/stat.h> |
c4ecaf21 | 12 | #include <string.h> |
199e939d | 13 | #include <assert.h> |
c87638ad | 14 | |
eb76ca98 | 15 | #include "c.h" |
ab65d635 | 16 | #include "nls.h" |
5d2a9849 | 17 | #include "strutils.h" |
c87638ad | 18 | #include "bitops.h" |
8abcf290 | 19 | |
9c8b9fba RM |
20 | static int STRTOXX_EXIT_CODE = EXIT_FAILURE; |
21 | ||
22 | void strutils_set_exitcode(int ex) { | |
23 | STRTOXX_EXIT_CODE = ex; | |
24 | } | |
25 | ||
8abcf290 DB |
26 | static int do_scale_by_power (uintmax_t *x, int base, int power) |
27 | { | |
28 | while (power--) { | |
29 | if (UINTMAX_MAX / base < *x) | |
3643958f | 30 | return -ERANGE; |
8abcf290 DB |
31 | *x *= base; |
32 | } | |
33 | return 0; | |
34 | } | |
35 | ||
cf8de26a KZ |
36 | /* |
37 | * strtosize() - convert string to size (uintmax_t). | |
38 | * | |
39 | * Supported suffixes: | |
40 | * | |
41 | * XiB or X for 2^N | |
23106a29 | 42 | * where X = {K,M,G,T,P,E,Z,Y} |
cf8de26a KZ |
43 | * or X = {k,m,g,t,p,e} (undocumented for backward compatibility only) |
44 | * for example: | |
45 | * 10KiB = 10240 | |
46 | * 10K = 10240 | |
47 | * | |
48 | * XB for 10^N | |
23106a29 | 49 | * where X = {K,M,G,T,P,E,Z,Y} |
cf8de26a KZ |
50 | * for example: |
51 | * 10KB = 10000 | |
52 | * | |
9e930041 | 53 | * The optional 'power' variable returns number associated with used suffix |
23106a29 KZ |
54 | * {K,M,G,T,P,E,Z,Y} = {1,2,3,4,5,6,7,8}. |
55 | * | |
0e65dcde | 56 | * The function also supports decimal point, for example: |
6c6750db KZ |
57 | * 0.5MB = 500000 |
58 | * 0.5MiB = 512000 | |
59 | * | |
cf8de26a KZ |
60 | * Note that the function does not accept numbers with '-' (negative sign) |
61 | * prefix. | |
cf8de26a | 62 | */ |
23106a29 | 63 | int parse_size(const char *str, uintmax_t *res, int *power) |
cf8de26a | 64 | { |
d54b8315 KZ |
65 | const char *p; |
66 | char *end; | |
6c6750db KZ |
67 | uintmax_t x, frac = 0; |
68 | int base = 1024, rc = 0, pwr = 0, frac_zeros = 0; | |
23106a29 | 69 | |
cab84ea3 KZ |
70 | static const char *suf = "KMGTPEZY"; |
71 | static const char *suf2 = "kmgtpezy"; | |
23106a29 | 72 | const char *sp; |
cf8de26a KZ |
73 | |
74 | *res = 0; | |
75 | ||
5296523d KZ |
76 | if (!str || !*str) { |
77 | rc = -EINVAL; | |
cf8de26a | 78 | goto err; |
5296523d | 79 | } |
cf8de26a KZ |
80 | |
81 | /* Only positive numbers are acceptable | |
82 | * | |
83 | * Note that this check is not perfect, it would be better to | |
84 | * use lconv->negative_sign. But coreutils use the same solution, | |
85 | * so it's probably good enough... | |
86 | */ | |
d54b8315 | 87 | p = str; |
cf8de26a KZ |
88 | while (isspace((unsigned char) *p)) |
89 | p++; | |
3643958f KZ |
90 | if (*p == '-') { |
91 | rc = -EINVAL; | |
cf8de26a | 92 | goto err; |
3643958f | 93 | } |
cf8de26a | 94 | |
d54b8315 KZ |
95 | errno = 0, end = NULL; |
96 | x = strtoumax(str, &end, 0); | |
cf8de26a | 97 | |
d54b8315 | 98 | if (end == str || |
3643958f | 99 | (errno != 0 && (x == UINTMAX_MAX || x == 0))) { |
f643d334 | 100 | rc = errno ? -errno : -EINVAL; |
cf8de26a | 101 | goto err; |
3643958f | 102 | } |
d54b8315 | 103 | if (!end || !*end) |
cf8de26a | 104 | goto done; /* without suffix */ |
d54b8315 | 105 | p = end; |
cf8de26a KZ |
106 | |
107 | /* | |
108 | * Check size suffixes | |
109 | */ | |
6c6750db | 110 | check_suffix: |
25e3dd17 | 111 | if (*(p + 1) == 'i' && (*(p + 2) == 'B' || *(p + 2) == 'b') && !*(p + 3)) |
cf8de26a | 112 | base = 1024; /* XiB, 2^N */ |
25e3dd17 | 113 | else if ((*(p + 1) == 'B' || *(p + 1) == 'b') && !*(p + 2)) |
cf8de26a | 114 | base = 1000; /* XB, 10^N */ |
3643958f | 115 | else if (*(p + 1)) { |
6c6750db | 116 | struct lconv const *l = localeconv(); |
d54b8315 | 117 | const char *dp = l ? l->decimal_point : NULL; |
6c6750db KZ |
118 | size_t dpsz = dp ? strlen(dp) : 0; |
119 | ||
120 | if (frac == 0 && *p && dp && strncmp(dp, p, dpsz) == 0) { | |
d54b8315 | 121 | const char *fstr = p + dpsz; |
6c6750db | 122 | |
8deb8161 | 123 | for (p = fstr; *p == '0'; p++) |
6c6750db | 124 | frac_zeros++; |
482e0a07 KZ |
125 | fstr = p; |
126 | if (isdigit(*fstr)) { | |
127 | errno = 0, end = NULL; | |
128 | frac = strtoumax(fstr, &end, 0); | |
129 | if (end == fstr || | |
130 | (errno != 0 && (frac == UINTMAX_MAX || frac == 0))) { | |
131 | rc = errno ? -errno : -EINVAL; | |
132 | goto err; | |
133 | } | |
134 | } else | |
135 | end = (char *) p; | |
136 | ||
d54b8315 | 137 | if (frac && (!end || !*end)) { |
6c6750db KZ |
138 | rc = -EINVAL; |
139 | goto err; /* without suffix, but with frac */ | |
140 | } | |
d54b8315 | 141 | p = end; |
6c6750db KZ |
142 | goto check_suffix; |
143 | } | |
3643958f | 144 | rc = -EINVAL; |
cf8de26a | 145 | goto err; /* unexpected suffix */ |
3643958f | 146 | } |
6c6750db | 147 | |
23106a29 KZ |
148 | sp = strchr(suf, *p); |
149 | if (sp) | |
150 | pwr = (sp - suf) + 1; | |
151 | else { | |
152 | sp = strchr(suf2, *p); | |
153 | if (sp) | |
154 | pwr = (sp - suf2) + 1; | |
3643958f KZ |
155 | else { |
156 | rc = -EINVAL; | |
23106a29 | 157 | goto err; |
3643958f | 158 | } |
cf8de26a KZ |
159 | } |
160 | ||
23106a29 KZ |
161 | rc = do_scale_by_power(&x, base, pwr); |
162 | if (power) | |
163 | *power = pwr; | |
6c6750db | 164 | if (frac && pwr) { |
8c368dc6 KZ |
165 | int i; |
166 | uintmax_t frac_div = 10, frac_poz = 1, frac_base = 1; | |
167 | ||
168 | /* mega, giga, ... */ | |
169 | do_scale_by_power(&frac_base, base, pwr); | |
170 | ||
171 | /* maximal divisor for last digit (e.g. for 0.05 is | |
172 | * frac_div=100, for 0.054 is frac_div=1000, etc.) | |
173 | */ | |
174 | while (frac_div < frac) | |
175 | frac_div *= 10; | |
176 | ||
177 | /* 'frac' is without zeros (5 means 0.5 as well as 0.05) */ | |
178 | for (i = 0; i < frac_zeros; i++) | |
179 | frac_div *= 10; | |
180 | ||
181 | /* | |
182 | * Go backwardly from last digit and add to result what the | |
183 | * digit represents in the frac_base. For example 0.25G | |
184 | * | |
185 | * 5 means 1GiB / (100/5) | |
186 | * 2 means 1GiB / (10/2) | |
187 | */ | |
188 | do { | |
189 | unsigned int seg = frac % 10; /* last digit of the frac */ | |
190 | uintmax_t seg_div = frac_div / frac_poz; /* what represents the segment 1000, 100, .. */ | |
191 | ||
192 | frac /= 10; /* remove last digit from frac */ | |
193 | frac_poz *= 10; | |
194 | ||
195 | if (seg) | |
196 | x += frac_base / (seg_div / seg); | |
197 | } while (frac); | |
6c6750db | 198 | } |
cf8de26a KZ |
199 | done: |
200 | *res = x; | |
cf8de26a | 201 | err: |
f643d334 RM |
202 | if (rc < 0) |
203 | errno = -rc; | |
3643958f | 204 | return rc; |
cf8de26a KZ |
205 | } |
206 | ||
23106a29 KZ |
207 | int strtosize(const char *str, uintmax_t *res) |
208 | { | |
209 | return parse_size(str, res, NULL); | |
210 | } | |
211 | ||
61cbc8a3 | 212 | int isdigit_strend(const char *str, const char **end) |
416c43a9 KZ |
213 | { |
214 | const char *p; | |
215 | ||
216 | for (p = str; p && *p && isdigit((unsigned char) *p); p++); | |
217 | ||
61cbc8a3 KZ |
218 | if (end) |
219 | *end = p; | |
416c43a9 KZ |
220 | return p && p > str && !*p; |
221 | } | |
222 | ||
61cbc8a3 | 223 | int isxdigit_strend(const char *str, const char **end) |
40f00b4f KZ |
224 | { |
225 | const char *p; | |
226 | ||
227 | for (p = str; p && *p && isxdigit((unsigned char) *p); p++); | |
228 | ||
61cbc8a3 KZ |
229 | if (end) |
230 | *end = p; | |
231 | ||
40f00b4f KZ |
232 | return p && p > str && !*p; |
233 | } | |
234 | ||
30b294c4 KZ |
235 | /* |
236 | * parse_switch(argv[i], "on", "off", "yes", "no", NULL); | |
237 | */ | |
238 | int parse_switch(const char *arg, const char *errmesg, ...) | |
e5cf1476 | 239 | { |
30b294c4 KZ |
240 | const char *a, *b; |
241 | va_list ap; | |
242 | ||
243 | va_start(ap, errmesg); | |
244 | do { | |
245 | a = va_arg(ap, char *); | |
246 | if (!a) | |
247 | break; | |
248 | b = va_arg(ap, char *); | |
249 | if (!b) | |
250 | break; | |
251 | ||
74819b5f AH |
252 | if (strcmp(arg, a) == 0) { |
253 | va_end(ap); | |
30b294c4 | 254 | return 1; |
74819b5f AH |
255 | } else if (strcmp(arg, b) == 0) { |
256 | va_end(ap); | |
30b294c4 | 257 | return 0; |
74819b5f | 258 | } |
30b294c4 KZ |
259 | } while (1); |
260 | va_end(ap); | |
261 | ||
262 | errx(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, arg); | |
e5cf1476 | 263 | } |
416c43a9 | 264 | |
02887b73 DT |
265 | #ifndef HAVE_MEMPCPY |
266 | void *mempcpy(void *restrict dest, const void *restrict src, size_t n) | |
267 | { | |
268 | return ((char *)memcpy(dest, src, n)) + n; | |
269 | } | |
270 | #endif | |
271 | ||
8abcf290 DB |
272 | #ifndef HAVE_STRNLEN |
273 | size_t strnlen(const char *s, size_t maxlen) | |
274 | { | |
64af1a29 | 275 | size_t i; |
cf8de26a | 276 | |
8abcf290 DB |
277 | for (i = 0; i < maxlen; i++) { |
278 | if (s[i] == '\0') | |
64af1a29 | 279 | return i; |
8abcf290 DB |
280 | } |
281 | return maxlen; | |
282 | } | |
283 | #endif | |
cf8de26a | 284 | |
8abcf290 DB |
285 | #ifndef HAVE_STRNCHR |
286 | char *strnchr(const char *s, size_t maxlen, int c) | |
cf8de26a | 287 | { |
8abcf290 DB |
288 | for (; maxlen-- && *s != '\0'; ++s) |
289 | if (*s == (char)c) | |
290 | return (char *)s; | |
291 | return NULL; | |
292 | } | |
293 | #endif | |
cf8de26a | 294 | |
8abcf290 DB |
295 | #ifndef HAVE_STRNDUP |
296 | char *strndup(const char *s, size_t n) | |
297 | { | |
298 | size_t len = strnlen(s, n); | |
fea1cbf7 | 299 | char *new = malloc((len + 1) * sizeof(char)); |
8abcf290 DB |
300 | if (!new) |
301 | return NULL; | |
302 | new[len] = '\0'; | |
303 | return (char *) memcpy(new, s, len); | |
304 | } | |
305 | #endif | |
cf8de26a | 306 | |
54394eab HC |
307 | static uint32_t _strtou32_or_err(const char *str, const char *errmesg, int base); |
308 | static uint64_t _strtou64_or_err(const char *str, const char *errmesg, int base); | |
309 | ||
551dae40 | 310 | int16_t strtos16_or_err(const char *str, const char *errmesg) |
a9f97001 | 311 | { |
551dae40 KZ |
312 | int32_t num = strtos32_or_err(str, errmesg); |
313 | ||
ca8eff2a RM |
314 | if (num < INT16_MIN || num > INT16_MAX) { |
315 | errno = ERANGE; | |
316 | err(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, str); | |
317 | } | |
551dae40 KZ |
318 | return num; |
319 | } | |
320 | ||
54394eab | 321 | static uint16_t _strtou16_or_err(const char *str, const char *errmesg, int base) |
551dae40 | 322 | { |
54394eab | 323 | uint32_t num = _strtou32_or_err(str, errmesg, base); |
551dae40 | 324 | |
ca8eff2a RM |
325 | if (num > UINT16_MAX) { |
326 | errno = ERANGE; | |
327 | err(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, str); | |
328 | } | |
551dae40 KZ |
329 | return num; |
330 | } | |
331 | ||
54394eab HC |
332 | uint16_t strtou16_or_err(const char *str, const char *errmesg) |
333 | { | |
334 | return _strtou16_or_err(str, errmesg, 10); | |
335 | } | |
336 | ||
337 | uint16_t strtox16_or_err(const char *str, const char *errmesg) | |
338 | { | |
339 | return _strtou16_or_err(str, errmesg, 16); | |
340 | } | |
341 | ||
551dae40 KZ |
342 | int32_t strtos32_or_err(const char *str, const char *errmesg) |
343 | { | |
344 | int64_t num = strtos64_or_err(str, errmesg); | |
345 | ||
ca8eff2a RM |
346 | if (num < INT32_MIN || num > INT32_MAX) { |
347 | errno = ERANGE; | |
348 | err(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, str); | |
349 | } | |
551dae40 KZ |
350 | return num; |
351 | } | |
352 | ||
54394eab | 353 | static uint32_t _strtou32_or_err(const char *str, const char *errmesg, int base) |
551dae40 | 354 | { |
54394eab | 355 | uint64_t num = _strtou64_or_err(str, errmesg, base); |
551dae40 | 356 | |
ca8eff2a RM |
357 | if (num > UINT32_MAX) { |
358 | errno = ERANGE; | |
359 | err(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, str); | |
360 | } | |
551dae40 KZ |
361 | return num; |
362 | } | |
363 | ||
54394eab HC |
364 | uint32_t strtou32_or_err(const char *str, const char *errmesg) |
365 | { | |
366 | return _strtou32_or_err(str, errmesg, 10); | |
367 | } | |
368 | ||
369 | uint32_t strtox32_or_err(const char *str, const char *errmesg) | |
370 | { | |
371 | return _strtou32_or_err(str, errmesg, 16); | |
372 | } | |
373 | ||
551dae40 KZ |
374 | int64_t strtos64_or_err(const char *str, const char *errmesg) |
375 | { | |
376 | int64_t num; | |
a9f97001 SK |
377 | char *end = NULL; |
378 | ||
73b9e094 | 379 | errno = 0; |
a9f97001 SK |
380 | if (str == NULL || *str == '\0') |
381 | goto err; | |
551dae40 | 382 | num = strtoimax(str, &end, 10); |
a9f97001 SK |
383 | |
384 | if (errno || str == end || (end && *end)) | |
385 | goto err; | |
386 | ||
387 | return num; | |
a99c9130 | 388 | err: |
73b9e094 | 389 | if (errno == ERANGE) |
a99c9130 KZ |
390 | err(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, str); |
391 | ||
392 | errx(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, str); | |
a9f97001 | 393 | } |
551dae40 | 394 | |
54394eab | 395 | static uint64_t _strtou64_or_err(const char *str, const char *errmesg, int base) |
8abcf290 | 396 | { |
551dae40 | 397 | uintmax_t num; |
a99c9130 | 398 | char *end = NULL; |
cf8de26a | 399 | |
73b9e094 | 400 | errno = 0; |
a99c9130 KZ |
401 | if (str == NULL || *str == '\0') |
402 | goto err; | |
54394eab | 403 | num = strtoumax(str, &end, base); |
cf8de26a | 404 | |
a99c9130 KZ |
405 | if (errno || str == end || (end && *end)) |
406 | goto err; | |
8abcf290 | 407 | |
a99c9130 | 408 | return num; |
8abcf290 | 409 | err: |
73b9e094 | 410 | if (errno == ERANGE) |
a99c9130 | 411 | err(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, str); |
551dae40 | 412 | |
a99c9130 | 413 | errx(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, str); |
8abcf290 | 414 | } |
551dae40 | 415 | |
54394eab HC |
416 | uint64_t strtou64_or_err(const char *str, const char *errmesg) |
417 | { | |
418 | return _strtou64_or_err(str, errmesg, 10); | |
419 | } | |
420 | ||
421 | uint64_t strtox64_or_err(const char *str, const char *errmesg) | |
422 | { | |
423 | return _strtou64_or_err(str, errmesg, 16); | |
424 | } | |
551dae40 KZ |
425 | |
426 | double strtod_or_err(const char *str, const char *errmesg) | |
94d32126 | 427 | { |
551dae40 | 428 | double num; |
a99c9130 | 429 | char *end = NULL; |
94d32126 | 430 | |
73b9e094 | 431 | errno = 0; |
a99c9130 KZ |
432 | if (str == NULL || *str == '\0') |
433 | goto err; | |
551dae40 | 434 | num = strtod(str, &end); |
94d32126 | 435 | |
a99c9130 KZ |
436 | if (errno || str == end || (end && *end)) |
437 | goto err; | |
94d32126 | 438 | |
a99c9130 | 439 | return num; |
94d32126 | 440 | err: |
73b9e094 | 441 | if (errno == ERANGE) |
a99c9130 KZ |
442 | err(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, str); |
443 | ||
444 | errx(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, str); | |
94d32126 | 445 | } |
551dae40 KZ |
446 | |
447 | long strtol_or_err(const char *str, const char *errmesg) | |
448 | { | |
449 | long num; | |
450 | char *end = NULL; | |
451 | ||
73b9e094 | 452 | errno = 0; |
551dae40 KZ |
453 | if (str == NULL || *str == '\0') |
454 | goto err; | |
551dae40 KZ |
455 | num = strtol(str, &end, 10); |
456 | ||
457 | if (errno || str == end || (end && *end)) | |
458 | goto err; | |
459 | ||
460 | return num; | |
461 | err: | |
73b9e094 | 462 | if (errno == ERANGE) |
551dae40 | 463 | err(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, str); |
73b9e094 | 464 | |
551dae40 KZ |
465 | errx(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, str); |
466 | } | |
467 | ||
e53bc960 SK |
468 | unsigned long strtoul_or_err(const char *str, const char *errmesg) |
469 | { | |
470 | unsigned long num; | |
471 | char *end = NULL; | |
472 | ||
73b9e094 | 473 | errno = 0; |
e53bc960 SK |
474 | if (str == NULL || *str == '\0') |
475 | goto err; | |
e53bc960 SK |
476 | num = strtoul(str, &end, 10); |
477 | ||
478 | if (errno || str == end || (end && *end)) | |
479 | goto err; | |
480 | ||
481 | return num; | |
482 | err: | |
73b9e094 | 483 | if (errno == ERANGE) |
a99c9130 KZ |
484 | err(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, str); |
485 | ||
486 | errx(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, str); | |
487 | } | |
488 | ||
489 | uintmax_t strtosize_or_err(const char *str, const char *errmesg) | |
490 | { | |
491 | uintmax_t num; | |
492 | ||
493 | if (strtosize(str, &num) == 0) | |
494 | return num; | |
495 | ||
496 | if (errno) | |
497 | err(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, str); | |
498 | ||
499 | errx(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, str); | |
e53bc960 | 500 | } |
ce877f2d | 501 | |
477254da KZ |
502 | |
503 | void strtotimeval_or_err(const char *str, struct timeval *tv, const char *errmesg) | |
504 | { | |
505 | double user_input; | |
506 | ||
507 | user_input = strtod_or_err(str, errmesg); | |
508 | tv->tv_sec = (time_t) user_input; | |
509 | tv->tv_usec = (long)((user_input - tv->tv_sec) * 1000000); | |
510 | } | |
511 | ||
ce877f2d KZ |
512 | /* |
513 | * Converts stat->st_mode to ls(1)-like mode string. The size of "str" must | |
d9554c97 | 514 | * be 11 bytes. |
ce877f2d | 515 | */ |
5b82289b | 516 | char *xstrmode(mode_t mode, char *str) |
ce877f2d | 517 | { |
7015df49 KZ |
518 | unsigned short i = 0; |
519 | ||
ce877f2d | 520 | if (S_ISDIR(mode)) |
7015df49 | 521 | str[i++] = 'd'; |
ce877f2d | 522 | else if (S_ISLNK(mode)) |
7015df49 | 523 | str[i++] = 'l'; |
ce877f2d | 524 | else if (S_ISCHR(mode)) |
7015df49 | 525 | str[i++] = 'c'; |
ce877f2d | 526 | else if (S_ISBLK(mode)) |
7015df49 | 527 | str[i++] = 'b'; |
ce877f2d | 528 | else if (S_ISSOCK(mode)) |
7015df49 | 529 | str[i++] = 's'; |
ce877f2d | 530 | else if (S_ISFIFO(mode)) |
7015df49 | 531 | str[i++] = 'p'; |
ce877f2d | 532 | else if (S_ISREG(mode)) |
7015df49 | 533 | str[i++] = '-'; |
ce877f2d | 534 | |
7015df49 KZ |
535 | str[i++] = mode & S_IRUSR ? 'r' : '-'; |
536 | str[i++] = mode & S_IWUSR ? 'w' : '-'; | |
537 | str[i++] = (mode & S_ISUID | |
ce877f2d KZ |
538 | ? (mode & S_IXUSR ? 's' : 'S') |
539 | : (mode & S_IXUSR ? 'x' : '-')); | |
7015df49 KZ |
540 | str[i++] = mode & S_IRGRP ? 'r' : '-'; |
541 | str[i++] = mode & S_IWGRP ? 'w' : '-'; | |
542 | str[i++] = (mode & S_ISGID | |
ce877f2d KZ |
543 | ? (mode & S_IXGRP ? 's' : 'S') |
544 | : (mode & S_IXGRP ? 'x' : '-')); | |
7015df49 KZ |
545 | str[i++] = mode & S_IROTH ? 'r' : '-'; |
546 | str[i++] = mode & S_IWOTH ? 'w' : '-'; | |
547 | str[i++] = (mode & S_ISVTX | |
ce877f2d KZ |
548 | ? (mode & S_IXOTH ? 't' : 'T') |
549 | : (mode & S_IXOTH ? 'x' : '-')); | |
7015df49 | 550 | str[i] = '\0'; |
5b82289b KZ |
551 | |
552 | return str; | |
ce877f2d | 553 | } |
c4ecaf21 DB |
554 | |
555 | /* | |
cab84ea3 | 556 | * returns exponent (2^x=n) in range KiB..EiB (2^10..2^60) |
c4ecaf21 DB |
557 | */ |
558 | static int get_exp(uint64_t n) | |
559 | { | |
560 | int shft; | |
561 | ||
562 | for (shft = 10; shft <= 60; shft += 10) { | |
563 | if (n < (1ULL << shft)) | |
564 | break; | |
565 | } | |
566 | return shft - 10; | |
567 | } | |
568 | ||
5d2a9849 | 569 | char *size_to_human_string(int options, uint64_t bytes) |
c4ecaf21 DB |
570 | { |
571 | char buf[32]; | |
f9e05daf DR |
572 | int dec, exp; |
573 | uint64_t frac; | |
574 | const char *letters = "BKMGTPE"; | |
5d2a9849 | 575 | char suffix[sizeof(" KiB")], *psuf = suffix; |
c4ecaf21 DB |
576 | char c; |
577 | ||
5d2a9849 FC |
578 | if (options & SIZE_SUFFIX_SPACE) |
579 | *psuf++ = ' '; | |
580 | ||
07b94c9f | 581 | |
c4ecaf21 DB |
582 | exp = get_exp(bytes); |
583 | c = *(letters + (exp ? exp / 10 : 0)); | |
584 | dec = exp ? bytes / (1ULL << exp) : bytes; | |
585 | frac = exp ? bytes % (1ULL << exp) : 0; | |
586 | ||
5d2a9849 FC |
587 | *psuf++ = c; |
588 | ||
589 | if ((options & SIZE_SUFFIX_3LETTER) && (c != 'B')) { | |
590 | *psuf++ = 'i'; | |
591 | *psuf++ = 'B'; | |
592 | } | |
593 | ||
594 | *psuf = '\0'; | |
595 | ||
596 | /* fprintf(stderr, "exp: %d, unit: %c, dec: %d, frac: %jd\n", | |
597 | * exp, suffix[0], dec, frac); | |
f9e05daf DR |
598 | */ |
599 | ||
07b94c9f | 600 | /* round */ |
c4ecaf21 | 601 | if (frac) { |
07b94c9f KZ |
602 | if (options & SIZE_DECIMAL_2DIGITS) { |
603 | frac = (frac / (1ULL << (exp - 10)) + 5) / 10; | |
604 | if (frac % 10 == 0) | |
605 | frac /= 10; /* convert N.90 to N.9 */ | |
606 | } else { | |
607 | frac = (frac / (1ULL << (exp - 10)) + 50) / 100; | |
608 | if (frac == 10) | |
609 | dec++, frac = 0; | |
610 | } | |
c4ecaf21 DB |
611 | } |
612 | ||
613 | if (frac) { | |
614 | struct lconv const *l = localeconv(); | |
615 | char *dp = l ? l->decimal_point : NULL; | |
616 | ||
617 | if (!dp || !*dp) | |
618 | dp = "."; | |
7231fb2a | 619 | snprintf(buf, sizeof(buf), "%d%s%" PRIu64 "%s", dec, dp, frac, suffix); |
c4ecaf21 | 620 | } else |
5d2a9849 | 621 | snprintf(buf, sizeof(buf), "%d%s", dec, suffix); |
c4ecaf21 DB |
622 | |
623 | return strdup(buf); | |
624 | } | |
013bff51 | 625 | |
c87638ad KZ |
626 | /* |
627 | * Parses comma delimited list to array with IDs, for example: | |
628 | * | |
629 | * "aaa,bbb,ccc" --> ary[0] = FOO_AAA; | |
630 | * ary[1] = FOO_BBB; | |
631 | * ary[3] = FOO_CCC; | |
632 | * | |
633 | * The function name2id() provides conversion from string to ID. | |
634 | * | |
635 | * Returns: >= 0 : number of items added to ary[] | |
636 | * -1 : parse error or unknown item | |
637 | * -2 : arysz reached | |
638 | */ | |
639 | int string_to_idarray(const char *list, int ary[], size_t arysz, | |
640 | int (name2id)(const char *, size_t)) | |
641 | { | |
642 | const char *begin = NULL, *p; | |
b28f2267 | 643 | size_t n = 0; |
c87638ad KZ |
644 | |
645 | if (!list || !*list || !ary || !arysz || !name2id) | |
646 | return -1; | |
647 | ||
648 | for (p = list; p && *p; p++) { | |
649 | const char *end = NULL; | |
650 | int id; | |
651 | ||
179b923a CR |
652 | if (n >= arysz) |
653 | return -2; | |
c87638ad KZ |
654 | if (!begin) |
655 | begin = p; /* begin of the column name */ | |
656 | if (*p == ',') | |
657 | end = p; /* terminate the name */ | |
658 | if (*(p + 1) == '\0') | |
659 | end = p + 1; /* end of string */ | |
660 | if (!begin || !end) | |
661 | continue; | |
662 | if (end <= begin) | |
663 | return -1; | |
664 | ||
665 | id = name2id(begin, end - begin); | |
666 | if (id == -1) | |
667 | return -1; | |
668 | ary[ n++ ] = id; | |
c87638ad KZ |
669 | begin = NULL; |
670 | if (end && !*end) | |
671 | break; | |
672 | } | |
673 | return n; | |
674 | } | |
675 | ||
f5077b51 MB |
676 | /* |
677 | * Parses the array like string_to_idarray but if format is "+aaa,bbb" | |
678 | * it adds fields to array instead of replacing them. | |
679 | */ | |
680 | int string_add_to_idarray(const char *list, int ary[], size_t arysz, | |
40b17508 | 681 | size_t *ary_pos, int (name2id)(const char *, size_t)) |
f5077b51 MB |
682 | { |
683 | const char *list_add; | |
684 | int r; | |
685 | ||
40b17508 | 686 | if (!list || !*list || !ary_pos || *ary_pos > arysz) |
f5077b51 MB |
687 | return -1; |
688 | ||
689 | if (list[0] == '+') | |
690 | list_add = &list[1]; | |
691 | else { | |
692 | list_add = list; | |
693 | *ary_pos = 0; | |
694 | } | |
695 | ||
696 | r = string_to_idarray(list_add, &ary[*ary_pos], arysz - *ary_pos, name2id); | |
697 | if (r > 0) | |
698 | *ary_pos += r; | |
699 | return r; | |
700 | } | |
701 | ||
c87638ad KZ |
702 | /* |
703 | * LIST ::= <item> [, <item>] | |
704 | * | |
705 | * The <item> is translated to 'id' by name2id() function and the 'id' is used | |
455fe9a0 | 706 | * as a position in the 'ary' bit array. It means that the 'id' has to be in |
c87638ad KZ |
707 | * range <0..N> where N < sizeof(ary) * NBBY. |
708 | * | |
a883c634 | 709 | * Returns: 0 on success, <0 on error. |
c87638ad KZ |
710 | */ |
711 | int string_to_bitarray(const char *list, | |
712 | char *ary, | |
713 | int (*name2bit)(const char *, size_t)) | |
714 | { | |
715 | const char *begin = NULL, *p; | |
716 | ||
717 | if (!list || !name2bit || !ary) | |
718 | return -EINVAL; | |
719 | ||
720 | for (p = list; p && *p; p++) { | |
721 | const char *end = NULL; | |
722 | int bit; | |
723 | ||
724 | if (!begin) | |
725 | begin = p; /* begin of the level name */ | |
726 | if (*p == ',') | |
727 | end = p; /* terminate the name */ | |
728 | if (*(p + 1) == '\0') | |
729 | end = p + 1; /* end of string */ | |
730 | if (!begin || !end) | |
731 | continue; | |
732 | if (end <= begin) | |
733 | return -1; | |
734 | ||
735 | bit = name2bit(begin, end - begin); | |
736 | if (bit < 0) | |
737 | return bit; | |
738 | setbit(ary, bit); | |
739 | begin = NULL; | |
740 | if (end && !*end) | |
741 | break; | |
742 | } | |
5ef16771 KZ |
743 | return 0; |
744 | } | |
745 | ||
746 | /* | |
747 | * LIST ::= <item> [, <item>] | |
748 | * | |
749 | * The <item> is translated to 'id' by name2flag() function and the flags is | |
750 | * set to the 'mask' | |
751 | * | |
752 | * Returns: 0 on success, <0 on error. | |
753 | */ | |
754 | int string_to_bitmask(const char *list, | |
755 | unsigned long *mask, | |
756 | long (*name2flag)(const char *, size_t)) | |
757 | { | |
758 | const char *begin = NULL, *p; | |
759 | ||
760 | if (!list || !name2flag || !mask) | |
761 | return -EINVAL; | |
762 | ||
763 | for (p = list; p && *p; p++) { | |
764 | const char *end = NULL; | |
765 | long flag; | |
766 | ||
767 | if (!begin) | |
768 | begin = p; /* begin of the level name */ | |
769 | if (*p == ',') | |
770 | end = p; /* terminate the name */ | |
771 | if (*(p + 1) == '\0') | |
772 | end = p + 1; /* end of string */ | |
773 | if (!begin || !end) | |
774 | continue; | |
775 | if (end <= begin) | |
776 | return -1; | |
777 | ||
778 | flag = name2flag(begin, end - begin); | |
779 | if (flag < 0) | |
780 | return flag; /* error */ | |
781 | *mask |= flag; | |
782 | begin = NULL; | |
783 | if (end && !*end) | |
784 | break; | |
785 | } | |
c87638ad KZ |
786 | return 0; |
787 | } | |
013bff51 | 788 | |
a883c634 DB |
789 | /* |
790 | * Parse the lower and higher values in a string containing | |
791 | * "lower:higher" or "lower-higher" format. Note that either | |
af7df9ee DB |
792 | * the lower or the higher values may be missing, and the def |
793 | * value will be assigned to it by default. | |
a883c634 DB |
794 | * |
795 | * Returns: 0 on success, <0 on error. | |
796 | */ | |
af7df9ee | 797 | int parse_range(const char *str, int *lower, int *upper, int def) |
a883c634 DB |
798 | { |
799 | char *end = NULL; | |
800 | ||
801 | if (!str) | |
802 | return 0; | |
803 | ||
af7df9ee | 804 | *upper = *lower = def; |
a883c634 DB |
805 | errno = 0; |
806 | ||
807 | if (*str == ':') { /* <:N> */ | |
808 | str++; | |
809 | *upper = strtol(str, &end, 10); | |
810 | if (errno || !end || *end || end == str) | |
811 | return -1; | |
812 | } else { | |
813 | *upper = *lower = strtol(str, &end, 10); | |
814 | if (errno || !end || end == str) | |
815 | return -1; | |
816 | ||
817 | if (*end == ':' && !*(end + 1)) /* <M:> */ | |
2d47fa39 | 818 | *upper = def; |
a883c634 DB |
819 | else if (*end == '-' || *end == ':') { /* <M:N> <M-N> */ |
820 | str = end + 1; | |
821 | end = NULL; | |
822 | errno = 0; | |
823 | *upper = strtol(str, &end, 10); | |
824 | ||
825 | if (errno || !end || *end || end == str) | |
826 | return -1; | |
827 | } | |
828 | } | |
829 | return 0; | |
830 | } | |
831 | ||
d4e89dea | 832 | static const char *next_path_segment(const char *str, size_t *sz) |
b106d052 | 833 | { |
d4e89dea | 834 | const char *start, *p; |
b106d052 | 835 | |
d4e89dea KZ |
836 | start = str; |
837 | *sz = 0; | |
838 | while (start && *start == '/' && *(start + 1) == '/') | |
839 | start++; | |
840 | ||
841 | if (!start || !*start) | |
842 | return NULL; | |
843 | ||
844 | for (*sz = 1, p = start + 1; *p && *p != '/'; p++) { | |
845 | (*sz)++; | |
846 | } | |
847 | ||
848 | return start; | |
849 | } | |
b106d052 | 850 | |
d4e89dea KZ |
851 | int streq_paths(const char *a, const char *b) |
852 | { | |
853 | while (a && b) { | |
854 | size_t a_sz, b_sz; | |
855 | const char *a_seg = next_path_segment(a, &a_sz); | |
856 | const char *b_seg = next_path_segment(b, &b_sz); | |
857 | ||
858 | /* | |
859 | fprintf(stderr, "A>>>(%zu) '%s'\n", a_sz, a_seg); | |
860 | fprintf(stderr, "B>>>(%zu) '%s'\n", b_sz, b_seg); | |
861 | */ | |
b106d052 | 862 | |
d4e89dea KZ |
863 | /* end of the path */ |
864 | if (a_sz + b_sz == 0) | |
865 | return 1; | |
866 | ||
867 | /* ignore tailing slash */ | |
868 | if (a_sz + b_sz == 1 && | |
869 | ((a_seg && *a_seg == '/') || (b_seg && *b_seg == '/'))) | |
870 | return 1; | |
b106d052 | 871 | |
6d92f7d7 KZ |
872 | if (!a_seg || !b_seg) |
873 | break; | |
d4e89dea | 874 | if (a_sz != b_sz || strncmp(a_seg, b_seg, a_sz) != 0) |
6d92f7d7 | 875 | break; |
b106d052 | 876 | |
d4e89dea KZ |
877 | a = a_seg + a_sz; |
878 | b = b_seg + b_sz; | |
879 | }; | |
b106d052 | 880 | |
d4e89dea | 881 | return 0; |
b106d052 PU |
882 | } |
883 | ||
548b9714 KZ |
884 | char *strnappend(const char *s, const char *suffix, size_t b) |
885 | { | |
886 | size_t a; | |
887 | char *r; | |
888 | ||
889 | if (!s && !suffix) | |
890 | return strdup(""); | |
891 | if (!s) | |
892 | return strndup(suffix, b); | |
893 | if (!suffix) | |
894 | return strdup(s); | |
895 | ||
896 | assert(s); | |
897 | assert(suffix); | |
898 | ||
899 | a = strlen(s); | |
900 | if (b > ((size_t) -1) - a) | |
901 | return NULL; | |
902 | ||
903 | r = malloc(a + b + 1); | |
904 | if (!r) | |
905 | return NULL; | |
906 | ||
907 | memcpy(r, s, a); | |
908 | memcpy(r + a, suffix, b); | |
909 | r[a+b] = 0; | |
910 | ||
911 | return r; | |
912 | } | |
913 | ||
914 | char *strappend(const char *s, const char *suffix) | |
915 | { | |
916 | return strnappend(s, suffix, suffix ? strlen(suffix) : 0); | |
917 | } | |
918 | ||
3c201431 KZ |
919 | char *strfappend(const char *s, const char *format, ...) |
920 | { | |
921 | va_list ap; | |
922 | char *val, *res; | |
923 | int sz; | |
924 | ||
925 | va_start(ap, format); | |
926 | sz = vasprintf(&val, format, ap); | |
927 | va_end(ap); | |
928 | ||
929 | if (sz < 0) | |
930 | return NULL; | |
931 | ||
932 | res = strnappend(s, val, sz); | |
933 | free(val); | |
934 | return res; | |
935 | } | |
548b9714 KZ |
936 | |
937 | static size_t strcspn_escaped(const char *s, const char *reject) | |
938 | { | |
939 | int escaped = 0; | |
940 | int n; | |
941 | ||
942 | for (n=0; s[n]; n++) { | |
943 | if (escaped) | |
944 | escaped = 0; | |
945 | else if (s[n] == '\\') | |
946 | escaped = 1; | |
947 | else if (strchr(reject, s[n])) | |
948 | break; | |
949 | } | |
950 | ||
951 | /* if s ends in \, return index of previous char */ | |
952 | return n - escaped; | |
953 | } | |
954 | ||
955 | /* Split a string into words. */ | |
956 | const char *split(const char **state, size_t *l, const char *separator, int quoted) | |
957 | { | |
958 | const char *current; | |
959 | ||
960 | current = *state; | |
961 | ||
962 | if (!*current) { | |
963 | assert(**state == '\0'); | |
964 | return NULL; | |
965 | } | |
966 | ||
967 | current += strspn(current, separator); | |
968 | if (!*current) { | |
969 | *state = current; | |
970 | return NULL; | |
971 | } | |
972 | ||
973 | if (quoted && strchr("\'\"", *current)) { | |
974 | char quotechars[2] = {*current, '\0'}; | |
975 | ||
976 | *l = strcspn_escaped(current + 1, quotechars); | |
977 | if (current[*l + 1] == '\0' || current[*l + 1] != quotechars[0] || | |
978 | (current[*l + 2] && !strchr(separator, current[*l + 2]))) { | |
979 | /* right quote missing or garbage at the end */ | |
980 | *state = current; | |
981 | return NULL; | |
982 | } | |
983 | *state = current++ + *l + 2; | |
984 | } else if (quoted) { | |
985 | *l = strcspn_escaped(current, separator); | |
986 | if (current[*l] && !strchr(separator, current[*l])) { | |
987 | /* unfinished escape */ | |
988 | *state = current; | |
989 | return NULL; | |
990 | } | |
991 | *state = current + *l; | |
992 | } else { | |
993 | *l = strcspn(current, separator); | |
994 | *state = current + *l; | |
995 | } | |
996 | ||
997 | return current; | |
998 | } | |
999 | ||
3e5a5455 SK |
1000 | /* Rewind file pointer forward to new line. */ |
1001 | int skip_fline(FILE *fp) | |
1002 | { | |
d551d668 | 1003 | int ch; |
3e5a5455 SK |
1004 | |
1005 | do { | |
1006 | if ((ch = fgetc(fp)) == EOF) | |
1007 | return 1; | |
1008 | if (ch == '\n') | |
1009 | return 0; | |
1010 | } while (1); | |
1011 | } | |
548b9714 | 1012 | |
e8f7acb0 | 1013 | #ifdef TEST_PROGRAM_STRUTILS |
013bff51 | 1014 | |
d4e89dea | 1015 | static int test_strutils_sizes(int argc, char *argv[]) |
013bff51 KZ |
1016 | { |
1017 | uintmax_t size = 0; | |
5d2a9849 | 1018 | char *hum, *hum2; |
013bff51 | 1019 | |
d4e89dea KZ |
1020 | if (argc < 2) |
1021 | return EXIT_FAILURE; | |
013bff51 KZ |
1022 | |
1023 | if (strtosize(argv[1], &size)) | |
1024 | errx(EXIT_FAILURE, "invalid size '%s' value", argv[1]); | |
1025 | ||
5d2a9849 FC |
1026 | hum = size_to_human_string(SIZE_SUFFIX_1LETTER, size); |
1027 | hum2 = size_to_human_string(SIZE_SUFFIX_3LETTER | | |
1028 | SIZE_SUFFIX_SPACE, size); | |
f9e05daf | 1029 | |
5d2a9849 | 1030 | printf("%25s : %20ju : %8s : %12s\n", argv[1], size, hum, hum2); |
f9e05daf | 1031 | free(hum); |
5d2a9849 | 1032 | free(hum2); |
f9e05daf | 1033 | |
958d2b71 | 1034 | return EXIT_SUCCESS; |
013bff51 | 1035 | } |
d4e89dea KZ |
1036 | |
1037 | static int test_strutils_cmp_paths(int argc, char *argv[]) | |
1038 | { | |
1039 | int rc = streq_paths(argv[1], argv[2]); | |
1040 | ||
1041 | if (argc < 3) | |
1042 | return EXIT_FAILURE; | |
1043 | ||
1044 | printf("%s: '%s' '%s'\n", rc == 1 ? "YES" : "NOT", argv[1], argv[2]); | |
1045 | return EXIT_SUCCESS; | |
1046 | } | |
1047 | ||
1048 | int main(int argc, char *argv[]) | |
1049 | { | |
1050 | if (argc == 3 && strcmp(argv[1], "--size") == 0) | |
1051 | return test_strutils_sizes(argc - 1, argv + 1); | |
1052 | ||
1053 | else if (argc == 4 && strcmp(argv[1], "--cmp-paths") == 0) | |
1054 | return test_strutils_cmp_paths(argc - 1, argv + 1); | |
1055 | ||
1056 | else { | |
1057 | fprintf(stderr, "usage: %1$s --size <number>[suffix]\n" | |
1058 | " %1$s --cmp-paths <path> <path>\n", | |
1059 | argv[0]); | |
1060 | exit(EXIT_FAILURE); | |
1061 | } | |
1062 | ||
1063 | return EXIT_FAILURE; | |
1064 | } | |
e8f7acb0 | 1065 | #endif /* TEST_PROGRAM_STRUTILS */ |