2007-07-17 Marcus Brinkmann <marcus@g10code.de>
[gpgme.git] / gpgme / version.c
1 /* version.c - Version check routines.
2    Copyright (C) 2000 Werner Koch (dd9jn)
3    Copyright (C) 2001, 2002, 2003, 2004, 2005, 2007 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   _gpgme_io_subsystem_init ();
59 #ifdef HAVE_ASSUAN_H
60   assuan_set_assuan_err_source (GPG_ERR_SOURCE_GPGME);
61 #ifdef HAVE_W32_SYSTEM
62   /* We need to make sure that the sockets are initialized.  */
63   {
64     WSADATA wsadat;
65     
66     WSAStartup (0x202, &wsadat);
67   }
68 #endif /*HAVE_W32_SYSTEM*/
69 #endif /*HAVE_ASSUAN_H*/
70
71   done = 1;
72 }
73
74
75 /* Read the next number in the version string STR and return it in
76    *NUMBER.  Return a pointer to the tail of STR after parsing, or
77    *NULL if the version string was invalid.  */
78 static const char *
79 parse_version_number (const char *str, int *number)
80 {
81 #define MAXVAL ((INT_MAX - 10) / 10)
82   int val = 0;
83
84   /* Leading zeros are not allowed.  */
85   if (*str == '0' && isdigit(str[1]))
86     return NULL;
87
88   while (isdigit (*str) && val <= MAXVAL)
89     {
90       val *= 10;
91       val += *(str++) - '0';
92     }
93   *number = val;
94   return val > MAXVAL ? NULL : str;
95 }
96
97
98 /* Parse the version string STR in the format MAJOR.MINOR.MICRO (for
99    example, 9.3.2) and return the components in MAJOR, MINOR and MICRO
100    as integers.  The function returns the tail of the string that
101    follows the version number.  This might be te empty string if there
102    is nothing following the version number, or a patchlevel.  The
103    function returns NULL if the version string is not valid.  */
104 static const char *
105 parse_version_string (const char *str, int *major, int *minor, int *micro)
106 {
107   str = parse_version_number (str, major);
108   if (!str || *str != '.')
109     return NULL;
110   str++;
111
112   str = parse_version_number (str, minor);
113   if (!str || *str != '.')
114     return NULL;
115   str++;
116
117   str = parse_version_number (str, micro);
118   if (!str)
119     return NULL;
120
121   /* A patchlevel might follow.  */
122   return str;
123 }
124
125
126 /* Return true if MY_VERSION is at least REQ_VERSION, and false
127    otherwise.  */
128 int
129 _gpgme_compare_versions (const char *my_version,
130                          const char *rq_version)
131 {
132   int my_major, my_minor, my_micro;
133   int rq_major, rq_minor, rq_micro;
134   const char *my_plvl, *rq_plvl;
135
136   if (!rq_version)
137     return 1;
138   if (!my_version)
139     return 0;
140
141   my_plvl = parse_version_string (my_version, &my_major, &my_minor, &my_micro);
142   if (!my_plvl)
143     return 0;
144
145   rq_plvl = parse_version_string (rq_version, &rq_major, &rq_minor, &rq_micro);
146   if (!rq_plvl)
147     return 0;
148
149   if (my_major > rq_major
150       || (my_major == rq_major && my_minor > rq_minor)
151       || (my_major == rq_major && my_minor == rq_minor 
152           && my_micro > rq_micro)
153       || (my_major == rq_major && my_minor == rq_minor
154           && my_micro == rq_micro && strcmp (my_plvl, rq_plvl) >= 0))
155     return 1;
156
157   return 0;
158 }
159
160
161 /* Check that the the version of the library is at minimum the
162    requested one and return the version string; return NULL if the
163    condition is not met.  If a NULL is passed to this function, no
164    check is done and the version string is simply returned.
165
166    This function must be run once at startup, as it also initializes
167    some subsystems.  Its invocation must be synchronized against
168    calling any of the other functions in a multi-threaded
169    environments.  */
170 const char *
171 gpgme_check_version (const char *req_version)
172 {
173   TRACE2 (DEBUG_INIT, "gpgme_check_version: ", 0,
174           "req_version=%s, VERSION=%s", req_version, VERSION);
175
176   do_subsystem_inits ();
177   return _gpgme_compare_versions (VERSION, req_version) ? VERSION : NULL;
178 }
179
180 \f
181 #define LINELENGTH 80
182
183 /* Retrieve the version number from the --version output of the
184    program FILE_NAME.  */
185 char *
186 _gpgme_get_program_version (const char *const file_name)
187 {
188   char line[LINELENGTH] = "";
189   int linelen = 0;
190   char *mark = NULL;
191   int rp[2];
192   int nread;
193   char *argv[] = {NULL /* file_name */, "--version", 0};
194   struct spawn_fd_item_s pfd[] = { {0, -1}, {-1, -1} };
195   struct spawn_fd_item_s cfd[] = { {-1, 1 /* STDOUT_FILENO */}, {-1, -1} };
196   int status;
197
198   if (!file_name)
199     return NULL;
200   argv[0] = (char *) file_name;
201
202   if (_gpgme_io_pipe (rp, 1) < 0)
203     return NULL;
204
205   pfd[0].fd = rp[1];
206   cfd[0].fd = rp[1];
207
208   status = _gpgme_io_spawn (file_name, argv, cfd, pfd);
209   if (status < 0)
210     {
211       _gpgme_io_close (rp[0]);
212       _gpgme_io_close (rp[1]);
213       return NULL;
214     }
215
216   do
217     {
218       nread = _gpgme_io_read (rp[0], &line[linelen], LINELENGTH - linelen - 1);
219       if (nread > 0)
220         {
221           line[linelen + nread] = '\0';
222           mark = strchr (&line[linelen], '\n');
223           if (mark)
224             {
225               if (mark > &line[0] && *mark == '\r')
226                 mark--;
227               *mark = '\0';
228               break;
229             }
230           linelen += nread;
231         }
232     }
233   while (nread > 0 && linelen < LINELENGTH - 1);
234
235   _gpgme_io_close (rp[0]);
236
237   if (mark)
238     {
239       mark = strrchr (line, ' ');
240       if (!mark)
241         return NULL;
242       return strdup (mark + 1);
243     }
244
245   return NULL;
246 }