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