123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253 |
- /*
- Copyright (C) 1995 Ian Jackson <iwj10@cus.cam.ac.uk>
- Copyright (C) 2001 Anthony Towns <aj@azure.humbug.org.au>
- Copyright (C) 2008-2022 Free Software Foundation, Inc.
- This file is free software: you can redistribute it and/or modify
- it under the terms of the GNU Lesser General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
- This file is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Lesser General Public License for more details.
- You should have received a copy of the GNU Lesser General Public License
- along with this program. If not, see <https://www.gnu.org/licenses/>.
- */
- #include <config.h>
- #include <stdlib.h>
- #include <limits.h>
- #include "lib/strutil.h"
- /*** global variables ****************************************************************************/
- /*** file scope macro definitions ****************************************************************/
- /*** file scope type declarations ****************************************************************/
- /*** forward declarations (file scope functions) *************************************************/
- /*** file scope variables ************************************************************************/
- /* --------------------------------------------------------------------------------------------- */
- /*** file scope functions ************************************************************************/
- /* --------------------------------------------------------------------------------------------- */
- /* Return the length of a prefix of @s that corresponds to the suffix defined by this extended
- * regular expression in the C locale: (\.[A-Za-z~][A-Za-z0-9~]*)*$
- *
- * Use the longest suffix matching this regular expression, except do not use all of s as a suffix
- * if s is nonempty.
- *
- * If *len is -1, s is a string; set *lem to s's length.
- * Otherwise, *len should be nonnegative, s is a char array, and *len does not change.
- */
- static ssize_t
- file_prefixlen (const char *s, ssize_t *len)
- {
- size_t n = (size_t) (*len); /* SIZE_MAX if N == -1 */
- size_t i = 0;
- size_t prefixlen = 0;
- while (TRUE)
- {
- gboolean done;
- if (*len < 0)
- done = s[i] == '\0';
- else
- done = i == n;
- if (done)
- {
- *len = (ssize_t) i;
- return (ssize_t) prefixlen;
- }
- i++;
- prefixlen = i;
- while (i + 1 < n && s[i] == '.' && (g_ascii_isalpha (s[i + 1]) || s[i + 1] == '~'))
- for (i += 2; i < n && (g_ascii_isalnum (s[i]) || s[i] == '~'); i++)
- ;
- }
- }
- /* --------------------------------------------------------------------------------------------- */
- /* Return a version sort comparison value for @s's byte at position @pos.
- *
- * @param s a string
- * @param pos a position in @s
- * @param len a length of @s. If @pos == @len, sort before all non-'~' bytes.
- */
- static int
- order (const char *s, size_t pos, size_t len)
- {
- unsigned char c;
- if (pos == len)
- return (-1);
- c = s[pos];
- if (g_ascii_isdigit (c))
- return 0;
- if (g_ascii_isalpha (c))
- return c;
- if (c == '~')
- return (-2);
- g_assert (UCHAR_MAX <= (INT_MAX - 1 - 2) / 2);
- return (int) c + UCHAR_MAX + 1;
- }
- /* --------------------------------------------------------------------------------------------- */
- /* Slightly modified verrevcmp function from dpkg
- *
- * This implements the algorithm for comparison of version strings
- * specified by Debian and now widely adopted. The detailed
- * specification can be found in the Debian Policy Manual in the
- * section on the 'Version' control field. This version of the code
- * implements that from s5.6.12 of Debian Policy v3.8.0.1
- * https://www.debian.org/doc/debian-policy/ch-controlfields.html#s-f-Version
- *
- * @param s1 first char array to compare
- * @param s1_len length of @s1
- * @param s2 second char array to compare
- * @param s2_len length of @s2
- *
- * @return an integer less than, equal to, or greater than zero, if @s1 is <, == or > than @s2.
- */
- static int
- verrevcmp (const char *s1, ssize_t s1_len, const char *s2, ssize_t s2_len)
- {
- ssize_t s1_pos = 0;
- ssize_t s2_pos = 0;
- while (s1_pos < s1_len || s2_pos < s2_len)
- {
- int first_diff = 0;
- while ((s1_pos < s1_len && !g_ascii_isdigit (s1[s1_pos]))
- || (s2_pos < s2_len && !g_ascii_isdigit (s2[s2_pos])))
- {
- int s1_c, s2_c;
- s1_c = order (s1, s1_pos, s1_len);
- s2_c = order (s2, s2_pos, s2_len);
- if (s1_c != s2_c)
- return (s1_c - s2_c);
- s1_pos++;
- s2_pos++;
- }
- while (s1_pos < s1_len && s1[s1_pos] == '0')
- s1_pos++;
- while (s2_pos < s2_len && s2[s2_pos] == '0')
- s2_pos++;
- while (s1_pos < s1_len && s2_pos < s2_len
- && g_ascii_isdigit (s1[s1_pos]) && g_ascii_isdigit (s2[s2_pos]))
- {
- if (first_diff == 0)
- first_diff = s1[s1_pos] - s2[s2_pos];
- s1_pos++;
- s2_pos++;
- }
- if (s1_pos < s1_len && g_ascii_isdigit (s1[s1_pos]))
- return 1;
- if (s2_pos < s2_len && g_ascii_isdigit (s2[s2_pos]))
- return (-1);
- if (first_diff != 0)
- return first_diff;
- }
- return 0;
- }
- /* --------------------------------------------------------------------------------------------- */
- /*** public functions ****************************************************************************/
- /* --------------------------------------------------------------------------------------------- */
- /* Compare version strings.
- *
- * @param a first string to compare
- * @param alen length of @a or (-1)
- * @param b second string to compare
- * @param blen length of @b or (-1)
- *
- * @return an integer less than, equal to, or greater than zero, if @s1 is <, == or > than @s2.
- */
- int
- filenvercmp (const char *a, ssize_t alen, const char *b, ssize_t blen)
- {
- gboolean aempty, bempty;
- ssize_t aprefixlen, bprefixlen;
- gboolean one_pass_only;
- int result;
- /* Special case for empty versions. */
- aempty = alen < 0 ? a[0] == '\0' : alen == 0;
- bempty = blen < 0 ? b[0] == '\0' : blen == 0;
- if (aempty)
- return (bempty ? 0 : -1);
- if (bempty)
- return 1;
- /* Special cases for leading ".": "." sorts first, then "..", then other names with leading ".",
- then other names. */
- if (a[0] == '.')
- {
- gboolean adot, bdot;
- gboolean adotdot, bdotdot;
- if (b[0] != '.')
- return (-1);
- adot = alen < 0 ? a[1] == '\0' : alen == 1;
- bdot = blen < 0 ? b[1] == '\0' : blen == 1;
- if (adot)
- return (bdot ? 0 : -1);
- if (bdot)
- return 1;
- adotdot = a[1] == '.' && (alen < 0 ? a[2] == '\0' : alen == 2);
- bdotdot = b[1] == '.' && (blen < 0 ? b[2] == '\0' : blen == 2);
- if (adotdot)
- return (bdotdot ? 0 : -1);
- if (bdotdot)
- return 1;
- }
- else if (b[0] == '.')
- return 1;
- /* Cut file suffixes. */
- aprefixlen = file_prefixlen (a, &alen);
- bprefixlen = file_prefixlen (b, &blen);
- /* If both suffixes are empty, a second pass would return the same thing. */
- one_pass_only = aprefixlen == alen && bprefixlen == blen;
- result = verrevcmp (a, aprefixlen, b, bprefixlen);
- /* Return the initial result if nonzero, or if no second pass is needed.
- Otherwise, restore the suffixes and try again. */
- return (result != 0 || one_pass_only ? result : verrevcmp (a, alen, b, blen));
- }
- /* --------------------------------------------------------------------------------------------- */
|