Skip to content

Commit 00fd035

Browse files
authored
Merge pull request #5262 from garlick/parse_size
libutil: add parse_size()
2 parents 7b6a459 + bd9c573 commit 00fd035

File tree

4 files changed

+279
-2
lines changed

4 files changed

+279
-2
lines changed

src/common/libutil/Makefile.am

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,9 @@ libutil_la_SOURCES = \
102102
basemoji.h \
103103
basemoji.c \
104104
sigutil.h \
105-
sigutil.c
105+
sigutil.c \
106+
parse_size.h \
107+
parse_size.c
106108

107109
EXTRA_DIST = veb_mach.c
108110

@@ -138,7 +140,8 @@ TESTS = test_sha1.t \
138140
test_timestamp.t \
139141
test_environment.t \
140142
test_basemoji.t \
141-
test_sigutil.t
143+
test_sigutil.t \
144+
test_parse_size.t
142145

143146
test_ldadd = \
144147
$(top_builddir)/src/common/libutil/libutil.la \
@@ -297,3 +300,7 @@ test_basemoji_t_LDADD = $(test_ldadd)
297300
test_sigutil_t_SOURCES = test/sigutil.c
298301
test_sigutil_t_CPPFLAGS = $(test_cppflags)
299302
test_sigutil_t_LDADD = $(test_ldadd)
303+
304+
test_parse_size_t_SOURCES = test/parse_size.c
305+
test_parse_size_t_CPPFLAGS = $(test_cppflags)
306+
test_parse_size_t_LDADD = $(test_ldadd)

src/common/libutil/parse_size.c

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
/************************************************************\
2+
* Copyright 2023 Lawrence Livermore National Security, LLC
3+
* (c.f. AUTHORS, NOTICE.LLNS, COPYING)
4+
*
5+
* This file is part of the Flux resource manager framework.
6+
* For details, see https://github.com/flux-framework.
7+
*
8+
* SPDX-License-Identifier: LGPL-3.0
9+
\************************************************************/
10+
11+
#if HAVE_CONFIG_H
12+
#include "config.h"
13+
#endif
14+
15+
#include <stdint.h>
16+
#include <errno.h>
17+
#include <stdlib.h>
18+
#include <string.h>
19+
#include <stdbool.h>
20+
#include <math.h>
21+
22+
#include <ccan/array_size/array_size.h>
23+
#include <ccan/str/str.h>
24+
25+
#include "parse_size.h"
26+
27+
struct scale {
28+
const char *s;
29+
uint64_t scale;
30+
};
31+
32+
static struct scale mtab[] = {
33+
{ "", 1 },
34+
{ "k", 1024 },
35+
{ "K", 1024 }, // upper case K is not the SI prefix but is unambiguous
36+
{ "M", 1024*1024 },
37+
{ "G", 1024UL*1024*1024 },
38+
{ "T", 1024ULL*1024*1024*1024 },
39+
{ "P", 1024ULL*1024*1024*1024*1024 },
40+
{ "E", 1024ULL*1024*1024*1024*1024*1024 },
41+
};
42+
43+
static int lookup_scale (const char *s, uint64_t *vp)
44+
{
45+
for (int i = 0; i < ARRAY_SIZE (mtab); i++) {
46+
if (streq (mtab[i].s, s)) {
47+
*vp = mtab[i].scale;
48+
return 0;
49+
}
50+
}
51+
return -1;
52+
}
53+
54+
static bool invalid_fp_size (double val)
55+
{
56+
switch (fpclassify (val)) {
57+
case FP_NORMAL: // [[fallthrough]]
58+
case FP_SUBNORMAL: // [[fallthrough]]
59+
case FP_ZERO: // [[fallthrough]]
60+
break; // OK
61+
case FP_NAN: // [[fallthrough]]
62+
case FP_INFINITE: // [[fallthrough]]
63+
default: // something else, bad
64+
return true;
65+
}
66+
if (val < 0.)
67+
return true;
68+
return false;
69+
}
70+
71+
static int parse_as_integer (const char *s, uint64_t *up)
72+
{
73+
char *endptr;
74+
uint64_t u;
75+
uint64_t scale;
76+
77+
// strtoull() allows a leading minus sign but we do not
78+
if (strchr (s, '-')) {
79+
errno = EINVAL;
80+
return -1;
81+
}
82+
errno = 0;
83+
u = strtoull (s, &endptr, 0);
84+
if (errno != 0
85+
|| endptr == s
86+
|| lookup_scale (endptr, &scale) < 0) {
87+
errno = EINVAL;
88+
return -1;
89+
}
90+
uint64_t result = u * scale;
91+
if (result < u) {
92+
errno = EOVERFLOW;
93+
return -1;
94+
}
95+
*up = result;
96+
return 0;
97+
}
98+
99+
static int parse_as_double (const char *s, uint64_t *up)
100+
{
101+
char *endptr;
102+
double d;
103+
uint64_t scale;
104+
105+
errno = 0;
106+
d = strtold (s, &endptr);
107+
if (errno != 0
108+
|| endptr == s
109+
|| lookup_scale (endptr, &scale) < 0
110+
|| invalid_fp_size (d)) {
111+
errno = EINVAL;
112+
return -1;
113+
}
114+
double result = floor (d * scale);
115+
if (result > UINT64_MAX) {
116+
errno = EOVERFLOW;
117+
return -1;
118+
}
119+
*up = (uint64_t)result;
120+
return 0;
121+
}
122+
123+
int parse_size (const char *s, uint64_t *vp)
124+
{
125+
if (!s || !vp) {
126+
errno = EINVAL;
127+
return -1;
128+
}
129+
if (parse_as_integer (s, vp) < 0
130+
&& parse_as_double (s, vp) < 0)
131+
return -1;
132+
return 0;
133+
}
134+
135+
// vi:ts=4 sw=4 expandtab

src/common/libutil/parse_size.h

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/************************************************************\
2+
* Copyright 2023 Lawrence Livermore National Security, LLC
3+
* (c.f. AUTHORS, NOTICE.LLNS, COPYING)
4+
*
5+
* This file is part of the Flux resource manager framework.
6+
* For details, see https://github.com/flux-framework.
7+
*
8+
* SPDX-License-Identifier: LGPL-3.0
9+
\************************************************************/
10+
11+
#ifndef _UTIL_PARSE_SIZE_H
12+
#define _UTIL_PARSE_SIZE_H
13+
14+
#include <stdint.h>
15+
16+
/* Parse 's' as a floating point quantity scaled by optional suffix:
17+
*
18+
* k,K 2^10 (1024)
19+
* M 2^20
20+
* G 2^30
21+
* T 2^40
22+
* P 2^50
23+
* E 2^60
24+
*
25+
* The numeric part is parsed with first strtoull(3) then strtod(3), so
26+
* so all input supported by those functions should work including
27+
* decimal (255), hex (0xf), octal (0377 prefix), exponent (2.55E2), etc.
28+
*
29+
* Assign the result to 'vp' and return 0 on success,
30+
* or return -1 on failure with errno set (EINVAL, EOVERFLOW).
31+
*/
32+
int parse_size (const char *s, uint64_t *vp);
33+
34+
#endif /* !_UTIL_PARSE_SIZE_H */
35+
36+
// vi:ts=4 sw=4 expandtab
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
/************************************************************\
2+
* Copyright 2023 Lawrence Livermore National Security, LLC
3+
* (c.f. AUTHORS, NOTICE.LLNS, COPYING)
4+
*
5+
* This file is part of the Flux resource manager framework.
6+
* For details, see https://github.com/flux-framework.
7+
*
8+
* SPDX-License-Identifier: LGPL-3.0
9+
\************************************************************/
10+
11+
#if HAVE_CONFIG_H
12+
#include "config.h"
13+
#endif
14+
15+
#include <errno.h>
16+
#include <string.h>
17+
#include <stdbool.h>
18+
#include <stdint.h>
19+
20+
#include "ccan/array_size/array_size.h"
21+
#include "src/common/libtap/tap.h"
22+
#include "src/common/libutil/parse_size.h"
23+
24+
struct entry {
25+
const char *s;
26+
uint64_t val;
27+
int errnum;
28+
};
29+
30+
const struct entry testvec[] = {
31+
// bad
32+
{ "xx", 0, EINVAL },
33+
{ "", 0, EINVAL },
34+
{ "1q", 0, EINVAL },
35+
{ "1kb", 0, EINVAL },
36+
{ "-1", 0, EINVAL },
37+
{ "1E20", 0, EOVERFLOW },
38+
{ "M", 0, EINVAL },
39+
{ "1m", 0, EINVAL },
40+
{ "1g", 0, EINVAL },
41+
{ "nan", 0, EINVAL },
42+
{ "inf", 0, EINVAL },
43+
{ "1b", 0, EINVAL },
44+
// good
45+
{ "0", 0, 0 },
46+
{ "0K", 0, 0 },
47+
{ "077", 63, 0 },
48+
{ "0xff", 255, 0 },
49+
{ "+42", 42, 0 },
50+
{ "1", 1, 0 },
51+
{ "1E2", 100, 0 },
52+
{ "4k", 4096, 0 },
53+
{ "1M", 1048576, 0 },
54+
{ "2G", 2147483648, 0 },
55+
{ "0.5k", 512, 0 },
56+
{ "4T", 4398046511104, 0 },
57+
{ "18446744073709551615", UINT64_MAX, 0 },
58+
{ " 42", 42, 0 },
59+
{ "1P", 1125899906842624, 0 },
60+
{ "0.5E", 576460752303423488, 0 },
61+
};
62+
63+
int main (int argc, char **argv)
64+
{
65+
uint64_t val;
66+
int rc;
67+
68+
plan (NO_PLAN);
69+
70+
lives_ok ({parse_size (NULL, &val);},
71+
"parse_size input=NULL doesn't crash");
72+
lives_ok ({parse_size ("x", NULL);},
73+
"parse_size value=NULL doesn't crash");
74+
75+
for (int i = 0; i < ARRAY_SIZE (testvec); i++) {
76+
val = 0;
77+
errno = 0;
78+
rc = parse_size (testvec[i].s, &val);
79+
if (testvec[i].errnum == 0) {
80+
ok (rc == 0 && val == testvec[i].val,
81+
"parse_size val=%s works", testvec[i].s);
82+
if (rc == 0 && val != testvec[i].val)
83+
diag ("got %ju", (uintmax_t)val);
84+
}
85+
else {
86+
ok (rc == -1 && errno == testvec[i].errnum,
87+
"parse_size val=%s fails with errno=%d",
88+
testvec[i].s,
89+
testvec[i].errnum);
90+
}
91+
}
92+
93+
done_testing ();
94+
95+
return 0;
96+
}
97+
98+
99+
// vi: ts=4 sw=4 expandtab

0 commit comments

Comments
 (0)