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