1 /*
2  * filesize.c -- Utilities for displaying file sizes
3  *
4  * ====================================================================
5  *    Licensed to the Apache Software Foundation (ASF) under one
6  *    or more contributor license agreements.  See the NOTICE file
7  *    distributed with this work for additional information
8  *    regarding copyright ownership.  The ASF licenses this file
9  *    to you under the Apache License, Version 2.0 (the
10  *    "License"); you may not use this file except in compliance
11  *    with the License.  You may obtain a copy of the License at
12  *
13  *      http://www.apache.org/licenses/LICENSE-2.0
14  *
15  *    Unless required by applicable law or agreed to in writing,
16  *    software distributed under the License is distributed on an
17  *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18  *    KIND, either express or implied.  See the License for the
19  *    specific language governing permissions and limitations
20  *    under the License.
21  * ====================================================================
22  */
23 
24 
25 /*** Includes. ***/
26 
27 #include <assert.h>
28 #include <math.h>
29 #include <stdio.h>
30 
31 #include <apr_strings.h>
32 
33 #include "cl.h"
34 
35 
36 /*** Code. ***/
37 
38 /* The structure that describes the units and their magnitudes. */
39 typedef struct filesize_order_t
40 {
41   svn_filesize_t mask;
42   const char *suffix;
43   const char *short_suffix;
44 } filesize_order_t;
45 
46 
47 /* Get the index of the order of magnitude of the given SIZE.
48    The returned index will be within [0 .. order_size - 1]. */
49 static apr_size_t
get_order_index(svn_filesize_t abs_size,const filesize_order_t * order,apr_size_t order_size)50 get_order_index(svn_filesize_t abs_size,
51                 const filesize_order_t *order,
52                 apr_size_t order_size)
53 {
54   /* It would be sexy to do a binary search here, but with only 7 elements
55      in the arrays ... we should ### FIXME: do the binary search anyway. */
56   apr_size_t index = order_size;
57   while (index > 0)
58     {
59       --index;
60       if (abs_size > order[index].mask)
61         break;
62     }
63   return index;
64 }
65 
66 
67 /* Format the adjusted size with the given units. */
68 static const char *
format_size(double human_readable_size,svn_boolean_t long_units,const filesize_order_t * order,apr_size_t index,apr_pool_t * result_pool)69 format_size(double human_readable_size,
70             svn_boolean_t long_units,
71             const filesize_order_t *order,
72             apr_size_t index,
73             apr_pool_t *result_pool)
74 {
75   /* NOTE: We want to display a locale-specific decimal sepratator, but
76            APR's formatter completely ignores the locale. So we use the
77            good, old, standard, *dangerous* sprintf() to format the size.
78 
79            But, on the bright side, we require that the number has no more
80            than 3 non-fractional digits. So the call to sprintf() here
81            should be safe. */
82   const double absolute_human_readable_size = fabs(human_readable_size);
83   const char *const suffix = (long_units ? order[index].suffix
84                               : order[index].short_suffix);
85 
86   /*   3 digits (or 2 digits and 1 decimal separator)
87      + 1 negative sign (which should not appear under normal circumstances)
88      + 1 nul terminator
89      ---
90      = 5 characters of space needed in the buffer. */
91     char buffer[64];
92 
93     assert(absolute_human_readable_size < 1000);
94 
95     /* When the adjusted size has only one significant digit left of
96        the decimal point, show tenths of a unit, too. Except when
97        the absolute size is actually a single-digit number, because
98        files can't have fractional byte sizes. */
99     if (absolute_human_readable_size >= 10)
100       sprintf(buffer, "%.0f", human_readable_size);
101     else
102       {
103         double integral;
104         const double frac = modf(absolute_human_readable_size, &integral);
105         const int decimals = (index > 0 && (integral < 9 || frac <= .949999999));
106         sprintf(buffer, "%.*f", decimals, human_readable_size);
107       }
108 
109     return apr_pstrcat(result_pool, buffer, suffix, SVN_VA_NULL);
110 }
111 
112 
113 static const char *
get_base2_unit_file_size(svn_filesize_t size,svn_boolean_t long_units,apr_pool_t * result_pool)114 get_base2_unit_file_size(svn_filesize_t size,
115                          svn_boolean_t long_units,
116                          apr_pool_t *result_pool)
117 {
118   static const filesize_order_t order[] =
119     {
120       {APR_INT64_C(0x0000000000000000), " B",   "B"}, /* byte */
121       {APR_INT64_C(0x00000000000003FF), " KiB", "K"}, /* kibi */
122       {APR_INT64_C(0x00000000000FFFFF), " MiB", "M"}, /* mibi */
123       {APR_INT64_C(0x000000003FFFFFFF), " GiB", "G"}, /* gibi */
124       {APR_INT64_C(0x000000FFFFFFFFFF), " TiB", "T"}, /* tibi */
125       {APR_INT64_C(0x0003FFFFFFFFFFFF), " EiB", "E"}, /* exbi */
126       {APR_INT64_C(0x0FFFFFFFFFFFFFFF), " PiB", "P"}  /* pibi */
127     };
128   static const apr_size_t order_size = sizeof(order) / sizeof(order[0]);
129 
130   const svn_filesize_t abs_size = ((size < 0) ? -size : size);
131   apr_size_t index = get_order_index(abs_size, order, order_size);
132   double human_readable_size;
133 
134   /* Adjust the size to the given order of magnitude.
135 
136      This is division by (order[index].mask + 1), which is the base-2^10
137      magnitude of the size; and that is the same as an arithmetic right
138      shift by (index * 10) bits. But we split it into an integer and a
139      floating-point division, so that we don't overflow the mantissa at
140      very large file sizes. */
141   if ((abs_size >> 10 * index) > 999)
142     {
143       /* This assertion should never fail, because we only have 4 binary
144          digits in the petabyte (all right, "pibibyte") range and so the
145          number of petabytes can't be large enough to cause the program
146          flow to enter this conditional block. */
147       assert(index < order_size - 1);
148       ++index;
149     }
150 
151   human_readable_size = (index == 0 ? (double)size
152                          : (size >> (10 * index - 10)) / 1024.0);
153 
154   return format_size(human_readable_size,
155                      long_units, order, index, result_pool);
156 }
157 
158 
159 static const char *
get_base10_unit_file_size(svn_filesize_t size,svn_boolean_t long_units,apr_pool_t * result_pool)160 get_base10_unit_file_size(svn_filesize_t size,
161                           svn_boolean_t long_units,
162                           apr_pool_t *result_pool)
163 {
164   static const filesize_order_t order[] =
165     {
166       {APR_INT64_C(                 0), " B",  "B"}, /* byte */
167       {APR_INT64_C(               999), " kB", "k"}, /* kilo */
168       {APR_INT64_C(            999999), " MB", "M"}, /* mega */
169       {APR_INT64_C(         999999999), " GB", "G"}, /* giga */
170       {APR_INT64_C(      999999999999), " TB", "T"}, /* tera */
171       {APR_INT64_C(   999999999999999), " EB", "E"}, /* exa  */
172       {APR_INT64_C(999999999999999999), " PB", "P"}  /* peta */
173       /*          9223372036854775807 is the maximum value.  */
174     };
175   static const apr_size_t order_size = sizeof(order) / sizeof(order[0]);
176 
177   const svn_filesize_t abs_size = ((size < 0) ? -size : size);
178   apr_size_t index = get_order_index(abs_size, order, order_size);
179   double human_readable_size;
180 
181   /* Adjust the size to the given order of magnitude.
182 
183      This is division by (order[index].mask + 1), which is the
184      base-1000 magnitude of the size. We split the operation into an
185      integer and a floating-point division, so that we don't
186      overflow the mantissa. */
187   if (index == 0)
188     human_readable_size = (double)size;
189   else
190     {
191       const svn_filesize_t divisor = (order[index - 1].mask + 1);
192       /*      [Keep integer arithmetic here!] */
193       human_readable_size = (size / divisor) / 1000.0;
194     }
195 
196   /* Adjust index and number for rounding. */
197   if (human_readable_size >= 999.5)
198     {
199       /* This assertion should never fail, because we only have one
200          decimal digit in the petabyte range and so the number of
201          petabytes can't be large enough to cause the program flow
202          to enter this conditional block. */
203       assert(index < order_size - 1);
204       human_readable_size /= 1000.0;
205       ++index;
206     }
207 
208   return format_size(human_readable_size,
209                      long_units, order, index, result_pool);
210 }
211 
212 
213 svn_error_t *
svn_cl__format_file_size(const char ** result,svn_filesize_t size,svn_cl__size_unit_t base,svn_boolean_t long_units,apr_pool_t * result_pool)214 svn_cl__format_file_size(const char **result,
215                          svn_filesize_t size,
216                          svn_cl__size_unit_t base,
217                          svn_boolean_t long_units,
218                          apr_pool_t *result_pool)
219 {
220   switch (base)
221     {
222     case SVN_CL__SIZE_UNIT_NONE:
223     case SVN_CL__SIZE_UNIT_XML:
224       *result = apr_psprintf(result_pool, "%" SVN_FILESIZE_T_FMT, size);
225       break;
226 
227     case SVN_CL__SIZE_UNIT_BASE_2:
228       *result = get_base2_unit_file_size(size, long_units, result_pool);
229       break;
230 
231     case SVN_CL__SIZE_UNIT_BASE_10:
232       *result = get_base10_unit_file_size(size, long_units, result_pool);
233       break;
234 
235     default:
236       SVN_ERR_MALFUNCTION();
237     }
238 
239   return SVN_NO_ERROR;
240 }
241