1 /////////////////////////////////////////////////////////////////////////////// 2 // 3 /// \file util.c 4 /// \brief Miscellaneous utility functions 5 // 6 // Author: Lasse Collin 7 // 8 // This file has been put into the public domain. 9 // You can do whatever you want with this file. 10 // 11 /////////////////////////////////////////////////////////////////////////////// 12 13 #include "private.h" 14 #include <stdarg.h> 15 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 24 extern void * 25 xrealloc(void *ptr, size_t size) 26 { 27 assert(size > 0); 28 29 ptr = realloc(ptr, size); 30 if (ptr == NULL) 31 message_fatal("%s", strerror(errno)); 32 33 return ptr; 34 } 35 36 37 extern char * 38 xstrdup(const char *src) 39 { 40 assert(src != NULL); 41 const size_t size = strlen(src) + 1; 42 char *dest = xmalloc(size); 43 return memcpy(dest, src, size); 44 } 45 46 47 extern uint64_t 48 str_to_uint64(const char *name, const char *value, uint64_t min, uint64_t max) 49 { 50 uint64_t result = 0; 51 52 // Skip blanks. 53 while (*value == ' ' || *value == '\t') 54 ++value; 55 56 // Accept special value "max". Supporting "min" doesn't seem useful. 57 if (strcmp(value, "max") == 0) 58 return max; 59 60 if (*value < '0' || *value > '9') 61 message_fatal(_("%s: Value is not a non-negative " 62 "decimal integer"), value); 63 64 do { 65 // Don't overflow. 66 if (result > UINT64_MAX / 10) 67 goto error; 68 69 result *= 10; 70 71 // Another overflow check 72 const uint32_t add = *value - '0'; 73 if (UINT64_MAX - add < result) 74 goto error; 75 76 result += add; 77 ++value; 78 } while (*value >= '0' && *value <= '9'); 79 80 if (*value != '\0') { 81 // Look for suffix. Originally this supported both base-2 82 // and base-10, but since there seems to be little need 83 // for base-10 in this program, treat everything as base-2 84 // and also be more relaxed about the case of the first 85 // letter of the suffix. 86 uint64_t multiplier = 0; 87 if (*value == 'k' || *value == 'K') 88 multiplier = UINT64_C(1) << 10; 89 else if (*value == 'm' || *value == 'M') 90 multiplier = UINT64_C(1) << 20; 91 else if (*value == 'g' || *value == 'G') 92 multiplier = UINT64_C(1) << 30; 93 94 ++value; 95 96 // Allow also e.g. Ki, KiB, and KB. 97 if (*value != '\0' && strcmp(value, "i") != 0 98 && strcmp(value, "iB") != 0 99 && strcmp(value, "B") != 0) 100 multiplier = 0; 101 102 if (multiplier == 0) { 103 message(V_ERROR, _("%s: Invalid multiplier suffix"), 104 value - 1); 105 message_fatal(_("Valid suffixes are `KiB' (2^10), " 106 "`MiB' (2^20), and `GiB' (2^30).")); 107 } 108 109 // Don't overflow here either. 110 if (result > UINT64_MAX / multiplier) 111 goto error; 112 113 result *= multiplier; 114 } 115 116 if (result < min || result > max) 117 goto error; 118 119 return result; 120 121 error: 122 message_fatal(_("Value of the option `%s' must be in the range " 123 "[%" PRIu64 ", %" PRIu64 "]"), 124 name, min, max); 125 } 126 127 128 extern uint64_t 129 round_up_to_mib(uint64_t n) 130 { 131 return (n >> 20) + ((n & ((UINT32_C(1) << 20) - 1)) != 0); 132 } 133 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 150 extern const char * 151 uint64_to_str(uint64_t value, uint32_t slot) 152 { 153 assert(slot < ARRAY_SIZE(bufs)); 154 155 check_thousand_sep(slot); 156 157 if (thousand == WORKS) 158 snprintf(bufs[slot], sizeof(bufs[slot]), "%'" PRIu64, value); 159 else 160 snprintf(bufs[slot], sizeof(bufs[slot]), "%" PRIu64, value); 161 162 return bufs[slot]; 163 } 164 165 166 extern const char * 167 uint64_to_nicestr(uint64_t value, enum nicestr_unit unit_min, 168 enum nicestr_unit unit_max, bool always_also_bytes, 169 uint32_t slot) 170 { 171 assert(unit_min <= unit_max); 172 assert(unit_max <= NICESTR_TIB); 173 assert(slot < ARRAY_SIZE(bufs)); 174 175 check_thousand_sep(slot); 176 177 enum nicestr_unit unit = NICESTR_B; 178 char *pos = bufs[slot]; 179 size_t left = sizeof(bufs[slot]); 180 181 if ((unit_min == NICESTR_B && value < 10000) 182 || unit_max == NICESTR_B) { 183 // The value is shown as bytes. 184 if (thousand == WORKS) 185 my_snprintf(&pos, &left, "%'u", (unsigned int)value); 186 else 187 my_snprintf(&pos, &left, "%u", (unsigned int)value); 188 } else { 189 // Scale the value to a nicer unit. Unless unit_min and 190 // unit_max limit us, we will show at most five significant 191 // digits with one decimal place. 192 double d = (double)(value); 193 do { 194 d /= 1024.0; 195 ++unit; 196 } while (unit < unit_min || (d > 9999.9 && unit < unit_max)); 197 198 if (thousand == WORKS) 199 my_snprintf(&pos, &left, "%'.1f", d); 200 else 201 my_snprintf(&pos, &left, "%.1f", d); 202 } 203 204 static const char suffix[5][4] = { "B", "KiB", "MiB", "GiB", "TiB" }; 205 my_snprintf(&pos, &left, " %s", suffix[unit]); 206 207 if (always_also_bytes && value >= 10000) { 208 if (thousand == WORKS) 209 snprintf(pos, left, " (%'" PRIu64 " B)", value); 210 else 211 snprintf(pos, left, " (%" PRIu64 " B)", value); 212 } 213 214 return bufs[slot]; 215 } 216 217 218 extern void 219 my_snprintf(char **pos, size_t *left, const char *fmt, ...) 220 { 221 va_list ap; 222 va_start(ap, fmt); 223 const int len = vsnprintf(*pos, *left, fmt, ap); 224 va_end(ap); 225 226 // If an error occurred, we want the caller to think that the whole 227 // buffer was used. This way no more data will be written to the 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. 232 if (len < 0 || (size_t)(len) >= *left) { 233 *left = 0; 234 } else { 235 *pos += len; 236 *left -= len; 237 } 238 239 return; 240 } 241 242 243 extern bool 244 is_empty_filename(const char *filename) 245 { 246 if (filename[0] == '\0') { 247 message_error(_("Empty filename, skipping")); 248 return true; 249 } 250 251 return false; 252 } 253 254 255 extern bool 256 is_tty_stdin(void) 257 { 258 const bool ret = isatty(STDIN_FILENO); 259 260 if (ret) 261 message_error(_("Compressed data cannot be read from " 262 "a terminal")); 263 264 return ret; 265 } 266 267 268 extern bool 269 is_tty_stdout(void) 270 { 271 const bool ret = isatty(STDOUT_FILENO); 272 273 if (ret) 274 message_error(_("Compressed data cannot be written to " 275 "a terminal")); 276 277 return ret; 278 } 279