Line data Source code
1 : /*-
2 : * Copyright (c) 2011 Philippe Pepiot <phil@philpep.org>
3 : * Copyright (c) 2014 Vsevolod Stakhov <vsevolod@FreeBSD.org>
4 : * All rights reserved.
5 : *
6 : * Redistribution and use in source and binary forms, with or without
7 : * modification, are permitted provided that the following conditions
8 : * are met:
9 : * 1. Redistributions of source code must retain the above copyright
10 : * notice, this list of conditions and the following disclaimer
11 : * in this position and unchanged.
12 : * 2. Redistributions in binary form must reproduce the above copyright
13 : * notice, this list of conditions and the following disclaimer in the
14 : * documentation and/or other materials provided with the distribution.
15 : *
16 : * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
17 : * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18 : * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19 : * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
20 : * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
21 : * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22 : * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23 : * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 : * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
25 : * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 : */
27 :
28 :
29 : #include <assert.h>
30 : #include <string.h>
31 : #include <stdlib.h>
32 : #include <ctype.h>
33 :
34 : #include "pkg.h"
35 : #include "private/event.h"
36 : #include "private/pkg.h"
37 :
38 : /*
39 : * split_version(pkgname, endname, epoch, revision) returns a pointer to
40 : * the version portion of a package name and the two special components.
41 : *
42 : * Syntax is: ${PORTNAME}-${PORTVERSION}[_${PORTREVISION}][,${PORTEPOCH}]
43 : *
44 : * Written by Oliver Eikemeier
45 : * Based on work of Jeremy D. Lea.
46 : */
47 : static const char *
48 56 : split_version(const char *pkgname, const char **endname,
49 : unsigned long *epoch, unsigned long *revision)
50 : {
51 : char *ch;
52 : const char *versionstr;
53 : const char *endversionstr;
54 :
55 56 : if (pkgname == NULL) {
56 0 : pkg_emit_error("%s: Passed NULL pkgname.", __func__);
57 0 : return (NULL);
58 : }
59 :
60 : /* Look for the last '-' the the pkgname */
61 56 : ch = strrchr(pkgname, '-');
62 : /* Cheat if we are just passed a version, not a valid package name */
63 56 : versionstr = ch ? ch + 1 : pkgname;
64 :
65 : /*
66 : * Look for the last '_' in the version string, advancing the
67 : * end pointer.
68 : */
69 56 : ch = strrchr(versionstr, '_');
70 :
71 56 : if (revision != NULL)
72 56 : *revision = ch ? strtoul(ch + 1, NULL, 10) : 0;
73 :
74 56 : endversionstr = ch;
75 :
76 : /* Look for the last ',' in the remaining version string */
77 56 : ch = strrchr(endversionstr ? endversionstr + 1 : versionstr, ',');
78 :
79 56 : if (epoch != NULL)
80 56 : *epoch = ch ? strtoul(ch + 1, NULL, 10) : 0;
81 :
82 56 : if (ch && !endversionstr)
83 1 : endversionstr = ch;
84 :
85 : /*
86 : * set the pointer behind the last character of the version without
87 : * revision or epoch
88 : */
89 56 : if (endname)
90 56 : *endname = endversionstr ? endversionstr :
91 : strrchr(versionstr, '\0');
92 :
93 56 : return versionstr;
94 : }
95 :
96 : /*
97 : * PORTVERSIONs are composed of components separated by dots. A component
98 : * consists of a version number, a letter and a patchlevel number. This does
99 : * not conform to the porter's handbook, but let us formulate rules that
100 : * fit the current practice and are far simpler than to make decisions
101 : * based on the order of netters and lumbers. Besides, people use versions
102 : * like 10b2 in the ports...
103 : */
104 :
105 : typedef struct {
106 : #ifdef __LONG_LONG_SUPPORTED
107 : long long n;
108 : long long pl;
109 : #else
110 : long n;
111 : long pl;
112 : #endif
113 : int a;
114 : } version_component;
115 :
116 : /*
117 : * get_component(position, component) gets the value of the next component
118 : * (number - letter - number triple) and returns a pointer to the next character
119 : * after any leading separators
120 : *
121 : * - components are separated by dots
122 : * - characters !~ [a-zA-Z0-9.+*] are treated as separators
123 : * (1.0:2003.09.16 = 1.0.2003.09.16), this may not be what you expect:
124 : * 1.0.1:2003.09.16 < 1.0:2003.09.16
125 : * - consecutive separators are collapsed (10..1 = 10.1)
126 : * - missing separators are inserted, essentially
127 : * letter number letter => letter number . letter (10a1b2 = 10a1.b2)
128 : * - missing components are assumed to be equal to 0 (10 = 10.0 = 10.0.0)
129 : * - the letter sort order is: [none], a, b, ..., z; numbers without letters
130 : * sort first (10 < 10a < 10b)
131 : * - missing version numbers (in components starting with a letter) sort as -1
132 : * (a < 0, 10.a < 10)
133 : * - a separator is inserted before the special strings "pl", "alpha", "beta",
134 : * "pre" and "rc".
135 : * - "pl" sorts before every other letter, "alpha", "beta", "pre" and "rc"
136 : * sort as a, b, p and r. (10alpha = 10.a < 10, but 10 < 10a; pl11 < alpha3
137 : * < 0.1beta2 = 0.1.b2 < 0.1)
138 : * - other strings use only the first letter for sorting, case is ignored
139 : * (1.d2 = 1.dev2 = 1.Development2)
140 : * - The special component `*' is guaranteed to be the smallest possible
141 : * component (2.* < 2pl1 < 2alpha3 < 2.9f7 < 3.*)
142 : * - components separated by `+' are handled by version_cmp below
143 : *
144 : * Oliver Eikemeier
145 : */
146 :
147 : static const struct {
148 : const char *name;
149 : size_t namelen;
150 : int value;
151 : } stage[] = {
152 : { "pl", 2, 0 },
153 : { "alpha", 5, 'a'-'a'+1 },
154 : { "beta", 4, 'b'-'a'+1 },
155 : { "pre", 3, 'p'-'a'+1 },
156 : { "rc", 2, 'r'-'a'+1 },
157 : { NULL, 0, -1 }
158 : };
159 :
160 : static const char *
161 60 : get_component(const char *position, version_component *component)
162 : {
163 60 : const char *pos = position;
164 60 : int hasstage = 0, haspatchlevel = 0;
165 :
166 60 : if (!pos) {
167 0 : pkg_emit_error("%s: Passed NULL position.", __func__);
168 0 : return (NULL);
169 : }
170 :
171 : /* handle version number */
172 60 : if (isdigit(*pos)) {
173 : char *endptr;
174 : #ifdef __LONG_LONG_SUPPORTED
175 60 : component->n = strtoll(pos, &endptr, 10);
176 : #else
177 : component->n = strtol(pos, &endptr, 10);
178 : #endif
179 : /* should we test for errno == ERANGE? */
180 60 : pos = endptr;
181 0 : } else if (*pos == '*') {
182 0 : component->n = -2;
183 : do {
184 0 : pos++;
185 0 : } while(*pos && *pos != '+');
186 : } else {
187 0 : component->n = -1;
188 0 : hasstage = 1;
189 : }
190 :
191 : /* handle letter */
192 60 : if (isalpha(*pos)) {
193 0 : int c = tolower(*pos);
194 0 : haspatchlevel = 1;
195 : /* handle special suffixes */
196 0 : if (isalpha(pos[1])) {
197 : unsigned int i;
198 0 : for (i = 0; stage[i].name; i++) {
199 0 : size_t len = stage[i].namelen;
200 0 : if (strncasecmp(pos, stage[i].name, len) == 0 &&
201 0 : !isalpha(pos[stage[i].namelen])) {
202 0 : if (hasstage) {
203 : /* stage to value */
204 0 : component->a = stage[i].value;
205 0 : pos += stage[i].namelen;
206 : } else {
207 : /* insert dot */
208 0 : component->a = 0;
209 0 : haspatchlevel = 0;
210 : }
211 0 : c = 0;
212 0 : break;
213 : }
214 : }
215 : }
216 : /* unhandled above */
217 0 : if (c) {
218 : /* use the first letter and skip following */
219 0 : component->a = c - 'a' + 1;
220 : do {
221 0 : ++pos;
222 0 : } while (isalpha(*pos));
223 : }
224 : } else {
225 60 : component->a = 0;
226 60 : haspatchlevel = 0;
227 : }
228 :
229 60 : if (haspatchlevel) {
230 : /* handle patch number */
231 0 : if (isdigit(*pos)) {
232 : char *endptr;
233 : #ifdef __LONG_LONG_SUPPORTED
234 0 : component->pl = strtoll(pos, &endptr, 10);
235 : #else
236 : component->pl = strtol(pos, &endptr, 10);
237 : #endif
238 : /* should we test for errno == ERANGE? */
239 0 : pos = endptr;
240 : } else {
241 0 : component->pl = -1;
242 : }
243 : } else {
244 60 : component->pl = 0;
245 : }
246 :
247 : /* skip trailing separators */
248 156 : while (*pos && !isdigit(*pos) && !isalpha(*pos)
249 36 : && *pos != '+' && *pos != '*')
250 36 : pos++;
251 :
252 60 : return pos;
253 : }
254 :
255 : /*
256 : * version_cmp(pkg1, pkg2) returns -1, 0 or 1 depending on if the version
257 : * components of pkg1 is less than, equal to or greater than pkg2. No
258 : * comparison of the basenames is done.
259 : *
260 : * The port version is defined by:
261 : * ${PORTVERSION}[_${PORTREVISION}][,${PORTEPOCH}]
262 : * ${PORTEPOCH} supersedes ${PORTVERSION} supersedes ${PORTREVISION}.
263 : * See the commit log for revision 1.349 of ports/Mk/bsd.port.mk
264 : * for more information.
265 : *
266 : * The epoch and revision are defined to be a single number, while the rest
267 : * of the version should conform to the porting guidelines. It can contain
268 : * multiple components, separated by a period, including letters.
269 : *
270 : * The tests allow for significantly more latitude in the version numbers
271 : * than is allowed in the guidelines. No point in enforcing them here.
272 : * That's what portlint is for.
273 : *
274 : * Jeremy D. Lea.
275 : * reimplemented by Oliver Eikemeier
276 : */
277 : int
278 28 : pkg_version_cmp(const char * const pkg1, const char * const pkg2)
279 : {
280 : const char *v1, *v2, *ve1, *ve2;
281 : unsigned long e1, e2, r1, r2;
282 28 : int result = 0;
283 :
284 28 : v1 = split_version(pkg1, &ve1, &e1, &r1);
285 28 : v2 = split_version(pkg2, &ve2, &e2, &r2);
286 :
287 28 : assert (v1 != NULL && v2 != NULL);
288 :
289 : /* Check epoch, port version, and port revision, in that order. */
290 28 : if (e1 != e2)
291 1 : result = (e1 < e2 ? -1 : 1);
292 :
293 : /* Shortcut check for equality before invoking the parsing routines. */
294 55 : if (result == 0 &&
295 54 : (ve1 - v1 != ve2 - v2 || strncasecmp(v1, v2, ve1 - v1) != 0)) {
296 : /*
297 : * Loop over different components (the parts separated by dots).
298 : * If any component differs, there is an inequality.
299 : */
300 54 : while (result == 0 && (v1 < ve1 || v2 < ve2)) {
301 30 : int block_v1 = 0;
302 30 : int block_v2 = 0;
303 30 : version_component vc1 = {0, 0, 0};
304 30 : version_component vc2 = {0, 0, 0};
305 30 : if (v1 < ve1 && *v1 != '+') {
306 30 : v1 = get_component(v1, &vc1);
307 30 : assert (v1 != NULL);
308 : } else {
309 0 : block_v1 = 1;
310 : }
311 30 : if (v2 < ve2 && *v2 != '+') {
312 30 : v2 = get_component(v2, &vc2);
313 30 : assert (v2 != NULL);
314 : } else {
315 0 : block_v2 = 1;
316 : }
317 30 : if (block_v1 && block_v2) {
318 0 : if (v1 < ve1)
319 0 : v1++;
320 0 : if (v2 < ve2)
321 0 : v2++;
322 30 : } else if (vc1.n != vc2.n) {
323 12 : result = (vc1.n < vc2.n ? -1 : 1);
324 18 : } else if (vc1.a != vc2.a) {
325 0 : result = (vc1.a < vc2.a ? -1 : 1);
326 18 : } else if (vc1.pl != vc2.pl) {
327 0 : result = (vc1.pl < vc2.pl ? -1 : 1);
328 : }
329 : }
330 : }
331 :
332 : /* Compare FreeBSD revision numbers. */
333 28 : if (result == 0 && r1 != r2)
334 0 : result = (r1 < r2 ? -1 : 1);
335 :
336 28 : return (result);
337 : }
338 :
339 : pkg_change_t
340 0 : pkg_version_change(const struct pkg * restrict pkg)
341 : {
342 :
343 0 : if (pkg->old_version == NULL)
344 0 : return (PKG_REINSTALL);
345 :
346 0 : switch (pkg_version_cmp(pkg->old_version, pkg->version)) {
347 : case -1:
348 0 : return (PKG_UPGRADE);
349 : default: /* placate the compiler */
350 : case 0:
351 0 : return (PKG_REINSTALL);
352 : case 1:
353 0 : return (PKG_DOWNGRADE);
354 : }
355 : }
356 :
357 : pkg_change_t
358 12 : pkg_version_change_between(const struct pkg * pkg1, const struct pkg *pkg2)
359 : {
360 12 : if (pkg2 == NULL)
361 0 : return PKG_REINSTALL;
362 :
363 12 : switch (pkg_version_cmp(pkg2->version, pkg1->version)) {
364 : case -1:
365 5 : return (PKG_UPGRADE);
366 : default: /* placate the compiler */
367 : case 0:
368 7 : return (PKG_REINSTALL);
369 : case 1:
370 0 : return (PKG_DOWNGRADE);
371 : }
372 : }
|