dte test coverage


Directory: ./
File: src/util/numtostr.c
Date: 2025-12-03 13:13:24
Coverage Exec Excl Total
Lines: 100.0% 98 0 98
Functions: 100.0% 11 0 11
Branches: 100.0% 36 0 36

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 3 → 4 taken 1951 times.
✓ Branch 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 4 → 4 taken 1951 times.
✓ Branch 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 3 → 4 taken 10 times.
✓ Branch 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 7 → 7 taken 102 times.
✓ Branch 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 3 → 4 taken 37000 times.
✓ Branch 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 FILE_PERMISSIONS_BUFSIZE])
107 {
108 20 static_assert(FILE_PERMISSIONS_BUFSIZE >= 10);
109 20 static const char xmap[8] = "-xSs-xTt";
110
111 // Owner
112
2/2
✓ Branch 2 → 3 taken 14 times.
✓ Branch 2 → 4 taken 6 times.
20 buf[0] = (mode & S_IRUSR) ? 'r' : '-';
113
2/2
✓ Branch 4 → 5 taken 15 times.
✓ Branch 4 → 6 taken 5 times.
20 buf[1] = (mode & S_IWUSR) ? 'w' : '-';
114 20 buf[2] = xmap[((mode & S_IXUSR) >> 6) | ((mode & S_ISUID) >> 10)];
115
116 // Group
117
2/2
✓ Branch 6 → 7 taken 15 times.
✓ Branch 6 → 8 taken 5 times.
20 buf[3] = (mode & S_IRGRP) ? 'r' : '-';
118
2/2
✓ Branch 8 → 9 taken 16 times.
✓ Branch 8 → 10 taken 4 times.
20 buf[4] = (mode & S_IWGRP) ? 'w' : '-';
119 20 buf[5] = xmap[((mode & S_IXGRP) >> 3) | ((mode & S_ISGID) >> 9)];
120
121 // Others
122
2/2
✓ Branch 10 → 11 taken 14 times.
✓ Branch 10 → 12 taken 6 times.
20 buf[6] = (mode & S_IROTH) ? 'r' : '-';
123
2/2
✓ Branch 12 → 13 taken 15 times.
✓ Branch 12 → 14 taken 5 times.
20 buf[7] = (mode & S_IWOTH) ? 'w' : '-';
124 20 buf[8] = xmap[4 + ((mode & S_IXOTH) | ((mode & VTXBIT) >> 8))];
125
126 20 buf[9] = '\0';
127 20 return buf;
128 }
129
130 // Write an approximate, human-readable representation of `bytes` into
131 // `buf`, using IEC 80000-13 units (e.g. KiB, MiB, etc.) and (optionally)
132 // a 2 digit decimal fraction
133 65 char *human_readable_size(uintmax_t bytes, char buf[static HRSIZE_MAX])
134 {
135 65 unsigned int sigbits = umax_bitwidth(bytes);
136 65 unsigned int nr_unit_shifts = (sigbits - !!sigbits) / 10;
137 65 uintmax_t ipart = bytes >> (nr_unit_shifts * 10);
138 65 uintmax_t unit = ((uintmax_t)1) << (nr_unit_shifts * 10);
139 65 uintmax_t hundredth = unit / 100;
140 65 unsigned int fpart = 0;
141
142
2/2
✓ Branch 2 → 3 taken 57 times.
✓ Branch 2 → 5 taken 8 times.
65 if (hundredth) {
143 57 uintmax_t remainder = bytes & (unit - 1);
144 57 unsigned int f = remainder / hundredth;
145 57 ipart += (f > 99);
146
2/2
✓ Branch 3 → 4 taken 6 times.
✓ Branch 3 → 5 taken 51 times.
57 fpart = (f > 99) ? 0 : f;
147 }
148
149 65 size_t i = buf_umax_to_str(ipart, buf);
150
151
2/2
✓ Branch 6 → 7 taken 24 times.
✓ Branch 6 → 8 taken 41 times.
65 if (fpart > 0) {
152 24 buf[i++] = '.';
153 24 buf[i++] = '0' + ((fpart / 10) % 10);
154 24 buf[i++] = '0' + (fpart % 10);
155 }
156
157
2/2
✓ Branch 8 → 9 taken 57 times.
✓ Branch 8 → 10 taken 8 times.
65 if (nr_unit_shifts > 0) {
158 57 buf[i++] = ' ';
159 57 buf[i++] = filesize_unit_prefixes[nr_unit_shifts - 1];
160 57 buf[i++] = 'i';
161 57 buf[i++] = 'B';
162 }
163
164 65 buf[i] = '\0';
165 65 return buf;
166 }
167
168 // Like human_readable_size(), but also printing the exact number
169 // of bytes in parentheses, e.g. 1024 → "1 KiB (1024)"
170 6 char *filesize_to_str(uintmax_t bytes, char buf[static FILESIZE_STR_MAX])
171 {
172 6 human_readable_size(bytes, buf);
173
2/2
✓ Branch 3 → 4 taken 3 times.
✓ Branch 3 → 6 taken 3 times.
6 if (bytes < 1024) {
174 return buf;
175 }
176 3 size_t i = strlen(buf);
177 3 buf[i++] = ' ';
178 3 buf[i++] = '(';
179 3 i += buf_umax_to_str(bytes, buf + i);
180 3 buf[i++] = ')';
181 3 buf[i] = '\0';
182 3 return buf;
183 }
184
185 // Like human_readable_size(), but done in a way that's always round-trippable
186 // (without loss in precision) and thus only made as "human readable" as is
187 // possible under that constraint. While filesize_to_str() takes the approach
188 // of also including the exact number of bytes in parentheses, this function is
189 // for uses cases that call for a single, trivially parseable integer and unit.
190 // In practical terms this means that 1.5GiB (3 << 29) is printed as "1536MiB"
191 // and `(3 << 29) + 1` is printed as "1610612737".
192 18 char *filesize_to_str_precise(uintmax_t bytes, char buf[static PRECISE_FILESIZE_STR_MAX])
193 {
194
2/2
✓ Branch 2 → 3 taken 17 times.
✓ Branch 2 → 4 taken 1 time.
18 unsigned int tzcount = bytes ? umax_ctz(bytes) : 0; // Special case zero
195 18 unsigned int nr_unit_shifts = tzcount / 10;
196 18 unsigned int shift = nr_unit_shifts * 10;
197 18 size_t i = buf_umax_to_str(bytes >> shift, buf);
198
2/2
✓ Branch 5 → 6 taken 9 times.
✓ Branch 5 → 7 taken 9 times.
18 if (nr_unit_shifts) {
199 9 buf[i++] = filesize_unit_prefixes[nr_unit_shifts - 1];
200 9 buf[i++] = 'i';
201 9 buf[i++] = 'B';
202 }
203 18 buf[i] = '\0';
204 18 return buf;
205 }
206