1 // SPDX-License-Identifier: 0BSD 2 3 /////////////////////////////////////////////////////////////////////////////// 4 // 5 /// \file util.c 6 /// \brief Miscellaneous utility functions 7 // 8 // Author: Lasse Collin 9 // 10 /////////////////////////////////////////////////////////////////////////////// 11 12 #include "private.h" 13 #include <stdarg.h> 14 15 16 /// Buffers for uint64_to_str() and uint64_to_nicestr() 17 static char bufs[4][128]; 18 19 20 // Thousand separator support in uint64_to_str() and uint64_to_nicestr(): 21 // 22 // DJGPP 2.05 added support for thousands separators but it's broken 23 // at least under WinXP with Finnish locale that uses a non-breaking space 24 // as the thousands separator. Workaround by disabling thousands separators 25 // for DJGPP builds. 26 // 27 // MSVC doesn't support thousand separators. 28 #if defined(__DJGPP__) || defined(_MSC_VER) 29 # define FORMAT_THOUSAND_SEP(prefix, suffix) prefix suffix 30 # define check_thousand_sep(slot) do { } while (0) 31 #else 32 # define FORMAT_THOUSAND_SEP(prefix, suffix) ((thousand == WORKS) \ 33 ? prefix "'" suffix \ 34 : prefix suffix) 35 36 static enum { UNKNOWN, WORKS, BROKEN } thousand = UNKNOWN; 37 38 /// Check if thousands separator is supported. Run-time checking is easiest 39 /// because it seems to be sometimes lacking even on a POSIXish system. 40 /// Note that trying to use thousands separators when snprintf() doesn't 41 /// support them results in undefined behavior. This just has happened to 42 /// work well enough in practice. 43 /// 44 /// This must be called before using the FORMAT_THOUSAND_SEP macro. 45 static void 46 check_thousand_sep(uint32_t slot) 47 { 48 if (thousand == UNKNOWN) { 49 bufs[slot][0] = '\0'; 50 snprintf(bufs[slot], sizeof(bufs[slot]), "%'u", 1U); 51 thousand = bufs[slot][0] == '1' ? WORKS : BROKEN; 52 } 53 54 return; 55 } 56 #endif 57 58 59 extern void * 60 xrealloc(void *ptr, size_t size) 61 { 62 assert(size > 0); 63 64 // Save ptr so that we can free it if realloc fails. 65 // The point is that message_fatal ends up calling stdio functions 66 // which in some libc implementations might allocate memory from 67 // the heap. Freeing ptr improves the chances that there's free 68 // memory for stdio functions if they need it. 69 void *p = ptr; 70 ptr = realloc(ptr, size); 71 72 if (ptr == NULL) { 73 const int saved_errno = errno; 74 free(p); 75 message_fatal("%s", strerror(saved_errno)); 76 } 77 78 return ptr; 79 } 80 81 82 extern char * 83 xstrdup(const char *src) 84 { 85 assert(src != NULL); 86 const size_t size = strlen(src) + 1; 87 char *dest = xmalloc(size); 88 return memcpy(dest, src, size); 89 } 90 91 92 extern uint64_t 93 str_to_uint64(const char *name, const char *value, uint64_t min, uint64_t max) 94 { 95 uint64_t result = 0; 96 97 // Skip blanks. 98 while (*value == ' ' || *value == '\t') 99 ++value; 100 101 // Accept special value "max". Supporting "min" doesn't seem useful. 102 if (strcmp(value, "max") == 0) 103 return max; 104 105 if (*value < '0' || *value > '9') 106 message_fatal(_("%s: Value is not a non-negative " 107 "decimal integer"), value); 108 109 do { 110 // Don't overflow. 111 if (result > UINT64_MAX / 10) 112 goto error; 113 114 result *= 10; 115 116 // Another overflow check 117 const uint32_t add = (uint32_t)(*value - '0'); 118 if (UINT64_MAX - add < result) 119 goto error; 120 121 result += add; 122 ++value; 123 } while (*value >= '0' && *value <= '9'); 124 125 if (*value != '\0') { 126 // Look for suffix. Originally this supported both base-2 127 // and base-10, but since there seems to be little need 128 // for base-10 in this program, treat everything as base-2 129 // and also be more relaxed about the case of the first 130 // letter of the suffix. 131 uint64_t multiplier = 0; 132 if (*value == 'k' || *value == 'K') 133 multiplier = UINT64_C(1) << 10; 134 else if (*value == 'm' || *value == 'M') 135 multiplier = UINT64_C(1) << 20; 136 else if (*value == 'g' || *value == 'G') 137 multiplier = UINT64_C(1) << 30; 138 139 ++value; 140 141 // Allow also e.g. Ki, KiB, and KB. 142 if (*value != '\0' && strcmp(value, "i") != 0 143 && strcmp(value, "iB") != 0 144 && strcmp(value, "B") != 0) 145 multiplier = 0; 146 147 if (multiplier == 0) { 148 message(V_ERROR, _("%s: Invalid multiplier suffix"), 149 value - 1); 150 message_fatal(_("Valid suffixes are 'KiB' (2^10), " 151 "'MiB' (2^20), and 'GiB' (2^30).")); 152 } 153 154 // Don't overflow here either. 155 if (result > UINT64_MAX / multiplier) 156 goto error; 157 158 result *= multiplier; 159 } 160 161 if (result < min || result > max) 162 goto error; 163 164 return result; 165 166 error: 167 message_fatal(_("Value of the option '%s' must be in the range " 168 "[%" PRIu64 ", %" PRIu64 "]"), 169 name, min, max); 170 } 171 172 173 extern uint64_t 174 round_up_to_mib(uint64_t n) 175 { 176 return (n >> 20) + ((n & ((UINT32_C(1) << 20) - 1)) != 0); 177 } 178 179 180 extern const char * 181 uint64_to_str(uint64_t value, uint32_t slot) 182 { 183 assert(slot < ARRAY_SIZE(bufs)); 184 185 check_thousand_sep(slot); 186 187 snprintf(bufs[slot], sizeof(bufs[slot]), 188 FORMAT_THOUSAND_SEP("%", PRIu64), value); 189 190 return bufs[slot]; 191 } 192 193 194 extern const char * 195 uint64_to_nicestr(uint64_t value, enum nicestr_unit unit_min, 196 enum nicestr_unit unit_max, bool always_also_bytes, 197 uint32_t slot) 198 { 199 assert(unit_min <= unit_max); 200 assert(unit_max <= NICESTR_TIB); 201 assert(slot < ARRAY_SIZE(bufs)); 202 203 check_thousand_sep(slot); 204 205 enum nicestr_unit unit = NICESTR_B; 206 char *pos = bufs[slot]; 207 size_t left = sizeof(bufs[slot]); 208 209 if ((unit_min == NICESTR_B && value < 10000) 210 || unit_max == NICESTR_B) { 211 // The value is shown as bytes. 212 my_snprintf(&pos, &left, FORMAT_THOUSAND_SEP("%", "u"), 213 (unsigned int)value); 214 } else { 215 // Scale the value to a nicer unit. Unless unit_min and 216 // unit_max limit us, we will show at most five significant 217 // digits with one decimal place. 218 double d = (double)(value); 219 do { 220 d /= 1024.0; 221 ++unit; 222 } while (unit < unit_min || (d > 9999.9 && unit < unit_max)); 223 224 my_snprintf(&pos, &left, FORMAT_THOUSAND_SEP("%", ".1f"), d); 225 } 226 227 static const char suffix[5][4] = { "B", "KiB", "MiB", "GiB", "TiB" }; 228 my_snprintf(&pos, &left, " %s", suffix[unit]); 229 230 if (always_also_bytes && value >= 10000) 231 snprintf(pos, left, FORMAT_THOUSAND_SEP(" (%", PRIu64 " B)"), 232 value); 233 234 return bufs[slot]; 235 } 236 237 238 extern void 239 my_snprintf(char **pos, size_t *left, const char *fmt, ...) 240 { 241 va_list ap; 242 va_start(ap, fmt); 243 const int len = vsnprintf(*pos, *left, fmt, ap); 244 va_end(ap); 245 246 // If an error occurred, we want the caller to think that the whole 247 // buffer was used. This way no more data will be written to the 248 // buffer. We don't need better error handling here, although it 249 // is possible that the result looks garbage on the terminal if 250 // e.g. an UTF-8 character gets split. That shouldn't (easily) 251 // happen though, because the buffers used have some extra room. 252 if (len < 0 || (size_t)(len) >= *left) { 253 *left = 0; 254 } else { 255 *pos += len; 256 *left -= (size_t)(len); 257 } 258 259 return; 260 } 261 262 263 extern bool 264 is_tty(int fd) 265 { 266 #if defined(_WIN32) && !defined(__CYGWIN__) 267 // There is no need to check if handle == INVALID_HANDLE_VALUE 268 // because it will return false anyway when used in GetConsoleMode(). 269 // The resulting HANDLE is owned by the file descriptor. 270 // The HANDLE must not be closed here. 271 intptr_t handle = _get_osfhandle(fd); 272 DWORD mode; 273 274 // GetConsoleMode() is an easy way to tell if the HANDLE is a 275 // console or not. We do not care about the value of mode since we 276 // do not plan to use any further Windows console functions. 277 return GetConsoleMode((HANDLE)handle, &mode); 278 #else 279 return isatty(fd); 280 #endif 281 } 282 283 284 extern bool 285 is_tty_stdin(void) 286 { 287 const bool ret = is_tty(STDIN_FILENO); 288 289 if (ret) 290 message_error(_("Compressed data cannot be read from " 291 "a terminal")); 292 293 return ret; 294 } 295 296 297 extern bool 298 is_tty_stdout(void) 299 { 300 const bool ret = is_tty(STDOUT_FILENO); 301 302 if (ret) 303 message_error(_("Compressed data cannot be written to " 304 "a terminal")); 305 306 return ret; 307 } 308