1 /* 2 * version.c 3 * version comparison 4 * 5 * SPDX-License-Identifier: pkgconf 6 * 7 * Copyright (c) 2011-2026 pkgconf authors (see AUTHORS). 8 * 9 * Permission to use, copy, modify, and/or distribute this software for any 10 * purpose with or without fee is hereby granted, provided that the above 11 * copyright notice and this permission notice appear in all copies. 12 * 13 * This software is provided 'as is' and without any warranty, express or 14 * implied. In no event shall the authors be liable for any damages arising 15 * from the use of this software. 16 */ 17 18 #include <libpkgconf/config.h> 19 #include <libpkgconf/stdinc.h> 20 #include <libpkgconf/libpkgconf.h> 21 22 typedef enum { 23 PKGCONF_VERSION_TOKEN_END = 0, 24 PKGCONF_VERSION_TOKEN_TILDE, 25 PKGCONF_VERSION_TOKEN_NUMERIC, 26 PKGCONF_VERSION_TOKEN_ALPHA 27 } pkgconf_version_token_kind_t; 28 29 typedef struct { 30 pkgconf_version_token_kind_t kind; 31 const char *start; 32 const char *end; 33 } pkgconf_version_token_t; 34 35 typedef struct { 36 const char *cur; 37 } pkgconf_version_iter_t; 38 39 static inline bool 40 pkgconf_version_is_separator(unsigned char ch) 41 { 42 return !isalnum(ch) && ch != '~'; 43 } 44 45 static const char * 46 pkgconf_version_skip_separators(const char *s) 47 { 48 while (*s && pkgconf_version_is_separator((unsigned char)*s)) 49 s++; 50 51 return s; 52 } 53 54 static pkgconf_version_token_t 55 pkgconf_version_next_token(pkgconf_version_iter_t *it) 56 { 57 pkgconf_version_token_t tok; 58 const char *s = pkgconf_version_skip_separators(it->cur); 59 60 tok.start = s; 61 tok.end = s; 62 tok.kind = PKGCONF_VERSION_TOKEN_END; 63 64 if (*s == '\0') 65 { 66 it->cur = s; 67 return tok; 68 } 69 70 if (*s == '~') 71 { 72 tok.kind = PKGCONF_VERSION_TOKEN_TILDE; 73 tok.end = s + 1; 74 it->cur = tok.end; 75 return tok; 76 } 77 78 if (isdigit((unsigned char)*s)) 79 { 80 tok.kind = PKGCONF_VERSION_TOKEN_NUMERIC; 81 while (*tok.end && isdigit((unsigned char)*tok.end)) 82 tok.end++; 83 it->cur = tok.end; 84 return tok; 85 } 86 87 /* 88 * Having skipped separators and ruled out end-of-string, tilde and 89 * digits, the only remaining possibility is alpha: isalnum(c) is by 90 * definition isalpha(c) || isdigit(c). 91 */ 92 tok.kind = PKGCONF_VERSION_TOKEN_ALPHA; 93 while (*tok.end && isalpha((unsigned char)*tok.end)) 94 tok.end++; 95 it->cur = tok.end; 96 97 return tok; 98 } 99 100 static int 101 pkgconf_version_compare_numeric(const pkgconf_version_token_t *a, const pkgconf_version_token_t *b) 102 { 103 const char *ap = a->start; 104 const char *bp = b->start; 105 size_t alen, blen; 106 int ret; 107 108 while (ap < a->end && *ap == '0') 109 ap++; 110 111 while (bp < b->end && *bp == '0') 112 bp++; 113 114 alen = (size_t)(a->end - ap); 115 blen = (size_t)(b->end - bp); 116 117 if (alen > blen) 118 return 1; 119 if (alen < blen) 120 return -1; 121 122 if (alen == 0) 123 return 0; 124 125 ret = strncmp(ap, bp, alen); 126 if (ret < 0) 127 return -1; 128 if (ret > 0) 129 return 1; 130 131 return 0; 132 } 133 134 static int 135 pkgconf_version_compare_alpha(const pkgconf_version_token_t *a, const pkgconf_version_token_t *b) 136 { 137 size_t alen = (size_t)(a->end - a->start); 138 size_t blen = (size_t)(b->end - b->start); 139 size_t len = alen < blen ? alen : blen; 140 int ret; 141 142 ret = strncmp(a->start, b->start, len); 143 if (ret < 0) 144 return -1; 145 if (ret > 0) 146 return 1; 147 148 if (alen < blen) 149 return -1; 150 if (alen > blen) 151 return 1; 152 153 return 0; 154 } 155 156 static int 157 pkgconf_version_compare_token(const pkgconf_version_token_t *a, const pkgconf_version_token_t *b) 158 { 159 if (a->kind == PKGCONF_VERSION_TOKEN_TILDE || b->kind == PKGCONF_VERSION_TOKEN_TILDE) 160 { 161 if (a->kind != PKGCONF_VERSION_TOKEN_TILDE) 162 return 1; 163 if (b->kind != PKGCONF_VERSION_TOKEN_TILDE) 164 return -1; 165 166 return 0; 167 } 168 169 if (a->kind == PKGCONF_VERSION_TOKEN_END || b->kind == PKGCONF_VERSION_TOKEN_END) 170 { 171 if (a->kind == PKGCONF_VERSION_TOKEN_END && b->kind == PKGCONF_VERSION_TOKEN_END) 172 return 0; 173 if (a->kind == PKGCONF_VERSION_TOKEN_END) 174 return -1; 175 176 return 1; 177 } 178 179 /* left-side is numeric, beats any right-side non-numeric */ 180 if (a->kind == PKGCONF_VERSION_TOKEN_NUMERIC) 181 { 182 if (b->kind != PKGCONF_VERSION_TOKEN_NUMERIC) 183 return 1; 184 185 return pkgconf_version_compare_numeric(a, b); 186 } 187 188 /* left-side is alpha, any right-side non-alpha wins */ 189 if (b->kind != PKGCONF_VERSION_TOKEN_ALPHA) 190 return -1; 191 192 return pkgconf_version_compare_alpha(a, b); 193 } 194 195 /* 196 * !doc 197 * 198 * .. c:function:: int pkgconf_compare_version(const char *a, const char *b) 199 * 200 * Compare versions using RPM version comparison rules as described in the LSB. 201 * 202 * :param char* a: The first version to compare in the pair. 203 * :param char* b: The second version to compare in the pair. 204 * :return: -1 if the first version is less than, 0 if both versions are equal, 1 if the second version is less than. 205 * :rtype: int 206 */ 207 int 208 pkgconf_compare_version(const char *a, const char *b) 209 { 210 pkgconf_version_iter_t ia, ib; 211 212 if (a == NULL) 213 return -1; 214 if (b == NULL) 215 return 1; 216 217 if (!strcasecmp(a, b)) 218 return 0; 219 220 ia.cur = a; 221 ib.cur = b; 222 223 for (;;) 224 { 225 pkgconf_version_token_t ta = pkgconf_version_next_token(&ia); 226 pkgconf_version_token_t tb = pkgconf_version_next_token(&ib); 227 int ret = pkgconf_version_compare_token(&ta, &tb); 228 229 if (ret != 0) 230 return ret; 231 232 if (ta.kind == PKGCONF_VERSION_TOKEN_END && 233 tb.kind == PKGCONF_VERSION_TOKEN_END) 234 { 235 return 0; 236 } 237 } 238 } 239