]> icculus.org git repositories - icculus/xz.git/blob - src/xz/util.c
Improve displaying of the memory usage limit.
[icculus/xz.git] / src / xz / util.c
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 extern void *
18 xrealloc(void *ptr, size_t size)
19 {
20         assert(size > 0);
21
22         ptr = realloc(ptr, size);
23         if (ptr == NULL)
24                 message_fatal("%s", strerror(errno));
25
26         return ptr;
27 }
28
29
30 extern char *
31 xstrdup(const char *src)
32 {
33         assert(src != NULL);
34         const size_t size = strlen(src) + 1;
35         char *dest = xmalloc(size);
36         return memcpy(dest, src, size);
37 }
38
39
40 extern uint64_t
41 str_to_uint64(const char *name, const char *value, uint64_t min, uint64_t max)
42 {
43         uint64_t result = 0;
44
45         // Skip blanks.
46         while (*value == ' ' || *value == '\t')
47                 ++value;
48
49         // Accept special value "max". Supporting "min" doesn't seem useful.
50         if (strcmp(value, "max") == 0)
51                 return max;
52
53         if (*value < '0' || *value > '9')
54                 message_fatal(_("%s: Value is not a non-negative "
55                                 "decimal integer"), value);
56
57         do {
58                 // Don't overflow.
59                 if (result > (UINT64_MAX - 9) / 10)
60                         goto error;
61
62                 result *= 10;
63                 result += *value - '0';
64                 ++value;
65         } while (*value >= '0' && *value <= '9');
66
67         if (*value != '\0') {
68                 // Look for suffix.
69                 static const struct {
70                         const char name[4];
71                         uint64_t multiplier;
72                 } suffixes[] = {
73                         { "k",   UINT64_C(1000) },
74                         { "kB",  UINT64_C(1000) },
75                         { "M",   UINT64_C(1000000) },
76                         { "MB",  UINT64_C(1000000) },
77                         { "G",   UINT64_C(1000000000) },
78                         { "GB",  UINT64_C(1000000000) },
79                         { "Ki",  UINT64_C(1024) },
80                         { "KiB", UINT64_C(1024) },
81                         { "Mi",  UINT64_C(1048576) },
82                         { "MiB", UINT64_C(1048576) },
83                         { "Gi",  UINT64_C(1073741824) },
84                         { "GiB", UINT64_C(1073741824) }
85                 };
86
87                 uint64_t multiplier = 0;
88                 for (size_t i = 0; i < ARRAY_SIZE(suffixes); ++i) {
89                         if (strcmp(value, suffixes[i].name) == 0) {
90                                 multiplier = suffixes[i].multiplier;
91                                 break;
92                         }
93                 }
94
95                 if (multiplier == 0) {
96                         message(V_ERROR, _("%s: Invalid multiplier suffix. "
97                                         "Valid suffixes:"), value);
98                         message_fatal("`k' (10^3), `M' (10^6), `G' (10^9) "
99                                         "`Ki' (2^10), `Mi' (2^20), "
100                                         "`Gi' (2^30)");
101                 }
102
103                 // Don't overflow here either.
104                 if (result > UINT64_MAX / multiplier)
105                         goto error;
106
107                 result *= multiplier;
108         }
109
110         if (result < min || result > max)
111                 goto error;
112
113         return result;
114
115 error:
116         message_fatal(_("Value of the option `%s' must be in the range "
117                                 "[%" PRIu64 ", %" PRIu64 "]"),
118                                 name, min, max);
119 }
120
121
122 extern uint64_t
123 round_up_to_mib(uint64_t n)
124 {
125         return (n >> 20) + ((n & ((UINT32_C(1) << 20) - 1)) != 0);
126 }
127
128
129 extern const char *
130 uint64_to_str(uint64_t value, uint32_t slot)
131 {
132         // 2^64 with thousand separators is 26 bytes plus trailing '\0'.
133         static char bufs[4][32];
134
135         assert(slot < ARRAY_SIZE(bufs));
136
137         static enum { UNKNOWN, WORKS, BROKEN } thousand = UNKNOWN;
138         if (thousand == UNKNOWN) {
139                 bufs[slot][0] = '\0';
140                 snprintf(bufs[slot], sizeof(bufs[slot]), "%'" PRIu64,
141                                 UINT64_C(1));
142                 thousand = bufs[slot][0] == '1' ? WORKS : BROKEN;
143         }
144
145         if (thousand == WORKS)
146                 snprintf(bufs[slot], sizeof(bufs[slot]), "%'" PRIu64, value);
147         else
148                 snprintf(bufs[slot], sizeof(bufs[slot]), "%" PRIu64, value);
149
150         return bufs[slot];
151 }
152
153
154 extern const char *
155 uint64_to_nicestr(uint64_t value, enum nicestr_unit unit_min,
156                 enum nicestr_unit unit_max, bool always_also_bytes,
157                 uint32_t slot)
158 {
159         assert(unit_min <= unit_max);
160         assert(unit_max <= NICESTR_TIB);
161
162         enum nicestr_unit unit = NICESTR_B;
163         const char *str;
164
165         if ((unit_min == NICESTR_B && value < 10000)
166                         || unit_max == NICESTR_B) {
167                 // The value is shown as bytes.
168                 str = uint64_to_str(value, slot);
169         } else {
170                 // Scale the value to a nicer unit. Unless unit_min and
171                 // unit_max limit us, we will show at most five significant
172                 // digits with one decimal place.
173                 double d = (double)(value);
174                 do {
175                         d /= 1024.0;
176                         ++unit;
177                 } while (unit < unit_min || (d > 9999.9 && unit < unit_max));
178
179                 str = double_to_str(d);
180         }
181
182         static const char suffix[5][4] = { "B", "KiB", "MiB", "GiB", "TiB" };
183
184         // Minimum buffer size:
185         // 26   2^64 with thousand separators
186         //  4   " KiB"
187         //  2   " ("
188         // 26   2^64 with thousand separators
189         //  3   " B)"
190         //  1   '\0'
191         // 62   Total
192         static char buf[4][64];
193         char *pos = buf[slot];
194         size_t left = sizeof(buf[slot]);
195         my_snprintf(&pos, &left, "%s %s", str, suffix[unit]);
196
197         if (always_also_bytes && value >= 10000)
198                 snprintf(pos, left, " (%s B)", uint64_to_str(value, slot));
199
200         return buf[slot];
201 }
202
203
204 extern const char *
205 double_to_str(double value)
206 {
207         static char buf[64];
208
209         static enum { UNKNOWN, WORKS, BROKEN } thousand = UNKNOWN;
210         if (thousand == UNKNOWN) {
211                 buf[0] = '\0';
212                 snprintf(buf, sizeof(buf), "%'.1f", 2.0);
213                 thousand = buf[0] == '2' ? WORKS : BROKEN;
214         }
215
216         if (thousand == WORKS)
217                 snprintf(buf, sizeof(buf), "%'.1f", value);
218         else
219                 snprintf(buf, sizeof(buf), "%.1f", value);
220
221         return buf;
222 }
223
224
225 extern void
226 my_snprintf(char **pos, size_t *left, const char *fmt, ...)
227 {
228         va_list ap;
229         va_start(ap, fmt);
230         const int len = vsnprintf(*pos, *left, fmt, ap);
231         va_end(ap);
232
233         // If an error occurred, we want the caller to think that the whole
234         // buffer was used. This way no more data will be written to the
235         // buffer. We don't need better error handling here.
236         if (len < 0 || (size_t)(len) >= *left) {
237                 *left = 0;
238         } else {
239                 *pos += len;
240                 *left -= len;
241         }
242
243         return;
244 }
245
246
247 /*
248 /// \brief      Simple quoting to get rid of ASCII control characters
249 ///
250 /// This is not so cool and locale-dependent, but should be good enough
251 /// At least we don't print any control characters on the terminal.
252 ///
253 extern char *
254 str_quote(const char *str)
255 {
256         size_t dest_len = 0;
257         bool has_ctrl = false;
258
259         while (str[dest_len] != '\0')
260                 if (*(unsigned char *)(str + dest_len++) < 0x20)
261                         has_ctrl = true;
262
263         char *dest = malloc(dest_len + 1);
264         if (dest != NULL) {
265                 if (has_ctrl) {
266                         for (size_t i = 0; i < dest_len; ++i)
267                                 if (*(unsigned char *)(str + i) < 0x20)
268                                         dest[i] = '?';
269                                 else
270                                         dest[i] = str[i];
271
272                         dest[dest_len] = '\0';
273
274                 } else {
275                         // Usually there are no control characters,
276                         // so we can optimize.
277                         memcpy(dest, str, dest_len + 1);
278                 }
279         }
280
281         return dest;
282 }
283 */
284
285
286 extern bool
287 is_empty_filename(const char *filename)
288 {
289         if (filename[0] == '\0') {
290                 message_error(_("Empty filename, skipping"));
291                 return true;
292         }
293
294         return false;
295 }
296
297
298 extern bool
299 is_tty_stdin(void)
300 {
301         const bool ret = isatty(STDIN_FILENO);
302
303         if (ret)
304                 message_error(_("Compressed data cannot be read from "
305                                 "a terminal"));
306
307         return ret;
308 }
309
310
311 extern bool
312 is_tty_stdout(void)
313 {
314         const bool ret = isatty(STDOUT_FILENO);
315
316         if (ret)
317                 message_error(_("Compressed data cannot be written to "
318                                 "a terminal"));
319
320         return ret;
321 }