2006-12-02 Marcus Brinkmann <marcus@g10code.de>
[gpgme.git] / gpgme / version.c
1 /* version.c - Version check routines.
2    Copyright (C) 2000 Werner Koch (dd9jn)
3    Copyright (C) 2001, 2002, 2003, 2004, 2005 g10 Code GmbH
4  
5    This file is part of GPGME.
6  
7    GPGME is free software; you can redistribute it and/or modify it
8    under the terms of the GNU Lesser General Public License as
9    published by the Free Software Foundation; either version 2.1 of
10    the License, or (at your option) any later version.
11    
12    GPGME is distributed in the hope that it will be useful, but
13    WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15    Lesser General Public License for more details.
16    
17    You should have received a copy of the GNU Lesser General Public
18    License along with this program; if not, write to the Free Software
19    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
20    02111-1307, USA.  */
21
22 #if HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25 #include <string.h>
26 #include <limits.h>
27 #include <ctype.h>
28
29 #include "gpgme.h"
30 #include "priv-io.h"
31
32 /* For _gpgme_sema_subsystem_init ().  */
33 #include "sema.h"
34
35 #include "assuan.h"
36
37 \f
38 /* Bootstrap the subsystems needed for concurrent operation.  This
39    must be done once at startup.  We can not guarantee this using a
40    lock, though, because the semaphore subsystem needs to be
41    initialized itself before it can be used.  So we expect that the
42    user performs the necessary syncrhonization.  */
43 static void
44 do_subsystem_inits (void)
45 {
46   static int done = 0;
47
48   if (done)
49     return;
50
51   _gpgme_sema_subsystem_init ();
52   _gpgme_io_subsystem_init ();
53   assuan_set_assuan_err_source (GPG_ERR_SOURCE_GPGME);
54
55   done = 1;
56 }
57
58
59 /* Read the next number in the version string STR and return it in
60    *NUMBER.  Return a pointer to the tail of STR after parsing, or
61    *NULL if the version string was invalid.  */
62 static const char *
63 parse_version_number (const char *str, int *number)
64 {
65 #define MAXVAL ((INT_MAX - 10) / 10)
66   int val = 0;
67
68   /* Leading zeros are not allowed.  */
69   if (*str == '0' && isdigit(str[1]))
70     return NULL;
71
72   while (isdigit (*str) && val <= MAXVAL)
73     {
74       val *= 10;
75       val += *(str++) - '0';
76     }
77   *number = val;
78   return val > MAXVAL ? NULL : str;
79 }
80
81
82 /* Parse the version string STR in the format MAJOR.MINOR.MICRO (for
83    example, 9.3.2) and return the components in MAJOR, MINOR and MICRO
84    as integers.  The function returns the tail of the string that
85    follows the version number.  This might be te empty string if there
86    is nothing following the version number, or a patchlevel.  The
87    function returns NULL if the version string is not valid.  */
88 static const char *
89 parse_version_string (const char *str, int *major, int *minor, int *micro)
90 {
91   str = parse_version_number (str, major);
92   if (!str || *str != '.')
93     return NULL;
94   str++;
95
96   str = parse_version_number (str, minor);
97   if (!str || *str != '.')
98     return NULL;
99   str++;
100
101   str = parse_version_number (str, micro);
102   if (!str)
103     return NULL;
104
105   /* A patchlevel might follow.  */
106   return str;
107 }
108
109
110 /* Return true if MY_VERSION is at least REQ_VERSION, and false
111    otherwise.  */
112 int
113 _gpgme_compare_versions (const char *my_version,
114                          const char *rq_version)
115 {
116   int my_major, my_minor, my_micro;
117   int rq_major, rq_minor, rq_micro;
118   const char *my_plvl, *rq_plvl;
119
120   if (!rq_version)
121     return 1;
122   if (!my_version)
123     return 0;
124
125   my_plvl = parse_version_string (my_version, &my_major, &my_minor, &my_micro);
126   if (!my_plvl)
127     return 0;
128
129   rq_plvl = parse_version_string (rq_version, &rq_major, &rq_minor, &rq_micro);
130   if (!rq_plvl)
131     return 0;
132
133   if (my_major > rq_major
134       || (my_major == rq_major && my_minor > rq_minor)
135       || (my_major == rq_major && my_minor == rq_minor 
136           && my_micro > rq_micro)
137       || (my_major == rq_major && my_minor == rq_minor
138           && my_micro == rq_micro && strcmp (my_plvl, rq_plvl) >= 0))
139     return 1;
140
141   return 0;
142 }
143
144
145 /* Check that the the version of the library is at minimum the
146    requested one and return the version string; return NULL if the
147    condition is not met.  If a NULL is passed to this function, no
148    check is done and the version string is simply returned.
149
150    This function must be run once at startup, as it also initializes
151    some subsystems.  Its invocation must be synchronized against
152    calling any of the other functions in a multi-threaded
153    environments.  */
154 const char *
155 gpgme_check_version (const char *req_version)
156 {
157   do_subsystem_inits ();
158   return _gpgme_compare_versions (VERSION, req_version) ? VERSION : NULL;
159 }
160
161 \f
162 #define LINELENGTH 80
163
164 /* Retrieve the version number from the --version output of the
165    program FILE_NAME.  */
166 char *
167 _gpgme_get_program_version (const char *const file_name)
168 {
169   char line[LINELENGTH] = "";
170   int linelen = 0;
171   char *mark = NULL;
172   int rp[2];
173   int nread;
174   char *argv[] = {NULL /* file_name */, "--version", 0};
175   struct spawn_fd_item_s pfd[] = { {0, -1}, {-1, -1} };
176   struct spawn_fd_item_s cfd[] = { {-1, 1 /* STDOUT_FILENO */}, {-1, -1} };
177   int status;
178
179   if (!file_name)
180     return NULL;
181   argv[0] = (char *) file_name;
182
183   if (_gpgme_io_pipe (rp, 1) < 0)
184     return NULL;
185
186   pfd[0].fd = rp[1];
187   cfd[0].fd = rp[1];
188
189   status = _gpgme_io_spawn (file_name, argv, cfd, pfd);
190   if (status < 0)
191     {
192       _gpgme_io_close (rp[0]);
193       _gpgme_io_close (rp[1]);
194       return NULL;
195     }
196
197   do
198     {
199       nread = _gpgme_io_read (rp[0], &line[linelen], LINELENGTH - linelen - 1);
200       if (nread > 0)
201         {
202           line[linelen + nread] = '\0';
203           mark = strchr (&line[linelen], '\n');
204           if (mark)
205             {
206               if (mark > &line[0] && *mark == '\r')
207                 mark--;
208               *mark = '\0';
209               break;
210             }
211           linelen += nread;
212         }
213     }
214   while (nread > 0 && linelen < LINELENGTH - 1);
215
216   _gpgme_io_close (rp[0]);
217
218   if (mark)
219     {
220       mark = strrchr (line, ' ');
221       if (!mark)
222         return NULL;
223       return strdup (mark + 1);
224     }
225
226   return NULL;
227 }