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