1 /*
2  * Redistribution and use in source and binary forms, with or without
3  * modification, are permitted provided that the following conditions
4  * are met:
5  * 1. Redistributions of source code must retain the above copyright
6  *    notice, this list of conditions and the following disclaimer.
7  * 2. Redistributions in binary form must reproduce the above copyright
8  *    notice, this list of conditions and the following disclaimer in the
9  *    documentation and/or other materials provided with the distribution.
10  *
11  * FreeBSD 5.x pkg_install version.c
12  * Jeremy D. Lea. June 2002
13  */
14 
15 #include <sys/param.h>
16 #include <stdlib.h>
17 #include <string.h>
18 
19 /*
20  * version_of(pkgname, epoch, revision) returns a pointer to the version
21  * portion of a package name and the two special components.
22  *
23  * Jeremy D. Lea.
24  */
25 const char *
version_of(const char * pkgname,int * epoch,int * revision)26 version_of(const char *pkgname, int *epoch, int *revision)
27 {
28     char *ch;
29 
30     if (epoch != NULL) {
31 	if ((ch = strrchr(pkgname, ',')) == NULL)
32 	    *epoch = 0;
33 	else
34 	    *epoch = atoi(&ch[1]);
35     }
36     if (revision != NULL) {
37 	if ((ch = strrchr(pkgname, '_')) == NULL)
38 	    *revision = 0;
39 	else
40 	    *revision = atoi(&ch[1]);
41     }
42     /* Cheat if we are just passed a version, not a valid package name */
43     if ((ch = strrchr(pkgname, '-')) == NULL)
44 	return pkgname;
45     else
46 	return &ch[1];
47 }
48 
49 /*
50  * version_cmp(pkg1, pkg2) returns -1, 0 or 1 depending on if the version
51  * components of pkg1 is less than, equal to or greater than pkg2. No
52  * comparison of the basenames is done.
53  *
54  * The port version is defined by:
55  * ${PORTVERSION}[_${PORTREVISION}][,${PORTEPOCH}]
56  * ${PORTEPOCH} supersedes ${PORTVERSION} supersedes ${PORTREVISION}.
57  * See the commit log for revision 1.349 of ports/Mk/bsd.port.mk
58  * for more information.
59  *
60  * The epoch and revision are defined to be a single number, while the rest
61  * of the version should conform to the porting guidelines. It can contain
62  * multiple components, separated by a period, including letters.
63  *
64  * The tests below allow for significantly more latitude in the version
65  * numbers than is allowed in the guidelines. No point in wasting user's
66  * time enforcing them here. That's what flamewars are for.
67  *
68  * Jeremy D. Lea.
69  */
70 int
version_cmp(const char * pkg1,const char * pkg2)71 version_cmp(const char *pkg1, const char *pkg2)
72 {
73     const char *c1, *c2, *v1, *v2;
74     char *t1, *t2;
75     int e1, e2, r1, r2, n1, n2;
76 
77     v1 = version_of(pkg1, &e1, &r1);
78     v2 = version_of(pkg2, &e2, &r2);
79     /* Minor optimisation. */
80     if (strcmp(v1, v2) == 0)
81 	return 0;
82     /* First compare epoch. */
83     if (e1 != e2)
84 	return (e1 < e2 ? -1 : 1);
85     else {
86 	/*
87 	 * We walk down the versions, trying to convert to numbers.
88 	 * We terminate when we reach an underscore, a comma or the
89 	 * string terminator, thanks to a nasty trick with strchr().
90 	 * strtol() conveniently gobbles up the chars it converts.
91 	 */
92 	c1 = strchr("_,", v1[0]);
93 	c2 = strchr("_,", v2[0]);
94 	while (c1 == NULL && c2 == NULL) {
95 	    n1 = strtol(v1, &t1, 10);
96 	    n2 = strtol(v2, &t2, 10);
97 	    if (n1 != n2)
98 		return (n1 < n2 ? -1 : 1);
99 	    /*
100 	     * The numbers are equal, check for letters. Assume they're
101 	     * letters purely because strtol() didn't chomp them.
102 	     */
103 	    c1 = strchr("_,.", t1[0]);
104 	    c2 = strchr("_,.", t2[0]);
105 	    if (c1 == NULL && c2 == NULL) {
106 		/* Both have letters. Compare them. */
107 		if (t1[0] != t2[0])
108 		    return (t1[0] < t2[0] ? -1 : 1);
109 		/* Boring. The letters are equal. Carry on. */
110 		v1 = &t1[1], v2 = &t2[1];
111 	    } else if (c1 == NULL) {
112 		/*
113 		 * Letters are strange. After a number, a letter counts
114 		 * as greater, but after a period it's less.
115 		 */
116 		return (isdigit(v1[0]) ? 1 : -1);
117 	    } else if (c2 == NULL) {
118 		return (isdigit(v2[0]) ? -1 : 1);
119 	    } else {
120 		/* Neither were letters.  Advance over the period. */
121 		v1 = (t1[0] == '.' ? &t1[1] : t1);
122 		v2 = (t2[0] == '.' ? &t2[1] : t2);
123 	    }
124 	    c1 = strchr("_,", v1[0]);
125 	    c2 = strchr("_,", v2[0]);
126 	}
127 	/* If we got here, check if one version has something left. */
128 	if (c1 == NULL)
129 	    return (isdigit(v1[0]) ? 1 : -1);
130 	if (c2 == NULL)
131 	    return (isdigit(v2[0]) ? -1 : 1);
132 	/* We've run out of version. Try the revision... */
133 	if (r1 != r2)
134 	    return (r1 < r2 ? -1 : 1);
135 	else
136 	    return 0;
137     }
138 }
139