1 /*
2  *  Copyright (c) 2006-2018 Pacman Development Team <pacman-dev@archlinux.org>
3  *
4  *  This program is free software; you can redistribute it and/or modify
5  *  it under the terms of the GNU General Public License as published by
6  *  the Free Software Foundation; either version 2 of the License, or
7  *  (at your option) any later version.
8  *
9  *  This program is distributed in the hope that it will be useful,
10  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *  GNU General Public License for more details.
13  *
14  *  You should have received a copy of the GNU General Public License
15  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
16  */
17 
18 #include <string.h>
19 #include <ctype.h>
20 
21 /* libalpm */
22 #include "util.h"
23 
24 /**
25  * Some functions in this file have been adopted from the rpm source, notably
26  * 'rpmvercmp' located at lib/rpmvercmp.c and 'parseEVR' located at
27  * lib/rpmds.c. It was most recently updated against rpm version 4.8.1. Small
28  * modifications have been made to make it more consistent with the libalpm
29  * coding style.
30  */
31 
32 /**
33  * Split EVR into epoch, version, and release components.
34  * @param evr		[epoch:]version[-release] string
35  * @retval *ep		pointer to epoch
36  * @retval *vp		pointer to version
37  * @retval *rp		pointer to release
38  */
parseEVR(char * evr,const char ** ep,const char ** vp,const char ** rp)39 static void parseEVR(char *evr, const char **ep, const char **vp,
40 		const char **rp)
41 {
42 	const char *epoch;
43 	const char *version;
44 	const char *release;
45 	char *s, *se;
46 
47 	s = evr;
48 	/* s points to epoch terminator */
49 	while (*s && isdigit(*s)) s++;
50 	/* se points to version terminator */
51 	se = strrchr(s, '-');
52 
53 	if(*s == ':') {
54 		epoch = evr;
55 		*s++ = '\0';
56 		version = s;
57 		if(*epoch == '\0') {
58 			epoch = "0";
59 		}
60 	} else {
61 		/* different from RPM- always assume 0 epoch */
62 		epoch = "0";
63 		version = evr;
64 	}
65 	if(se) {
66 		*se++ = '\0';
67 		release = se;
68 	} else {
69 		release = NULL;
70 	}
71 
72 	if(ep) *ep = epoch;
73 	if(vp) *vp = version;
74 	if(rp) *rp = release;
75 }
76 
77 /**
78  * Compare alpha and numeric segments of two versions.
79  * return 1: a is newer than b
80  *        0: a and b are the same version
81  *       -1: b is newer than a
82  */
rpmvercmp(const char * a,const char * b)83 static int rpmvercmp(const char *a, const char *b)
84 {
85 	char oldch1, oldch2;
86 	char *str1, *str2;
87 	char *ptr1, *ptr2;
88 	char *one, *two;
89 	int rc;
90 	int isnum;
91 	int ret = 0;
92 
93 	/* easy comparison to see if versions are identical */
94 	if(strcmp(a, b) == 0) return 0;
95 
96 	str1 = strdup(a);
97 	str2 = strdup(b);
98 
99 	one = ptr1 = str1;
100 	two = ptr2 = str2;
101 
102 	/* loop through each version segment of str1 and str2 and compare them */
103 	while (*one && *two) {
104 		while (*one && !isalnum((int)*one)) one++;
105 		while (*two && !isalnum((int)*two)) two++;
106 
107 		/* If we ran to the end of either, we are finished with the loop */
108 		if (!(*one && *two)) break;
109 
110 		/* If the separator lengths were different, we are also finished */
111 		if ((one - ptr1) != (two - ptr2)) {
112 			ret = (one - ptr1) < (two - ptr2) ? -1 : 1;
113 			goto cleanup;
114 		}
115 
116 		ptr1 = one;
117 		ptr2 = two;
118 
119 		/* grab first completely alpha or completely numeric segment */
120 		/* leave one and two pointing to the start of the alpha or numeric */
121 		/* segment and walk ptr1 and ptr2 to end of segment */
122 		if (isdigit((int)*ptr1)) {
123 			while (*ptr1 && isdigit((int)*ptr1)) ptr1++;
124 			while (*ptr2 && isdigit((int)*ptr2)) ptr2++;
125 			isnum = 1;
126 		} else {
127 			while (*ptr1 && isalpha((int)*ptr1)) ptr1++;
128 			while (*ptr2 && isalpha((int)*ptr2)) ptr2++;
129 			isnum = 0;
130 		}
131 
132 		/* save character at the end of the alpha or numeric segment */
133 		/* so that they can be restored after the comparison */
134 		oldch1 = *ptr1;
135 		*ptr1 = '\0';
136 		oldch2 = *ptr2;
137 		*ptr2 = '\0';
138 
139 		/* this cannot happen, as we previously tested to make sure that */
140 		/* the first string has a non-null segment */
141 		if (one == ptr1) {
142 			ret = -1;	/* arbitrary */
143 			goto cleanup;
144 		}
145 
146 		/* take care of the case where the two version segments are */
147 		/* different types: one numeric, the other alpha (i.e. empty) */
148 		/* numeric segments are always newer than alpha segments */
149 		/* XXX See patch #60884 (and details) from bugzilla #50977. */
150 		if (two == ptr2) {
151 			ret = isnum ? 1 : -1;
152 			goto cleanup;
153 		}
154 
155 		if (isnum) {
156 			/* this used to be done by converting the digit segments */
157 			/* to ints using atoi() - it's changed because long  */
158 			/* digit segments can overflow an int - this should fix that. */
159 
160 			/* throw away any leading zeros - it's a number, right? */
161 			while (*one == '0') one++;
162 			while (*two == '0') two++;
163 
164 			/* whichever number has more digits wins */
165 			if (strlen(one) > strlen(two)) {
166 				ret = 1;
167 				goto cleanup;
168 			}
169 			if (strlen(two) > strlen(one)) {
170 				ret = -1;
171 				goto cleanup;
172 			}
173 		}
174 
175 		/* strcmp will return which one is greater - even if the two */
176 		/* segments are alpha or if they are numeric.  don't return  */
177 		/* if they are equal because there might be more segments to */
178 		/* compare */
179 		rc = strcmp(one, two);
180 		if (rc) {
181 			ret = rc < 1 ? -1 : 1;
182 			goto cleanup;
183 		}
184 
185 		/* restore character that was replaced by null above */
186 		*ptr1 = oldch1;
187 		one = ptr1;
188 		*ptr2 = oldch2;
189 		two = ptr2;
190 	}
191 
192 	/* this catches the case where all numeric and alpha segments have */
193 	/* compared identically but the segment separating characters were */
194 	/* different */
195 	if ((!*one) && (!*two)) {
196 		ret = 0;
197 		goto cleanup;
198 	}
199 
200 	/* the final showdown. we never want a remaining alpha string to
201 	 * beat an empty string. the logic is a bit weird, but:
202 	 * - if one is empty and two is not an alpha, two is newer.
203 	 * - if one is an alpha, two is newer.
204 	 * - otherwise one is newer.
205 	 * */
206 	if ( (!*one && !isalpha((int)*two))
207 			|| isalpha((int)*one) ) {
208 		ret = -1;
209 	} else {
210 		ret = 1;
211 	}
212 
213 cleanup:
214 	free(str1);
215 	free(str2);
216 	return ret;
217 }
218 
219 /** Compare two version strings and determine which one is 'newer'.
220  * Returns a value comparable to the way strcmp works. Returns 1
221  * if a is newer than b, 0 if a and b are the same version, or -1
222  * if b is newer than a.
223  *
224  * Different epoch values for version strings will override any further
225  * comparison. If no epoch is provided, 0 is assumed.
226  *
227  * Keep in mind that the pkgrel is only compared if it is available
228  * on both versions handed to this function. For example, comparing
229  * 1.5-1 and 1.5 will yield 0; comparing 1.5-1 and 1.5-2 will yield
230  * -1 as expected. This is mainly for supporting versioned dependencies
231  * that do not include the pkgrel.
232  */
alpm_pkg_vercmp(const char * a,const char * b)233 int SYMEXPORT alpm_pkg_vercmp(const char *a, const char *b)
234 {
235 	char *full1, *full2;
236 	const char *epoch1, *ver1, *rel1;
237 	const char *epoch2, *ver2, *rel2;
238 	int ret;
239 
240 	/* ensure our strings are not null */
241 	if(!a && !b) {
242 		return 0;
243 	} else if(!a) {
244 		return -1;
245 	} else if(!b) {
246 		return 1;
247 	}
248 	/* another quick shortcut- if full version specs are equal */
249 	if(strcmp(a, b) == 0) {
250 		return 0;
251 	}
252 
253 	/* Parse both versions into [epoch:]version[-release] triplets. We probably
254 	 * don't need epoch and release to support all the same magic, but it is
255 	 * easier to just run it all through the same code. */
256 	full1 = strdup(a);
257 	full2 = strdup(b);
258 
259 	/* parseEVR modifies passed in version, so have to dupe it first */
260 	parseEVR(full1, &epoch1, &ver1, &rel1);
261 	parseEVR(full2, &epoch2, &ver2, &rel2);
262 
263 	ret = rpmvercmp(epoch1, epoch2);
264 	if(ret == 0) {
265 		ret = rpmvercmp(ver1, ver2);
266 		if(ret == 0 && rel1 && rel2) {
267 			ret = rpmvercmp(rel1, rel2);
268 		}
269 	}
270 
271 	free(full1);
272 	free(full2);
273 	return ret;
274 }
275