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)
/* ![[previous]](../icons/n_left.png)
![[next]](../icons/right.png)
![[first]](../icons/n_first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
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)
/* ![[previous]](../icons/left.png)
![[next]](../icons/right.png)
![[first]](../icons/first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
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)
/* ![[previous]](../icons/left.png)
![[next]](../icons/right.png)
![[first]](../icons/first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
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 && g_ascii_isdigit (s1[s1_pos])
161 && 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)
/* ![[previous]](../icons/left.png)
![[next]](../icons/n_right.png)
![[first]](../icons/first.png)
![[last]](../icons/n_last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
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 /* --------------------------------------------------------------------------------------------- */