2003-04-25 Marcus Brinkmann <marcus@g10code.de>
[gpgme.git] / gpgme / version.c
index cac1982..d64e67a 100644 (file)
-/* version.c -  version check
- *     Copyright (C) 2000 Werner Koch (dd9jn)
- *
- * This file is part of GPGME.
- *
- * GPGME 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 2 of the License, or
- * (at your option) any later version.
- *
- * GPGME 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, write to the Free Software
- * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
- */
-
+/* version.c - Version check routines.
+   Copyright (C) 2000 Werner Koch (dd9jn)
+   Copyright (C) 2001, 2002, 2003 g10 Code GmbH
+   This file is part of GPGME.
+   GPGME 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 2 of the License, or
+   (at your option) any later version.
+   GPGME 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 GPGME; if not, write to the Free Software Foundation,
+   Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
+
+#if HAVE_CONFIG_H
 #include <config.h>
-#include <stdio.h>
-#include <stdlib.h>
+#endif
 #include <string.h>
+#include <limits.h>
 #include <ctype.h>
 
 #include "gpgme.h"
-#include "context.h"
-#include "rungpg.h"
-#include "sema.h"
-#include "util.h"
-
-
-static int lineno;
-static char *tmp_engine_version;
-
-static const char *get_engine_info (void);
+#include "io.h"
 
+/* For _gpgme_sema_subsystem_init ().  */
+#include "sema.h"
 
+\f
+/* Bootstrap the subsystems needed for concurrent operation.  This
+   must be done once at startup.  We can not guarantee this using a
+   lock, though, because the semaphore subsystem needs to be
+   initialized itself before it can be used.  So we expect that the
+   user performs the necessary syncrhonization.  */
 static void
 do_subsystem_inits (void)
 {
-    static int done = 0;
+  static int done = 0;
 
-    if (done)
-        return;
-    _gpgme_sema_subsystem_init ();
-}
+  if (done)
+    return;
 
+  _gpgme_sema_subsystem_init ();
+  done = 1;
+}
 
 
-static const char*
-parse_version_number ( const char *s, int *number )
+/* Read the next number in the version string STR and return it in
+   *NUMBER.  Return a pointer to the tail of STR after parsing, or
+   *NULL if the version string was invalid.  */
+static const char *
+parse_version_number (const char *str, int *number)
 {
-    int val = 0;
+#define MAXVAL ((INT_MAX - 10) / 10)
+  int val = 0;
 
-    if ( *s == '0' && isdigit(s[1]) )
-       return NULL; /* leading zeros are not allowed */
-    for ( ; isdigit(*s); s++ ) {
-       val *= 10;
-       val += *s - '0';
+  /* Leading zeros are not allowed.  */
+  if (*str == '0' && isdigit(str[1]))
+    return NULL;
+
+  while (isdigit (*str) && val <= MAXVAL)
+    {
+      val *= 10;
+      val += *(str++) - '0';
     }
-    *number = val;
-    return val < 0? NULL : s;
+  *number = val;
+  return val > MAXVAL ? NULL : str;
 }
 
 
+/* Parse the version string STR in the format MAJOR.MINOR.MICRO (for
+   example, 9.3.2) and return the components in MAJOR, MINOR and MICRO
+   as integers.  The function returns the tail of the string that
+   follows the version number.  This might be te empty string if there
+   is nothing following the version number, or a patchlevel.  The
+   function returns NULL if the version string is not valid.  */
 static const char *
-parse_version_string( const char *s, int *major, int *minor, int *micro )
+parse_version_string (const char *str, 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 || *s != '.' )
-       return NULL;
-    s++;
-    s = parse_version_number ( s, micro );
-    if ( !s )
-       return NULL;
-    return s; /* patchlevel */
-}
+  str = parse_version_number (str, major);
+  if (!str || *str != '.')
+    return NULL;
+  str++;
 
-static const char *
-compare_versions ( const char *my_version, const char *req_version )
-{
-    int my_major, my_minor, my_micro;
-    int rq_major, rq_minor, rq_micro;
-    const char *my_plvl, *rq_plvl;
-
-    if ( !req_version )
-       return my_version;
-
-    my_plvl = parse_version_string ( my_version,
-                                     &my_major, &my_minor, &my_micro );
-    if ( !my_plvl )
-       return NULL;  /* very strange: our own version is bogus */
-    rq_plvl = parse_version_string( req_version,
-                                    &rq_major, &rq_minor, &rq_micro );
-    if ( !rq_plvl )
-       return NULL;  /* req version string is invalid */
-
-    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)
-         || (my_major == rq_major && my_minor == rq_minor
-             && my_micro == rq_micro
-             && strcmp( my_plvl, rq_plvl ) >= 0) ) {
-       return my_version;
-    }
+  str = parse_version_number (str, minor);
+  if (!str || *str != '.')
     return NULL;
-}
+  str++;
 
+  str = parse_version_number (str, micro);
+  if (!str)
+    return NULL;
 
-/**
- * gpgme_check_version:
- * @req_version: A string with a version
- * 
- * Check that the the version of the library is at minimum the requested one
- * and return the version string; return NULL if the condition is not
- * met.  If a NULL is passed to this function, no check is done and
- * the version string is simply returned.  It is a pretty good idea to
- * run this function as soon as poossible, becuase it also intializes 
- * some subsystems.  In a multithreaded environment if should be called
- * before the first thread is created.
- * 
- * Return value: The version string or NULL
- **/
-const char *
-gpgme_check_version ( const char *req_version )
-{
-    do_subsystem_inits ();
-    return compare_versions ( VERSION, req_version );
+  /* A patchlevel might follow.  */
+  return str;
 }
 
 
-/**
- * gpgme_get_engine_info:
- *  
- * Return information about the underlying crypto engine.  This is an
- * XML string with various information.  To get the version of the
- * crypto engine it should be sufficient to grep for the first
- * <literal>version</literal> tag and use it's content.  A string is
- * always returned even if the crypto engine is not installed; in this
- * case a XML string with some error information is returned.
- * 
- * Return value: A XML string with information about the crypto engine.
- **/
 const char *
-gpgme_get_engine_info ()
+_gpgme_compare_versions (const char *my_version,
+                        const char *rq_version)
 {
-    do_subsystem_inits ();
-    return get_engine_info ();
-}
+  int my_major, my_minor, my_micro;
+  int rq_major, rq_minor, rq_micro;
+  const char *my_plvl, *rq_plvl;
 
-/**
- * gpgme_check_engine:
- * 
- * Check whether the installed crypto engine matches the requirement of
- * GPGME.
- *
- * Return value: 0 or an error code.
- **/
-GpgmeError
-gpgme_check_engine ()
-{
-    const char *info = gpgme_get_engine_info ();
-    const char *s, *s2;
-
-    s = strstr (info, "<version>");
-    if (s) {
-        s += 9;
-        s2 = strchr (s, '>');
-        if (s2) {
-            char *ver = xtrymalloc (s2 - s + 1);
-            if (!ver)
-                return mk_error (Out_Of_Core);
-            memcpy (ver, s, s2-s);
-            ver[s2-s] = 0;
-            s = compare_versions ( ver, NEED_GPG_VERSION );
-            xfree (ver);
-            if (s)
-                return 0;
-        }
-    }
-    return mk_error (Invalid_Engine);
+  if (!rq_version)
+    return my_version;
+  if (!my_version)
+    return NULL;
+
+  my_plvl = parse_version_string (my_version, &my_major, &my_minor, &my_micro);
+  if (!my_plvl)
+    return NULL;
+
+  rq_plvl = parse_version_string (rq_version, &rq_major, &rq_minor, &rq_micro);
+  if (!rq_plvl)
+    return NULL;
+
+  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)
+      || (my_major == rq_major && my_minor == rq_minor
+         && my_micro == rq_micro && strcmp (my_plvl, rq_plvl) >= 0))
+    return my_version;
+
+  return NULL;
 }
 
 
-\f
-static void
-version_line_handler ( GpgmeCtx c, char *line )
+/* Check that the the version of the library is at minimum the
+   requested one and return the version string; return NULL if the
+   condition is not met.  If a NULL is passed to this function, no
+   check is done and the version string is simply returned.
+
+   This function must be run once at startup, as it also initializes
+   some subsystems.  Its invocation must be synchronized against
+   calling any of the other functions in a multi-threaded
+   environments.  */
+const char *
+gpgme_check_version (const char *req_version)
 {
-    char *p;
-    size_t len;
-
-    lineno++;
-    if ( c->out_of_core )
-        return;
-    if (!line)
-        return; /* EOF */
-    if (lineno==1) {
-        if ( memcmp (line, "gpg ", 4) )
-            return;
-        if ( !(p = strpbrk (line, "0123456789")) )
-            return;
-        len = strcspn (p, " \t\r\n()<>" );
-        p[len] = 0;
-        tmp_engine_version = xtrystrdup (p);
-    }
+  do_subsystem_inits ();
+  return _gpgme_compare_versions (VERSION, req_version);
 }
 
+\f
+#define LINELENGTH 80
 
-static const char *
-get_engine_info (void)
+/* Retrieve the version number from the --version output of the
+   program FILE_NAME.  */
+char *
+_gpgme_get_program_version (const char *const file_name)
 {
-    static const char *engine_info =NULL;
-    GpgmeCtx c = NULL;
-    GpgmeError err = 0;
-    const char *path = NULL;
-
-    /* FIXME: make sure that only one instance does run */
-    if (engine_info)
-        return engine_info;
-
-    path = _gpgme_get_gpg_path ();
-    err = gpgme_new (&c);
-    if (err) 
-        goto leave;
-    err = _gpgme_gpg_new ( &c->gpg );
-    if (err)
-        goto leave;
-
-    err = _gpgme_gpg_set_simple_line_handler ( c->gpg,
-                                               version_line_handler, c );
-    if (err)
-        goto leave;
-
-    _gpgme_gpg_add_arg ( c->gpg, "--version" );
-    lineno = 0;
-    xfree (tmp_engine_version); tmp_engine_version = NULL;
-    err = _gpgme_gpg_spawn ( c->gpg, c );
-    if (err)
-        goto leave;
-    gpgme_wait (c, 1);
-    if (tmp_engine_version) {
-        const char *fmt;
-        char *p;
-
-        fmt = "<GnupgInfo>\n"
-              " <engine>\n"
-              "  <version>%s</version>\n"
-              "  <path>%s</path>\n"
-              " </engine>\n"
-              "</GnupgInfo>\n";
-        /*(yes, I know that we allocating 2 extra bytes)*/
-        p = xtrymalloc ( strlen(fmt) + strlen(path)
-                         + strlen (tmp_engine_version) + 1);
-        if (!p) {
-            err = mk_error (Out_Of_Core);
-            goto leave;
-        }
-        sprintf (p, fmt, tmp_engine_version, path);
-        engine_info = p;
-        xfree (tmp_engine_version); tmp_engine_version = NULL;
-    }
-    else {
-        err = mk_error (General_Error);
-    }
+  char line[LINELENGTH] = "";
+  int linelen = 0;
+  char *mark = NULL;
+  int rp[2];
+  int nread;
+  char *argv[] = {NULL /* file_name */, "--version", 0};
+  struct spawn_fd_item_s pfd[] = { {0, -1}, {-1, -1} };
+  struct spawn_fd_item_s cfd[] = { {-1, 1 /* STDOUT_FILENO */}, {-1, -1} };
+  int status;
+
+  if (!file_name)
+    return NULL;
+  argv[0] = (char *) file_name;
 
- leave:
-    if (err) {
-        const char *fmt;
-        const char *errstr = gpgme_strerror (err);
-        char *p;
-
-        fmt = "<GnupgInfo>\n"
-            " <engine>\n"
-            "  <error>%s</error>\n"                
-            "  <path>%s</path>\n"
-            " </engine>\n"
-            "</GnupgInfo>\n";
-
-        p = xtrymalloc ( strlen(fmt) + strlen(errstr) + strlen(path) + 1);
-        if (p) { 
-            sprintf (p, fmt, errstr, path);
-            engine_info = p;
-        }
-        else {
-            engine_info = "<GnupgInfo>\n"
-                          "  <error>Out of core</error>\n"
-                          "</GnupgInfo>\n";
-        }
-    }
-    gpgme_release ( c );
-    return engine_info;
-}
+  if (_gpgme_io_pipe (rp, 1) < 0)
+    return NULL;
 
+  pfd[0].fd = rp[1];
+  cfd[0].fd = rp[1];
 
+  status = _gpgme_io_spawn (file_name, argv, cfd, pfd);
+  if (status < 0)
+    {
+      _gpgme_io_close (rp[0]);
+      _gpgme_io_close (rp[1]);
+      return NULL;
+    }
 
+  do
+    {
+      nread = _gpgme_io_read (rp[0], &line[linelen], LINELENGTH - linelen - 1);
+      if (nread > 0)
+       {
+         line[linelen + nread] = '\0';
+         mark = strchr (&line[linelen], '\n');
+         if (mark)
+           {
+             *mark = '\0';
+             break;
+           }
+         linelen += nread;
+       }
+    }
+  while (nread > 0 && linelen < LINELENGTH - 1);
 
+  _gpgme_io_close (rp[0]);
 
+  if (mark)
+    {
+      mark = strrchr (line, ' ');
+      if (!mark)
+       return NULL;
+      return strdup (mark + 1);
+    }
 
+  return NULL;
+}