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 | const char hextab_lower[16] = "0123456789abcdef"; | ||
13 | const char hextab_upper[16] = "0123456789ABCDEF"; | ||
14 | |||
15 | 1078 | static size_t umax_count_base10_digits(uintmax_t x) | |
16 | { | ||
17 | 1078 | size_t digits = 0; | |
18 | 2746 | do { | |
19 | 2746 | x /= 10; | |
20 | 2746 | digits++; | |
21 |
2/2✓ Branch 0 (3→4) taken 1668 times.
✓ Branch 1 (3→5) taken 1078 times.
|
2746 | } while (x); |
22 | 1078 | return digits; | |
23 | } | ||
24 | |||
25 | // Writes the decimal string representation of `x` into `buf`, | ||
26 | // which must have enough space available for a known/constant | ||
27 | // value of `x` or `DECIMAL_STR_MAX(x)` bytes for arbitrary values. | ||
28 | // Returns the number of bytes (digits) written. | ||
29 | 1078 | size_t buf_umax_to_str(uintmax_t x, char *buf) | |
30 | { | ||
31 | 1078 | const size_t ndigits = umax_count_base10_digits(x); | |
32 | 1078 | size_t i = ndigits; | |
33 | 1078 | buf[i--] = '\0'; | |
34 | 2746 | do { | |
35 | 2746 | buf[i--] = (x % 10) + '0'; | |
36 |
2/2✓ Branch 0 (4→4) taken 1668 times.
✓ Branch 1 (4→5) taken 1078 times.
|
2746 | } while (x /= 10); |
37 | 1078 | return ndigits; | |
38 | } | ||
39 | |||
40 | // Like buf_umax_to_str(), but writing a hexadecimal string and | ||
41 | // needing `HEX_STR_MAX(x)` bytes of buffer space for arbitrary | ||
42 | // values. A minimum number of digits can also be specified, e.g. | ||
43 | // for use cases like "U+000A". | ||
44 | 16 | size_t buf_umax_to_hex_str(uintmax_t x, char *buf, size_t min_digits) | |
45 | { | ||
46 |
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)); |
47 | 10 | BUG_ON(ndigits < 1); | |
48 | 16 | size_t i = ndigits; | |
49 | 16 | buf[i--] = '\0'; | |
50 | 118 | do { | |
51 | 118 | unsigned int nibble = x & 0xF; | |
52 | 118 | buf[i] = hextab_upper[nibble]; | |
53 | 118 | x >>= 4; | |
54 |
2/2✓ Branch 0 (7→7) taken 102 times.
✓ Branch 1 (7→8) taken 16 times.
|
118 | } while (i--); |
55 | 16 | return ndigits; | |
56 | } | ||
57 | |||
58 | // Writes the decimal string representation of `x` into a static | ||
59 | // buffer and returns a pointer to it. Unlike buf_umax_to_str(), | ||
60 | // this can be done without counting the number of digits first, | ||
61 | // by beginning at the end of the buffer and returning a pointer | ||
62 | // to the "first" (last written) byte offset. | ||
63 | 12033 | const char *umax_to_str(uintmax_t x) | |
64 | { | ||
65 | 12033 | static char buf[DECIMAL_STR_MAX(x)]; | |
66 | 12033 | size_t i = sizeof(buf) - 2; | |
67 | 49018 | do { | |
68 | 49018 | buf[i--] = (x % 10) + '0'; | |
69 |
2/2✓ Branch 0 (3→4) taken 36985 times.
✓ Branch 1 (3→5) taken 12033 times.
|
49018 | } while (x /= 10); |
70 | 12033 | return &buf[i + 1]; | |
71 | } | ||
72 | |||
73 | // Like buf_umax_to_str() but for uint8_t values (1-3 digits) | ||
74 | 28 | size_t buf_u8_to_str(uint8_t x, char *buf) | |
75 | { | ||
76 | 28 | size_t ndigits = 1; | |
77 |
2/2✓ Branch 0 (2→3) taken 15 times.
✓ Branch 1 (2→4) taken 13 times.
|
28 | if (x >= 100) { |
78 | 15 | buf[2] = (x % 10) + '0'; | |
79 | 15 | x /= 10; | |
80 | 15 | ndigits++; | |
81 | } | ||
82 | |||
83 |
2/2✓ Branch 0 (4→5) taken 24 times.
✓ Branch 1 (4→6) taken 4 times.
|
28 | if (x >= 10) { |
84 | 24 | buf[1] = (x % 10) + '0'; | |
85 | 24 | x /= 10; | |
86 | 24 | ndigits++; | |
87 | } | ||
88 | |||
89 | 28 | buf[0] = (x % 10) + '0'; | |
90 | 28 | return ndigits; | |
91 | } | ||
92 | |||
93 | 12021 | const char *uint_to_str(unsigned int x) | |
94 | { | ||
95 | 12021 | return umax_to_str(x); | |
96 | } | ||
97 | |||
98 | 2 | const char *ulong_to_str(unsigned long x) | |
99 | { | ||
100 | 2 | return umax_to_str(x); | |
101 | } | ||
102 | |||
103 | 995 | size_t buf_uint_to_str(unsigned int x, char *buf) | |
104 | { | ||
105 | 995 | return buf_umax_to_str(x, buf); | |
106 | } | ||
107 | |||
108 | #ifdef S_ISVTX | ||
109 | # define VTXBIT (S_ISVTX) | ||
110 | #else | ||
111 | # define VTXBIT ((mode_t)0) | ||
112 | #endif | ||
113 | |||
114 | /* | ||
115 | * Write the string representation of permission bits from `mode` into `buf`. | ||
116 | * This follows the POSIX ls(1) format, but excludes the "is a directory" | ||
117 | * clause for the T/t field (as permitted by the spec). | ||
118 | * | ||
119 | * See also: | ||
120 | * | ||
121 | * • https://pubs.opengroup.org/onlinepubs/9699919799/utilities/ls.html#tag_20_73_10:~:text=three%20character%20positions | ||
122 | * • https://gnu.org/software/coreutils/manual/html_node/What-information-is-listed.html#index-long-ls-format | ||
123 | */ | ||
124 | 20 | char *file_permissions_to_str(mode_t mode, char buf[10]) | |
125 | { | ||
126 | 20 | static const char xmap[8] = "-xSs-xTt"; | |
127 | |||
128 | // Owner | ||
129 |
2/2✓ Branch 0 (2→3) taken 14 times.
✓ Branch 1 (2→4) taken 6 times.
|
20 | buf[0] = (mode & S_IRUSR) ? 'r' : '-'; |
130 |
2/2✓ Branch 0 (4→5) taken 15 times.
✓ Branch 1 (4→6) taken 5 times.
|
20 | buf[1] = (mode & S_IWUSR) ? 'w' : '-'; |
131 | 20 | buf[2] = xmap[((mode & S_IXUSR) >> 6) | ((mode & S_ISUID) >> 10)]; | |
132 | |||
133 | // Group | ||
134 |
2/2✓ Branch 0 (6→7) taken 15 times.
✓ Branch 1 (6→8) taken 5 times.
|
20 | buf[3] = (mode & S_IRGRP) ? 'r' : '-'; |
135 |
2/2✓ Branch 0 (8→9) taken 16 times.
✓ Branch 1 (8→10) taken 4 times.
|
20 | buf[4] = (mode & S_IWGRP) ? 'w' : '-'; |
136 | 20 | buf[5] = xmap[((mode & S_IXGRP) >> 3) | ((mode & S_ISGID) >> 9)]; | |
137 | |||
138 | // Others | ||
139 |
2/2✓ Branch 0 (10→11) taken 14 times.
✓ Branch 1 (10→12) taken 6 times.
|
20 | buf[6] = (mode & S_IROTH) ? 'r' : '-'; |
140 |
2/2✓ Branch 0 (12→13) taken 15 times.
✓ Branch 1 (12→14) taken 5 times.
|
20 | buf[7] = (mode & S_IWOTH) ? 'w' : '-'; |
141 | 20 | buf[8] = xmap[4 + ((mode & S_IXOTH) | ((mode & VTXBIT) >> 8))]; | |
142 | |||
143 | 20 | buf[9] = '\0'; | |
144 | 20 | return buf; | |
145 | } | ||
146 | |||
147 | // Write an approximate, human-readable representation of `bytes` into | ||
148 | // `buf`, using IEC 80000-13 units (e.g. KiB, MiB, etc.) and (optionally) | ||
149 | // a 2 digit decimal fraction | ||
150 | 65 | char *human_readable_size(uintmax_t bytes, char buf[HRSIZE_MAX]) | |
151 | { | ||
152 | 65 | static const char suffixes[8] = "KMGTPEZY"; | |
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++] = suffixes[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 |