aboutsummaryrefslogblamecommitdiffstats
path: root/src/slibc-human/machinesize.c
blob: 9d33a5d14da6cdba83dcf0160d539c337fe7a772 (plain) (tree)






















                                                                        
                   















                                                                                         
                                        










































































                                                                                            

                



























                                                          
                             






                                                   
                      



                            
/**
 * slibc — Yet another C library
 * Copyright © 2015  Mattias Andrée (maandree@member.fsf.org)
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
#include <slibc-human.h>
#include <slibc/internals.h>
#include <errno.h>
#include <stddef.h>
#include <stdint.h>
#include <ctype.h>
#include <string.h>



/**
 * Get prefix-value.
 * 
 * @param   str   Pointer to the string, will be updated to point to the end of the unit.
 * @param   mode  How to parse ambiguous units.
 * @return        The multiple for the value, 0 on error.
 * 
 * @throws  EINVAL  The prefix is unrecognised.
 * @throws  ERANGE  The prefix is too large.
 */
__attribute__((nonnull))
static size_t prefix(char** restrict str, enum machinesize_mode mode)
{
#define P(A, B)  case A: case B: power++
  char* p = *str;
  size_t power = 0;
  size_t base = 0;
  size_t rc = 1;
  
  switch (*p++)
    {
      P('Y', 'y');
      P('Z', 'z');
      P('E', 'e');
      P('P', 'p');
      P('T', 't');
      P('G', 'g');
      P('M', 'm');
      P('k', 'K');
    case 'B': case 'b':
      break;
      default:
	return errno = EINVAL, (size_t)0;
    }
  if (power == 0)
    goto done;
  
  if ((*p == 'i') || (*p == 'I'))
    base = 1024, p++;
  if ((*p == 'B') || (*p == 'b'))
    {
      p++;
      if ((!base) && (mode == (MACHINESIZE_SI | MACHINESIZE_IEC)))
	base = 1000;
    }
  if (!base)
    base = (mode == MACHINESIZE_SI) ? 1000 : 1024;
  
  while (power--)
    OVERFLOW(umull, rc, base, &rc, ERANGE, (size_t)0);
  
 done:
  return *str = p, rc;
#undef P
}


/**
 * Parses a human representation of storage size or file offset.
 * 
 * If no unit is used, bytes are assumed. If you rather it be
 * (for example) kilobytes, you can multiply it if
 * `strpbrk(str, "qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM") == NULL`.
 * 
 * @etymology  Convert to (machine)-representation: `(size)_t`.
 * 
 * @param   size   Output parameter for the value, must not be `NULL`.
 * @param   str    The value to parse, must not `NULL`.
 * @param   mode   How to parse ambiguous strings, 0 for default.
 * @param   space  Characters to ignore (thousand-group delimiters).
 *                 Supports UTF-8. `NULL` for default. Default value
 *                 is " '".
 * @param   point  Decimal pointer chracters. Supports UTF-8. `NULL`
 *                 for default. Default value is ",.".
 * @return         Zero on success, -1 on error.
 * 
 * @throws  EINVAL  If `mode` is invalid.
 * @throws  EINVAL  If `str` is not parseable.
 * @throws  ERANGE  If the value is too range to fit in a `size_t`.
 * 
 * @since  Always.
 */
int machinesize(size_t* restrict size, const char* restrict str, enum machinesize_mode mode,
		const char* restrict space, const char* restrict point)
{
  size_t r = 0;
  size_t word;
  long double dword;
  size_t u;
  const char* p;
  const char* q;
  int started = 0;
  int pluses = 0;
  int have_unitless = 0;
  size_t words = 0;
  
  if (space == NULL)  space = " '";
  if (point == NULL)  point = ".,";
  
  if (mode == 0)  mode = MACHINESIZE_SI | MACHINESIZE_IEC;
  if (mode > 3)   goto invalid;
  
  for (p = str; *p;)
    if (strchr(" \t+", *p))
      {
	if ((pluses += (*p++ == '+')) > 1)
	  goto invalid;
      }
    else if ((q = machinefloat(&dword, p, space, point)))
      {
	p = q, words++, pluses = 0, started = 1;
	while (strchr(" \t-", *p))
	  p++;
	if (isalpha(*p) == 0)  u = 1, have_unitless = 1;
	else                   u = prefix(&p, mode);
	if (u == 0)            return -1;
	dword *= (long double)u;
	if (dword > (long double)SIZE_MAX)
	  return errno = ERANGE, -1;
	word = (size_t)dword;
	OVERFLOW(uaddl, word, r, &r, ERANGE, -1);
      }
    else
      return -1;
  
  if ((!started) || (have_unitless && (words > 1)))
    goto invalid;
  return *size = r, 0;
 invalid:
  return errno = EINVAL, -1;
}