LCOV - code coverage report
Current view: top level - libpkg - pkg_version.c (source / functions) Hit Total Coverage
Test: cov.info Lines: 64 114 56.1 %
Date: 2015-08-15 Functions: 4 5 80.0 %

          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             : }

Generated by: LCOV version 1.10