Line data Source code
1 : // Copyright (c) 2006-2024 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 : // snapdev
53 : //
54 : #include <snapdev/int128_literal.h>
55 : #include <snapdev/math.h>
56 :
57 :
58 : // last include
59 : //
60 : #include <snapdev/poison.h>
61 :
62 :
63 :
64 :
65 : namespace advgetopt
66 : {
67 :
68 :
69 :
70 : namespace
71 : {
72 :
73 :
74 :
75 : class validator_size_factory
76 : : public validator_factory
77 : {
78 : public:
79 2 : validator_size_factory()
80 2 : {
81 2 : validator::register_validator(*this);
82 2 : }
83 :
84 4 : virtual std::string get_name() const override
85 : {
86 4 : return std::string("size");
87 : }
88 :
89 4 : virtual std::shared_ptr<validator> create(string_list_t const & data) const override
90 : {
91 4 : return std::make_shared<validator_size>(data);
92 : }
93 : };
94 :
95 : validator_size_factory g_validator_size_factory;
96 :
97 :
98 :
99 : } // no name namespace
100 :
101 :
102 :
103 :
104 :
105 : /** \brief Initialize the size validator.
106 : *
107 : * The constructor accepts a string defining the acceptable sizes.
108 : *
109 : * The string uses the following format:
110 : *
111 : * \code
112 : * start: flags
113 : * | start flags
114 : *
115 : * flags: 'si'
116 : * | 'legacy'
117 : * \endcode
118 : *
119 : * 'si' stands for "Systeme International" (French for International
120 : * System of Units); this means "1kB" will stand for "1000 bytes".
121 : *
122 : * 'legacy' means that one kilo bytes will be represented by 1024 bytes.
123 : * So "1kB" with the legacy flag turned on represents "1024 bytes".
124 : *
125 : * The 'si' and 'legacy' flags are exclusive, the last one will be effective.
126 : *
127 : * \param[in] flag_list The flags used to define how to interpret the data.
128 : */
129 4 : validator_size::validator_size(string_list_t const & flag_list)
130 : {
131 9 : for(auto r : flag_list)
132 : {
133 5 : if(r == "si")
134 : {
135 2 : f_flags &= ~VALIDATOR_SIZE_POWER_OF_TWO;
136 : }
137 3 : else if(r == "legacy")
138 : {
139 2 : f_flags |= VALIDATOR_SIZE_POWER_OF_TWO;
140 : }
141 : else
142 : {
143 2 : cppthread::log << cppthread::log_level_t::error
144 1 : << r
145 1 : << " is not a valid flag for the size validator."
146 2 : << cppthread::end;
147 1 : continue;
148 : }
149 5 : }
150 4 : }
151 :
152 :
153 : /** \brief Return the name of this validator.
154 : *
155 : * This function returns "size".
156 : *
157 : * \return "size".
158 : */
159 3 : std::string validator_size::name() const
160 : {
161 3 : return std::string("size");
162 : }
163 :
164 :
165 : /** \brief Determine whether value is a valid size.
166 : *
167 : * This function verifies that the specified value is a valid size.
168 : *
169 : * It makes sures that the value is a valid decimal number which optionally
170 : * starts with a sign (`[-+]?`) and is optionally followed by a known
171 : * measurement suffix.
172 : *
173 : * \param[in] value The value to validate.
174 : *
175 : * \return true if the value validates.
176 : */
177 : #pragma GCC diagnostic push
178 : #pragma GCC diagnostic ignored "-Wpedantic"
179 592307 : bool validator_size::validate(std::string const & value) const
180 : {
181 : using namespace snapdev::literals;
182 592307 : __int128 result(0_int128);
183 1184614 : return convert_string(value, f_flags, result);
184 : }
185 : #pragma GCC diagnostic pop
186 :
187 :
188 : /** \brief Convert a string to a large integer (128 bits) value representing a size.
189 : *
190 : * This function is used to convert a string to a double representing a
191 : * size. The size can be specified with one of the following suffixes:
192 : *
193 : * * "B" -- 1000^0 bytes
194 : * * "kB" -- 1000^1 bytes
195 : * * "MB" -- 1000^2 bytes
196 : * * "GB" -- 1000^3 bytes
197 : * * "TB" -- 1000^4 bytes
198 : * * "PB" -- 1000^5 bytes
199 : * * "EB" -- 1000^6 bytes
200 : * * "ZB" -- 1000^7 bytes
201 : * * "YB" -- 1000^8 bytes
202 : * * "RB" -- 1000^9 bytes
203 : * * "QB" -- 1000^10 bytes
204 : * * "KiB" -- 1024^1 bytes
205 : * * "MiB" -- 1024^2 bytes
206 : * * "GiB" -- 1024^3 bytes
207 : * * "TiB" -- 1024^4 bytes
208 : * * "PiB" -- 1024^5 bytes
209 : * * "EiB" -- 1024^6 bytes
210 : * * "ZiB" -- 1024^7 bytes
211 : * * "YiB" -- 1024^8 bytes
212 : * * "RiB" -- 1024^9 bytes
213 : * * "QiB" -- 1024^10 bytes
214 : *
215 : * The suffix capitalization is not important since we can always distinguish
216 : * both types (power of 1000 or 1024). The 'B' represents bytes either way so
217 : * it does not need to be dinstinguished.
218 : *
219 : * In legacy mode (VALIDATOR_SIZE_POWER_OF_TWO flag set), the 1024 power
220 : * is always used.
221 : *
222 : * The final result is an integer representing bytes. If you use a decimal
223 : * number, it will be rounded down (floor). So "1.9B" returns 1. A decimal
224 : * number is practical for larger sizes such as "1.3GiB".
225 : *
226 : * \note
227 : * The result is returned in a 128 bit number because Zeta and Yeta values
228 : * do not fit in 64 bits.
229 : *
230 : * \todo
231 : * We may want to support full names instead of just the minimal abbreviated
232 : * suffixes (i.e. "3 bytes" fails). Also we could support bits but that is
233 : * \em complicated because the only difference is whether you use upper or
234 : * lower case characters (i.e. kB is kilo bytes, kb is kilo bits).
235 : *
236 : * \param[in] value The value to be converted to a size.
237 : * \param[in] flags The flags to determine how to interpret the suffix.
238 : * \param[out] result The resulting size in bits or bytes.
239 : *
240 : * \return true if the conversion succeeded.
241 : */
242 : #pragma GCC diagnostic push
243 : #pragma GCC diagnostic ignored "-Wpedantic"
244 988307 : bool validator_size::convert_string(
245 : std::string const & value
246 : , flag_t flags
247 : , __int128 & result)
248 : {
249 : using namespace snapdev::literals;
250 :
251 : // determine the factor by checking the suffix
252 : //
253 988307 : __int128 factor(1_int128);
254 988307 : std::string::size_type pos(value.length());
255 5750145 : for(; pos > 0; --pos)
256 : {
257 5750140 : char c(value[pos - 1]);
258 5750140 : if(c >= '0'
259 3279416 : && c <= '9'
260 4761838 : || c == '.')
261 : {
262 : break;
263 : }
264 : }
265 988307 : if(pos == 0)
266 : {
267 5 : return false;
268 : }
269 : __int128 const base(
270 988302 : (flags & VALIDATOR_SIZE_POWER_OF_TWO) != 0
271 988302 : ? 1024_int128
272 660000 : : 1000_int128);
273 988302 : std::string const number(value.substr(0, pos));
274 3459022 : for(; pos < value.length() && isspace(value[pos]); ++pos);
275 988302 : if(pos < value.length())
276 : {
277 : // copy and force lowercase (although the case is important
278 : // when writing such a measurement, it does not matter when
279 : // testing here)
280 : //
281 943380 : std::string suffix;
282 3234490 : for(; pos < value.length(); ++pos)
283 : {
284 2291110 : if(value[pos] >= 'A'
285 2291110 : && value[pos] <= 'Z')
286 : {
287 1796889 : suffix += value[pos] + 0x20;
288 : }
289 : else
290 : {
291 494221 : suffix += value[pos];
292 : }
293 : }
294 :
295 943380 : switch(suffix[0])
296 : {
297 44924 : case 'b':
298 44924 : if(suffix != "b")
299 : {
300 1 : return false;
301 : }
302 44923 : break;
303 :
304 89845 : case 'e':
305 89845 : if(suffix == "eb")
306 : {
307 44922 : factor = snapdev::pow(base, 6);
308 : }
309 44923 : else if(suffix == "eib")
310 : {
311 44922 : factor = snapdev::pow(1024_int128, 6);
312 : }
313 : else
314 : {
315 1 : return false;
316 : }
317 89844 : break;
318 :
319 89846 : case 'g':
320 89846 : if(suffix == "gb")
321 : {
322 44922 : factor = snapdev::pow(base, 3);
323 : }
324 44924 : else if(suffix == "gib")
325 : {
326 44923 : factor = snapdev::pow(1024_int128, 3);
327 : }
328 : else
329 : {
330 1 : return false;
331 : }
332 89845 : break;
333 :
334 89846 : case 'k':
335 89846 : if(suffix == "kb")
336 : {
337 44922 : factor = base;
338 : }
339 44924 : else if(suffix == "kib")
340 : {
341 44923 : factor = 1024_int128;
342 : }
343 : else
344 : {
345 1 : return false;
346 : }
347 89845 : break;
348 :
349 89847 : case 'm':
350 89847 : if(suffix == "mb")
351 : {
352 44922 : factor = snapdev::pow(base, 2);
353 : }
354 44925 : else if(suffix == "mib")
355 : {
356 44923 : factor = snapdev::pow(1024_int128, 2);
357 : }
358 : else
359 : {
360 2 : return false;
361 : }
362 89845 : break;
363 :
364 89846 : case 'p':
365 89846 : if(suffix == "pb")
366 : {
367 44922 : factor = snapdev::pow(base, 5);
368 : }
369 44924 : else if(suffix == "pib")
370 : {
371 44923 : factor = snapdev::pow(1024_int128, 5);
372 : }
373 : else
374 : {
375 1 : return false;
376 : }
377 89845 : break;
378 :
379 89844 : case 'q':
380 89844 : if(suffix == "qb")
381 : {
382 44922 : factor = snapdev::pow(base, 10);
383 : }
384 44922 : else if(suffix == "qib")
385 : {
386 44922 : factor = snapdev::pow(1024_int128, 10);
387 : }
388 : else
389 : {
390 0 : return false;
391 : }
392 89844 : break;
393 :
394 89844 : case 'r':
395 89844 : if(suffix == "rb")
396 : {
397 44922 : factor = snapdev::pow(base, 9);
398 : }
399 44922 : else if(suffix == "rib")
400 : {
401 44922 : factor = snapdev::pow(1024_int128, 9);
402 : }
403 : else
404 : {
405 0 : return false;
406 : }
407 89844 : break;
408 :
409 89846 : case 't':
410 89846 : if(suffix == "tb")
411 : {
412 44922 : factor = snapdev::pow(base, 4);
413 : }
414 44924 : else if(suffix == "tib")
415 : {
416 44922 : factor = snapdev::pow(1024_int128, 4);
417 : }
418 : else
419 : {
420 2 : return false;
421 : }
422 89844 : break;
423 :
424 89845 : case 'y':
425 89845 : if(suffix == "yb")
426 : {
427 44922 : factor = snapdev::pow(base, 8);
428 : }
429 44923 : else if(suffix == "yib")
430 : {
431 44922 : factor = snapdev::pow(1024_int128, 8);
432 : }
433 : else
434 : {
435 1 : return false;
436 : }
437 89844 : break;
438 :
439 89845 : case 'z':
440 89845 : if(suffix == "zb")
441 : {
442 44922 : factor = snapdev::pow(base, 7);
443 : }
444 44923 : else if(suffix == "zib")
445 : {
446 44922 : factor = snapdev::pow(1024_int128, 7);
447 : }
448 : else
449 : {
450 1 : return false;
451 : }
452 89844 : break;
453 :
454 2 : default:
455 2 : return false;
456 :
457 : }
458 943380 : }
459 :
460 988289 : double base_number(0.0);
461 988289 : if(!validator_double::convert_string(number, base_number))
462 : {
463 5 : return false;
464 : }
465 :
466 : // TODO: I think that even with the long double this will lose bits
467 988284 : result = static_cast<long double>(base_number) * factor;
468 :
469 988284 : return true;
470 988302 : }
471 : #pragma GCC diagnostic pop
472 :
473 :
474 :
475 : } // namespace advgetopt
476 : // vim: ts=4 sw=4 et
|