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