| 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 |