File: humansize.c

package info (click to toggle)
scrypt 1.2.0%2Bgit.3.c1a9826-1
  • links: PTS, VCS
  • area: main
  • in suites: stretch
  • size: 456 kB
  • ctags: 328
  • sloc: ansic: 3,307; sh: 391; makefile: 103
file content (165 lines) | stat: -rw-r--r-- 3,240 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
#include <stdio.h>

#include "asprintf.h"
#include "warnp.h"

#include "humansize.h"

/**
 * humansize(size):
 * Given a size in bytes, allocate and return a string of the form "<N> B"
 * for 0 <= N <= 999 or "<X> <prefix>B" where either 10 <= X <= 999 or
 * 1.0 <= X <= 9.9 and <prefix> is "k", "M", "G", "T", "P", or "E"; and where
 * the value returned is the largest valid value <= the provided size.
 */
char *
humansize(uint64_t size)
{
	char * s;
	char prefix;
	int shiftcnt;
	int rc;

	/* Special-case for size < 1000. */
	if (size < 1000) {
		rc = asprintf(&s, "%d B", (int)size);
	} else {
		/* Keep 10 * size / 1000^(3n) in size. */
		for (size /= 100, shiftcnt = 1; size >= 10000; shiftcnt++)
			size /= 1000;

		/*
		 * Figure out what prefix to use.  Since 1 EB = 10^18 B and
		 * the maximum value of a uint64_t is 2^64 which is roughly
		 * 18.4 * 10^18, this cannot reference beyond the end of the
		 * string.
		 */
		prefix = " kMGTPE"[shiftcnt];

		/* Construct the string. */
		if (size < 100)
			rc = asprintf(&s, "%d.%d %cB", (int)size / 10,
			    (int)size % 10, prefix);
		else
			rc = asprintf(&s, "%d %cB", (int)size / 10, prefix);
	}

	if (rc == -1) {
		warnp("asprintf");
		goto err0;
	}

	/* Success! */
	return (s);

err0:
	/* Failure! */
	return (NULL);
}

/**
 * humansize_parse(s, size):
 * Parse a string matching /[0-9]+ ?[kMGTPE]?B?/ as a size in bytes.
 */
int
humansize_parse(const char * s, uint64_t * size)
{
	int state = 0;
	uint64_t multiplier = 1;

	while (*s != '\0') {
		switch (state) {
		case -1:
			/* Error state. */
			break;
		case 0:
			/* Initial state. */
			*size = 0;

			/* We must start with at least one digit. */
			if ((*s < '0') || (*s > '9')) {
				state = -1;
				break;
			}

			/* FALLTHROUGH */
		case 1:
			/* We're now processing digits. */
			state = 1;

			/* Digit-parsing state. */
			if (('0' <= *s) && (*s <= '9')) {
				if (*size > UINT64_MAX / 10)
					state = -1;
				else
					*size *= 10;
				if (*size > UINT64_MAX - (uint64_t)(*s - '0'))
					state = -1;
				else
					*size += (uint64_t)(*s - '0');
				break;
			}

			/* FALLTHROUGH */
		case 2:
			/* We move into state 3 after an optional ' '. */
			state = 3;
			if (*s == ' ')
				break;

			/* FALLTHROUGH */
		case 3:
			/* We may have one SI prefix. */
			switch (*s) {
			case 'E':
				multiplier *= 1000;
				/* FALLTHROUGH */
			case 'P':
				multiplier *= 1000;
				/* FALLTHROUGH */
			case 'T':
				multiplier *= 1000;
				/* FALLTHROUGH */
			case 'G':
				multiplier *= 1000;
				/* FALLTHROUGH */
			case 'M':
				multiplier *= 1000;
				/* FALLTHROUGH */
			case 'k':
				multiplier *= 1000;
				break;
			}

			/* We move into state 4 after the optional prefix. */
			state = 4;
			if (multiplier != 1)
				break;

			/* FALLTHROUGH */
		case 4:
			/* We move into state 5 after an optional 'B'. */
			state = 5;
			if (*s == 'B')
				break;

			/* FALLTHROUGH */
		case 5:
			/* We have trailing garbage. */
			state = -1;
			break;
		}

		/* Move on to the next character. */
		s++;
	}

	/* Multiply by multiplier. */
	if (*size > UINT64_MAX / multiplier)
		state = -1;
	else
		*size *= multiplier;

	/* Anything other than state -1 is success. */
	return ((state == -1) ? -1 : 0);
}