humansize.c 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. #include <stdio.h>
  2. #include "asprintf.h"
  3. #include "warnp.h"
  4. #include "humansize.h"
  5. /**
  6. * humansize(size):
  7. * Given a size ${size} in bytes, allocate and return a string of the form
  8. * "<N> B" for 0 <= N <= 999 or "<X> <prefix>B" where either 10 <= X <= 999 or
  9. * 1.0 <= X <= 9.9 and <prefix> is "k", "M", "G", "T", "P", or "E"; and where
  10. * the value returned is the largest valid value <= the provided size.
  11. */
  12. char *
  13. humansize(uint64_t size)
  14. {
  15. char * s;
  16. char prefix;
  17. int shiftcnt;
  18. int rc;
  19. /* Special-case for size < 1000. */
  20. if (size < 1000) {
  21. rc = asprintf(&s, "%d B", (int)size);
  22. } else {
  23. /* Keep 10 * size / 1000^(3n) in size. */
  24. for (size /= 100, shiftcnt = 1; size >= 10000; shiftcnt++)
  25. size /= 1000;
  26. /*
  27. * Figure out what prefix to use. Since 1 EB = 10^18 B and
  28. * the maximum value of a uint64_t is 2^64 which is roughly
  29. * 18.4 * 10^18, this cannot reference beyond the end of the
  30. * string.
  31. */
  32. prefix = " kMGTPE"[shiftcnt];
  33. /* Construct the string. */
  34. if (size < 100)
  35. rc = asprintf(&s, "%d.%d %cB", (int)size / 10,
  36. (int)size % 10, prefix);
  37. else
  38. rc = asprintf(&s, "%d %cB", (int)size / 10, prefix);
  39. }
  40. if (rc == -1) {
  41. warnp("asprintf");
  42. goto err0;
  43. }
  44. /* Success! */
  45. return (s);
  46. err0:
  47. /* Failure! */
  48. return (NULL);
  49. }
  50. /**
  51. * humansize_parse(s, size):
  52. * Parse a string matching /[0-9]+ ?[kMGTPE]?B?/ as a size ${size} in bytes.
  53. */
  54. int
  55. humansize_parse(const char * s, uint64_t * size)
  56. {
  57. int state = 0;
  58. uint64_t multiplier = 1;
  59. do {
  60. switch (state) {
  61. case -1:
  62. /* Error state. */
  63. break;
  64. case 0:
  65. /* Initial state. */
  66. *size = 0;
  67. /* We must start with at least one digit. */
  68. if ((*s < '0') || (*s > '9')) {
  69. state = -1;
  70. break;
  71. }
  72. /* FALLTHROUGH */
  73. case 1:
  74. /* We're now processing digits. */
  75. state = 1;
  76. /* Digit-parsing state. */
  77. if (('0' <= *s) && (*s <= '9')) {
  78. if (*size > UINT64_MAX / 10)
  79. state = -1;
  80. else
  81. *size *= 10;
  82. if (*size > UINT64_MAX - (uint64_t)(*s - '0'))
  83. state = -1;
  84. else
  85. *size += (uint64_t)(*s - '0');
  86. break;
  87. }
  88. /* FALLTHROUGH */
  89. case 2:
  90. /* We move into state 3 after an optional ' '. */
  91. state = 3;
  92. if (*s == ' ')
  93. break;
  94. /* FALLTHROUGH */
  95. case 3:
  96. /* We may have one SI prefix. */
  97. switch (*s) {
  98. case 'E':
  99. multiplier *= 1000;
  100. /* FALLTHROUGH */
  101. case 'P':
  102. multiplier *= 1000;
  103. /* FALLTHROUGH */
  104. case 'T':
  105. multiplier *= 1000;
  106. /* FALLTHROUGH */
  107. case 'G':
  108. multiplier *= 1000;
  109. /* FALLTHROUGH */
  110. case 'M':
  111. multiplier *= 1000;
  112. /* FALLTHROUGH */
  113. case 'k':
  114. multiplier *= 1000;
  115. break;
  116. }
  117. /* We move into state 4 after the optional prefix. */
  118. state = 4;
  119. if (multiplier != 1)
  120. break;
  121. /* FALLTHROUGH */
  122. case 4:
  123. /* We move into state 5 after an optional 'B'. */
  124. state = 5;
  125. if (*s == 'B')
  126. break;
  127. /* FALLTHROUGH */
  128. case 5:
  129. /* We have trailing garbage. */
  130. state = -1;
  131. break;
  132. }
  133. /* Move on to the next character. */
  134. s++;
  135. } while ((state != -1) && (*s != '\0'));
  136. /* Multiply by multiplier. */
  137. if (*size > UINT64_MAX / multiplier)
  138. state = -1;
  139. else
  140. *size *= multiplier;
  141. /* Anything other than state -1 is success. */
  142. return ((state == -1) ? -1 : 0);
  143. }