Fixed version string and W32 spawn function
[gpgme.git] / gpgme / version.c
1 /* version.c -  version check
2  *      Copyright (C) 2000 Werner Koch (dd9jn)
3  *
4  * This file is part of GPGME.
5  *
6  * GPGME is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * GPGME is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
19  */
20
21 #include <config.h>
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <string.h>
25 #include <ctype.h>
26
27 #include "gpgme.h"
28 #include "context.h"
29 #include "rungpg.h"
30 #include "sema.h"
31 #include "util.h"
32
33
34 static int lineno;
35 static char *tmp_engine_version;
36
37 static const char *get_engine_info (void);
38
39
40 static void
41 do_subsystem_inits (void)
42 {
43     static int done = 0;
44
45     if (done)
46         return;
47     _gpgme_sema_subsystem_init ();
48 }
49
50
51
52 static const char*
53 parse_version_number ( const char *s, int *number )
54 {
55     int val = 0;
56
57     if ( *s == '0' && isdigit(s[1]) )
58         return NULL; /* leading zeros are not allowed */
59     for ( ; isdigit(*s); s++ ) {
60         val *= 10;
61         val += *s - '0';
62     }
63     *number = val;
64     return val < 0? NULL : s;
65 }
66
67
68 static const char *
69 parse_version_string( const char *s, int *major, int *minor, int *micro )
70 {
71     s = parse_version_number ( s, major );
72     if ( !s || *s != '.' )
73         return NULL;
74     s++;
75     s = parse_version_number ( s, minor );
76     if ( !s || *s != '.' )
77         return NULL;
78     s++;
79     s = parse_version_number ( s, micro );
80     if ( !s )
81         return NULL;
82     return s; /* patchlevel */
83 }
84
85 static const char *
86 compare_versions ( const char *my_version, const char *req_version )
87 {
88     int my_major, my_minor, my_micro;
89     int rq_major, rq_minor, rq_micro;
90     const char *my_plvl, *rq_plvl;
91
92     if ( !req_version )
93         return my_version;
94
95     my_plvl = parse_version_string ( my_version,
96                                      &my_major, &my_minor, &my_micro );
97     if ( !my_plvl )
98         return NULL;  /* very strange: our own version is bogus */
99     rq_plvl = parse_version_string( req_version,
100                                     &rq_major, &rq_minor, &rq_micro );
101     if ( !rq_plvl )
102         return NULL;  /* req version string is invalid */
103
104     if ( my_major > rq_major
105          || (my_major == rq_major && my_minor > rq_minor)
106          || (my_major == rq_major && my_minor == rq_minor
107              && my_micro > rq_micro)
108          || (my_major == rq_major && my_minor == rq_minor
109              && my_micro == rq_micro
110              && strcmp( my_plvl, rq_plvl ) >= 0) ) {
111         return my_version;
112     }
113     return NULL;
114 }
115
116
117 /**
118  * gpgme_check_version:
119  * @req_version: A string with a version
120  * 
121  * Check that the the version of the library is at minimum the requested one
122  * and return the version string; return NULL if the condition is not
123  * met.  If a NULL is passed to this function, no check is done and
124  * the version string is simply returned.  It is a pretty good idea to
125  * run this function as soon as poossible, becuase it also intializes 
126  * some subsystems.  In a multithreaded environment if should be called
127  * before the first thread is created.
128  * 
129  * Return value: The version string or NULL
130  **/
131 const char *
132 gpgme_check_version ( const char *req_version )
133 {
134     do_subsystem_inits ();
135     return compare_versions ( VERSION, req_version );
136 }
137
138
139 /**
140  * gpgme_get_engine_info:
141  *  
142  * Return information about the underlying crypto engine.  This is an
143  * XML string with various information.  To get the version of the
144  * crypto engine it should be sufficient to grep for the first
145  * <literal>version</literal> tag and use it's content.  A string is
146  * always returned even if the crypto engine is not installed; in this
147  * case a XML string with some error information is returned.
148  * 
149  * Return value: A XML string with information about the crypto engine.
150  **/
151 const char *
152 gpgme_get_engine_info ()
153 {
154     do_subsystem_inits ();
155     return get_engine_info ();
156 }
157
158 /**
159  * gpgme_check_engine:
160  * 
161  * Check whether the installed crypto engine matches the requirement of
162  * GPGME.
163  *
164  * Return value: 0 or an error code.
165  **/
166 GpgmeError
167 gpgme_check_engine ()
168 {
169     const char *info = gpgme_get_engine_info ();
170     const char *s, *s2;
171
172     s = strstr (info, "<version>");
173     if (s) {
174         s += 9;
175         s2 = strchr (s, '>');
176         if (s2) {
177             char *ver = xtrymalloc (s2 - s + 1);
178             if (!ver)
179                 return mk_error (Out_Of_Core);
180             memcpy (ver, s, s2-s);
181             ver[s2-s] = 0;
182             s = compare_versions ( ver, NEED_GPG_VERSION );
183             xfree (ver);
184             if (s)
185                 return 0;
186         }
187     }
188     return mk_error (Invalid_Engine);
189 }
190
191
192 \f
193 static void
194 version_line_handler ( GpgmeCtx c, char *line )
195 {
196     char *p;
197     size_t len;
198
199     lineno++;
200     if ( c->out_of_core )
201         return;
202     if (!line)
203         return; /* EOF */
204     if (lineno==1) {
205         if ( memcmp (line, "gpg ", 4) )
206             return;
207         if ( !(p = strpbrk (line, "0123456789")) )
208             return;
209         len = strcspn (p, " \t\r\n()<>" );
210         p[len] = 0;
211         tmp_engine_version = xtrystrdup (p);
212     }
213 }
214
215
216 static const char *
217 get_engine_info (void)
218 {
219     const char *engine_info =NULL;
220     GpgmeCtx c = NULL;
221     GpgmeError err = 0;
222     const char *path = NULL;
223
224     /* FIXME: make sure that only one instance does run */
225     if (engine_info)
226         goto leave;
227
228     path = _gpgme_get_gpg_path ();
229     err = gpgme_new (&c);
230     if (err) 
231         goto leave;
232     err = _gpgme_gpg_new ( &c->gpg );
233     if (err)
234         goto leave;
235
236     err = _gpgme_gpg_set_simple_line_handler ( c->gpg,
237                                                version_line_handler, c );
238     if (err)
239         goto leave;
240
241     _gpgme_gpg_add_arg ( c->gpg, "--version" );
242     lineno = 0;
243     xfree (tmp_engine_version); tmp_engine_version = NULL;
244     err = _gpgme_gpg_spawn ( c->gpg, c );
245     if (err)
246         goto leave;
247     gpgme_wait (c, 1);
248     if (tmp_engine_version) {
249         const char *fmt;
250         char *p;
251
252         fmt = "<GnupgInfo>\n"
253               " <engine>\n"
254               "  <version>%s</version>\n"
255               "  <path>%s</path>\n"
256               " </engine>\n"
257               "</GnupgInfo>\n";
258         /*(yes, I know that we allocating 2 extra bytes)*/
259         p = xtrymalloc ( strlen(fmt) + strlen(path)
260                          + strlen (tmp_engine_version) + 1);
261         if (!p) {
262             err = mk_error (Out_Of_Core);
263             goto leave;
264         }
265         sprintf (p, fmt, tmp_engine_version, path);
266         engine_info = p;
267         xfree (tmp_engine_version); tmp_engine_version = NULL;
268     }
269     else {
270         err = mk_error (General_Error);
271     }
272
273  leave:
274     if (err) {
275         const char *fmt;
276         const char *errstr = gpgme_strerror (err);
277         char *p;
278
279         fmt = "<GnupgInfo>\n"
280             " <engine>\n"
281             "  <error>%s</error>\n"                
282             "  <path>%s</path>\n"
283             " </engine>\n"
284             "</GnupgInfo>\n";
285
286         p = xtrymalloc ( strlen(fmt) + strlen(errstr) + strlen(path) + 1);
287         if (p) { 
288             sprintf (p, fmt, errstr, path);
289             engine_info = p;
290         }
291         else {
292             engine_info = "<GnupgInfo>\n"
293                           "  <error>Out of core</error>\n"
294                           "</GnupgInfo>\n";
295         }
296     }
297     gpgme_release ( c );
298     return engine_info;
299 }
300
301