common: New function compare_version_strings.
authorWerner Koch <wk@gnupg.org>
Fri, 8 Jan 2016 07:58:21 +0000 (08:58 +0100)
committerWerner Koch <wk@gnupg.org>
Fri, 8 Jan 2016 07:58:21 +0000 (08:58 +0100)
* common/stringhelp.c (parse_version_number): New.
(parse_version_string): New.
(compare_version_strings): New.
* common/t-stringhelp.c (test_compare_version_strings): New.
(main): Call test.  Return ERRCOUNT instead of 0.
--

The code for that function is based on code from libgcrypt.  Similar
code is in all GnuPG related libraries this function is
a candidates for inclusion in libgpg-error.

Signed-off-by: Werner Koch <wk@gnupg.org>
common/stringhelp.c
common/stringhelp.h
common/t-stringhelp.c

index e8b990a..8b47a1c 100644 (file)
@@ -1329,6 +1329,91 @@ strtokenize (const char *string, const char *delim)
 }
 
 
+\f
+/* Version number parsing.  */
+
+/* This function parses the first portion of the version number S and
+   stores it in *NUMBER.  On success, this function returns a pointer
+   into S starting with the first character, which is not part of the
+   initial number portion; on failure, NULL is returned.  */
+static const char*
+parse_version_number (const char *s, int *number)
+{
+  int val = 0;
+
+  if (*s == '0' && digitp (s+1))
+    return NULL;  /* Leading zeros are not allowed.  */
+  for (; digitp (s); s++)
+    {
+      val *= 10;
+      val += *s - '0';
+    }
+  *number = val;
+  return val < 0 ? NULL : s;
+}
+
+
+/* This function breaks up the complete string-representation of the
+   version number S, which is of the following struture: <major
+   number>.<minor number>.<micro number><patch level>.  The major,
+   minor and micro number components will be stored in *MAJOR, *MINOR
+   and *MICRO.
+
+   On success, the last component, the patch level, will be returned;
+   in failure, NULL will be returned.  */
+static const char *
+parse_version_string (const char *s, int *major, int *minor, int *micro)
+{
+  s = parse_version_number (s, major);
+  if (!s || *s != '.')
+    return NULL;
+  s++;
+  s = parse_version_number (s, minor);
+  if (!s)
+    return NULL;
+  if (*s == '.')
+    {
+      s++;
+      s = parse_version_number (s, micro);
+      if (!s)
+        return NULL;
+    }
+  else
+    *micro = 0;
+  return s;  /* Patchlevel.  */
+}
+
+
+/* Check that the version string MY_VERSION is greater or equal than
+   REQ_VERSION.  Returns true if the condition is satisfied or false
+   if not.  This works with 3 part and two part version strings; for a
+   two part version string the micor part is assumed to be 0.  */
+int
+compare_version_strings (const char *my_version, const char *req_version)
+{
+  int my_major, my_minor, my_micro;
+  int rq_major, rq_minor, rq_micro;
+
+  if (!my_version || !req_version)
+    return 0;
+
+  if (!parse_version_string (my_version, &my_major, &my_minor, &my_micro))
+    return 0;
+  if (!parse_version_string(req_version, &rq_major, &rq_minor, &rq_micro))
+    return 0;
+
+  if (my_major > rq_major
+      || (my_major == rq_major && my_minor > rq_minor)
+      || (my_major == rq_major && my_minor == rq_minor
+         && my_micro >= rq_micro))
+    {
+      return 1;
+    }
+  return 0;
+}
+
+
+\f
 /* Format a string so that it fits within about TARGET_COLS columns.
    If IN_PLACE is 0, then TEXT is copied to a new buffer, which is
    returned.  Otherwise, TEXT is modified in place and returned.
index c813662..d9225a3 100644 (file)
@@ -148,6 +148,9 @@ char **strsplit (char *string, char delim, char replacement, int *count);
 /* Tokenize STRING using the set of delimiters in DELIM.  */
 char **strtokenize (const char *string, const char *delim);
 
+/* Return True if MYVERSION is greater or equal than REQ_VERSION.  */
+int compare_version_strings (const char *my_version, const char *req_version);
+
 /* Format a string so that it fits within about TARGET_COLS columns.  */
 char *format_text (char *text, int in_place, int target_cols, int max_cols);
 
index af79cb5..b4a41ac 100644 (file)
@@ -705,6 +705,7 @@ stresc (char *s)
   return p;
 }
 
+
 static void
 test_format_text (void)
 {
@@ -813,6 +814,65 @@ test_format_text (void)
     fail(0);
 }
 
+
+static void
+test_compare_version_strings (void)
+{
+  struct { const char *a; const char *b; int okay; } tests[] = {
+    { "1.0.0",   "1.0.0", 1 },
+    { "1.0.0-",  "1.0.0", 1 },
+    { "1.0.0-1", "1.0.0", 1 },
+    { "1.0.0.1", "1.0.0", 1 },
+    { "1.0.0",   "1.0.1", 0 },
+    { "1.0.0-",  "1.0.1", 0 },
+    { "1.0.0-1", "1.0.1", 0 },
+    { "1.0.0.1", "1.0.1", 0 },
+    { "1.0.0",   "1.1.0", 0 },
+    { "1.0.0-",  "1.1.0", 0 },
+    { "1.0.0-1", "1.1.0", 0 },
+    { "1.0.0.1", "1.1.0", 0 },
+
+    { "1.0.0",   "1.0.0-", 1 },
+    { "1.0.0",   "1.0.0-1", 1 },
+    { "1.0.0",   "1.0.0.1", 1 },
+    { "1.1.0",   "1.0.0", 1 },
+    { "1.1.1",   "1.1.0", 1 },
+    { "1.1.2",   "1.1.2", 1 },
+    { "1.1.2",   "1.0.2", 1 },
+    { "1.1.2",   "0.0.2", 1 },
+    { "1.1.2",   "1.1.3", 0 },
+
+    { "0.99.1",  "0.9.9", 1 },
+    { "0.9.1",   "0.91.0", 0 },
+
+    { "1.5.3",   "1.5",  1 },
+    { "1.5.0",   "1.5",  1 },
+    { "1.4.99",  "1.5",  0 },
+    { "1.5",     "1.4.99",  1 },
+    { "1.5",     "1.5.0",  1 },
+    { "1.5",     "1.5.1",  0 },
+
+    { "1.5.3-x17",   "1.5-23",  1 },
+
+    { "1.5.3a",   "1.5.3",  1 },
+    { "1.5.3a",   "1.5.3b",  1 },
+
+    { NULL, NULL, 0 }
+  };
+  int idx;
+  int res;
+
+  for (idx=0; idx < DIM(tests); idx++)
+    {
+      res = compare_version_strings (tests[idx].a, tests[idx].b);
+      /* printf ("test %d: '%s'  '%s'  %d  ->  %d\n", */
+      /*         idx, tests[idx].a, tests[idx].b, tests[idx].okay, res); */
+      if (res != tests[idx].okay)
+        fail (idx);
+    }
+}
+
+
 int
 main (int argc, char **argv)
 {
@@ -827,8 +887,9 @@ main (int argc, char **argv)
   test_make_absfilename_try ();
   test_strsplit ();
   test_strtokenize ();
+  test_compare_version_strings ();
   test_format_text ();
 
   xfree (home_buffer);
-  return 0;
+  return !!errcount;
 }