filevercmp.c 8.2 KB

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