1/* $NetBSD: dewey.c,v 1.6 2024/06/11 09:26:57 wiz Exp $ */
2
3/*
4 * Copyright (c) 2002 Alistair G. Crooks.  All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 *    notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 *    notice, this list of conditions and the following disclaimer in the
13 *    documentation and/or other materials provided with the distribution.
14 * 3. The name of the author may not be used to endorse or promote
15 *    products derived from this software without specific prior written
16 *    permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
19 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
22 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
24 * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
26 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
27 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
31#if HAVE_CONFIG_H
32#include "config.h"
33#endif
34#include <nbcompat.h>
35
36#if HAVE_CTYPE_H
37#include <ctype.h>
38#endif
39#if HAVE_STDLIB_H
40#include <stdlib.h>
41#endif
42
43#include "defs.h"
44#include "dewey.h"
45
46#define PKG_PATTERN_MAX 1024
47
48/* do not modify these values, or things will NOT work */
49enum {
50	Alpha = -3,
51	Beta = -2,
52	RC = -1,
53	Dot = 0,
54	Patch = 1
55};
56
57/* this struct defines a version number */
58typedef struct arr_t {
59	unsigned	c;		/* # of version numbers */
60	unsigned	size;		/* size of array */
61	int	       *v;		/* array of decimal numbers */
62	int		netbsd;		/* any "nb" suffix */
63} arr_t;
64
65/* this struct describes a test */
66typedef struct test_t {
67	const char     *s;		/* string representation */
68	unsigned	len;		/* length of string */
69	int		t;		/* enumerated type of test */
70} test_t;
71
72
73/* the tests that are recognised. */
74const test_t	tests[] = {
75	{	"<=",	2,	DEWEY_LE	},
76	{	"<",	1,	DEWEY_LT	},
77	{	">=",	2,	DEWEY_GE	},
78	{	">",	1,	DEWEY_GT	},
79	{	"==",	2,	DEWEY_EQ	},
80	{	"!=",	2,	DEWEY_NE	},
81	{	NULL,	0,	0	}
82};
83
84const test_t	modifiers[] = {
85	{	"alpha",	5,	Alpha	},
86	{	"beta",		4,	Beta	},
87	{	"pre",		3,	RC	},
88	{	"rc",		2,	RC	},
89	{	"pl",		2,	Dot	},
90	{	"_",		1,	Dot	},
91	{	".",		1,	Dot	},
92	{	NULL,		0,	0	}
93};
94
95
96
97/* locate the test in the tests array */
98int
99dewey_mktest(int *op, const char *test)
100{
101	const test_t *tp;
102
103	for (tp = tests ; tp->s ; tp++) {
104		if (strncasecmp(test, tp->s, tp->len) == 0) {
105			*op = tp->t;
106			return tp->len;
107		}
108	}
109	return -1;
110}
111
112/*
113 * make a component of a version number.
114 * '.' encodes as Dot which is '0'
115 * '_' encodes as 'patch level', or 'Dot', which is 0.
116 * 'pl' encodes as 'patch level', or 'Dot', which is 0.
117 * 'alpha' encodes as 'alpha version', or Alpha, which is -3.
118 * 'beta' encodes as 'beta version', or Beta, which is -2.
119 * 'rc' encodes as 'release candidate', or RC, which is -1.
120 * 'nb' encodes as 'netbsd version', which is used after all other tests
121 */
122static int
123mkcomponent(arr_t *ap, const char *num)
124{
125	static const char alphas[] = "abcdefghijklmnopqrstuvwxyz";
126	const test_t *modp;
127	int n;
128	const char *cp;
129
130	if (ap->c == ap->size) {
131		if (ap->size == 0) {
132			ap->size = 62;
133			if ((ap->v = malloc(ap->size * sizeof(int))) == NULL)
134				err(EXIT_FAILURE, "mkver malloc failed");
135		} else {
136			ap->size *= 2;
137			if ((ap->v = realloc(ap->v, ap->size * sizeof(int)))
138			    == NULL)
139				err(EXIT_FAILURE, "mkver realloc failed");
140		}
141	}
142	if (isdigit((unsigned char)*num)) {
143		for (cp = num, n = 0 ; isdigit((unsigned char)*num) ; num++) {
144			n = (n * 10) + (*num - '0');
145		}
146		ap->v[ap->c++] = n;
147		return (int)(num - cp);
148	}
149	for (modp = modifiers ; modp->s ; modp++) {
150		if (strncasecmp(num, modp->s, modp->len) == 0) {
151			ap->v[ap->c++] = modp->t;
152			return modp->len;
153		}
154	}
155	if (strncasecmp(num, "nb", 2) == 0) {
156		for (cp = num, num += 2, n = 0 ; isdigit((unsigned char)*num) ; num++) {
157			n = (n * 10) + (*num - '0');
158		}
159		ap->netbsd = n;
160		return (int)(num - cp);
161	}
162	if (isalpha((unsigned char)*num)) {
163		ap->v[ap->c++] = Dot;
164		cp = strchr(alphas, tolower((unsigned char)*num));
165		if (ap->c == ap->size) {
166			ap->size *= 2;
167			if ((ap->v = realloc(ap->v, ap->size * sizeof(int))) == NULL)
168				err(EXIT_FAILURE, "mkver realloc failed");
169		}
170		ap->v[ap->c++] = (int)(cp - alphas) + 1;
171		return 1;
172	}
173	return 1;
174}
175
176/* make a version number string into an array of comparable ints */
177static int
178mkversion(arr_t *ap, const char *num)
179{
180	ap->c = 0;
181	ap->size = 0;
182	ap->v = NULL;
183	ap->netbsd = 0;
184
185	while (*num) {
186		num += mkcomponent(ap, num);
187	}
188	return 1;
189}
190
191static void
192freeversion(arr_t *ap)
193{
194	free(ap->v);
195	ap->v = NULL;
196	ap->c = 0;
197	ap->size = 0;
198}
199
200#define DIGIT(v, c, n) (((n) < (c)) ? v[n] : 0)
201
202/* compare the result against the test we were expecting */
203static int
204result(int cmp, int tst)
205{
206	switch(tst) {
207	case DEWEY_LT:
208		return cmp < 0;
209	case DEWEY_LE:
210		return cmp <= 0;
211	case DEWEY_GT:
212		return cmp > 0;
213	case DEWEY_GE:
214		return cmp >= 0;
215	case DEWEY_EQ:
216		return cmp == 0;
217	case DEWEY_NE:
218		return cmp != 0;
219	default:
220		return 0;
221	}
222}
223
224/* do the test on the 2 vectors */
225static int
226vtest(arr_t *lhs, int tst, arr_t *rhs)
227{
228	int cmp;
229	unsigned int c, i;
230
231	for (i = 0, c = MAX(lhs->c, rhs->c) ; i < c ; i++) {
232		if ((cmp = DIGIT(lhs->v, lhs->c, i) - DIGIT(rhs->v, rhs->c, i)) != 0) {
233			return result(cmp, tst);
234		}
235	}
236	return result(lhs->netbsd - rhs->netbsd, tst);
237}
238
239/*
240 * Compare two dewey decimal numbers
241 */
242int
243dewey_cmp(const char *lhs, int op, const char *rhs)
244{
245	arr_t	right;
246	arr_t	left;
247	int retval;
248
249	if (!mkversion(&left, lhs))
250		return 0;
251	if (!mkversion(&right, rhs)) {
252		freeversion(&left);
253		return 0;
254	}
255	retval = vtest(&left, op, &right);
256	freeversion(&left);
257	freeversion(&right);
258	return retval;
259}
260
261/*
262 * Perform dewey match on "pkg" against "pattern".
263 * Return 1 on match, 0 on non-match, -1 on error.
264 */
265int
266dewey_match(const char *pattern, const char *pkg)
267{
268	const char *version;
269	const char *sep, *sep2;
270	int op, op2;
271	int n;
272
273	/* compare names */
274	if ((version=strrchr(pkg, '-')) == NULL) {
275		return 0;
276	}
277	if ((sep = strpbrk(pattern, "<>")) == NULL)
278		return -1;
279	/* compare name lengths */
280	if ((sep-pattern != version-pkg) ||
281	    strncmp(pkg, pattern, (size_t)(version-pkg)) != 0)
282		return 0;
283	version++;
284
285	/* extract comparison operator */
286	if ((n = dewey_mktest(&op, sep)) < 0) {
287		return 0;
288	}
289	/* skip operator */
290	sep += n;
291
292	/* if greater than, look for less than */
293	sep2 = NULL;
294	if (op == DEWEY_GT || op == DEWEY_GE) {
295		if ((sep2 = strchr(sep, '<')) != NULL) {
296			if ((n = dewey_mktest(&op2, sep2)) < 0) {
297				return 0;
298			}
299			/* compare upper limit */
300			if (!dewey_cmp(version, op2, sep2+n))
301				return 0;
302		}
303	}
304
305	/* compare only pattern / lower limit */
306	if (sep2) {
307		char ver[PKG_PATTERN_MAX];
308
309		strlcpy(ver, sep, MIN((ssize_t)sizeof(ver), sep2-sep+1));
310		if (dewey_cmp(version, op, ver))
311			return 1;
312	}
313	else {
314		if (dewey_cmp(version, op, sep))
315			return 1;
316	}
317
318	return 0;
319}
320