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