/* * filesize.c -- Utilities for displaying file sizes * * ==================================================================== * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. * ==================================================================== */ /*** Includes. ***/ #include #include #include #include #include "cl.h" /*** Code. ***/ /* The structure that describes the units and their magnitudes. */ typedef struct filesize_order_t { svn_filesize_t mask; const char *suffix; const char *short_suffix; } filesize_order_t; /* Get the index of the order of magnitude of the given SIZE. The returned index will be within [0 .. order_size - 1]. */ static apr_size_t get_order_index(svn_filesize_t abs_size, const filesize_order_t *order, apr_size_t order_size) { /* It would be sexy to do a binary search here, but with only 7 elements in the arrays ... we should ### FIXME: do the binary search anyway. */ apr_size_t index = order_size; while (index > 0) { --index; if (abs_size > order[index].mask) break; } return index; } /* Format the adjusted size with the given units. */ 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) { /* NOTE: We want to display a locale-specific decimal sepratator, but APR's formatter completely ignores the locale. So we use the good, old, standard, *dangerous* sprintf() to format the size. But, on the bright side, we require that the number has no more than 3 non-fractional digits. So the call to sprintf() here should be safe. */ const double absolute_human_readable_size = fabs(human_readable_size); const char *const suffix = (long_units ? order[index].suffix : order[index].short_suffix); /* 3 digits (or 2 digits and 1 decimal separator) + 1 negative sign (which should not appear under normal circumstances) + 1 nul terminator --- = 5 characters of space needed in the buffer. */ char buffer[8]; assert(absolute_human_readable_size < 1000.0); /* When the adjusted size has only one significant digit left of the decimal point, show tenths of a unit, too. */ sprintf(buffer, "%.*f", absolute_human_readable_size < 10.0 ? 1 : 0, human_readable_size); return apr_pstrcat(result_pool, buffer, suffix, SVN_VA_NULL); } static const char * get_base2_unit_file_size(svn_filesize_t size, svn_boolean_t long_units, apr_pool_t *result_pool) { static const filesize_order_t order[] = { {APR_INT64_C(0x0000000000000000), " B", "B"}, /* byte */ {APR_INT64_C(0x00000000000003FF), " KiB", "K"}, /* kibi */ {APR_INT64_C(0x00000000000FFFFF), " MiB", "M"}, /* mibi */ {APR_INT64_C(0x000000003FFFFFFF), " GiB", "G"}, /* gibi */ {APR_INT64_C(0x000000FFFFFFFFFF), " TiB", "T"}, /* tibi */ {APR_INT64_C(0x0003FFFFFFFFFFFF), " EiB", "E"}, /* exbi */ {APR_INT64_C(0x0FFFFFFFFFFFFFFF), " PiB", "P"} /* pibi */ }; static const apr_size_t order_size = sizeof(order) / sizeof(order[0]); const svn_filesize_t abs_size = ((size < 0) ? -size : size); apr_size_t index = get_order_index(abs_size, order, order_size); double human_readable_size; /* Adjust the size to the given order of magnitude. This is division by (order[index].mask + 1), which is the base-2^10 magnitude of the size; and that is the same as an arithmetic right shift by (index * 10) bits. But we split it into an integer and a floating-point division, so that we don't overflow the mantissa at very large file sizes. */ if ((abs_size >> 10 * index) > 999) { /* This assertion should never fail, because we only have 4 binary digits in the petabyte (all right, "pibibyte") range and so the number of petabytes can't be large enough to cause the program flow to enter this conditional block. */ assert(index < order_size - 1); ++index; } human_readable_size = (index == 0 ? (double)size : (size >> 3 * index) / 128.0 / index); return format_size(human_readable_size, long_units, order, index, result_pool); } static const char * get_base10_unit_file_size(svn_filesize_t size, svn_boolean_t long_units, apr_pool_t *result_pool) { static const filesize_order_t order[] = { {APR_INT64_C( 0), " B", "B"}, /* byte */ {APR_INT64_C( 999), " kB", "k"}, /* kilo */ {APR_INT64_C( 999999), " MB", "M"}, /* mega */ {APR_INT64_C( 999999999), " GB", "G"}, /* giga */ {APR_INT64_C( 999999999999), " TB", "T"}, /* tera */ {APR_INT64_C( 999999999999999), " EB", "E"}, /* exa */ {APR_INT64_C(999999999999999999), " PB", "P"} /* peta */ /* 18446744073709551615 is the maximum value. */ }; static const apr_size_t order_size = sizeof(order) / sizeof(order[0]); const svn_filesize_t abs_size = ((size < 0) ? -size : size); apr_size_t index = get_order_index(abs_size, order, order_size); double human_readable_size; /* Adjust the size to the given order of magnitude. This is division by (order[index].mask + 1), which is the base-1000 magnitude of the size. For large file sizes, we split the operation into an integer and a floating-point division, so that we don't overflow the mantissa. */ if (index == 0) human_readable_size = (double)size; else if (index <= 3) human_readable_size = (double)size / (order[index].mask + 1); else { /* [ Keep integer division here! ] */ const double divisor = (double)((order[index].mask + 1) / 1000000); human_readable_size = (size / 1000000) / divisor; /* [ And here! ] */ } return format_size(human_readable_size, long_units, order, index, result_pool); } 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) { switch (base) { case SVN_CL__SIZE_UNIT_NONE: case SVN_CL__SIZE_UNIT_XML: *result = apr_psprintf(result_pool, "%" SVN_FILESIZE_T_FMT, size); break; case SVN_CL__SIZE_UNIT_BASE_2: *result = get_base2_unit_file_size(size, long_units, result_pool); break; case SVN_CL__SIZE_UNIT_BASE_10: *result = get_base10_unit_file_size(size, long_units, result_pool); break; default: SVN_ERR_MALFUNCTION(); } return SVN_NO_ERROR; }