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.
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.
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.
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/>.
25 #include "lib/strutil.h"
27 /*** global variables ****************************************************************************/
29 /*** file scope macro definitions ****************************************************************/
31 /*** file scope type declarations ****************************************************************/
33 /*** forward declarations (file scope functions) *************************************************/
35 /*** file scope variables ************************************************************************/
37 /* --------------------------------------------------------------------------------------------- */
38 /*** file scope functions ************************************************************************/
39 /* --------------------------------------------------------------------------------------------- */
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~]*)*$
44 * Use the longest suffix matching this regular expression, except do not use all of s as a suffix
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.
51 file_prefixlen (const char *s
, ssize_t
*len
)
53 size_t n
= (size_t) (*len
); /* SIZE_MAX if N == -1 */
69 return (ssize_t
) prefixlen
;
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
++)
81 /* --------------------------------------------------------------------------------------------- */
83 /* Return a version sort comparison value for @s's byte at position @pos.
86 * @param pos a position in @s
87 * @param len a length of @s. If @pos == @len, sort before all non-'~' bytes.
91 order (const char *s
, size_t pos
, size_t len
)
100 if (g_ascii_isdigit (c
))
102 if (g_ascii_isalpha (c
))
107 g_assert (UCHAR_MAX
<= (INT_MAX
- 1 - 2) / 2);
109 return (int) c
+ UCHAR_MAX
+ 1;
112 /* --------------------------------------------------------------------------------------------- */
114 /* Slightly modified verrevcmp function from dpkg
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
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
128 * @return an integer less than, equal to, or greater than zero, if @s1 is <, == or > than @s2.
131 verrevcmp (const char *s1
, ssize_t s1_len
, const char *s2
, ssize_t s2_len
)
136 while (s1_pos
< s1_len
|| s2_pos
< s2_len
)
140 while ((s1_pos
< s1_len
&& !g_ascii_isdigit (s1
[s1_pos
]))
141 || (s2_pos
< s2_len
&& !g_ascii_isdigit (s2
[s2_pos
])))
145 s1_c
= order (s1
, s1_pos
, s1_len
);
146 s2_c
= order (s2
, s2_pos
, s2_len
);
149 return (s1_c
- s2_c
);
155 while (s1_pos
< s1_len
&& s1
[s1_pos
] == '0')
157 while (s2_pos
< s2_len
&& s2
[s2_pos
] == '0')
160 while (s1_pos
< s1_len
&& s2_pos
< s2_len
161 && g_ascii_isdigit (s1
[s1_pos
]) && g_ascii_isdigit (s2
[s2_pos
]))
164 first_diff
= s1
[s1_pos
] - s2
[s2_pos
];
170 if (s1_pos
< s1_len
&& g_ascii_isdigit (s1
[s1_pos
]))
172 if (s2_pos
< s2_len
&& g_ascii_isdigit (s2
[s2_pos
]))
181 /* --------------------------------------------------------------------------------------------- */
182 /*** public functions ****************************************************************************/
183 /* --------------------------------------------------------------------------------------------- */
185 /* Compare version strings.
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)
192 * @return an integer less than, equal to, or greater than zero, if @s1 is <, == or > than @s2.
195 filenvercmp (const char *a
, ssize_t alen
, const char *b
, ssize_t blen
)
197 gboolean aempty
, bempty
;
198 ssize_t aprefixlen
, bprefixlen
;
199 gboolean one_pass_only
;
202 /* Special case for empty versions. */
203 aempty
= alen
< 0 ? a
[0] == '\0' : alen
== 0;
204 bempty
= blen
< 0 ? b
[0] == '\0' : blen
== 0;
207 return (bempty
? 0 : -1);
211 /* Special cases for leading ".": "." sorts first, then "..", then other names with leading ".",
216 gboolean adotdot
, bdotdot
;
221 adot
= alen
< 0 ? a
[1] == '\0' : alen
== 1;
222 bdot
= blen
< 0 ? b
[1] == '\0' : blen
== 1;
225 return (bdot
? 0 : -1);
229 adotdot
= a
[1] == '.' && (alen
< 0 ? a
[2] == '\0' : alen
== 2);
230 bdotdot
= b
[1] == '.' && (blen
< 0 ? b
[2] == '\0' : blen
== 2);
232 return (bdotdot
? 0 : -1);
236 else if (b
[0] == '.')
239 /* Cut file suffixes. */
240 aprefixlen
= file_prefixlen (a
, &alen
);
241 bprefixlen
= file_prefixlen (b
, &blen
);
243 /* If both suffixes are empty, a second pass would return the same thing. */
244 one_pass_only
= aprefixlen
== alen
&& bprefixlen
== blen
;
246 result
= verrevcmp (a
, aprefixlen
, b
, bprefixlen
);
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
));
253 /* --------------------------------------------------------------------------------------------- */