Coverage Report

Created: 2025-06-22 06:56

/src/lvm2/libdm/libdm-string.c
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * Copyright (C) 2006-2015 Red Hat, Inc. All rights reserved.
3
 *
4
 * This file is part of the device-mapper userspace tools.
5
 *
6
 * This copyrighted material is made available to anyone wishing to use,
7
 * modify, copy, or redistribute it subject to the terms and conditions
8
 * of the GNU Lesser General Public License v.2.1.
9
 *
10
 * You should have received a copy of the GNU Lesser General Public License
11
 * along with this program; if not, write to the Free Software Foundation,
12
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
13
 */
14
15
#include "libdm/misc/dmlib.h"
16
17
#include <ctype.h>
18
#include <stdarg.h>
19
#include <math.h>  /* fabs() */
20
#include <float.h> /* DBL_EPSILON */
21
22
/*
23
 * consume characters while they match the predicate function.
24
 */
25
static char *_consume(char *buffer, int (*fn) (int))
26
0
{
27
0
  while (*buffer && fn(*buffer))
28
0
    buffer++;
29
30
0
  return buffer;
31
0
}
32
33
static int _isword(int c)
34
0
{
35
0
  return !isspace(c);
36
0
}
37
38
/*
39
 * Split buffer into NULL-separated words in argv.
40
 * Returns number of words.
41
 */
42
int dm_split_words(char *buffer, unsigned max,
43
       unsigned ignore_comments __attribute__((unused)),
44
       char **argv)
45
0
{
46
0
  unsigned arg;
47
48
0
  for (arg = 0; arg < max; arg++) {
49
0
    buffer = _consume(buffer, isspace);
50
0
    if (!*buffer)
51
0
      break;
52
53
0
    argv[arg] = buffer;
54
0
    buffer = _consume(buffer, _isword);
55
56
0
    if (*buffer) {
57
0
      *buffer = '\0';
58
0
      buffer++;
59
0
    }
60
0
  }
61
62
0
  return arg;
63
0
}
64
65
/*
66
 * Remove hyphen quoting from a component of a name.
67
 * NULL-terminates the component and returns start of next component.
68
 */
69
static char *_unquote(char *component)
70
0
{
71
0
  char *c = component;
72
0
  char *o = c;
73
0
  char *r;
74
75
0
  while (*c) {
76
0
    if (*(c + 1)) {
77
0
      if (*c == '-') {
78
0
        if (*(c + 1) == '-')
79
0
          c++;
80
0
        else
81
0
          break;
82
0
      }
83
0
    }
84
0
    *o = *c;
85
0
    o++;
86
0
    c++;
87
0
  }
88
89
0
  r = (*c) ? c + 1 : c;
90
0
  *o = '\0';
91
92
0
  return r;
93
0
}
94
95
int dm_split_lvm_name(struct dm_pool *mem, const char *dmname,
96
          char **vgname, char **lvname, char **layer)
97
0
{
98
0
  if (!vgname || !lvname || !layer) {
99
0
    log_error(INTERNAL_ERROR "dm_split_lvm_name: Forbidden NULL parameter detected.");
100
0
    return 0;
101
0
  }
102
103
0
  if (mem && (!dmname || !(*vgname = dm_pool_strdup(mem, dmname)))) {
104
0
    log_error("Failed to duplicate lvm name.");
105
0
    return 0;
106
0
  } else if (!*vgname) {
107
0
    log_error("Missing lvm name for split.");
108
0
    return 0;
109
0
  }
110
111
0
  _unquote(*layer = _unquote(*lvname = _unquote(*vgname)));
112
113
0
  return 1;
114
0
}
115
116
/*
117
 * On error, up to glibc 2.0.6, snprintf returned -1 if buffer was too small;
118
 * From glibc 2.1 it returns number of chars (excl. trailing null) that would 
119
 * have been written had there been room.
120
 *
121
 * dm_snprintf reverts to the old behaviour.
122
 */
123
int dm_snprintf(char *buf, size_t bufsize, const char *format, ...)
124
0
{
125
0
  int n;
126
0
  va_list ap;
127
128
0
  va_start(ap, format);
129
0
  n = vsnprintf(buf, bufsize, format, ap);
130
0
  va_end(ap);
131
132
0
  if (n < 0 || ((unsigned) n >= bufsize))
133
0
    return -1;
134
135
0
  return n;
136
0
}
137
138
const char *dm_basename(const char *path)
139
0
{
140
0
  const char *p = strrchr(path, '/');
141
142
0
  return p ? p + 1 : path;
143
0
}
144
145
int dm_vasprintf(char **result, const char *format, va_list aq)
146
0
{
147
0
  int i, n, size = 16;
148
0
  va_list ap;
149
0
  char *buf = dm_malloc(size);
150
151
0
  *result = 0;
152
153
0
  if (!buf)
154
0
    return -1;
155
156
0
  for (i = 0;; i++) {
157
0
    va_copy(ap, aq);
158
0
    n = vsnprintf(buf, size, format, ap);
159
0
    va_end(ap);
160
161
0
    if (0 <= n && n < size)
162
0
      break;
163
164
0
    dm_free(buf);
165
    /* Up to glibc 2.0.6 returns -1 */
166
0
    size = (n < 0) ? size * 2 : n + 1;
167
0
    if (!(buf = dm_malloc(size)))
168
0
      return -1;
169
0
  }
170
171
0
  if (i > 1) {
172
    /* Reallocating more than once? */
173
0
    if (!(*result = dm_strdup(buf))) {
174
0
      dm_free(buf);
175
0
      return -1;
176
0
    }
177
0
    dm_free(buf);
178
0
  } else
179
0
    *result = buf;
180
181
0
  return n + 1;
182
0
}
183
184
int dm_asprintf(char **result, const char *format, ...)
185
0
{
186
0
  int r;
187
0
  va_list ap;
188
0
  va_start(ap, format);
189
0
  r = dm_vasprintf(result, format, ap);
190
0
  va_end(ap);
191
0
  return r;
192
0
}
193
194
/*
195
 * Count occurrences of 'c' in 'str' until we reach a null char.
196
 *
197
 * Returns:
198
 *  len - incremented for each char we encounter.
199
 *  count - number of occurrences of 'c' and 'c2'.
200
 */
201
static void _count_chars(const char *str, size_t *len, int *count,
202
       const int c1, const int c2)
203
0
{
204
0
  const char *ptr;
205
206
0
  for (ptr = str; *ptr; ptr++, (*len)++)
207
0
    if (*ptr == c1 || *ptr == c2)
208
0
      (*count)++;
209
0
}
210
211
/*
212
 * Count occurrences of 'c' in 'str' of length 'size'.
213
 *
214
 * Returns:
215
 *   Number of occurrences of 'c'
216
 */
217
unsigned dm_count_chars(const char *str, size_t len, const int c)
218
0
{
219
0
  size_t i;
220
0
  unsigned count = 0;
221
222
0
  for (i = 0; i < len; i++)
223
0
    if (str[i] == c)
224
0
      count++;
225
226
0
  return count;
227
0
}
228
229
/*
230
 * Length of string after escaping double quotes and backslashes.
231
 */
232
size_t dm_escaped_len(const char *str)
233
0
{
234
0
  size_t len = 1;
235
0
  int count = 0;
236
237
0
  _count_chars(str, &len, &count, '\"', '\\');
238
239
0
  return count + len;
240
0
}
241
242
/*
243
 * Copies a string, quoting orig_char with quote_char.
244
 * Optionally also quote quote_char.
245
 */
246
static void _quote_characters(char **out, const char *src,
247
            const int orig_char, const int quote_char,
248
            int quote_quote_char)
249
0
{
250
0
  while (*src) {
251
0
    if (*src == orig_char ||
252
0
        (*src == quote_char && quote_quote_char))
253
0
      *(*out)++ = quote_char;
254
255
0
    *(*out)++ = *src++;
256
0
  }
257
0
}
258
259
static void _unquote_one_character(char *src, const char orig_char,
260
           const char quote_char)
261
0
{
262
0
  char *out;
263
0
  char s, n;
264
265
  /* Optimise for the common case where no changes are needed. */
266
0
  while ((s = *src++)) {
267
0
    if (s == quote_char &&
268
0
        ((n = *src) == orig_char || n == quote_char)) {
269
0
      out = src++;
270
0
      *(out - 1) = n;
271
272
0
      while ((s = *src++)) {
273
0
        if (s == quote_char &&
274
0
            ((n = *src) == orig_char || n == quote_char)) {
275
0
          s = n;
276
0
          src++;
277
0
        }
278
0
        *out = s;
279
0
        out++;
280
0
      }
281
282
0
      *out = '\0';
283
0
      return;
284
0
    }
285
0
  }
286
0
}
287
288
/*
289
 * Unquote each character given in orig_char array and unquote quote_char
290
 * as well. Also save the first occurrence of each character from orig_char
291
 * that was found unquoted in arr_substr_first_unquoted array. This way we can
292
 * process several characters in one go.
293
 */
294
static void _unquote_characters(char *src, const char *orig_chars,
295
        size_t num_orig_chars,
296
        const char quote_char,
297
        char *arr_substr_first_unquoted[])
298
0
{
299
0
  char *out = src;
300
0
  char c, s, n;
301
0
  unsigned i;
302
303
0
  while ((s = *src++)) {
304
0
    for (i = 0; i < num_orig_chars; i++) {
305
0
      c = orig_chars[i];
306
0
      if (s == quote_char &&
307
0
          ((n = *src) == c || n == quote_char)) {
308
0
        s = n;
309
0
        src++;
310
0
        break;
311
0
      }
312
0
      if (arr_substr_first_unquoted && (s == c) &&
313
0
          !arr_substr_first_unquoted[i])
314
0
        arr_substr_first_unquoted[i] = out;
315
0
    };
316
0
    *out++ = s;
317
0
  }
318
319
0
  *out = '\0';
320
0
}
321
322
/*
323
 * Copies a string, quoting hyphens with hyphens.
324
 */
325
static void _quote_hyphens(char **out, const char *src)
326
0
{
327
0
  _quote_characters(out, src, '-', '-', 0);
328
0
}
329
330
/*
331
 * <vg>-<lv>-<layer> or if !layer just <vg>-<lv>.
332
 */
333
char *dm_build_dm_name(struct dm_pool *mem, const char *vgname,
334
           const char *lvname, const char *layer)
335
0
{
336
0
  size_t len = 1;
337
0
  int hyphens = 1;
338
0
  char *r, *out;
339
340
0
  _count_chars(vgname, &len, &hyphens, '-', 0);
341
0
  _count_chars(lvname, &len, &hyphens, '-', 0);
342
343
0
  if (layer && *layer) {
344
0
    _count_chars(layer, &len, &hyphens, '-', 0);
345
0
    hyphens++;
346
0
  }
347
348
0
  len += hyphens;
349
350
0
  if (!(r = dm_pool_alloc(mem, len))) {
351
0
    log_error("build_dm_name: Allocation failed for %" PRIsize_t
352
0
        " for %s %s %s.", len, vgname, lvname, layer);
353
0
    return NULL;
354
0
  }
355
356
0
  out = r;
357
0
  _quote_hyphens(&out, vgname);
358
0
  *out++ = '-';
359
0
  _quote_hyphens(&out, lvname);
360
361
0
  if (layer && *layer) {
362
    /* No hyphen if the layer begins with _ e.g. _mlog */
363
0
    if (*layer != '_')
364
0
      *out++ = '-';
365
0
    _quote_hyphens(&out, layer);
366
0
  }
367
0
  *out = '\0';
368
369
0
  return r;
370
0
}
371
372
char *dm_build_dm_uuid(struct dm_pool *mem, const char *uuid_prefix, const char *lvid, const char *layer)
373
0
{
374
0
  char *dmuuid;
375
0
  size_t len;
376
377
0
  if (!layer)
378
0
    layer = "";
379
380
0
  len = strlen(uuid_prefix) + strlen(lvid) + strlen(layer) + 2;
381
382
0
  if (!(dmuuid = dm_pool_alloc(mem, len))) {
383
0
    log_error("build_dm_name: Allocation failed for %" PRIsize_t
384
0
        " %s %s.", len, lvid, layer);
385
0
    return NULL;
386
0
  }
387
388
0
  snprintf(dmuuid, len, "%s%s%s%s", uuid_prefix, lvid, (*layer) ? "-" : "", layer);
389
390
0
  return dmuuid;
391
0
}
392
393
/*
394
 * Copies a string, quoting double quotes with backslashes.
395
 */
396
char *dm_escape_double_quotes(char *out, const char *src)
397
0
{
398
0
  char *buf = out;
399
400
0
  _quote_characters(&buf, src, '\"', '\\', 1);
401
0
  *buf = '\0';
402
403
0
  return out;
404
0
}
405
406
/*
407
 * Undo quoting in situ.
408
 */
409
void dm_unescape_double_quotes(char *src)
410
0
{
411
0
  _unquote_one_character(src, '\"', '\\');
412
0
}
413
414
/*
415
 * Unescape colons and "at" signs in situ and save the substrings
416
 * starting at the position of the first unescaped colon and the
417
 * first unescaped "at" sign. This is normally used to unescape
418
 * device names used as PVs.
419
 */
420
void dm_unescape_colons_and_at_signs(char *src,
421
             char **substr_first_unquoted_colon,
422
             char **substr_first_unquoted_at_sign)
423
0
{
424
0
  const char *orig_chars = ":@";
425
0
  char *arr_substr_first_unquoted[] = {NULL, NULL, NULL};
426
427
0
  _unquote_characters(src, orig_chars, 2, '\\', arr_substr_first_unquoted);
428
429
0
  if (substr_first_unquoted_colon)
430
0
    *substr_first_unquoted_colon = arr_substr_first_unquoted[0];
431
432
0
  if (substr_first_unquoted_at_sign)
433
0
    *substr_first_unquoted_at_sign = arr_substr_first_unquoted[1];
434
0
}
435
436
int dm_strncpy(char *dest, const char *src, size_t n)
437
0
{
438
0
  if (memccpy(dest, src, 0, n))
439
0
    return 1;
440
441
0
  if (n > 0)
442
0
    dest[n - 1] = '\0';
443
444
0
  return 0;
445
0
}
446
447
/* Test if the doubles are close enough to be considered equal */
448
static int _close_enough(double d1, double d2)
449
0
{
450
0
  return fabs(d1 - d2) < DBL_EPSILON;
451
0
}
452
453
0
#define BASE_UNKNOWN 0
454
0
#define BASE_SHARED 1
455
0
#define BASE_1024 8
456
0
#define BASE_1000 15
457
0
#define BASE_SPECIAL 21
458
0
#define NUM_UNIT_PREFIXES 6
459
0
#define NUM_SPECIAL 3
460
461
0
#define SIZE_BUF 128
462
463
const char *dm_size_to_string(struct dm_pool *mem, uint64_t size,
464
            char unit_type, int use_si_units, 
465
            uint64_t unit_factor, int include_suffix, 
466
            dm_size_suffix_t suffix_type)
467
0
{
468
0
  unsigned base = BASE_UNKNOWN;
469
0
  unsigned s;
470
0
  int precision;
471
0
  double d;
472
0
  uint64_t byte = UINT64_C(0);
473
0
  uint64_t units = UINT64_C(1024);
474
0
  char *size_buf;
475
0
  char new_unit_type = '\0', unit_type_buf[2];
476
0
  const char *prefix = "";
477
0
  static const char _size_str[][3][12] = {
478
    /* BASE_UNKNOWN */
479
0
    {"         ", "   ", " "},  /* [0] */
480
481
    /* BASE_SHARED - Used if use_si_units = 0 */
482
0
    {" Exabyte", " EB", "E"}, /* [1] */
483
0
    {" Petabyte", " PB", "P"},  /* [2] */
484
0
    {" Terabyte", " TB", "T"},  /* [3] */
485
0
    {" Gigabyte", " GB", "G"},  /* [4] */
486
0
    {" Megabyte", " MB", "M"},  /* [5] */
487
0
    {" Kilobyte", " KB", "K"},  /* [6] */
488
0
    {" Byte    ", " B", "B"}, /* [7] */
489
490
    /* BASE_1024 - Used if use_si_units = 1 */
491
0
    {" Exbibyte", " EiB", "e"}, /* [8] */
492
0
    {" Pebibyte", " PiB", "p"}, /* [9] */
493
0
    {" Tebibyte", " TiB", "t"}, /* [10] */
494
0
    {" Gibibyte", " GiB", "g"}, /* [11] */
495
0
    {" Mebibyte", " MiB", "m"}, /* [12] */
496
0
    {" Kibibyte", " KiB", "k"}, /* [13] */
497
0
    {" Byte    ", " B", "b"}, /* [14] */
498
499
    /* BASE_1000 - Used if use_si_units = 1 */
500
0
    {" Exabyte",  " EB", "E"},  /* [15] */
501
0
    {" Petabyte", " PB", "P"},  /* [16] */
502
0
    {" Terabyte", " TB", "T"},  /* [17] */
503
0
    {" Gigabyte", " GB", "G"},  /* [18] */
504
0
    {" Megabyte", " MB", "M"},  /* [19] */
505
0
    {" Kilobyte", " kB", "K"},  /* [20] */
506
507
    /* BASE_SPECIAL */
508
0
    {" Byte    ", " B ", "B"},  /* [21] (shared with BASE_1000) */
509
0
    {" Units   ", " Un", "U"},  /* [22] */
510
0
    {" Sectors ", " Se", "S"},  /* [23] */
511
0
  };
512
513
0
  if (!(size_buf = dm_pool_alloc(mem, SIZE_BUF))) {
514
0
    log_error("no memory for size display buffer");
515
0
    return "";
516
0
  }
517
518
0
  if (!use_si_units) {
519
    /* Case-independent match */
520
0
    for (s = 0; s < NUM_UNIT_PREFIXES; s++)
521
0
      if (toupper((int) unit_type) ==
522
0
          *_size_str[BASE_SHARED + s][2]) {
523
0
        base = BASE_SHARED;
524
0
        break;
525
0
      }
526
0
  } else {
527
    /* Case-dependent match for powers of 1000 */
528
0
    for (s = 0; s < NUM_UNIT_PREFIXES; s++)
529
0
      if (unit_type == *_size_str[BASE_1000 + s][2]) {
530
0
        base = BASE_1000;
531
0
        break;
532
0
      }
533
534
    /* Case-dependent match for powers of 1024 */
535
0
    if (base == BASE_UNKNOWN)
536
0
      for (s = 0; s < NUM_UNIT_PREFIXES; s++)
537
0
      if (unit_type == *_size_str[BASE_1024 + s][2]) {
538
0
        base = BASE_1024;
539
0
        break;
540
0
      }
541
0
  }
542
543
0
  if (base == BASE_UNKNOWN)
544
    /* Check for special units - s, b or u */
545
0
    for (s = 0; s < NUM_SPECIAL; s++)
546
0
      if (toupper((int) unit_type) ==
547
0
          *_size_str[BASE_SPECIAL + s][2]) {
548
0
        base = BASE_SPECIAL;
549
0
        break;
550
0
      }
551
552
0
  if (size == UINT64_C(0)) {
553
0
    if (base == BASE_UNKNOWN)
554
0
      s = 0;
555
0
    snprintf(size_buf, SIZE_BUF, "0%s", include_suffix ? _size_str[base + s][suffix_type] : "");
556
0
    return size_buf;
557
0
  }
558
559
0
  size *= UINT64_C(512);
560
561
0
  if (base != BASE_UNKNOWN) {
562
0
    if (!unit_factor) {
563
0
      unit_type_buf[0] = unit_type;
564
0
      unit_type_buf[1] = '\0';
565
0
      if (!(unit_factor = dm_units_to_factor(&unit_type_buf[0], &new_unit_type, 1, NULL)) ||
566
0
          unit_type != new_unit_type) {
567
        /* The two functions should match (and unrecognised units get treated like 'h'). */
568
0
        log_error(INTERNAL_ERROR "Inconsistent units: %c and %c.", unit_type, new_unit_type);
569
0
        return "";
570
0
      }
571
0
    }
572
0
    byte = unit_factor;
573
0
  } else {
574
    /* Human-readable style */
575
0
    if (unit_type == 'H' || unit_type == 'R') {
576
0
      units = UINT64_C(1000);
577
0
      base = BASE_1000;
578
0
    } else {
579
0
      units = UINT64_C(1024);
580
0
      base = BASE_1024;
581
0
    }
582
583
0
    if (!use_si_units)
584
0
      base = BASE_SHARED;
585
586
0
    byte = units * units * units * units * units * units;
587
588
0
    for (s = 0; s < NUM_UNIT_PREFIXES && size < byte; s++)
589
0
      byte /= units;
590
591
0
    if ((s < NUM_UNIT_PREFIXES) &&
592
0
        ((unit_type == 'R') || (unit_type == 'r'))) {
593
      /* When the rounding would cause difference, add '<' prefix
594
       * i.e.  2043M is more than 1.9949G prints <2.00G
595
       * This version is for 2 digits fixed precision */
596
0
      d = 100. * (double) size / byte;
597
0
      if (!_close_enough(floorl(d), nearbyintl(d)))
598
0
        prefix = "<";
599
0
    }
600
601
0
    include_suffix = 1;
602
0
  }
603
604
  /* FIXME Make precision configurable */
605
0
  switch (toupper(*_size_str[base + s][DM_SIZE_UNIT])) {
606
0
  case 'B':
607
0
  case 'S':
608
0
    precision = 0;
609
0
    break;
610
0
  default:
611
0
    precision = 2;
612
0
  }
613
614
0
  snprintf(size_buf, SIZE_BUF, "%s%.*f%s", prefix, precision,
615
0
     (double) size / byte, include_suffix ? _size_str[base + s][suffix_type] : "");
616
617
0
  return size_buf;
618
0
}
619
620
uint64_t dm_units_to_factor(const char *units, char *unit_type,
621
          int strict, const char **endptr)
622
0
{
623
0
  char *ptr = NULL;
624
0
  uint64_t v;
625
0
  double custom_value = 0;
626
0
  uint64_t multiplier;
627
628
0
  if (endptr)
629
0
    *endptr = units;
630
631
0
  if (isdigit(*units)) {
632
0
    custom_value = strtod(units, &ptr);
633
0
    if (ptr == units)
634
0
      return 0;
635
0
    v = (uint64_t) strtoull(units, NULL, 10);
636
0
    if (_close_enough((double) v, custom_value))
637
0
      custom_value = 0; /* Use integer arithmetic */
638
0
    units = ptr;
639
0
  } else
640
0
    v = 1;
641
642
  /* Only one units char permitted in strict mode. */
643
0
  if (strict && units[0] && units[1])
644
0
    return 0;
645
646
0
  if (v == 1)
647
0
    *unit_type = *units;
648
0
  else
649
0
    *unit_type = 'U';
650
651
0
  switch (*units) {
652
0
  case 'h':
653
0
  case 'H':
654
0
  case 'r':
655
0
  case 'R':
656
0
    multiplier = v = UINT64_C(1);
657
0
    *unit_type = *units;
658
0
    break;
659
0
  case 'b':
660
0
  case 'B':
661
0
    multiplier = UINT64_C(1);
662
0
    break;
663
0
#define KILO UINT64_C(1024)
664
0
  case 's':
665
0
  case 'S':
666
0
    multiplier = (KILO/2);
667
0
    break;
668
0
  case 'k':
669
0
    multiplier = KILO;
670
0
    break;
671
0
  case 'm':
672
0
    multiplier = KILO * KILO;
673
0
    break;
674
0
  case 'g':
675
0
    multiplier = KILO * KILO * KILO;
676
0
    break;
677
0
  case 't':
678
0
    multiplier = KILO * KILO * KILO * KILO;
679
0
    break;
680
0
  case 'p':
681
0
    multiplier = KILO * KILO * KILO * KILO * KILO;
682
0
    break;
683
0
  case 'e':
684
0
    multiplier = KILO * KILO * KILO * KILO * KILO * KILO;
685
0
    break;
686
0
#undef KILO
687
0
#define KILO UINT64_C(1000)
688
0
  case 'K':
689
0
    multiplier = KILO;
690
0
    break;
691
0
  case 'M':
692
0
    multiplier = KILO * KILO;
693
0
    break;
694
0
  case 'G':
695
0
    multiplier = KILO * KILO * KILO;
696
0
    break;
697
0
  case 'T':
698
0
    multiplier = KILO * KILO * KILO * KILO;
699
0
    break;
700
0
  case 'P':
701
0
    multiplier = KILO * KILO * KILO * KILO * KILO;
702
0
    break;
703
0
  case 'E':
704
0
    multiplier = KILO * KILO * KILO * KILO * KILO * KILO;
705
0
    break;
706
0
#undef KILO
707
0
  default:
708
0
    return 0;
709
0
  }
710
711
0
  if (endptr)
712
0
    *endptr = units + 1;
713
714
0
  if (_close_enough(custom_value, 0.))
715
0
    return v * multiplier; /* Use integer arithmetic */
716
0
  else
717
0
    return (uint64_t) (custom_value * multiplier);
718
0
}