1 /* 2 Copyright (C) 1995 Ian Jackson <iwj10@cus.cam.ac.uk> 3 Copyright (C) 2001 Anthony Towns <aj@azure.humbug.org.au> 4 Copyright (C) 2008-2022 Free Software Foundation, Inc. 5 6 This file is free software: you can redistribute it and/or modify 7 it under the terms of the GNU Lesser General Public License as 8 published by the Free Software Foundation, either version 3 of the 9 License, or (at your option) any later version. 10 11 This file is distributed in the hope that it will be useful, 12 but WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 GNU Lesser General Public License for more details. 15 16 You should have received a copy of the GNU Lesser General Public License 17 along with this program. If not, see <https://www.gnu.org/licenses/>. 18 */ 19 20 #include <config.h> 21 22 #include <stdlib.h> 23 #include <limits.h> 24 25 #include "lib/strutil.h" 26 27 /*** global variables ****************************************************************************/ 28 29 /*** file scope macro definitions ****************************************************************/ 30 31 /*** file scope type declarations ****************************************************************/ 32 33 /*** forward declarations (file scope functions) *************************************************/ 34 35 /*** file scope variables ************************************************************************/ 36 37 /* --------------------------------------------------------------------------------------------- */ 38 /*** file scope functions ************************************************************************/ 39 /* --------------------------------------------------------------------------------------------- */ 40 41 /* Return the length of a prefix of @s that corresponds to the suffix defined by this extended 42 * regular expression in the C locale: (\.[A-Za-z~][A-Za-z0-9~]*)*$ 43 * 44 * Use the longest suffix matching this regular expression, except do not use all of s as a suffix 45 * if s is nonempty. 46 * 47 * If *len is -1, s is a string; set *lem to s's length. 48 * Otherwise, *len should be nonnegative, s is a char array, and *len does not change. 49 */ 50 static ssize_t 51 file_prefixlen (const char *s, ssize_t *len) /* */ 52 { 53 size_t n = (size_t) (*len); /* SIZE_MAX if N == -1 */ 54 size_t i = 0; 55 size_t prefixlen = 0; 56 57 while (TRUE) 58 { 59 gboolean done; 60 61 if (*len < 0) 62 done = s[i] == '\0'; 63 else 64 done = i == n; 65 66 if (done) 67 { 68 *len = (ssize_t) i; 69 return (ssize_t) prefixlen; 70 } 71 72 i++; 73 prefixlen = i; 74 75 while (i + 1 < n && s[i] == '.' && (g_ascii_isalpha (s[i + 1]) || s[i + 1] == '~')) 76 for (i += 2; i < n && (g_ascii_isalnum (s[i]) || s[i] == '~'); i++) 77 ; 78 } 79 } 80 81 /* --------------------------------------------------------------------------------------------- */ 82 83 /* Return a version sort comparison value for @s's byte at position @pos. 84 * 85 * @param s a string 86 * @param pos a position in @s 87 * @param len a length of @s. If @pos == @len, sort before all non-'~' bytes. 88 */ 89 90 static int 91 order (const char *s, size_t pos, size_t len) /* */ 92 { 93 unsigned char c; 94 95 if (pos == len) 96 return (-1); 97 98 c = s[pos]; 99 100 if (g_ascii_isdigit (c)) 101 return 0; 102 if (g_ascii_isalpha (c)) 103 return c; 104 if (c == '~') 105 return (-2); 106 107 g_assert (UCHAR_MAX <= (INT_MAX - 1 - 2) / 2); 108 109 return (int) c + UCHAR_MAX + 1; 110 } 111 112 /* --------------------------------------------------------------------------------------------- */ 113 114 /* Slightly modified verrevcmp function from dpkg 115 * 116 * This implements the algorithm for comparison of version strings 117 * specified by Debian and now widely adopted. The detailed 118 * specification can be found in the Debian Policy Manual in the 119 * section on the 'Version' control field. This version of the code 120 * implements that from s5.6.12 of Debian Policy v3.8.0.1 121 * https://www.debian.org/doc/debian-policy/ch-controlfields.html#s-f-Version 122 * 123 * @param s1 first char array to compare 124 * @param s1_len length of @s1 125 * @param s2 second char array to compare 126 * @param s2_len length of @s2 127 * 128 * @return an integer less than, equal to, or greater than zero, if @s1 is <, == or > than @s2. 129 */ 130 static int 131 verrevcmp (const char *s1, ssize_t s1_len, const char *s2, ssize_t s2_len) /* */ 132 { 133 ssize_t s1_pos = 0; 134 ssize_t s2_pos = 0; 135 136 while (s1_pos < s1_len || s2_pos < s2_len) 137 { 138 int first_diff = 0; 139 140 while ((s1_pos < s1_len && !g_ascii_isdigit (s1[s1_pos])) 141 || (s2_pos < s2_len && !g_ascii_isdigit (s2[s2_pos]))) 142 { 143 int s1_c, s2_c; 144 145 s1_c = order (s1, s1_pos, s1_len); 146 s2_c = order (s2, s2_pos, s2_len); 147 148 if (s1_c != s2_c) 149 return (s1_c - s2_c); 150 151 s1_pos++; 152 s2_pos++; 153 } 154 155 while (s1_pos < s1_len && s1[s1_pos] == '0') 156 s1_pos++; 157 while (s2_pos < s2_len && s2[s2_pos] == '0') 158 s2_pos++; 159 160 while (s1_pos < s1_len && s2_pos < s2_len 161 && g_ascii_isdigit (s1[s1_pos]) && g_ascii_isdigit (s2[s2_pos])) 162 { 163 if (first_diff == 0) 164 first_diff = s1[s1_pos] - s2[s2_pos]; 165 166 s1_pos++; 167 s2_pos++; 168 } 169 170 if (s1_pos < s1_len && g_ascii_isdigit (s1[s1_pos])) 171 return 1; 172 if (s2_pos < s2_len && g_ascii_isdigit (s2[s2_pos])) 173 return (-1); 174 if (first_diff != 0) 175 return first_diff; 176 } 177 178 return 0; 179 } 180 181 /* --------------------------------------------------------------------------------------------- */ 182 /*** public functions ****************************************************************************/ 183 /* --------------------------------------------------------------------------------------------- */ 184 185 /* Compare version strings. 186 * 187 * @param a first string to compare 188 * @param alen length of @a or (-1) 189 * @param b second string to compare 190 * @param blen length of @b or (-1) 191 * 192 * @return an integer less than, equal to, or greater than zero, if @s1 is <, == or > than @s2. 193 */ 194 int 195 filenvercmp (const char *a, ssize_t alen, const char *b, ssize_t blen) /* */ 196 { 197 gboolean aempty, bempty; 198 ssize_t aprefixlen, bprefixlen; 199 gboolean one_pass_only; 200 int result; 201 202 /* Special case for empty versions. */ 203 aempty = alen < 0 ? a[0] == '\0' : alen == 0; 204 bempty = blen < 0 ? b[0] == '\0' : blen == 0; 205 206 if (aempty) 207 return (bempty ? 0 : -1); 208 if (bempty) 209 return 1; 210 211 /* Special cases for leading ".": "." sorts first, then "..", then other names with leading ".", 212 then other names. */ 213 if (a[0] == '.') 214 { 215 gboolean adot, bdot; 216 gboolean adotdot, bdotdot; 217 218 if (b[0] != '.') 219 return (-1); 220 221 adot = alen < 0 ? a[1] == '\0' : alen == 1; 222 bdot = blen < 0 ? b[1] == '\0' : blen == 1; 223 224 if (adot) 225 return (bdot ? 0 : -1); 226 if (bdot) 227 return 1; 228 229 adotdot = a[1] == '.' && (alen < 0 ? a[2] == '\0' : alen == 2); 230 bdotdot = b[1] == '.' && (blen < 0 ? b[2] == '\0' : blen == 2); 231 if (adotdot) 232 return (bdotdot ? 0 : -1); 233 if (bdotdot) 234 return 1; 235 } 236 else if (b[0] == '.') 237 return 1; 238 239 /* Cut file suffixes. */ 240 aprefixlen = file_prefixlen (a, &alen); 241 bprefixlen = file_prefixlen (b, &blen); 242 243 /* If both suffixes are empty, a second pass would return the same thing. */ 244 one_pass_only = aprefixlen == alen && bprefixlen == blen; 245 246 result = verrevcmp (a, aprefixlen, b, bprefixlen); 247 248 /* Return the initial result if nonzero, or if no second pass is needed. 249 Otherwise, restore the suffixes and try again. */ 250 return (result != 0 || one_pass_only ? result : verrevcmp (a, alen, b, blen)); 251 } 252 253 /* --------------------------------------------------------------------------------------------- */