| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | // Locale-independent integer to string conversion. | ||
| 2 | // Copyright © Craig Barnes. | ||
| 3 | // SPDX-License-Identifier: GPL-2.0-only | ||
| 4 | |||
| 5 | #include <string.h> | ||
| 6 | #include <sys/stat.h> | ||
| 7 | #include "numtostr.h" | ||
| 8 | #include "bit.h" | ||
| 9 | #include "debug.h" | ||
| 10 | |||
| 11 | const char hextable[32] = | ||
| 12 | "0123456789ABCDEF" | ||
| 13 | "0123456789abcdef"; | ||
| 14 | |||
| 15 | static const char filesize_unit_prefixes[8] = "KMGTPEZY"; | ||
| 16 | |||
| 17 | 1259 | static size_t umax_count_base10_digits(uintmax_t x) | |
| 18 | { | ||
| 19 | 1259 | size_t digits = 0; | |
| 20 | 3210 | do { | |
| 21 | 3210 | x /= 10; | |
| 22 | 3210 | digits++; | |
| 23 |
2/2✓ Branch 0 (3→4) taken 1951 times.
✓ Branch 1 (3→5) taken 1259 times.
|
3210 | } while (x); |
| 24 | 1259 | return digits; | |
| 25 | } | ||
| 26 | |||
| 27 | // Writes the decimal string representation of `x` into `buf`, | ||
| 28 | // which must have enough space available for a known/constant | ||
| 29 | // value of `x` or `DECIMAL_STR_MAX(x)` bytes for arbitrary values. | ||
| 30 | // Returns the number of bytes (digits) written. | ||
| 31 | 1259 | size_t buf_umax_to_str(uintmax_t x, char *buf) | |
| 32 | { | ||
| 33 | 1259 | const size_t ndigits = umax_count_base10_digits(x); | |
| 34 | 1259 | size_t i = ndigits; | |
| 35 | 1259 | buf[i--] = '\0'; | |
| 36 | 3210 | do { | |
| 37 | 3210 | buf[i--] = (x % 10) + '0'; | |
| 38 |
2/2✓ Branch 0 (4→4) taken 1951 times.
✓ Branch 1 (4→5) taken 1259 times.
|
3210 | } while (x /= 10); |
| 39 | 1259 | return ndigits; | |
| 40 | } | ||
| 41 | |||
| 42 | // Like buf_umax_to_str(), but writing a hexadecimal string and | ||
| 43 | // needing `HEX_STR_MAX(x)` bytes of buffer space for arbitrary | ||
| 44 | // values. A minimum number of digits can also be specified, e.g. | ||
| 45 | // for use cases like "U+000A". | ||
| 46 | 16 | size_t buf_umax_to_hex_str(uintmax_t x, char *buf, size_t min_digits) | |
| 47 | { | ||
| 48 |
2/2✓ Branch 0 (3→4) taken 10 times.
✓ Branch 1 (3→6) taken 6 times.
|
16 | const size_t ndigits = MAX(min_digits, umax_count_base16_digits(x)); |
| 49 | 10 | BUG_ON(ndigits < 1); | |
| 50 | 16 | size_t i = ndigits; | |
| 51 | 16 | buf[i--] = '\0'; | |
| 52 | 118 | do { | |
| 53 | 118 | unsigned int nibble = x & 0xF; | |
| 54 | 118 | buf[i] = hextable[nibble]; | |
| 55 | 118 | x >>= 4; | |
| 56 |
2/2✓ Branch 0 (7→7) taken 102 times.
✓ Branch 1 (7→8) taken 16 times.
|
118 | } while (i--); |
| 57 | 16 | return ndigits; | |
| 58 | } | ||
| 59 | |||
| 60 | // Writes the decimal string representation of `x` into a static | ||
| 61 | // buffer and returns a pointer to it. Unlike buf_umax_to_str(), | ||
| 62 | // this can be done without counting the number of digits first, | ||
| 63 | // by beginning at the end of the buffer and returning a pointer | ||
| 64 | // to the "first" (last written) byte offset. | ||
| 65 | 12050 | const char *umax_to_str(uintmax_t x) | |
| 66 | { | ||
| 67 | 12050 | static char buf[DECIMAL_STR_MAX(x)]; | |
| 68 | 12050 | size_t i = sizeof(buf) - 2; | |
| 69 | 49050 | do { | |
| 70 | 49050 | buf[i--] = (x % 10) + '0'; | |
| 71 |
2/2✓ Branch 0 (3→4) taken 37000 times.
✓ Branch 1 (3→5) taken 12050 times.
|
49050 | } while (x /= 10); |
| 72 | 12050 | return &buf[i + 1]; | |
| 73 | } | ||
| 74 | |||
| 75 | 12035 | const char *uint_to_str(unsigned int x) | |
| 76 | { | ||
| 77 | 12035 | return umax_to_str(x); | |
| 78 | } | ||
| 79 | |||
| 80 | 2 | const char *ulong_to_str(unsigned long x) | |
| 81 | { | ||
| 82 | 2 | return umax_to_str(x); | |
| 83 | } | ||
| 84 | |||
| 85 | 1158 | size_t buf_uint_to_str(unsigned int x, char *buf) | |
| 86 | { | ||
| 87 | 1158 | return buf_umax_to_str(x, buf); | |
| 88 | } | ||
| 89 | |||
| 90 | #ifdef S_ISVTX | ||
| 91 | # define VTXBIT (S_ISVTX) | ||
| 92 | #else | ||
| 93 | # define VTXBIT ((mode_t)0) | ||
| 94 | #endif | ||
| 95 | |||
| 96 | /* | ||
| 97 | * Write the string representation of permission bits from `mode` into `buf`. | ||
| 98 | * This follows the POSIX ls(1) format, but excludes the "is a directory" | ||
| 99 | * clause for the T/t field (as permitted by the spec). | ||
| 100 | * | ||
| 101 | * See also: | ||
| 102 | * | ||
| 103 | * • https://pubs.opengroup.org/onlinepubs/9699919799/utilities/ls.html#tag_20_73_10:~:text=three%20character%20positions | ||
| 104 | * • https://gnu.org/software/coreutils/manual/html_node/What-information-is-listed.html#index-long-ls-format | ||
| 105 | */ | ||
| 106 | 20 | char *file_permissions_to_str(mode_t mode, char buf[static 10]) | |
| 107 | { | ||
| 108 | 20 | static const char xmap[8] = "-xSs-xTt"; | |
| 109 | |||
| 110 | // Owner | ||
| 111 |
2/2✓ Branch 0 (2→3) taken 14 times.
✓ Branch 1 (2→4) taken 6 times.
|
20 | buf[0] = (mode & S_IRUSR) ? 'r' : '-'; |
| 112 |
2/2✓ Branch 0 (4→5) taken 15 times.
✓ Branch 1 (4→6) taken 5 times.
|
20 | buf[1] = (mode & S_IWUSR) ? 'w' : '-'; |
| 113 | 20 | buf[2] = xmap[((mode & S_IXUSR) >> 6) | ((mode & S_ISUID) >> 10)]; | |
| 114 | |||
| 115 | // Group | ||
| 116 |
2/2✓ Branch 0 (6→7) taken 15 times.
✓ Branch 1 (6→8) taken 5 times.
|
20 | buf[3] = (mode & S_IRGRP) ? 'r' : '-'; |
| 117 |
2/2✓ Branch 0 (8→9) taken 16 times.
✓ Branch 1 (8→10) taken 4 times.
|
20 | buf[4] = (mode & S_IWGRP) ? 'w' : '-'; |
| 118 | 20 | buf[5] = xmap[((mode & S_IXGRP) >> 3) | ((mode & S_ISGID) >> 9)]; | |
| 119 | |||
| 120 | // Others | ||
| 121 |
2/2✓ Branch 0 (10→11) taken 14 times.
✓ Branch 1 (10→12) taken 6 times.
|
20 | buf[6] = (mode & S_IROTH) ? 'r' : '-'; |
| 122 |
2/2✓ Branch 0 (12→13) taken 15 times.
✓ Branch 1 (12→14) taken 5 times.
|
20 | buf[7] = (mode & S_IWOTH) ? 'w' : '-'; |
| 123 | 20 | buf[8] = xmap[4 + ((mode & S_IXOTH) | ((mode & VTXBIT) >> 8))]; | |
| 124 | |||
| 125 | 20 | buf[9] = '\0'; | |
| 126 | 20 | return buf; | |
| 127 | } | ||
| 128 | |||
| 129 | // Write an approximate, human-readable representation of `bytes` into | ||
| 130 | // `buf`, using IEC 80000-13 units (e.g. KiB, MiB, etc.) and (optionally) | ||
| 131 | // a 2 digit decimal fraction | ||
| 132 | 65 | char *human_readable_size(uintmax_t bytes, char buf[static HRSIZE_MAX]) | |
| 133 | { | ||
| 134 | 65 | unsigned int sigbits = umax_bitwidth(bytes); | |
| 135 | 65 | unsigned int nr_unit_shifts = (sigbits - !!sigbits) / 10; | |
| 136 | 65 | uintmax_t ipart = bytes >> (nr_unit_shifts * 10); | |
| 137 | 65 | uintmax_t unit = ((uintmax_t)1) << (nr_unit_shifts * 10); | |
| 138 | 65 | uintmax_t hundredth = unit / 100; | |
| 139 | 65 | unsigned int fpart = 0; | |
| 140 | |||
| 141 |
2/2✓ Branch 0 (2→3) taken 57 times.
✓ Branch 1 (2→5) taken 8 times.
|
65 | if (hundredth) { |
| 142 | 57 | uintmax_t remainder = bytes & (unit - 1); | |
| 143 | 57 | unsigned int f = remainder / hundredth; | |
| 144 | 57 | ipart += (f > 99); | |
| 145 |
2/2✓ Branch 0 (3→4) taken 6 times.
✓ Branch 1 (3→5) taken 51 times.
|
57 | fpart = (f > 99) ? 0 : f; |
| 146 | } | ||
| 147 | |||
| 148 | 65 | size_t i = buf_umax_to_str(ipart, buf); | |
| 149 | |||
| 150 |
2/2✓ Branch 0 (6→7) taken 24 times.
✓ Branch 1 (6→8) taken 41 times.
|
65 | if (fpart > 0) { |
| 151 | 24 | buf[i++] = '.'; | |
| 152 | 24 | buf[i++] = '0' + ((fpart / 10) % 10); | |
| 153 | 24 | buf[i++] = '0' + (fpart % 10); | |
| 154 | } | ||
| 155 | |||
| 156 |
2/2✓ Branch 0 (8→9) taken 57 times.
✓ Branch 1 (8→10) taken 8 times.
|
65 | if (nr_unit_shifts > 0) { |
| 157 | 57 | buf[i++] = ' '; | |
| 158 | 57 | buf[i++] = filesize_unit_prefixes[nr_unit_shifts - 1]; | |
| 159 | 57 | buf[i++] = 'i'; | |
| 160 | 57 | buf[i++] = 'B'; | |
| 161 | } | ||
| 162 | |||
| 163 | 65 | buf[i] = '\0'; | |
| 164 | 65 | return buf; | |
| 165 | } | ||
| 166 | |||
| 167 | // Like human_readable_size(), but also printing the exact number | ||
| 168 | // of bytes in parentheses, e.g. 1024 → "1 KiB (1024)" | ||
| 169 | 6 | char *filesize_to_str(uintmax_t bytes, char buf[static FILESIZE_STR_MAX]) | |
| 170 | { | ||
| 171 | 6 | human_readable_size(bytes, buf); | |
| 172 |
2/2✓ Branch 0 (3→4) taken 3 times.
✓ Branch 1 (3→6) taken 3 times.
|
6 | if (bytes < 1024) { |
| 173 | return buf; | ||
| 174 | } | ||
| 175 | 3 | size_t i = strlen(buf); | |
| 176 | 3 | buf[i++] = ' '; | |
| 177 | 3 | buf[i++] = '('; | |
| 178 | 3 | i += buf_umax_to_str(bytes, buf + i); | |
| 179 | 3 | buf[i++] = ')'; | |
| 180 | 3 | buf[i] = '\0'; | |
| 181 | 3 | return buf; | |
| 182 | } | ||
| 183 | |||
| 184 | // Like human_readable_size(), but done in a way that's always round-trippable | ||
| 185 | // (without loss in precision) and thus only made as "human readable" as is | ||
| 186 | // possible under that constraint. While filesize_to_str() takes the approach | ||
| 187 | // of also including the exact number of bytes in parentheses, this function is | ||
| 188 | // for uses cases that call for a single, trivially parseable integer and unit. | ||
| 189 | // In practical terms this means that 1.5GiB (3 << 29) is printed as "1536MiB" | ||
| 190 | // and `(3 << 29) + 1` is printed as "1610612737". | ||
| 191 | 18 | char *filesize_to_str_precise(uintmax_t bytes, char buf[static PRECISE_FILESIZE_STR_MAX]) | |
| 192 | { | ||
| 193 |
2/2✓ Branch 0 (2→3) taken 17 times.
✓ Branch 1 (2→4) taken 1 times.
|
18 | unsigned int tzcount = bytes ? umax_ctz(bytes) : 0; // Special case zero |
| 194 | 18 | unsigned int nr_unit_shifts = tzcount / 10; | |
| 195 | 18 | unsigned int shift = nr_unit_shifts * 10; | |
| 196 | 18 | size_t i = buf_umax_to_str(bytes >> shift, buf); | |
| 197 |
2/2✓ Branch 0 (5→6) taken 9 times.
✓ Branch 1 (5→7) taken 9 times.
|
18 | if (nr_unit_shifts) { |
| 198 | 9 | buf[i++] = filesize_unit_prefixes[nr_unit_shifts - 1]; | |
| 199 | 9 | buf[i++] = 'i'; | |
| 200 | 9 | buf[i++] = 'B'; | |
| 201 | } | ||
| 202 | 18 | buf[i] = '\0'; | |
| 203 | 18 | return buf; | |
| 204 | } | ||
| 205 |