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