Line data Source code
1 : // Copyright (c) 2006-2022 Made to Order Software Corp. All Rights Reserved
2 : //
3 : // https://snapwebsites.org/project/advgetopt
4 : // contact@m2osw.com
5 : //
6 : // This program is free software; you can redistribute it and/or modify
7 : // it under the terms of the GNU General Public License as published by
8 : // the Free Software Foundation; either version 2 of the License, or
9 : // (at your option) any later version.
10 : //
11 : // This program is distributed in the hope that it will be useful,
12 : // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 : // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 : // GNU General Public License for more details.
15 : //
16 : // You should have received a copy of the GNU General Public License along
17 : // with this program; if not, write to the Free Software Foundation, Inc.,
18 : // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 :
20 : /** \file
21 : * \brief Implementation of the size validator.
22 : *
23 : * The advgetopt allows for validating the input parameters automatically.
24 : * This one validator checks whether the input represents what is considered
25 : * a valid size of bits or bytes.
26 : *
27 : * This includes a floating point number followed by a suffix such as "kB"
28 : * or "Gb" or "TiB".
29 : *
30 : * \note
31 : * The size can also represents bits, even though it was written to read
32 : * bytes. If your command line option is expecting a size in bits, this works
33 : * just as expected.
34 : *
35 : * The size conversions are based on the International System of Units (SI).
36 : *
37 : * See: https://en.wikipedia.org/wiki/Kilobyte
38 : */
39 :
40 : // self
41 : //
42 : #include "advgetopt/validator_size.h"
43 :
44 : #include "advgetopt/validator_double.h"
45 :
46 :
47 : // cppthread
48 : //
49 : #include <cppthread/log.h>
50 :
51 :
52 : // last include
53 : //
54 : #include <snapdev/poison.h>
55 :
56 :
57 :
58 :
59 : namespace advgetopt
60 : {
61 :
62 :
63 :
64 : namespace
65 : {
66 :
67 :
68 :
69 2 : class validator_size_factory
70 : : public validator_factory
71 : {
72 : public:
73 2 : validator_size_factory()
74 2 : {
75 2 : validator::register_validator(*this);
76 2 : }
77 :
78 4 : virtual std::string get_name() const override
79 : {
80 4 : return std::string("size");
81 : }
82 :
83 3 : virtual std::shared_ptr<validator> create(string_list_t const & data) const override
84 : {
85 3 : return std::make_shared<validator_size>(data);
86 : }
87 : };
88 :
89 2 : validator_size_factory g_validator_size_factory;
90 :
91 :
92 :
93 : #pragma GCC diagnostic push
94 : #pragma GCC diagnostic ignored "-Wpedantic"
95 313866 : constexpr __int128 power128(__int128 value, int power)
96 : {
97 313866 : __int128 result(value);
98 1569330 : for(--power; power > 0; --power)
99 : {
100 1255464 : result *= value;
101 : }
102 313866 : return result;
103 : }
104 : #pragma GCC diagnostic pop
105 :
106 :
107 :
108 : } // no name namespace
109 :
110 :
111 :
112 :
113 :
114 : /** \brief Initialize the size validator.
115 : *
116 : * The constructor accepts a string defining the acceptable sizes.
117 : *
118 : * The string uses the following format:
119 : *
120 : * \code
121 : * start: flags
122 : * | start flags
123 : *
124 : * flags: 'si'
125 : * | 'legacy'
126 : * \endcode
127 : *
128 : * 'si' stands for "Systeme International" (French for International
129 : * System of Units); this means "1kB" will stand for "1000 bytes".
130 : *
131 : * 'legacy' means that one kilo bytes will be represented by 1024 bytes.
132 : * So "1kB" with the legacy flag turned on represents "1024 bytes".
133 : *
134 : * The 'si' and 'legacy' flags are exclusive, the last one will be effective.
135 : *
136 : * \param[in] flag_list The flags used to define how to interpret the data.
137 : */
138 3 : validator_size::validator_size(string_list_t const & flag_list)
139 : {
140 5 : for(auto r : flag_list)
141 : {
142 2 : if(r == "si")
143 : {
144 1 : f_flags &= ~VALIDATOR_SIZE_POWER_OF_TWO;
145 : }
146 1 : else if(r == "legacy")
147 : {
148 1 : f_flags |= VALIDATOR_SIZE_POWER_OF_TWO;
149 : }
150 : else
151 : {
152 0 : cppthread::log << cppthread::log_level_t::error
153 0 : << r
154 0 : << " is not a valid flag for the size validator."
155 0 : << cppthread::end;
156 0 : continue;
157 : }
158 : }
159 3 : }
160 :
161 :
162 : /** \brief Return the name of this validator.
163 : *
164 : * This function returns "size".
165 : *
166 : * \return "size".
167 : */
168 3 : std::string validator_size::name() const
169 : {
170 3 : return std::string("size");
171 : }
172 :
173 :
174 : /** \brief Determine whether value is a valid size.
175 : *
176 : * This function verifies that the specified value is a valid size.
177 : *
178 : * It makes sures that the value is a valid decimal number which optionally
179 : * starts with a sign (`[-+]?`) and is optionally followed by a known
180 : * measurement suffix.
181 : *
182 : * \param[in] value The value to validate.
183 : *
184 : * \return true if the value validates.
185 : */
186 : #pragma GCC diagnostic push
187 : #pragma GCC diagnostic ignored "-Wpedantic"
188 483108 : bool validator_size::validate(std::string const & value) const
189 : {
190 483108 : __int128 result(0);
191 483108 : return convert_string(value, f_flags, result);
192 : }
193 : #pragma GCC diagnostic pop
194 :
195 :
196 : /** \brief Convert a string to a large integer (128 bits) value representing a size.
197 : *
198 : * This function is used to convert a string to a double representing a
199 : * size. The size can be specified with one of the following suffixes:
200 : *
201 : * * "B" -- 1000^0 bytes
202 : * * "kB" -- 1000^1 bytes
203 : * * "MB" -- 1000^2 bytes
204 : * * "GB" -- 1000^3 bytes
205 : * * "TB" -- 1000^4 bytes
206 : * * "PB" -- 1000^5 bytes
207 : * * "EB" -- 1000^6 bytes
208 : * * "ZB" -- 1000^7 bytes
209 : * * "YB" -- 1000^8 bytes
210 : * * "KiB" -- 1024^1 bytes
211 : * * "MiB" -- 1024^2 bytes
212 : * * "GiB" -- 1024^3 bytes
213 : * * "TiB" -- 1024^4 bytes
214 : * * "PiB" -- 1024^5 bytes
215 : * * "EiB" -- 1024^6 bytes
216 : * * "ZiB" -- 1024^7 bytes
217 : * * "YiB" -- 1024^8 bytes
218 : *
219 : * The suffix capitalization is not important since we can always distinguish
220 : * both types (power of 1000 or 1024). The 'B' represents bytes either way so
221 : * it does not need to be dinstinguished.
222 : *
223 : * In legacy mode (VALIDATOR_SIZE_POWER_OF_TWO flag set), the 1024 power
224 : * is always used.
225 : *
226 : * The final result is an integer representing bytes. If you use a decimal
227 : * number, it will be rounded down. So "1.9B" returns 1. A decimal number
228 : * is practical for larger sizes such as writing "1.3GiB".
229 : *
230 : * \note
231 : * The number is returned in a 128 bit number because Zeta and Yeta numbers
232 : * do not fit in 64 bits.
233 : *
234 : * \param[in] value The value to be converted to a size.
235 : * \param[in] flags The flags to determine how to interpret the suffix.
236 : * \param[out] result The resulting size in bits or bytes.
237 : *
238 : * \return true if the conversion succeeded.
239 : */
240 : #pragma GCC diagnostic push
241 : #pragma GCC diagnostic ignored "-Wpedantic"
242 807108 : bool validator_size::convert_string(
243 : std::string const & value
244 : , flag_t flags
245 : , __int128 & result)
246 : {
247 : // determine the factor by checking the suffix
248 : //
249 807108 : __int128 factor(1);
250 807108 : std::string::size_type pos(value.length());
251 8519400 : for(; pos > 0; --pos)
252 : {
253 4663242 : char c(value[pos - 1]);
254 4663242 : if(c >= '0'
255 2645520 : && c <= '9'
256 3856146 : || c == '.')
257 : {
258 : break;
259 : }
260 : }
261 807108 : if(pos == 0)
262 : {
263 12 : return false;
264 : }
265 807096 : __int128 const base(
266 807096 : (flags & VALIDATOR_SIZE_POWER_OF_TWO) != 0
267 807096 : ? 1024
268 : : 1000);
269 1614192 : std::string const number(value.substr(0, pos));
270 2824812 : for(; pos < value.length() && isspace(value[pos]); ++pos);
271 807096 : if(pos < value.length())
272 : {
273 : // copy and force lowercase (although the case is important
274 : // when writing such a measurement, it does not matter when
275 : // testing here)
276 : //
277 1524504 : std::string suffix;
278 4439076 : for(; pos < value.length(); ++pos)
279 : {
280 3676818 : if(value[pos] >= 'A'
281 1838409 : && value[pos] <= 'Z')
282 : {
283 1434816 : suffix += value[pos] + 0x20;
284 : }
285 : else
286 : {
287 403593 : suffix += value[pos];
288 : }
289 : }
290 :
291 762258 : switch(suffix[0])
292 : {
293 44841 : case 'b':
294 44841 : if(suffix != "b")
295 : {
296 3 : return false;
297 : }
298 44838 : break;
299 :
300 89679 : case 'e':
301 89679 : if(suffix == "eb")
302 : {
303 44838 : factor = base * base * base * base * base * base;
304 : }
305 44841 : else if(suffix == "eib")
306 : {
307 44838 : factor = power128(1024, 6);
308 : }
309 : else
310 : {
311 3 : return false;
312 : }
313 89676 : break;
314 :
315 89679 : case 'g':
316 89679 : if(suffix == "gb")
317 : {
318 44838 : factor = base * base * base;
319 : }
320 44841 : else if(suffix == "gib")
321 : {
322 44838 : factor = power128(1024, 3);
323 : }
324 : else
325 : {
326 3 : return false;
327 : }
328 89676 : break;
329 :
330 89676 : case 'k':
331 89676 : if(suffix == "kb")
332 : {
333 44838 : factor = base;
334 : }
335 44838 : else if(suffix == "kib")
336 : {
337 44838 : factor = 1024;
338 : }
339 : else
340 : {
341 0 : return false;
342 : }
343 89676 : break;
344 :
345 89676 : case 'm':
346 89676 : if(suffix == "mb")
347 : {
348 44838 : factor = base * base;
349 : }
350 44838 : else if(suffix == "mib")
351 : {
352 44838 : factor = power128(1024, 2);
353 : }
354 : else
355 : {
356 0 : return false;
357 : }
358 89676 : break;
359 :
360 89676 : case 'p':
361 89676 : if(suffix == "pb")
362 : {
363 44838 : factor = base * base * base * base * base;
364 : }
365 44838 : else if(suffix == "pib")
366 : {
367 44838 : factor = power128(1024, 5);
368 : }
369 : else
370 : {
371 0 : return false;
372 : }
373 89676 : break;
374 :
375 89676 : case 't':
376 89676 : if(suffix == "tb")
377 : {
378 44838 : factor = base * base * base * base;
379 : }
380 44838 : else if(suffix == "tib")
381 : {
382 44838 : factor = power128(1024, 4);
383 : }
384 : else
385 : {
386 0 : return false;
387 : }
388 89676 : break;
389 :
390 89676 : case 'y':
391 89676 : if(suffix == "yb")
392 : {
393 44838 : factor = base * base * base * base * base * base * base * base;
394 : }
395 44838 : else if(suffix == "yib")
396 : {
397 44838 : factor = power128(1024, 8);
398 : }
399 : else
400 : {
401 0 : return false;
402 : }
403 89676 : break;
404 :
405 89676 : case 'z':
406 89676 : if(suffix == "zb")
407 : {
408 44838 : factor = base * base * base * base * base * base * base;
409 : }
410 44838 : else if(suffix == "zib")
411 : {
412 44838 : factor = power128(1024, 7);
413 : }
414 : else
415 : {
416 0 : return false;
417 : }
418 89676 : break;
419 :
420 3 : default:
421 3 : return false;
422 :
423 : }
424 : }
425 :
426 807084 : double base_number(0.0);
427 807084 : if(!validator_double::convert_string(number, base_number))
428 : {
429 0 : return false;
430 : }
431 :
432 : // TODO: I think that even with the long double this will lose bits
433 807084 : result = static_cast<long double>(base_number) * factor;
434 :
435 807084 : return true;
436 : }
437 : #pragma GCC diagnostic pop
438 :
439 :
440 :
441 6 : } // namespace advgetopt
442 : // vim: ts=4 sw=4 et
|