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