1/*	$NetBSD: t_humanize_number.c,v 1.10 2019/03/11 17:45:12 kre Exp $	*/
2
3/*-
4 * Copyright (c) 2010, 2011 The NetBSD Foundation, Inc.
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 *    notice, this list of conditions and the following disclaimer in the
14 *    documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
17 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
18 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
20 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26 * POSSIBILITY OF SUCH DAMAGE.
27 */
28
29#include <atf-c.h>
30
31#include <err.h>
32#include <inttypes.h>
33#include <stdarg.h>
34#include <stdio.h>
35#include <stdlib.h>
36#include <string.h>
37#include <util.h>
38
39const struct hnopts {
40	size_t ho_len;
41	int64_t ho_num;
42	const char *ho_suffix;
43	int ho_scale;
44	int ho_flags;
45	int ho_retval;			/* expected return value */
46	const char *ho_retstr;		/* expected string in buffer */
47} hnopts[] = {
48	/*
49	 * Rev. 1.6 produces "10.0".
50	 */
51	{ 5, 10737418236ULL * 1024, "",
52	  HN_AUTOSCALE, HN_B | HN_NOSPACE | HN_DECIMAL, 3, "10T" },
53
54	{ 5, 10450000, "",
55	  HN_AUTOSCALE, HN_B | HN_NOSPACE | HN_DECIMAL, 3, "10M" },
56	{ 5, 10500000, "",		/* just for reference */
57	  HN_AUTOSCALE, HN_B | HN_NOSPACE | HN_DECIMAL, 3, "10M" },
58
59	/*
60	 * Trailing space.  Rev. 1.7 produces "1 ".
61	 */
62	{ 5, 1, "", 0, HN_NOSPACE, 1, "1" },
63
64	{ 5, 1, "", 0, 0, 2, "1 " }, /* just for reference */
65	{ 5, 1, "", 0, HN_B, 3, "1 B" }, /* and more ... */
66	{ 5, 1, "", 0, HN_DECIMAL, 2, "1 " },
67	{ 5, 1, "", 0, HN_NOSPACE | HN_B, 2, "1B" },
68	{ 5, 1, "", 0, HN_B | HN_DECIMAL, 3, "1 B" },
69	{ 5, 1, "", 0, HN_NOSPACE | HN_B | HN_DECIMAL, 2, "1B" },
70
71	/*
72	 * Space and HN_B.  Rev. 1.7 produces "1B".
73	 */
74	{ 5, 1, "", HN_AUTOSCALE, HN_B, 3, "1 B" },
75	{ 5, 1000, "",			/* just for reference */
76	  HN_AUTOSCALE, HN_B, 3, "1 K" },
77
78	/*
79	 * Truncated output.  Rev. 1.7 produces "1.0 K".
80	 */
81	{ 6, 1000, "A", HN_AUTOSCALE, HN_DECIMAL, -1, "" },
82
83	/*
84	 * Failure case reported by Greg Troxel <gdt@NetBSD.org>.
85	 * Rev. 1.11 incorrectly returns 5 with filling the buffer
86	 * with "1000".
87	 */
88	{ 5, 1048258238, "",
89	  HN_AUTOSCALE, HN_B | HN_NOSPACE | HN_DECIMAL, 4, "1.0G" },
90	/* Similar case it prints 1000 where it shouldn't */
91	{ 5, 1023488, "",
92	  HN_AUTOSCALE, HN_B | HN_NOSPACE | HN_DECIMAL, 4, "1.0M" },
93	{ 5, 1023999, "",
94	  HN_AUTOSCALE, HN_B | HN_NOSPACE | HN_DECIMAL, 4, "1.0M" },
95};
96
97struct hnflags {
98	int hf_flags;
99	const char *hf_name;
100};
101
102const struct hnflags scale_flags[] = {
103	{ HN_GETSCALE, "HN_GETSCALE" },
104	{ HN_AUTOSCALE, "HN_AUTOSCALE" },
105};
106const struct hnflags normal_flags[] = {
107	{ HN_DECIMAL, "HN_DECIMAL" },
108	{ HN_NOSPACE, "HN_NOSPACE" },
109	{ HN_B, "HN_B" },
110	{ HN_DIVISOR_1000, "HN_DIVISOR_1000" },
111};
112
113const char *formatflags(char *, size_t, const struct hnflags *, size_t, int);
114void	    newline(void);
115void	    w_printf(const char *, ...) __printflike(1, 2);
116int	    main(int, char *[]);
117
118const char *
119formatflags(char *buf, size_t buflen, const struct hnflags *hfs,
120    size_t hfslen, int flags)
121{
122	const struct hnflags *hf;
123	char *p = buf;
124	ssize_t len = buflen;
125	unsigned int i, found;
126	int n;
127
128	if (flags == 0) {
129		snprintf(buf, buflen, "0");
130		return (buf);
131	}
132	for (i = found = 0; i < hfslen && flags & ~found; i++) {
133		hf = &hfs[i];
134		if (flags & hf->hf_flags) {
135			found |= hf->hf_flags;
136			n = snprintf(p, len, "|%s", hf->hf_name);
137			if (n >= len) {
138				p = buf;
139				len = buflen;
140				/* Print `flags' as number */
141				goto bad;
142			}
143			p += n;
144			len -= n;
145		}
146	}
147	flags &= ~found;
148	if (flags)
149bad:
150		snprintf(p, len, "|0x%x", flags);
151	return (*buf == '|' ? buf + 1 : buf);
152}
153
154static int col, bol = 1;
155void
156newline(void)
157{
158
159	fprintf(stderr, "\n");
160	col = 0;
161	bol = 1;
162}
163
164void
165w_printf(const char *fmt, ...)
166{
167	char buf[80];
168	va_list ap;
169	int n;
170
171	va_start(ap, fmt);
172	if (col >= 0) {
173		n = vsnprintf(buf, sizeof(buf), fmt, ap);
174		if (n >= (int)sizeof(buf)) {
175			col = -1;
176			goto overflow;
177		} else if (n == 0)
178			goto out;
179
180		if (!bol) {
181			if (col + n > 75)
182				fprintf(stderr, "\n    "), col = 4;
183			else
184				fprintf(stderr, " "), col++;
185		}
186		fprintf(stderr, "%s", buf);
187		col += n;
188		bol = 0;
189	} else {
190overflow:
191		vfprintf(stderr, fmt, ap);
192	}
193out:
194	va_end(ap);
195}
196
197ATF_TC(humanize_number_basic);
198ATF_TC_HEAD(humanize_number_basic, tc)
199{
200
201	atf_tc_set_md_var(tc, "descr", "Test humanize_number(3)");
202}
203
204ATF_TC_BODY(humanize_number_basic, tc)
205{
206	char fbuf[128];
207	const struct hnopts *ho;
208	char *buf = NULL;
209	size_t buflen = 0;
210	unsigned int i;
211	int rv = 0;
212
213	for (i = 0; i < __arraycount(hnopts); i++) {
214		ho = &hnopts[i];
215		if (buflen < ho->ho_len) {
216			buflen = ho->ho_len;
217			buf = realloc(buf, buflen);
218			if (buf == NULL)
219				atf_tc_fail("realloc(..., %zu) failed", buflen);
220		}
221
222		rv = humanize_number(buf, ho->ho_len, ho->ho_num,
223		    ho->ho_suffix, ho->ho_scale, ho->ho_flags);
224
225		if (rv == ho->ho_retval &&
226		    (rv == -1 || strcmp(buf, ho->ho_retstr) == 0))
227			continue;
228
229		w_printf("humanize_number(\"%s\", %zu, %" PRId64 ",",
230		    ho->ho_retstr, ho->ho_len, ho->ho_num);
231		w_printf("\"%s\",", ho->ho_suffix);
232		w_printf("%s,", formatflags(fbuf, sizeof(fbuf), scale_flags,
233		    sizeof(scale_flags) / sizeof(scale_flags[0]),
234		    ho->ho_scale));
235		w_printf("%s)", formatflags(fbuf, sizeof(fbuf), normal_flags,
236		    sizeof(normal_flags) / sizeof(normal_flags[0]),
237		    ho->ho_flags));
238		w_printf("= %d,", ho->ho_retval);
239		w_printf("but got");
240		w_printf("%d/[%s]", rv, rv == -1 ? "" : buf);
241		newline();
242		atf_tc_fail_nonfatal("Failed for table entry %d", i);
243	}
244	free(buf);
245}
246
247ATF_TC(humanize_number_big);
248ATF_TC_HEAD(humanize_number_big, tc)
249{
250
251	atf_tc_set_md_var(tc, "descr", "Test humanize "
252	    "big numbers (PR lib/44097)");
253}
254
255ATF_TC_BODY(humanize_number_big, tc)
256{
257	char buf[1024];
258	int rv;
259
260	/*
261	 * Seems to work.
262	 */
263	(void)memset(buf, 0, sizeof(buf));
264
265	rv = humanize_number(buf, 10, 10000, "", HN_AUTOSCALE, HN_NOSPACE);
266
267	ATF_REQUIRE(rv != -1);
268	ATF_CHECK_STREQ(buf, "10000");
269
270	/*
271	 * A bogus value with large number.
272	 */
273	(void)memset(buf, 0, sizeof(buf));
274
275	rv = humanize_number(buf, 10, INT64_MAX, "", HN_AUTOSCALE, HN_NOSPACE);
276
277	ATF_REQUIRE(rv != -1);
278	ATF_REQUIRE(strcmp(buf, "0") != 0);
279
280	/*
281	 * Large buffer with HN_AUTOSCALE. Entirely bogus.
282	 */
283	(void)memset(buf, 0, sizeof(buf));
284
285	rv = humanize_number(buf, sizeof(buf), 10000, "",
286	    HN_AUTOSCALE, HN_NOSPACE);
287
288	ATF_REQUIRE(rv != -1);
289	ATF_REQUIRE(strcmp(buf, "0%d%s%d%s%s%s") != 0);
290	/*
291	 * PR lib/54053: before version 1.18 the output was nonsense
292	 * with HN_AUTOSCALE and a buffer big enough to not need scaling
293	 */
294	ATF_REQUIRE(strcmp(buf, "10000") == 0);
295
296	/*
297	 * Tight buffer.
298	 *
299	 * The man page says that len must be at least 4.
300	 * 3 works, but anything less that will not. This
301	 * is because baselen starts with 2 for positive
302	 * numbers.
303	 */
304	(void)memset(buf, 0, sizeof(buf));
305
306	rv = humanize_number(buf, 3, 1, "", HN_AUTOSCALE, HN_NOSPACE);
307
308	ATF_REQUIRE(rv != -1);
309}
310
311ATF_TP_ADD_TCS(tp)
312{
313
314	ATF_TP_ADD_TC(tp, humanize_number_basic);
315	ATF_TP_ADD_TC(tp, humanize_number_big);
316
317	return atf_no_error();
318}
319