/** * slibc — Yet another C library * Copyright © 2015 Mattias Andrée (maandree@member.fsf.org) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include /** * Get prefix-value. * * @param str Pointer to the string, will be updated to point to the end of the unit. * @param mode How to parse ambiguous units. * @return The multiple for the value, 0 on error. * * @throws EINVAL The prefix is unrecognised. * @throws ERANGE The prefix is too large. */ __attribute__((nonnull)) static size_t prefix(char** restrict str, enum machinesize_mode mode) { #define P(A, B) case A: case B: power++ char* p = *str; size_t power = 0; size_t base = 0; size_t rc = 1; switch (*p++) { P('Y', 'y'); P('Z', 'z'); P('E', 'e'); P('P', 'p'); P('T', 't'); P('G', 'g'); P('M', 'm'); P('k', 'K'); case 'B': case 'b': break; default: return errno = EINVAL, (size_t)0; } if (power == 0) goto done; if ((*p == 'i') || (*p == 'I')) base = 1024, p++; if ((*p == 'B') || (*p == 'b')) { p++; if ((!base) && (mode == (MACHINESIZE_SI | MACHINESIZE_IEC))) base = 1000; } if (!base) base = (mode == MACHINESIZE_SI) ? 1000 : 1024; while (power--) OVERFLOW(umull, rc, base, &rc, ERANGE, (size_t)0); done: return *str = p, rc; #undef P } /** * Parses a human representation of storage size or file offset. * * If no unit is used, bytes are assumed. If you rather it be * (for example) kilobytes, you can multiply it if * `strpbrk(str, "qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM") == NULL`. * * @etymology Convert to (machine)-representation: `(size)_t`. * * @param size Output parameter for the value, must not be `NULL`. * @param str The value to parse, must not `NULL`. * @param mode How to parse ambiguous strings, 0 for default. * @param space Characters to ignore (thousand-group delimiters). * Supports UTF-8. `NULL` for default. Default value * is " '". * @param point Decimal pointer chracters. Supports UTF-8. `NULL` * for default. Default value is ",.". * @return Zero on success, -1 on error. * * @throws EINVAL If `mode` is invalid. * @throws EINVAL If `str` is not parseable. * @throws ERANGE If the value is too range to fit in a `size_t`. * * @since Always. */ int machinesize(size_t* restrict size, const char* restrict str, enum machinesize_mode mode, const char* restrict space, const char* restrict point) { size_t r = 0; size_t word; long double dword; size_t u; const char* p; const char* q; int started = 0; int pluses = 0; int have_unitless = 0; size_t words = 0; if (space == NULL) space = " '"; if (point == NULL) point = ".,"; if (mode == 0) mode = MACHINESIZE_SI | MACHINESIZE_IEC; if (mode > 3) goto invalid; for (p = str; *p;) if (strchr(" \t+", *p)) { if ((pluses += (*p++ == '+')) > 1) goto invalid; } else if ((q = machinefloat(&dword, p, space, point))) { p = q, words++, pluses = 0, started = 1; while (strchr(" \t-", *p)) p++; if (isalpha(*p) == 0) u = 1, have_unitless = 1; else u = prefix(&p, mode); if (u == 0) return -1; dword *= (long double)u; if (dword > (long double)SIZE_MAX) return errno = ERANGE, -1; word = (size_t)dword; OVERFLOW(uaddl, word, r, &r, ERANGE, -1); } else return -1; if ((!started) || (have_unitless && (words > 1))) goto invalid; return *size = r, 0; invalid: return errno = EINVAL, -1; }