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