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 |