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