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