diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/slibc-human/humansize.c | 332 |
1 files changed, 214 insertions, 118 deletions
diff --git a/src/slibc-human/humansize.c b/src/slibc-human/humansize.c index 798cca6..cabd9ab 100644 --- a/src/slibc-human/humansize.c +++ b/src/slibc-human/humansize.c @@ -23,6 +23,204 @@ +#define SPRINTF(...) do if (m = sprintf(__VA_ARGS__), m < 0) goto fail; while (0) +#define SNPRINTF(...) do if (m = snprintf(__VA_ARGS__), m < 0) goto fail; while (0) +#define ADD_PREFIX(prefix, buffer, n) \ + do \ + if (prefix == 0) \ + buffer[n++] = 'B'; \ + else \ + { \ + buffer[n++] = prefixes[prefix]; \ + if (mode & HUMANSIZE_IEC_EXPLICIT) buffer[n++] = 'i'; \ + if (!(mode & HUMANSIZE_PREFIX_ONLY)) buffer[n++] = 'B'; \ + } \ + while (0) + + + +/** + * Convert a file size of file offset from machine representation to human representation. + * The representation is exact. + * + * @param buffer A buffer than shall be used if it is sufficiently large. + * @param bufsize The allocation size of `buffer`. + * Must be 0 if and only if `buffer` is `NULL`. + * @param mode Representation style, 0 for default. + * @param detail See documentation for the select value on `mode`. + * @param intraspacing Spacing between values and units. `NULL` or empty for none. + * This value should depend on language and context. For English + * this value should be "" or "-", but in for example Swedish it + * should always be " ". Hence this value is a string rather than + * a booleanic integer. + * @param interspacing Spacing between value–unit-pairs. `NULL` for default (" "). + * This value should depend on language and context. + * @param prefixes Prefix characters (including prefixless.) + * @param values Values for prefixes. + * @param buf Create intermediate write buffer. + * @return Human representation of the file size/offset, `NULL` on error. + * On success, the caller is responsible for deallocating the + * returned pointer, if and only if it is not `buffer`. + * + * @throws ENOMEM The process cannot allocate more memory. + */ +static char* humansize_exact(char* buffer, size_t bufsize, enum humansize_mode mode, int detail, + const char* restrict intraspacing, const char* restrict interspacing, size_t words, + const char* restrict prefixes, const size_t* restrict values, char* restrict buf) +{ + size_t i, n = 0; + char* new = NULL; + int m, saved_errno; + + if (detail == 0) + detail = 999; + + for (i = words; (i-- > 0) && detail--;) + { + /* Check non-zero only word. */ + if (!(values[i] || (!i && !n))) + continue; + + /* Add interspacing. */ + if (i != words) + { + memcpy(buffer + n, interspacing, strlen(interspacing) * sizeof(char)); + n += strlen(interspacing); + } + + /* Construct word (and intraspacing). */ + SPRINTF(buf, "%zu%s", values[i], intraspacing); + ADD_PREFIX(i, buf, m); + + /* Ensure the buffer is large enougth. */ + if (n + (size_t)m > bufsize / sizeof(char)) + { + bufsize = 7 * detail + strlen(interspacing) * (detail - 1) + 1; + new = malloc(bufsize *= sizeof(char)); + if (new == NULL) + goto fail; + memcpy(new, buffer, n * sizeof(char)); + buffer = new; + } + + /* Append word. */ + memcpy(buffer + n, buf, (size_t)m * sizeof(char)); + n += (size_t)m; + } + + return buffer[n] = 0, buffer; + fail: + saved_errno = errno; + free(new); + return errno = saved_errno, NULL; +} + + +/** + * Convert a file size of file offset from machine representation to human representation. + * The representation is rounded. + * + * @param buffer A buffer than shall be used if it is sufficiently large. + * @param bufsize The allocation size of `buffer`. + * Must be 0 if and only if `buffer` is `NULL`. + * @param mode Representation style, 0 for default. + * @param detail See documentation for the select value on `mode`. + * @param point The symbol to use for decimal points. `NULL` or empty for default. + * @param intraspacing Spacing between values and units. `NULL` or empty for none. + * This value should depend on language and context. For English + * this value should be "" or "-", but in for example Swedish it + * should always be " ". Hence this value is a string rather than + * a booleanic integer. + * @param interspacing Spacing between value–unit-pairs. `NULL` for default (" "). + * This value should depend on language and context. + * @param prefixes Prefix characters (including prefixless.) + * @param values Values for prefixes. + * @param buf Create intermediate write buffer. + * @param div K-multiple (1000 for SI, 1024 for IEC.) + * @return Human representation of the file size/offset, `NULL` on error. + * On success, the caller is responsible for deallocating the + * returned pointer, if and only if it is not `buffer`. + * + * @throws ENOMEM The process cannot allocate more memory. + */ +static char* humansize_round(char* buffer, size_t bufsize, enum humansize_mode mode, int detail, + const char* restrict point, const char* restrict intraspacing, + const char* restrict interspacing, size_t words, const char* restrict prefixes, + const size_t* restrict values, char* restrict buf, size_t div) +{ + double total = 0, dividend = 1; + size_t prefix = words - 1, i, n, det; + char* p; + char c; + int m, saved_errno; + + /* Get single-unit value. */ + while (words--) + total += ((double)values[words]) / dividend, dividend *= (double)div; + + /* Ensure the buffer is large enougth. */ + SNPRINTF(NULL, 0, "%.*lf%\zn", (detail < 0 ? 0 : detail), (double)total, (ssize_t*)&n); + if (n + strlen(point) + 3 - (detail < 0 ? 0 : 1) > bufsize / sizeof(char)) + { + bufsize = n + strlen(point) + 3 - (detail < 0 ? 0 : 1); + new = malloc(bufsize *= sizeof(char)); + if (new == NULL) + goto fail; + buffer = new; + } + + /* Construct string. */ + SPRINTF(buffer, "%.*lf", (detail < 0 ? 0 : detail), (double)total); + + /* Replace decimal point sign. */ + if ((p = strchr(buffer, '.'))) + { + if (detail <= 0) + n = (size_t)(p - buffer); + else + { + memmove(p + strlen(point), p + 1, (n - 1 - (size_t)(p - buffer)) * sizeof(char)); + memcpy(p, point, strlen(point) * sizeof(char)); + } + } + + /* Round value if detail < 0. */ + if ((detail < 0) && (n > 1)) + { + det = (size_t)-detail; + if (det >= n) + det = n - 1; + c = buffer[n - detail]; + for (i = n - detail; i < n; i++) + buffer[i] = '0'; + if (c >= '5') + { + buffer[n] = 0; + i = (size_t)atoll(buffer); + div = 10; + while (det--) + div *= 10; + i += div; + SPRINTF(buffer, "%zu%\zn", i, (ssize_t*)&n); + } + } + + /* Add prefix (and intraspacing). */ + if (*intraspacing) + { + memcpy(buffer + n, intraspacing, strlen(intraspacing) * sizeof(char)); + n += strlen(intraspacing); + } + ADD_PREFIX(prefix, buffer, n); + + return buffer[n] = 0, buffer; + fail: + saved_errno = errno; + free(new); + return errno = saved_errno, NULL; +} + + /** * Convert a file size of file offset from machine representation to human representation. * @@ -58,19 +256,18 @@ char* humansize(char* buffer, size_t bufsize, size_t size, enum humansize_mode m char prefixes[] = { '\0', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y' }; size_t values[sizeof(prefixes) / sizeof(*prefixes)] = { 0 }; - size_t div, i, n = 0, words = 0; - char* p; - char* new = NULL; + size_t div, i, words = 0; char* buf; - int m, saved_errno; - if (intraspacing == NULL) - intraspacing = ""; - buf = alloca((BUFFER_SIZE + strlen(intraspacing)) * sizeof(char)); + /* Default parameters. */ + if (intraspacing == NULL) intraspacing = ""; + if (interspacing == NULL) interspacing = " "; + if (!point || !*point) point = "."; - if (interspacing == NULL) - interspacing = " "; + /* Create intermediate write buffer. */ + buf = alloca((BUFFER_SIZE + strlen(intraspacing)) * sizeof(char)); + /* Get K-multiple and case of the kilo-prefix. */ switch (mode & 7) { case 0: @@ -88,131 +285,30 @@ char* humansize(char* buffer, size_t bufsize, size_t size, enum humansize_mode m goto invalid; } + /* Get value on prefixes. */ for (i = 0; size && (i < sizeof(values) / sizeof(*values)); i++) values[i] = size % div, size /= div, words++; words = words ? words : 1; + /* Construct string. */ switch (mode & 48) { case HUMANSIZE_EXACT: - if (detail < 0) goto invalid; - if (detail == 0) detail = 999; - for (i = words; (i-- > 0) && detail--;) - { - if (!(values[i] || (!i && !n))) - break; - if (i != words) - { - memcpy(buffer + n, interspacing, strlen(interspacing) * sizeof(char)); - n += strlen(interspacing); - } - if (m = sprintf(buf, "%zu%s", values[i], intraspacing), m < 0) - goto fail; - if (i == 0) - buf[m++] = 'B'; - else - { - buf[m++] = prefixes[i]; - if (mode & HUMANSIZE_IEC_EXPLICIT) buf[m++] = 'i'; - if (!(mode & HUMANSIZE_PREFIX_ONLY)) buf[m++] = 'B'; - } - if (n + (size_t)m > bufsize / sizeof(char)) - { - bufsize = 7 * detail + strlen(interspacing) * (detail - 1) + 1; - new = malloc(bufsize *= sizeof(char)); - if (new == NULL) - goto fail; - memcpy(new, buffer, n * sizeof(char)); - buffer = new; - } - memcpy(buffer + n, buf, (size_t)m * sizeof(char)); - n += (size_t)m; - } - buffer[n] = 0; - break; + if (detail < 0) + goto invalid; + return humansize_exact(buffer, bufsize, mode, detail, intraspacing, + interspacing, words, prefixes, values, buf); case 0: case HUMANSIZE_ROUND: - if (!point || !*point) - point = "."; - i = words - 1; - { - double total = 0, dividend = 1; - while (words--) - total += ((double)values[words]) / dividend, dividend *= (double)div; - m = snprintf(NULL, 0, "%.*lf%\zn", (detail < 0 ? 0 : detail), (double)total, (ssize_t*)&n); - if (m < 0) - goto fail; - if (n + strlen(point) + 3 - (detail < 0 ? 0 : 1) > bufsize / sizeof(char)) - { - bufsize = (size_t)m + strlen(point) + 3 - (detail < 0 ? 0 : 1); - new = malloc(bufsize * sizeof(char)); - if (new == NULL) - goto fail; - buffer = new; - } - m = sprintf(buffer, "%.*lf", (detail < 0 ? 0 : detail), (double)total); - if (m < 0) - goto fail; - if ((p = strchr(buffer, '.'))) - { - if (detail <= 0) - n = (size_t)(p - buffer); - else - { - memmove(p + strlen(point), p + 1, (n - 1 - (size_t)(p - buffer)) * sizeof(char)); - memcpy(p, point, strlen(point) * sizeof(char)); - } - } - if ((detail < 0) && (n > 1)) - { - char c; - size_t det = (size_t)-detail; - if (det >= n) - det = n - 1; - c = buffer[n - detail]; - for (i = n - detail; i < n; i++) - buffer[i] = '0'; - if (c >= '5') - { - buffer[n] = 0; - i = (size_t)atoll(buffer); - div = 10; - while (det--) - div *= 10; - i += div; - m = sprintf(buffer, "%zu%\zn", i, (ssize_t*)&n); - if (m < 0) - goto fail; - } - } - } - if (*intraspacing) - { - memcpy(buffer + n, intraspacing, strlen(intraspacing) * sizeof(char)); - m += strlen(intraspacing); - } - if (i == 0) - buffer[n++] = 'B'; - else - { - buffer[n++] = prefixes[i]; - if (mode & HUMANSIZE_IEC_EXPLICIT) buffer[n++] = 'i'; - if (!(mode & HUMANSIZE_PREFIX_ONLY)) buffer[n++] = 'B'; - } - buffer[n] = 0; - break; + return humansize_round(buffer, bufsize, mode, detail, point, intraspacing, + interspacing, words, prefixes, values, buf, div); default: goto invalid; } - return buffer; invalid: return errno = EINVAL, NULL; - fail: - saved_errno = errno; - free(new); - return errno = saved_errno, NULL; } |