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