xref: /freebsd/contrib/pkgconf/libpkgconf/version.c (revision 592efe252472a3385acf36b1f49ecf710a7f3d9c)
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