1 /*
2  * This code is taken from the RPM package manager.
3  *
4  * RPM is Copyright (c) 1998 by Red Hat Software, Inc.,
5  * and may be distributed under the terms of the GPL and LGPL.
6  * See http://rpm.org/gitweb?p=rpm.git;a=blob_plain;f=COPYING;hb=HEAD
7  *
8  * The code should follow upstream as closely as possible.
9  * See http://rpm.org/gitweb?p=rpm.git;a=blob_plain;f=lib/rpmvercmp.c;hb=HEAD
10  *
11  * Currently the only difference as a policy is that upstream uses C99
12  * features and pkg-config does not require a C99 compiler yet.
13  */
14 
15 #ifdef HAVE_CONFIG_H
16 #include "config.h"
17 #endif
18 
19 #include <string.h>
20 #include <ctype.h>
21 #include <stdlib.h>
22 
23 /* macros to help code look more like upstream */
24 #define rstreq(a, b)	(strcmp(a, b) == 0)
25 #define risalnum(c)	isalnum((char)(c))
26 #define risdigit(c)	isdigit((char)(c))
27 #define risalpha(c)	isalpha((char)(c))
28 
29 int rmpvercmp_impl(const char *a, const char *b, char *buf1, char *buf2);
30 
31 /* compare alpha and numeric segments of two versions */
32 /* return 1: a is newer than b */
33 /*        0: a and b are the same version */
34 /*       -1: b is newer than a */
rpmvercmp(const char * a,const char * b)35 int rpmvercmp(const char * a, const char * b)
36 {
37     /* easy comparison to see if versions are identical */
38     if (rstreq(a, b)) return 0;
39 
40     char *buf1 = malloc(strlen(a) + 1);
41     char *buf2 = malloc(strlen(b) + 1);
42 
43     if (!buf1 || !buf2) return 0; // arbitrary
44 
45     int r = rmpvercmp_impl(a, b, buf1, buf2);
46 
47     free(buf1);
48     free(buf2);
49 
50     return r;
51 }
52 
rmpvercmp_impl(const char * a,const char * b,char * str1,char * str2)53 int rmpvercmp_impl(const char *a, const char *b, char *str1, char *str2) {
54     char oldch1, oldch2;
55     char * one, * two;
56     int rc;
57     int isnum;
58 
59     strcpy(str1, a);
60     strcpy(str2, b);
61 
62     one = str1;
63     two = str2;
64 
65     /* loop through each version segment of str1 and str2 and compare them */
66     while (*one && *two) {
67 	while (*one && !risalnum(*one)) one++;
68 	while (*two && !risalnum(*two)) two++;
69 
70 	/* If we ran to the end of either, we are finished with the loop */
71 	if (!(*one && *two)) break;
72 
73 	str1 = one;
74 	str2 = two;
75 
76 	/* grab first completely alpha or completely numeric segment */
77 	/* leave one and two pointing to the start of the alpha or numeric */
78 	/* segment and walk str1 and str2 to end of segment */
79 	if (risdigit(*str1)) {
80 	    while (*str1 && risdigit(*str1)) str1++;
81 	    while (*str2 && risdigit(*str2)) str2++;
82 	    isnum = 1;
83 	} else {
84 	    while (*str1 && risalpha(*str1)) str1++;
85 	    while (*str2 && risalpha(*str2)) str2++;
86 	    isnum = 0;
87 	}
88 
89 	/* save character at the end of the alpha or numeric segment */
90 	/* so that they can be restored after the comparison */
91 	oldch1 = *str1;
92 	*str1 = '\0';
93 	oldch2 = *str2;
94 	*str2 = '\0';
95 
96 	/* this cannot happen, as we previously tested to make sure that */
97 	/* the first string has a non-null segment */
98 	if (one == str1) return -1;	/* arbitrary */
99 
100 	/* take care of the case where the two version segments are */
101 	/* different types: one numeric, the other alpha (i.e. empty) */
102 	/* numeric segments are always newer than alpha segments */
103 	/* XXX See patch #60884 (and details) from bugzilla #50977. */
104 	if (two == str2) return (isnum ? 1 : -1);
105 
106 	if (isnum) {
107 	    /* this used to be done by converting the digit segments */
108 	    /* to ints using atoi() - it's changed because long  */
109 	    /* digit segments can overflow an int - this should fix that. */
110 
111 	    /* throw away any leading zeros - it's a number, right? */
112 	    while (*one == '0') one++;
113 	    while (*two == '0') two++;
114 
115 	    /* whichever number has more digits wins */
116 	    if (strlen(one) > strlen(two)) return 1;
117 	    if (strlen(two) > strlen(one)) return -1;
118 	}
119 
120 	/* strcmp will return which one is greater - even if the two */
121 	/* segments are alpha or if they are numeric.  don't return  */
122 	/* if they are equal because there might be more segments to */
123 	/* compare */
124 	rc = strcmp(one, two);
125 	if (rc) return (rc < 1 ? -1 : 1);
126 
127 	/* restore character that was replaced by null above */
128 	*str1 = oldch1;
129 	one = str1;
130 	*str2 = oldch2;
131 	two = str2;
132     }
133 
134     /* this catches the case where all numeric and alpha segments have */
135     /* compared identically but the segment sepparating characters were */
136     /* different */
137     if ((!*one) && (!*two)) return 0;
138 
139     /* whichever version still has characters left over wins */
140     if (!*one) return -1; else return 1;
141 }
142