1 /*
2 * version.c
3 * version comparison
4 *
5 * SPDX-License-Identifier: pkgconf
6 *
7 * Copyright (c) 2011-2026 pkgconf authors (see AUTHORS).
8 *
9 * Permission to use, copy, modify, and/or distribute this software for any
10 * purpose with or without fee is hereby granted, provided that the above
11 * copyright notice and this permission notice appear in all copies.
12 *
13 * This software is provided 'as is' and without any warranty, express or
14 * implied. In no event shall the authors be liable for any damages arising
15 * from the use of this software.
16 */
17
18 #include <libpkgconf/config.h>
19 #include <libpkgconf/stdinc.h>
20 #include <libpkgconf/libpkgconf.h>
21
22 typedef enum {
23 PKGCONF_VERSION_TOKEN_END = 0,
24 PKGCONF_VERSION_TOKEN_TILDE,
25 PKGCONF_VERSION_TOKEN_NUMERIC,
26 PKGCONF_VERSION_TOKEN_ALPHA
27 } pkgconf_version_token_kind_t;
28
29 typedef struct {
30 pkgconf_version_token_kind_t kind;
31 const char *start;
32 const char *end;
33 } pkgconf_version_token_t;
34
35 typedef struct {
36 const char *cur;
37 } pkgconf_version_iter_t;
38
39 static inline bool
pkgconf_version_is_separator(unsigned char ch)40 pkgconf_version_is_separator(unsigned char ch)
41 {
42 return !isalnum(ch) && ch != '~';
43 }
44
45 static const char *
pkgconf_version_skip_separators(const char * s)46 pkgconf_version_skip_separators(const char *s)
47 {
48 while (*s && pkgconf_version_is_separator((unsigned char)*s))
49 s++;
50
51 return s;
52 }
53
54 static pkgconf_version_token_t
pkgconf_version_next_token(pkgconf_version_iter_t * it)55 pkgconf_version_next_token(pkgconf_version_iter_t *it)
56 {
57 pkgconf_version_token_t tok;
58 const char *s = pkgconf_version_skip_separators(it->cur);
59
60 tok.start = s;
61 tok.end = s;
62 tok.kind = PKGCONF_VERSION_TOKEN_END;
63
64 if (*s == '\0')
65 {
66 it->cur = s;
67 return tok;
68 }
69
70 if (*s == '~')
71 {
72 tok.kind = PKGCONF_VERSION_TOKEN_TILDE;
73 tok.end = s + 1;
74 it->cur = tok.end;
75 return tok;
76 }
77
78 if (isdigit((unsigned char)*s))
79 {
80 tok.kind = PKGCONF_VERSION_TOKEN_NUMERIC;
81 while (*tok.end && isdigit((unsigned char)*tok.end))
82 tok.end++;
83 it->cur = tok.end;
84 return tok;
85 }
86
87 /*
88 * Having skipped separators and ruled out end-of-string, tilde and
89 * digits, the only remaining possibility is alpha: isalnum(c) is by
90 * definition isalpha(c) || isdigit(c).
91 */
92 tok.kind = PKGCONF_VERSION_TOKEN_ALPHA;
93 while (*tok.end && isalpha((unsigned char)*tok.end))
94 tok.end++;
95 it->cur = tok.end;
96
97 return tok;
98 }
99
100 static int
pkgconf_version_compare_numeric(const pkgconf_version_token_t * a,const pkgconf_version_token_t * b)101 pkgconf_version_compare_numeric(const pkgconf_version_token_t *a, const pkgconf_version_token_t *b)
102 {
103 const char *ap = a->start;
104 const char *bp = b->start;
105 size_t alen, blen;
106 int ret;
107
108 while (ap < a->end && *ap == '0')
109 ap++;
110
111 while (bp < b->end && *bp == '0')
112 bp++;
113
114 alen = (size_t)(a->end - ap);
115 blen = (size_t)(b->end - bp);
116
117 if (alen > blen)
118 return 1;
119 if (alen < blen)
120 return -1;
121
122 if (alen == 0)
123 return 0;
124
125 ret = strncmp(ap, bp, alen);
126 if (ret < 0)
127 return -1;
128 if (ret > 0)
129 return 1;
130
131 return 0;
132 }
133
134 static int
pkgconf_version_compare_alpha(const pkgconf_version_token_t * a,const pkgconf_version_token_t * b)135 pkgconf_version_compare_alpha(const pkgconf_version_token_t *a, const pkgconf_version_token_t *b)
136 {
137 size_t alen = (size_t)(a->end - a->start);
138 size_t blen = (size_t)(b->end - b->start);
139 size_t len = alen < blen ? alen : blen;
140 int ret;
141
142 ret = strncmp(a->start, b->start, len);
143 if (ret < 0)
144 return -1;
145 if (ret > 0)
146 return 1;
147
148 if (alen < blen)
149 return -1;
150 if (alen > blen)
151 return 1;
152
153 return 0;
154 }
155
156 static int
pkgconf_version_compare_token(const pkgconf_version_token_t * a,const pkgconf_version_token_t * b)157 pkgconf_version_compare_token(const pkgconf_version_token_t *a, const pkgconf_version_token_t *b)
158 {
159 if (a->kind == PKGCONF_VERSION_TOKEN_TILDE || b->kind == PKGCONF_VERSION_TOKEN_TILDE)
160 {
161 if (a->kind != PKGCONF_VERSION_TOKEN_TILDE)
162 return 1;
163 if (b->kind != PKGCONF_VERSION_TOKEN_TILDE)
164 return -1;
165
166 return 0;
167 }
168
169 if (a->kind == PKGCONF_VERSION_TOKEN_END || b->kind == PKGCONF_VERSION_TOKEN_END)
170 {
171 if (a->kind == PKGCONF_VERSION_TOKEN_END && b->kind == PKGCONF_VERSION_TOKEN_END)
172 return 0;
173 if (a->kind == PKGCONF_VERSION_TOKEN_END)
174 return -1;
175
176 return 1;
177 }
178
179 /* left-side is numeric, beats any right-side non-numeric */
180 if (a->kind == PKGCONF_VERSION_TOKEN_NUMERIC)
181 {
182 if (b->kind != PKGCONF_VERSION_TOKEN_NUMERIC)
183 return 1;
184
185 return pkgconf_version_compare_numeric(a, b);
186 }
187
188 /* left-side is alpha, any right-side non-alpha wins */
189 if (b->kind != PKGCONF_VERSION_TOKEN_ALPHA)
190 return -1;
191
192 return pkgconf_version_compare_alpha(a, b);
193 }
194
195 /*
196 * !doc
197 *
198 * .. c:function:: int pkgconf_compare_version(const char *a, const char *b)
199 *
200 * Compare versions using RPM version comparison rules as described in the LSB.
201 *
202 * :param char* a: The first version to compare in the pair.
203 * :param char* b: The second version to compare in the pair.
204 * :return: -1 if the first version is less than, 0 if both versions are equal, 1 if the second version is less than.
205 * :rtype: int
206 */
207 int
pkgconf_compare_version(const char * a,const char * b)208 pkgconf_compare_version(const char *a, const char *b)
209 {
210 pkgconf_version_iter_t ia, ib;
211
212 if (a == NULL)
213 return -1;
214 if (b == NULL)
215 return 1;
216
217 if (!strcasecmp(a, b))
218 return 0;
219
220 ia.cur = a;
221 ib.cur = b;
222
223 for (;;)
224 {
225 pkgconf_version_token_t ta = pkgconf_version_next_token(&ia);
226 pkgconf_version_token_t tb = pkgconf_version_next_token(&ib);
227 int ret = pkgconf_version_compare_token(&ta, &tb);
228
229 if (ret != 0)
230 return ret;
231
232 if (ta.kind == PKGCONF_VERSION_TOKEN_END &&
233 tb.kind == PKGCONF_VERSION_TOKEN_END)
234 {
235 return 0;
236 }
237 }
238 }
239