OLD | NEW |
1 /////////////////////////////////////////////////////////////////////////////// | 1 /////////////////////////////////////////////////////////////////////////////// |
2 // | 2 // |
3 /// \file util.c | 3 /// \file util.c |
4 /// \brief Miscellaneous utility functions | 4 /// \brief Miscellaneous utility functions |
5 // | 5 // |
6 // Author: Lasse Collin | 6 // Author: Lasse Collin |
7 // | 7 // |
8 // This file has been put into the public domain. | 8 // This file has been put into the public domain. |
9 // You can do whatever you want with this file. | 9 // You can do whatever you want with this file. |
10 // | 10 // |
11 /////////////////////////////////////////////////////////////////////////////// | 11 /////////////////////////////////////////////////////////////////////////////// |
12 | 12 |
13 #include "private.h" | 13 #include "private.h" |
14 #include <stdarg.h> | 14 #include <stdarg.h> |
15 | 15 |
16 | 16 |
| 17 /// Buffers for uint64_to_str() and uint64_to_nicestr() |
| 18 static char bufs[4][128]; |
| 19 |
| 20 /// Thousand separator support in uint64_to_str() and uint64_to_nicestr() |
| 21 static enum { UNKNOWN, WORKS, BROKEN } thousand = UNKNOWN; |
| 22 |
| 23 |
17 extern void * | 24 extern void * |
18 xrealloc(void *ptr, size_t size) | 25 xrealloc(void *ptr, size_t size) |
19 { | 26 { |
20 assert(size > 0); | 27 assert(size > 0); |
21 | 28 |
22 ptr = realloc(ptr, size); | 29 ptr = realloc(ptr, size); |
23 if (ptr == NULL) | 30 if (ptr == NULL) |
24 message_fatal("%s", strerror(errno)); | 31 message_fatal("%s", strerror(errno)); |
25 | 32 |
26 return ptr; | 33 return ptr; |
(...skipping 91 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
118 } | 125 } |
119 | 126 |
120 | 127 |
121 extern uint64_t | 128 extern uint64_t |
122 round_up_to_mib(uint64_t n) | 129 round_up_to_mib(uint64_t n) |
123 { | 130 { |
124 return (n >> 20) + ((n & ((UINT32_C(1) << 20) - 1)) != 0); | 131 return (n >> 20) + ((n & ((UINT32_C(1) << 20) - 1)) != 0); |
125 } | 132 } |
126 | 133 |
127 | 134 |
| 135 /// Check if thousand separator is supported. Run-time checking is easiest, |
| 136 /// because it seems to be sometimes lacking even on POSIXish system. |
| 137 static void |
| 138 check_thousand_sep(uint32_t slot) |
| 139 { |
| 140 if (thousand == UNKNOWN) { |
| 141 bufs[slot][0] = '\0'; |
| 142 snprintf(bufs[slot], sizeof(bufs[slot]), "%'u", 1U); |
| 143 thousand = bufs[slot][0] == '1' ? WORKS : BROKEN; |
| 144 } |
| 145 |
| 146 return; |
| 147 } |
| 148 |
| 149 |
128 extern const char * | 150 extern const char * |
129 uint64_to_str(uint64_t value, uint32_t slot) | 151 uint64_to_str(uint64_t value, uint32_t slot) |
130 { | 152 { |
131 // 2^64 with thousand separators is 26 bytes plus trailing '\0'. | |
132 static char bufs[4][32]; | |
133 | |
134 assert(slot < ARRAY_SIZE(bufs)); | 153 assert(slot < ARRAY_SIZE(bufs)); |
135 | 154 |
136 » static enum { UNKNOWN, WORKS, BROKEN } thousand = UNKNOWN; | 155 » check_thousand_sep(slot); |
137 » if (thousand == UNKNOWN) { | |
138 » » bufs[slot][0] = '\0'; | |
139 » » snprintf(bufs[slot], sizeof(bufs[slot]), "%'" PRIu64, | |
140 » » » » UINT64_C(1)); | |
141 » » thousand = bufs[slot][0] == '1' ? WORKS : BROKEN; | |
142 » } | |
143 | 156 |
144 if (thousand == WORKS) | 157 if (thousand == WORKS) |
145 snprintf(bufs[slot], sizeof(bufs[slot]), "%'" PRIu64, value); | 158 snprintf(bufs[slot], sizeof(bufs[slot]), "%'" PRIu64, value); |
146 else | 159 else |
147 snprintf(bufs[slot], sizeof(bufs[slot]), "%" PRIu64, value); | 160 snprintf(bufs[slot], sizeof(bufs[slot]), "%" PRIu64, value); |
148 | 161 |
149 return bufs[slot]; | 162 return bufs[slot]; |
150 } | 163 } |
151 | 164 |
152 | 165 |
153 extern const char * | 166 extern const char * |
154 uint64_to_nicestr(uint64_t value, enum nicestr_unit unit_min, | 167 uint64_to_nicestr(uint64_t value, enum nicestr_unit unit_min, |
155 enum nicestr_unit unit_max, bool always_also_bytes, | 168 enum nicestr_unit unit_max, bool always_also_bytes, |
156 uint32_t slot) | 169 uint32_t slot) |
157 { | 170 { |
158 assert(unit_min <= unit_max); | 171 assert(unit_min <= unit_max); |
159 assert(unit_max <= NICESTR_TIB); | 172 assert(unit_max <= NICESTR_TIB); |
| 173 assert(slot < ARRAY_SIZE(bufs)); |
| 174 |
| 175 check_thousand_sep(slot); |
160 | 176 |
161 enum nicestr_unit unit = NICESTR_B; | 177 enum nicestr_unit unit = NICESTR_B; |
162 » const char *str; | 178 » char *pos = bufs[slot]; |
| 179 » size_t left = sizeof(bufs[slot]); |
163 | 180 |
164 if ((unit_min == NICESTR_B && value < 10000) | 181 if ((unit_min == NICESTR_B && value < 10000) |
165 || unit_max == NICESTR_B) { | 182 || unit_max == NICESTR_B) { |
166 // The value is shown as bytes. | 183 // The value is shown as bytes. |
167 » » str = uint64_to_str(value, slot); | 184 » » if (thousand == WORKS) |
| 185 » » » my_snprintf(&pos, &left, "%'u", (unsigned int)value); |
| 186 » » else |
| 187 » » » my_snprintf(&pos, &left, "%u", (unsigned int)value); |
168 } else { | 188 } else { |
169 // Scale the value to a nicer unit. Unless unit_min and | 189 // Scale the value to a nicer unit. Unless unit_min and |
170 // unit_max limit us, we will show at most five significant | 190 // unit_max limit us, we will show at most five significant |
171 // digits with one decimal place. | 191 // digits with one decimal place. |
172 double d = (double)(value); | 192 double d = (double)(value); |
173 do { | 193 do { |
174 d /= 1024.0; | 194 d /= 1024.0; |
175 ++unit; | 195 ++unit; |
176 } while (unit < unit_min || (d > 9999.9 && unit < unit_max)); | 196 } while (unit < unit_min || (d > 9999.9 && unit < unit_max)); |
177 | 197 |
178 » » str = double_to_str(d); | 198 » » if (thousand == WORKS) |
| 199 » » » my_snprintf(&pos, &left, "%'.1f", d); |
| 200 » » else |
| 201 » » » my_snprintf(&pos, &left, "%.1f", d); |
179 } | 202 } |
180 | 203 |
181 static const char suffix[5][4] = { "B", "KiB", "MiB", "GiB", "TiB" }; | 204 static const char suffix[5][4] = { "B", "KiB", "MiB", "GiB", "TiB" }; |
| 205 my_snprintf(&pos, &left, " %s", suffix[unit]); |
182 | 206 |
183 » // Minimum buffer size: | 207 » if (always_also_bytes && value >= 10000) { |
184 » // 26 2^64 with thousand separators | 208 » » if (thousand == WORKS) |
185 » // 4 " KiB" | 209 » » » snprintf(pos, left, " (%'" PRIu64 " B)", value); |
186 » // 2 " (" | 210 » » else |
187 » // 26 2^64 with thousand separators | 211 » » » snprintf(pos, left, " (%" PRIu64 " B)", value); |
188 » // 3 " B)" | 212 » } |
189 » // 1 '\0' | |
190 » // 62 Total | |
191 » static char buf[4][64]; | |
192 » char *pos = buf[slot]; | |
193 » size_t left = sizeof(buf[slot]); | |
194 » my_snprintf(&pos, &left, "%s %s", str, suffix[unit]); | |
195 | 213 |
196 » if (always_also_bytes && value >= 10000) | 214 » return bufs[slot]; |
197 » » snprintf(pos, left, " (%s B)", uint64_to_str(value, slot)); | |
198 | |
199 » return buf[slot]; | |
200 } | 215 } |
201 | 216 |
202 | 217 |
203 extern const char * | |
204 double_to_str(double value) | |
205 { | |
206 static char buf[64]; | |
207 | |
208 static enum { UNKNOWN, WORKS, BROKEN } thousand = UNKNOWN; | |
209 if (thousand == UNKNOWN) { | |
210 buf[0] = '\0'; | |
211 snprintf(buf, sizeof(buf), "%'.1f", 2.0); | |
212 thousand = buf[0] == '2' ? WORKS : BROKEN; | |
213 } | |
214 | |
215 if (thousand == WORKS) | |
216 snprintf(buf, sizeof(buf), "%'.1f", value); | |
217 else | |
218 snprintf(buf, sizeof(buf), "%.1f", value); | |
219 | |
220 return buf; | |
221 } | |
222 | |
223 | |
224 extern void | 218 extern void |
225 my_snprintf(char **pos, size_t *left, const char *fmt, ...) | 219 my_snprintf(char **pos, size_t *left, const char *fmt, ...) |
226 { | 220 { |
227 va_list ap; | 221 va_list ap; |
228 va_start(ap, fmt); | 222 va_start(ap, fmt); |
229 const int len = vsnprintf(*pos, *left, fmt, ap); | 223 const int len = vsnprintf(*pos, *left, fmt, ap); |
230 va_end(ap); | 224 va_end(ap); |
231 | 225 |
232 // If an error occurred, we want the caller to think that the whole | 226 // If an error occurred, we want the caller to think that the whole |
233 // buffer was used. This way no more data will be written to the | 227 // buffer was used. This way no more data will be written to the |
234 » // buffer. We don't need better error handling here. | 228 » // buffer. We don't need better error handling here, although it |
| 229 » // is possible that the result looks garbage on the terminal if |
| 230 » // e.g. an UTF-8 character gets split. That shouldn't (easily) |
| 231 » // happen though, because the buffers used have some extra room. |
235 if (len < 0 || (size_t)(len) >= *left) { | 232 if (len < 0 || (size_t)(len) >= *left) { |
236 *left = 0; | 233 *left = 0; |
237 } else { | 234 } else { |
238 *pos += len; | 235 *pos += len; |
239 *left -= len; | 236 *left -= len; |
240 } | 237 } |
241 | 238 |
242 return; | 239 return; |
243 } | 240 } |
244 | 241 |
245 | 242 |
246 /* | |
247 /// \brief Simple quoting to get rid of ASCII control characters | |
248 /// | |
249 /// This is not so cool and locale-dependent, but should be good enough | |
250 /// At least we don't print any control characters on the terminal. | |
251 /// | |
252 extern char * | |
253 str_quote(const char *str) | |
254 { | |
255 size_t dest_len = 0; | |
256 bool has_ctrl = false; | |
257 | |
258 while (str[dest_len] != '\0') | |
259 if (*(unsigned char *)(str + dest_len++) < 0x20) | |
260 has_ctrl = true; | |
261 | |
262 char *dest = malloc(dest_len + 1); | |
263 if (dest != NULL) { | |
264 if (has_ctrl) { | |
265 for (size_t i = 0; i < dest_len; ++i) | |
266 if (*(unsigned char *)(str + i) < 0x20) | |
267 dest[i] = '?'; | |
268 else | |
269 dest[i] = str[i]; | |
270 | |
271 dest[dest_len] = '\0'; | |
272 | |
273 } else { | |
274 // Usually there are no control characters, | |
275 // so we can optimize. | |
276 memcpy(dest, str, dest_len + 1); | |
277 } | |
278 } | |
279 | |
280 return dest; | |
281 } | |
282 */ | |
283 | |
284 | |
285 extern bool | 243 extern bool |
286 is_empty_filename(const char *filename) | 244 is_empty_filename(const char *filename) |
287 { | 245 { |
288 if (filename[0] == '\0') { | 246 if (filename[0] == '\0') { |
289 message_error(_("Empty filename, skipping")); | 247 message_error(_("Empty filename, skipping")); |
290 return true; | 248 return true; |
291 } | 249 } |
292 | 250 |
293 return false; | 251 return false; |
294 } | 252 } |
(...skipping 16 matching lines...) Expand all Loading... |
311 is_tty_stdout(void) | 269 is_tty_stdout(void) |
312 { | 270 { |
313 const bool ret = isatty(STDOUT_FILENO); | 271 const bool ret = isatty(STDOUT_FILENO); |
314 | 272 |
315 if (ret) | 273 if (ret) |
316 message_error(_("Compressed data cannot be written to " | 274 message_error(_("Compressed data cannot be written to " |
317 "a terminal")); | 275 "a terminal")); |
318 | 276 |
319 return ret; | 277 return ret; |
320 } | 278 } |
OLD | NEW |