187 lines
5.6 KiB
C
187 lines
5.6 KiB
C
/* Compare file names containing version numbers.
|
|
|
|
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 "filevercmp.h"
|
|
|
|
#include <stdbool.h>
|
|
#include <c-ctype.h>
|
|
#include <limits.h>
|
|
#include <idx.h>
|
|
#include <verify.h>
|
|
|
|
/* 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~]*)*$
|
|
If *LEN is -1, S is a string; set *LEN to S's length.
|
|
Otherwise, *LEN should be nonnegative, S is a char array,
|
|
and *LEN does not change. */
|
|
static idx_t
|
|
file_prefixlen (char const *s, ptrdiff_t *len)
|
|
{
|
|
size_t n = *len; /* SIZE_MAX if N == -1. */
|
|
|
|
for (idx_t i = 0; ; i++)
|
|
{
|
|
idx_t prefixlen = i;
|
|
while (i + 1 < n && s[i] == '.' && (c_isalpha (s[i + 1])
|
|
|| s[i + 1] == '~'))
|
|
for (i += 2; i < n && (c_isalnum (s[i]) || s[i] == '~'); i++)
|
|
continue;
|
|
|
|
if (*len < 0 ? !s[i] : i == n)
|
|
{
|
|
*len = i;
|
|
return prefixlen;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Return a version sort comparison value for S's byte at position POS.
|
|
S has length LEN. If POS == LEN, sort before all non-'~' bytes. */
|
|
|
|
static int
|
|
order (char const *s, idx_t pos, idx_t len)
|
|
{
|
|
if (pos == len)
|
|
return -1;
|
|
|
|
unsigned char c = s[pos];
|
|
if (c_isdigit (c))
|
|
return 0;
|
|
else if (c_isalpha (c))
|
|
return c;
|
|
else if (c == '~')
|
|
return -2;
|
|
else
|
|
{
|
|
verify (UCHAR_MAX <= (INT_MAX - 1 - 2) / 2);
|
|
return c + UCHAR_MAX + 1;
|
|
}
|
|
}
|
|
|
|
/* slightly modified verrevcmp function from dpkg
|
|
S1, S2 - compared char array
|
|
S1_LEN, S2_LEN - length of arrays to be scanned
|
|
|
|
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 */
|
|
static int _GL_ATTRIBUTE_PURE
|
|
verrevcmp (const char *s1, idx_t s1_len, const char *s2, idx_t s2_len)
|
|
{
|
|
idx_t s1_pos = 0;
|
|
idx_t s2_pos = 0;
|
|
while (s1_pos < s1_len || s2_pos < s2_len)
|
|
{
|
|
int first_diff = 0;
|
|
while ((s1_pos < s1_len && !c_isdigit (s1[s1_pos]))
|
|
|| (s2_pos < s2_len && !c_isdigit (s2[s2_pos])))
|
|
{
|
|
int s1_c = order (s1, s1_pos, s1_len);
|
|
int 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
|
|
&& c_isdigit (s1[s1_pos]) && c_isdigit (s2[s2_pos]))
|
|
{
|
|
if (!first_diff)
|
|
first_diff = s1[s1_pos] - s2[s2_pos];
|
|
s1_pos++;
|
|
s2_pos++;
|
|
}
|
|
if (s1_pos < s1_len && c_isdigit (s1[s1_pos]))
|
|
return 1;
|
|
if (s2_pos < s2_len && c_isdigit (s2[s2_pos]))
|
|
return -1;
|
|
if (first_diff)
|
|
return first_diff;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* Compare version strings S1 and S2.
|
|
See filevercmp.h for function description. */
|
|
int
|
|
filevercmp (const char *s1, const char *s2)
|
|
{
|
|
return filenvercmp (s1, -1, s2, -1);
|
|
}
|
|
|
|
/* Compare versions A (of length ALEN) and B (of length BLEN).
|
|
See filevercmp.h for function description. */
|
|
int
|
|
filenvercmp (char const *a, ptrdiff_t alen, char const *b, ptrdiff_t blen)
|
|
{
|
|
/* Special case for empty versions. */
|
|
bool aempty = alen < 0 ? !a[0] : !alen;
|
|
bool bempty = blen < 0 ? !b[0] : !blen;
|
|
if (aempty)
|
|
return -!bempty;
|
|
if (bempty)
|
|
return 1;
|
|
|
|
/* Special cases for leading ".": "." sorts first, then "..", then
|
|
other names with leading ".", then other names. */
|
|
if (a[0] == '.')
|
|
{
|
|
if (b[0] != '.')
|
|
return -1;
|
|
|
|
bool adot = alen < 0 ? !a[1] : alen == 1;
|
|
bool bdot = blen < 0 ? !b[1] : blen == 1;
|
|
if (adot)
|
|
return -!bdot;
|
|
if (bdot)
|
|
return 1;
|
|
|
|
bool adotdot = a[1] == '.' && (alen < 0 ? !a[2] : alen == 2);
|
|
bool bdotdot = b[1] == '.' && (blen < 0 ? !b[2] : blen == 2);
|
|
if (adotdot)
|
|
return -!bdotdot;
|
|
if (bdotdot)
|
|
return 1;
|
|
}
|
|
else if (b[0] == '.')
|
|
return 1;
|
|
|
|
/* Cut file suffixes. */
|
|
idx_t aprefixlen = file_prefixlen (a, &alen);
|
|
idx_t bprefixlen = file_prefixlen (b, &blen);
|
|
|
|
/* If both suffixes are empty, a second pass would return the same thing. */
|
|
bool one_pass_only = aprefixlen == alen && bprefixlen == blen;
|
|
|
|
int 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 || one_pass_only ? result : verrevcmp (a, alen, b, blen);
|
|
}
|