filevercmp.c 8.1 KB

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