libalpm
Arch Linux Package Manager Library
version.c
Go to the documentation of this file.
00001 /*
00002  *  Copyright (c) 2006-2011 Pacman Development Team <pacman-dev@archlinux.org>
00003  *
00004  *  This program is free software; you can redistribute it and/or modify
00005  *  it under the terms of the GNU General Public License as published by
00006  *  the Free Software Foundation; either version 2 of the License, or
00007  *  (at your option) any later version.
00008  *
00009  *  This program is distributed in the hope that it will be useful,
00010  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00011  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00012  *  GNU General Public License for more details.
00013  *
00014  *  You should have received a copy of the GNU General Public License
00015  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
00016  */
00017 
00018 #include <string.h>
00019 #include <ctype.h>
00020 
00021 /* libalpm */
00022 #include "util.h"
00023 
00024 /**
00025  * Some functions in this file have been adopted from the rpm source, notably
00026  * 'rpmvercmp' located at lib/rpmvercmp.c and 'parseEVR' located at
00027  * lib/rpmds.c. It was most recently updated against rpm version 4.8.1. Small
00028  * modifications have been made to make it more consistent with the libalpm
00029  * coding style.
00030  */
00031 
00032 /**
00033  * Split EVR into epoch, version, and release components.
00034  * @param evr       [epoch:]version[-release] string
00035  * @retval *ep      pointer to epoch
00036  * @retval *vp      pointer to version
00037  * @retval *rp      pointer to release
00038  */
00039 static void parseEVR(char *evr, const char **ep, const char **vp,
00040         const char **rp)
00041 {
00042     const char *epoch;
00043     const char *version;
00044     const char *release;
00045     char *s, *se;
00046 
00047     s = evr;
00048     /* s points to epoch terminator */
00049     while (*s && isdigit(*s)) s++;
00050     /* se points to version terminator */
00051     se = strrchr(s, '-');
00052 
00053     if(*s == ':') {
00054         epoch = evr;
00055         *s++ = '\0';
00056         version = s;
00057         if(*epoch == '\0') {
00058             epoch = "0";
00059         }
00060     } else {
00061         /* different from RPM- always assume 0 epoch */
00062         epoch = "0";
00063         version = evr;
00064     }
00065     if(se) {
00066         *se++ = '\0';
00067         release = se;
00068     } else {
00069         release = NULL;
00070     }
00071 
00072     if(ep) *ep = epoch;
00073     if(vp) *vp = version;
00074     if(rp) *rp = release;
00075 }
00076 
00077 /**
00078  * Compare alpha and numeric segments of two versions.
00079  * return 1: a is newer than b
00080  *        0: a and b are the same version
00081  *       -1: b is newer than a
00082  */
00083 static int rpmvercmp(const char *a, const char *b)
00084 {
00085     char oldch1, oldch2;
00086     char *str1, *str2;
00087     char *ptr1, *ptr2;
00088     char *one, *two;
00089     int rc;
00090     int isnum;
00091     int ret = 0;
00092 
00093     /* easy comparison to see if versions are identical */
00094     if(strcmp(a, b) == 0) return 0;
00095 
00096     str1 = strdup(a);
00097     str2 = strdup(b);
00098 
00099     one = ptr1 = str1;
00100     two = ptr2 = str2;
00101 
00102     /* loop through each version segment of str1 and str2 and compare them */
00103     while (*one && *two) {
00104         while (*one && !isalnum((int)*one)) one++;
00105         while (*two && !isalnum((int)*two)) two++;
00106 
00107         /* If we ran to the end of either, we are finished with the loop */
00108         if (!(*one && *two)) break;
00109 
00110         /* If the separator lengths were different, we are also finished */
00111         if ((one - ptr1) != (two - ptr2)) {
00112             return (one - ptr1) < (two - ptr2) ? -1 : 1;
00113         }
00114 
00115         ptr1 = one;
00116         ptr2 = two;
00117 
00118         /* grab first completely alpha or completely numeric segment */
00119         /* leave one and two pointing to the start of the alpha or numeric */
00120         /* segment and walk ptr1 and ptr2 to end of segment */
00121         if (isdigit((int)*ptr1)) {
00122             while (*ptr1 && isdigit((int)*ptr1)) ptr1++;
00123             while (*ptr2 && isdigit((int)*ptr2)) ptr2++;
00124             isnum = 1;
00125         } else {
00126             while (*ptr1 && isalpha((int)*ptr1)) ptr1++;
00127             while (*ptr2 && isalpha((int)*ptr2)) ptr2++;
00128             isnum = 0;
00129         }
00130 
00131         /* save character at the end of the alpha or numeric segment */
00132         /* so that they can be restored after the comparison */
00133         oldch1 = *ptr1;
00134         *ptr1 = '\0';
00135         oldch2 = *ptr2;
00136         *ptr2 = '\0';
00137 
00138         /* this cannot happen, as we previously tested to make sure that */
00139         /* the first string has a non-null segment */
00140         if (one == ptr1) {
00141             ret = -1;   /* arbitrary */
00142             goto cleanup;
00143         }
00144 
00145         /* take care of the case where the two version segments are */
00146         /* different types: one numeric, the other alpha (i.e. empty) */
00147         /* numeric segments are always newer than alpha segments */
00148         /* XXX See patch #60884 (and details) from bugzilla #50977. */
00149         if (two == ptr2) {
00150             ret = isnum ? 1 : -1;
00151             goto cleanup;
00152         }
00153 
00154         if (isnum) {
00155             /* this used to be done by converting the digit segments */
00156             /* to ints using atoi() - it's changed because long  */
00157             /* digit segments can overflow an int - this should fix that. */
00158 
00159             /* throw away any leading zeros - it's a number, right? */
00160             while (*one == '0') one++;
00161             while (*two == '0') two++;
00162 
00163             /* whichever number has more digits wins */
00164             if (strlen(one) > strlen(two)) {
00165                 ret = 1;
00166                 goto cleanup;
00167             }
00168             if (strlen(two) > strlen(one)) {
00169                 ret = -1;
00170                 goto cleanup;
00171             }
00172         }
00173 
00174         /* strcmp will return which one is greater - even if the two */
00175         /* segments are alpha or if they are numeric.  don't return  */
00176         /* if they are equal because there might be more segments to */
00177         /* compare */
00178         rc = strcmp(one, two);
00179         if (rc) {
00180             ret = rc < 1 ? -1 : 1;
00181             goto cleanup;
00182         }
00183 
00184         /* restore character that was replaced by null above */
00185         *ptr1 = oldch1;
00186         one = ptr1;
00187         *ptr2 = oldch2;
00188         two = ptr2;
00189     }
00190 
00191     /* this catches the case where all numeric and alpha segments have */
00192     /* compared identically but the segment separating characters were */
00193     /* different */
00194     if ((!*one) && (!*two)) {
00195         ret = 0;
00196         goto cleanup;
00197     }
00198 
00199     /* the final showdown. we never want a remaining alpha string to
00200      * beat an empty string. the logic is a bit weird, but:
00201      * - if one is empty and two is not an alpha, two is newer.
00202      * - if one is an alpha, two is newer.
00203      * - otherwise one is newer.
00204      * */
00205     if ( (!*one && !isalpha((int)*two))
00206             || isalpha((int)*one) ) {
00207         ret = -1;
00208     } else {
00209         ret = 1;
00210     }
00211 
00212 cleanup:
00213     free(str1);
00214     free(str2);
00215     return ret;
00216 }
00217 
00218 /** Compare two version strings and determine which one is 'newer'.
00219  * Returns a value comparable to the way strcmp works. Returns 1
00220  * if a is newer than b, 0 if a and b are the same version, or -1
00221  * if b is newer than a.
00222  *
00223  * Different epoch values for version strings will override any further
00224  * comparison. If no epoch is provided, 0 is assumed.
00225  *
00226  * Keep in mind that the pkgrel is only compared if it is available
00227  * on both versions handed to this function. For example, comparing
00228  * 1.5-1 and 1.5 will yield 0; comparing 1.5-1 and 1.5-2 will yield
00229  * -1 as expected. This is mainly for supporting versioned dependencies
00230  * that do not include the pkgrel.
00231  */
00232 int SYMEXPORT alpm_pkg_vercmp(const char *a, const char *b)
00233 {
00234     char *full1, *full2;
00235     const char *epoch1, *ver1, *rel1;
00236     const char *epoch2, *ver2, *rel2;
00237     int ret;
00238 
00239     /* ensure our strings are not null */
00240     if(!a && !b) {
00241         return 0;
00242     } else if(!a) {
00243         return -1;
00244     } else if(!b) {
00245         return 1;
00246     }
00247     /* another quick shortcut- if full version specs are equal */
00248     if(strcmp(a, b) == 0) {
00249         return 0;
00250     }
00251 
00252     /* Parse both versions into [epoch:]version[-release] triplets. We probably
00253      * don't need epoch and release to support all the same magic, but it is
00254      * easier to just run it all through the same code. */
00255     full1 = strdup(a);
00256     full2 = strdup(b);
00257 
00258     /* parseEVR modifies passed in version, so have to dupe it first */
00259     parseEVR(full1, &epoch1, &ver1, &rel1);
00260     parseEVR(full2, &epoch2, &ver2, &rel2);
00261 
00262     ret = rpmvercmp(epoch1, epoch2);
00263     if(ret == 0) {
00264         ret = rpmvercmp(ver1, ver2);
00265         if(ret == 0 && rel1 && rel2) {
00266             ret = rpmvercmp(rel1, rel2);
00267         }
00268     }
00269 
00270     free(full1);
00271     free(full2);
00272     return ret;
00273 }
00274 
00275 /* vim: set ts=2 sw=2 noet: */