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