Browse Source

Add functions to transform string to unsigned integer:

 * (xstrtoumax): from gnulib.
 * (parse_integer): from coreutils (dd.c).

Signed-off-by: Andrew Borodin <aborodin@vmail.ru>
Andrew Borodin 12 years ago
parent
commit
ccf82ada12

+ 24 - 0
lib/strutil.h

@@ -4,6 +4,7 @@
 #include "lib/global.h"         /* include glib.h */
 
 #include <sys/types.h>
+#include <inttypes.h>
 #include <string.h>
 #ifdef HAVE_ASSERT_H
 #include <assert.h>             /* assert() */
@@ -86,6 +87,20 @@ typedef enum
     J_CENTER_LEFT_FIT = 0x14
 } align_crt_t;
 
+/* string-to-integer parsing results
+ */
+typedef enum
+{
+    LONGINT_OK = 0,
+
+    /* These two values can be ORed together, to indicate that both errors occurred. */
+    LONGINT_OVERFLOW = 1,
+    LONGINT_INVALID_SUFFIX_CHAR = 2,
+
+    LONGINT_INVALID_SUFFIX_CHAR_WITH_OVERFLOW = (LONGINT_INVALID_SUFFIX_CHAR | LONGINT_OVERFLOW),
+    LONGINT_INVALID = 4
+} strtol_error_t;
+
 /*** structures declarations (and typedefs of structures)*****************************************/
 
 /* all functions in str_class must be defined for every encoding */
@@ -540,7 +555,13 @@ char *strrstr_skip_count (const char *haystack, const char *needle, size_t skip_
 
 char *str_replace_all (const char *haystack, const char *needle, const char *replacement);
 
+strtol_error_t xstrtoumax (const char *s, char **ptr, int base, uintmax_t * val,
+                           const char *valid_suffixes);
+uintmax_t parse_integer (const char *str, gboolean * invalid);
+
+/* --------------------------------------------------------------------------------------------- */
 /*** inline functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
 
 static inline void
 str_replace (char *s, char from, char to)
@@ -552,6 +573,7 @@ str_replace (char *s, char from, char to)
     }
 }
 
+/* --------------------------------------------------------------------------------------------- */
 /*
  * strcpy is unsafe on overlapping memory areas, so define memmove-alike
  * string function.
@@ -584,4 +606,6 @@ str_move (char *dest, const char *src)
     return (char *) memmove (dest, src, n);
 }
 
+/* --------------------------------------------------------------------------------------------- */
+
 #endif /* MC_STRUTIL_H */

+ 2 - 1
lib/strutil/Makefile.am

@@ -7,6 +7,7 @@ libmcstrutil_la_SOURCES = \
 	strutilascii.c \
 	strutil.c \
 	strutilutf8.c \
-	strverscmp.c
+	strverscmp.c \
+	xstrtol.c
 
 AM_CPPFLAGS = $(GLIB_CFLAGS) -I$(top_srcdir)

+ 39 - 0
lib/strutil/strutil.c

@@ -832,3 +832,42 @@ strrstr_skip_count (const char *haystack, const char *needle, size_t skip_count)
 }
 
 /* --------------------------------------------------------------------------------------------- */
+/* Interprete string as a non-negative decimal integer, optionally multiplied by various values.
+ *
+ * @param str input value
+ * @param invalid set to TRUE if "str" does not represent a number in this format
+ *
+ * @return non-integer representation of "str", 0 in case of error.
+ */
+
+uintmax_t
+parse_integer (const char *str, gboolean * invalid)
+{
+    uintmax_t n;
+    char *suffix;
+    strtol_error_t e;
+
+    e = xstrtoumax (str, &suffix, 10, &n, "bcEGkKMPTwYZ0");
+    if (e == LONGINT_INVALID_SUFFIX_CHAR && *suffix == 'x')
+    {
+        uintmax_t multiplier;
+
+        multiplier = parse_integer (suffix + 1, invalid);
+        if (multiplier != 0 && n * multiplier / multiplier != n)
+        {
+            *invalid = TRUE;
+            return 0;
+        }
+
+        n *= multiplier;
+    }
+    else if (e != LONGINT_OK)
+    {
+        *invalid = TRUE;
+        n = 0;
+    }
+
+    return n;
+}
+
+/* --------------------------------------------------------------------------------------------- */

+ 238 - 0
lib/strutil/xstrtol.c

@@ -0,0 +1,238 @@
+/* A more useful interface to strtol.
+
+   Copyright (C) 1995-1996, 1998-2001, 2003-2007, 2009-2012 Free Software
+   Foundation, Inc.
+
+   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/>.  */
+
+/* Written by Jim Meyering. */
+
+#include <config.h>
+
+/* Some pre-ANSI implementations (e.g. SunOS 4)
+   need stderr defined if assertion checking is enabled.  */
+#include <stdio.h>
+
+#ifdef HAVE_ASSERT_H
+#include <assert.h>
+#endif
+#include <ctype.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "lib/strutil.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static strtol_error_t
+bkm_scale (uintmax_t * x, int scale_factor)
+{
+    if (UINTMAX_MAX / scale_factor < *x)
+    {
+        *x = UINTMAX_MAX;
+        return LONGINT_OVERFLOW;
+    }
+
+    *x *= scale_factor;
+    return LONGINT_OK;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static strtol_error_t
+bkm_scale_by_power (uintmax_t * x, int base, int power)
+{
+    strtol_error_t err = LONGINT_OK;
+    while (power-- != 0)
+        err |= bkm_scale (x, base);
+    return err;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+strtol_error_t
+xstrtoumax (const char *s, char **ptr, int base, uintmax_t * val, const char *valid_suffixes)
+{
+    char *t_ptr;
+    char **p;
+    uintmax_t tmp;
+    strtol_error_t err = LONGINT_OK;
+
+#ifdef HAVE_ASSERT_H
+    assert (0 <= base && base <= 36);
+#endif
+
+    p = (ptr != NULL ? ptr : &t_ptr);
+
+    {
+        const char *q = s;
+        unsigned char ch = *q;
+
+        while (isspace (ch))
+            ch = *++q;
+
+        if (ch == '-')
+            return LONGINT_INVALID;
+    }
+
+    errno = 0;
+    tmp = strtol (s, p, base);
+
+    if (*p == s)
+    {
+        /* If there is no number but there is a valid suffix, assume the
+           number is 1.  The string is invalid otherwise.  */
+        if (valid_suffixes != NULL && **p != '\0' && strchr (valid_suffixes, **p) != NULL)
+            tmp = 1;
+        else
+            return LONGINT_INVALID;
+    }
+    else if (errno != 0)
+    {
+        if (errno != ERANGE)
+            return LONGINT_INVALID;
+        err = LONGINT_OVERFLOW;
+    }
+
+    /* Let valid_suffixes == NULL mean "allow any suffix".  */
+    /* FIXME: update all callers except the ones that allow suffixes
+       after the number, changing last parameter NULL to "".  */
+    if (valid_suffixes == NULL)
+    {
+        *val = tmp;
+        return err;
+    }
+
+    if (**p != '\0')
+    {
+        int suffixes = 1;
+        strtol_error_t overflow;
+
+        if (strchr (valid_suffixes, **p) == NULL)
+        {
+            *val = tmp;
+            return err | LONGINT_INVALID_SUFFIX_CHAR;
+        }
+
+        base = 1024;
+
+        if (strchr (valid_suffixes, '0') != NULL)
+        {
+            /* The "valid suffix" '0' is a special flag meaning that
+               an optional second suffix is allowed, which can change
+               the base.  A suffix "B" (e.g. "100MB") stands for a power
+               of 1000, whereas a suffix "iB" (e.g. "100MiB") stands for
+               a power of 1024.  If no suffix (e.g. "100M"), assume
+               power-of-1024.  */
+
+            switch (p[0][1])
+            {
+            case 'i':
+                if (p[0][2] == 'B')
+                    suffixes += 2;
+                break;
+
+            case 'B':
+            case 'D':          /* 'D' is obsolescent */
+                base = 1000;
+                suffixes++;
+                break;
+            }
+        }
+
+        switch (**p)
+        {
+        case 'b':
+            overflow = bkm_scale (&tmp, 512);
+            break;
+
+        case 'B':
+            overflow = bkm_scale (&tmp, 1024);
+            break;
+
+        case 'c':
+            overflow = 0;
+            break;
+
+        case 'E':              /* exa or exbi */
+            overflow = bkm_scale_by_power (&tmp, base, 6);
+            break;
+
+        case 'G':              /* giga or gibi */
+        case 'g':              /* 'g' is undocumented; for compatibility only */
+            overflow = bkm_scale_by_power (&tmp, base, 3);
+            break;
+
+        case 'k':              /* kilo */
+        case 'K':              /* kibi */
+            overflow = bkm_scale_by_power (&tmp, base, 1);
+            break;
+
+        case 'M':              /* mega or mebi */
+        case 'm':              /* 'm' is undocumented; for compatibility only */
+            overflow = bkm_scale_by_power (&tmp, base, 2);
+            break;
+
+        case 'P':              /* peta or pebi */
+            overflow = bkm_scale_by_power (&tmp, base, 5);
+            break;
+
+        case 'T':              /* tera or tebi */
+        case 't':              /* 't' is undocumented; for compatibility only */
+            overflow = bkm_scale_by_power (&tmp, base, 4);
+            break;
+
+        case 'w':
+            overflow = bkm_scale (&tmp, 2);
+            break;
+
+        case 'Y':              /* yotta or 2**80 */
+            overflow = bkm_scale_by_power (&tmp, base, 8);
+            break;
+
+        case 'Z':              /* zetta or 2**70 */
+            overflow = bkm_scale_by_power (&tmp, base, 7);
+            break;
+
+        default:
+            *val = tmp;
+            return err | LONGINT_INVALID_SUFFIX_CHAR;
+        }
+
+        err |= overflow;
+        *p += suffixes;
+        if (**p != '\0')
+            err |= LONGINT_INVALID_SUFFIX_CHAR;
+    }
+
+    *val = tmp;
+    return err;
+}
+
+/* --------------------------------------------------------------------------------------------- */

+ 5 - 1
tests/lib/strutil/Makefile.am

@@ -8,9 +8,13 @@ AM_CPPFLAGS = \
 LIBS = @CHECK_LIBS@ $(top_builddir)/lib/libmc.la
 
 TESTS = \
-	replace__str_replace_all
+	replace__str_replace_all \
+	parse_integer
 
 check_PROGRAMS = $(TESTS)
 
 replace__str_replace_all_SOURCES = \
 	replace__str_replace_all.c
+
+parse_integer_SOURCES = \
+	parse_integer.c

+ 168 - 0
tests/lib/strutil/parse_integer.c

@@ -0,0 +1,168 @@
+/*
+   lib/strutil - tests for lib/strutil/parse_integer function.
+
+   Copyright (C) 2013
+   The Free Software Foundation, Inc.
+
+   Written by:
+   Andrew Borodin <aborodin@vmail.ru>, 2013
+
+   This file is part of the Midnight Commander.
+
+   The Midnight Commander 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.
+
+   The Midnight Commander 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/>.
+ */
+
+#define TEST_SUITE_NAME "/lib/strutil"
+
+#include "tests/mctest.h"
+
+#include <inttypes.h>
+
+#include "lib/strutil.h"
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* @Before */
+static void
+setup (void)
+{
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* @After */
+static void
+teardown (void)
+{
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* @DataSource("str_replace_all_test_ds") */
+/* *INDENT-OFF* */
+static const struct parse_integer_test_ds
+{
+    const char *haystack;
+    uintmax_t expected_result;
+    gboolean invalid;
+} parse_integer_test_ds[] =
+{
+    {
+        /* too big */
+        "99999999999999999999999999999999999999999999999999999999999999999999",
+        0,
+        TRUE
+    },
+    {
+        "x",
+        0,
+        TRUE
+    },
+    {
+        "9x",
+        0,
+        TRUE
+    },
+    {
+        "1",
+        1,
+        FALSE
+    },
+    {
+        "-1",
+        0,
+        TRUE
+    },
+    {
+        "1k",
+        1024,
+        FALSE
+    },
+    {
+        "1K",
+        1024,
+        FALSE
+    },
+    {
+        "1M",
+        1024 * 1024,
+        FALSE
+    },
+    {
+        "1m",
+        0,
+        TRUE
+    },
+    {
+        "64M",
+        64 * 1024 * 1024,
+        FALSE
+    },
+    {
+        "1G",
+        1 * 1024 * 1024 * 1024,
+        FALSE
+    }
+};
+/* *INDENT-ON* */
+
+/* @Test(dataSource = "str_replace_all_test_ds") */
+/* *INDENT-OFF* */
+START_TEST (parse_integer_test)
+/* *INDENT-ON* */
+{
+    /* given */
+    uintmax_t actual_result;
+    gboolean invalid = FALSE;
+    const struct parse_integer_test_ds *data = &parse_integer_test_ds[_i];
+
+    /* when */
+    actual_result = parse_integer (data->haystack, &invalid);
+
+    /* then */
+    fail_unless (invalid == data->invalid && actual_result == data->expected_result,
+                 "actial ( %" PRIuMAX ") not equal to\nexpected (%" PRIuMAX  ")",
+                 actual_result, data->expected_result);
+}
+/* *INDENT-OFF* */
+END_TEST
+/* *INDENT-ON* */
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+main (void)
+{
+    int number_failed;
+
+    Suite *s = suite_create (TEST_SUITE_NAME);
+    TCase *tc_core = tcase_create ("Core");
+    SRunner *sr;
+
+    tcase_add_checked_fixture (tc_core, setup, teardown);
+
+    /* Add new tests here: *************** */
+    tcase_add_loop_test (tc_core, parse_integer_test, 0, G_N_ELEMENTS (parse_integer_test_ds));
+    /* *********************************** */
+
+    suite_add_tcase (s, tc_core);
+    sr = srunner_create (s);
+    srunner_set_log (sr, "parse_integer.log");
+    srunner_run_all (sr, CK_NOFORK);
+    number_failed = srunner_ntests_failed (sr);
+    srunner_free (sr);
+    return (number_failed == 0) ? 0 : 1;
+}
+
+/* --------------------------------------------------------------------------------------------- */