Rework status table to be less dynamically generated.
[gpgme.git] / src / version.c
1 /* version.c - Version check routines.
2    Copyright (C) 2000 Werner Koch (dd9jn)
3    Copyright (C) 2001, 2002, 2003, 2004, 2005, 2007, 2008 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 <stdlib.h>
26 #include <string.h>
27 #include <limits.h>
28 #include <ctype.h>
29 #ifdef HAVE_W32_SYSTEM
30 #include <winsock2.h>
31 #endif
32
33 #include "gpgme.h"
34 #include "priv-io.h"
35 #include "debug.h"
36 #include "context.h"
37
38 /* For _gpgme_sema_subsystem_init and _gpgme_status_init.  */
39 #include "sema.h"
40 #include "util.h"
41
42 #ifdef HAVE_ASSUAN_H
43 #include "assuan.h"
44 #endif
45
46 #ifdef HAVE_W32_SYSTEM
47 #include "windows.h"
48 #endif
49
50 /* We implement this function, so we have to disable the overriding
51    macro.  */
52 #undef gpgme_check_version
53
54 \f
55 /* Bootstrap the subsystems needed for concurrent operation.  This
56    must be done once at startup.  We can not guarantee this using a
57    lock, though, because the semaphore subsystem needs to be
58    initialized itself before it can be used.  So we expect that the
59    user performs the necessary synchronization.  */
60 static void
61 do_subsystem_inits (void)
62 {
63   static int done = 0;
64
65   if (done)
66     return;
67
68 #ifdef HAVE_W32_SYSTEM
69   /* We need to make sure that the sockets are initialized.  */
70   {
71     WSADATA wsadat;
72     
73     WSAStartup (0x202, &wsadat);
74   }
75 #endif
76
77   _gpgme_sema_subsystem_init ();
78   _gpgme_debug_subsystem_init ();
79   _gpgme_io_subsystem_init ();
80   _gpgme_status_init ();
81
82   done = 1;
83 }
84
85
86 /* Read the next number in the version string STR and return it in
87    *NUMBER.  Return a pointer to the tail of STR after parsing, or
88    *NULL if the version string was invalid.  */
89 static const char *
90 parse_version_number (const char *str, int *number)
91 {
92 #define MAXVAL ((INT_MAX - 10) / 10)
93   int val = 0;
94
95   /* Leading zeros are not allowed.  */
96   if (*str == '0' && isdigit(str[1]))
97     return NULL;
98
99   while (isdigit (*str) && val <= MAXVAL)
100     {
101       val *= 10;
102       val += *(str++) - '0';
103     }
104   *number = val;
105   return val > MAXVAL ? NULL : str;
106 }
107
108
109 /* Parse the version string STR in the format MAJOR.MINOR.MICRO (for
110    example, 9.3.2) and return the components in MAJOR, MINOR and MICRO
111    as integers.  The function returns the tail of the string that
112    follows the version number.  This might be te empty string if there
113    is nothing following the version number, or a patchlevel.  The
114    function returns NULL if the version string is not valid.  */
115 static const char *
116 parse_version_string (const char *str, int *major, int *minor, int *micro)
117 {
118   str = parse_version_number (str, major);
119   if (!str || *str != '.')
120     return NULL;
121   str++;
122
123   str = parse_version_number (str, minor);
124   if (!str || *str != '.')
125     return NULL;
126   str++;
127
128   str = parse_version_number (str, micro);
129   if (!str)
130     return NULL;
131
132   /* A patchlevel might follow.  */
133   return str;
134 }
135
136
137 /* Return true if MY_VERSION is at least REQ_VERSION, and false
138    otherwise.  */
139 int
140 _gpgme_compare_versions (const char *my_version,
141                          const char *rq_version)
142 {
143   int my_major, my_minor, my_micro;
144   int rq_major, rq_minor, rq_micro;
145   const char *my_plvl, *rq_plvl;
146
147   if (!rq_version)
148     return 1;
149   if (!my_version)
150     return 0;
151
152   my_plvl = parse_version_string (my_version, &my_major, &my_minor, &my_micro);
153   if (!my_plvl)
154     return 0;
155
156   rq_plvl = parse_version_string (rq_version, &rq_major, &rq_minor, &rq_micro);
157   if (!rq_plvl)
158     return 0;
159
160   if (my_major > rq_major
161       || (my_major == rq_major && my_minor > rq_minor)
162       || (my_major == rq_major && my_minor == rq_minor 
163           && my_micro > rq_micro)
164       || (my_major == rq_major && my_minor == rq_minor
165           && my_micro == rq_micro && strcmp (my_plvl, rq_plvl) >= 0))
166     return 1;
167
168   return 0;
169 }
170
171
172 /* Check that the the version of the library is at minimum the
173    requested one and return the version string; return NULL if the
174    condition is not met.  If a NULL is passed to this function, no
175    check is done and the version string is simply returned.
176
177    This function must be run once at startup, as it also initializes
178    some subsystems.  Its invocation must be synchronized against
179    calling any of the other functions in a multi-threaded
180    environments.  */
181 const char *
182 gpgme_check_version (const char *req_version)
183 {
184   char *result;
185   do_subsystem_inits ();
186
187   /* Catch-22: We need to get at least the debug subsystem ready
188      before using the trace facility.  If we won't the trace would
189      automagically initialize the debug system with out the locks
190      being initialized and missing the assuan log level setting. */
191   TRACE2 (DEBUG_INIT, "gpgme_check_version", 0,
192           "req_version=%s, VERSION=%s",
193           req_version? req_version:"(null)", VERSION);
194  
195   result = _gpgme_compare_versions (VERSION, req_version) ? VERSION : NULL;
196   if (result != NULL)
197     _gpgme_selftest = 0;
198
199   return result;
200 }
201
202 /* Check the version and also at runtime if the struct layout of the
203    library matches the one of the user.  This is particular useful for
204    Windows targets (-mms-bitfields).  */
205 const char *
206 gpgme_check_version_internal (const char *req_version,
207                               size_t offset_sig_validity)
208 {
209   const char *result;
210
211   result = gpgme_check_version (req_version);
212   if (result == NULL)
213     return result;
214
215   /* Catch-22, see above.  */
216   TRACE2 (DEBUG_INIT, "gpgme_check_version_internal", 0,
217           "req_version=%s, offset_sig_validity=%i",
218           req_version ? req_version : "(null)", offset_sig_validity);
219
220   if (offset_sig_validity != offsetof (struct _gpgme_signature, validity))
221     {
222       TRACE1 (DEBUG_INIT, "gpgme_check_version_internal", 0,
223               "offset_sig_validity mismatch: expected %i",
224               offsetof (struct _gpgme_signature, validity));
225       _gpgme_selftest = GPG_ERR_SELFTEST_FAILED;
226     }
227
228   return result;
229 }
230
231 \f
232 #define LINELENGTH 80
233
234 /* Extract the version string of a program from STRING.  The version
235    number is expected to be in GNU style format:
236    
237      foo 1.2.3
238      foo (bar system) 1.2.3
239      foo 1.2.3 cruft
240      foo (bar system) 1.2.3 cruft.
241
242   Spaces and tabs are skipped and used as delimiters, a term in
243   (nested) parenthesis before the version string is skipped, the
244   version string may consist of any non-space and non-tab characters
245   but needs to bstart with a digit.
246 */
247 static const char *
248 extract_version_string (const char *string, size_t *r_len)
249 {
250   const char *s;
251   int count, len;
252
253   for (s=string; *s; s++)
254     if (*s == ' ' || *s == '\t')
255         break;
256   while (*s == ' ' || *s == '\t')
257     s++;
258   if (*s == '(')
259     {
260       for (count=1, s++; count && *s; s++)
261         if (*s == '(')
262           count++;
263         else if (*s == ')')
264           count--;
265     }
266   /* For robustness we look for a digit.  */
267   while ( *s && !(*s >= '0' && *s <= '9') )
268     s++;
269   if (*s >= '0' && *s <= '9')
270     {
271       for (len=0; s[len]; len++)
272         if (s[len] == ' ' || s[len] == '\t')
273           break;
274     }
275   else
276     len = 0;
277
278   *r_len = len;
279   return s;
280 }
281
282
283 /* Retrieve the version number from the --version output of the
284    program FILE_NAME.  */
285 char *
286 _gpgme_get_program_version (const char *const file_name)
287 {
288   char line[LINELENGTH] = "";
289   int linelen = 0;
290   char *mark = NULL;
291   int rp[2];
292   int nread;
293   char *argv[] = {NULL /* file_name */, "--version", 0};
294   struct spawn_fd_item_s cfd[] = { {-1, 1 /* STDOUT_FILENO */, -1, 0},
295                                    {-1, -1} };
296   int status;
297
298   if (!file_name)
299     return NULL;
300   argv[0] = (char *) file_name;
301
302   if (_gpgme_io_pipe (rp, 1) < 0)
303     return NULL;
304
305   cfd[0].fd = rp[1];
306
307   status = _gpgme_io_spawn (file_name, argv, 0, cfd, NULL, NULL, NULL);
308   if (status < 0)
309     {
310       _gpgme_io_close (rp[0]);
311       _gpgme_io_close (rp[1]);
312       return NULL;
313     }
314
315   do
316     {
317       nread = _gpgme_io_read (rp[0], &line[linelen], LINELENGTH - linelen - 1);
318       if (nread > 0)
319         {
320           line[linelen + nread] = '\0';
321           mark = strchr (&line[linelen], '\n');
322           if (mark)
323             {
324               if (mark > &line[0] && *mark == '\r')
325                 mark--;
326               *mark = '\0';
327               break;
328             }
329           linelen += nread;
330         }
331     }
332   while (nread > 0 && linelen < LINELENGTH - 1);
333
334   _gpgme_io_close (rp[0]);
335
336   if (mark)
337     {
338       size_t len;
339       const char *s;
340
341       s = extract_version_string (line, &len);
342       if (!len)
343         return NULL;
344       mark = malloc (len + 1);
345       if (!mark)
346         return NULL;
347       memcpy (mark, s, len);
348       mark[len] = 0;
349       return mark;
350     }
351
352   return NULL;
353 }