dte test coverage


Directory: ./
File: src/util/numtostr.c
Date: 2025-07-05 20:19:48
Exec Total Coverage
Lines: 109 109 100.0%
Functions: 12 12 100.0%
Branches: 40 40 100.0%

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