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