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