]> git.proxmox.com Git - systemd.git/blame - src/fundamental/string-util-fundamental.c
New upstream version 249~rc1
[systemd.git] / src / fundamental / string-util-fundamental.c
CommitLineData
3a6ce677
BR
1/* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3#ifndef SD_BOOT
4#include <ctype.h>
5
6#include "macro.h"
7#endif
8#include "string-util-fundamental.h"
9
10sd_char *startswith(const sd_char *s, const sd_char *prefix) {
11 sd_size_t l;
12
13 assert(s);
14 assert(prefix);
15
16 l = strlen(prefix);
17 if (!strneq(s, prefix, l))
18 return NULL;
19
20 return (sd_char*) s + l;
21}
22
23#ifndef SD_BOOT
24sd_char *startswith_no_case(const sd_char *s, const sd_char *prefix) {
25 sd_size_t l;
26
27 assert(s);
28 assert(prefix);
29
30 l = strlen(prefix);
31 if (!strncaseeq(s, prefix, l))
32 return NULL;
33
34 return (sd_char*) s + l;
35}
36#endif
37
38sd_char* endswith(const sd_char *s, const sd_char *postfix) {
39 sd_size_t sl, pl;
40
41 assert(s);
42 assert(postfix);
43
44 sl = strlen(s);
45 pl = strlen(postfix);
46
47 if (pl == 0)
48 return (sd_char*) s + sl;
49
50 if (sl < pl)
51 return NULL;
52
53 if (strcmp(s + sl - pl, postfix) != 0)
54 return NULL;
55
56 return (sd_char*) s + sl - pl;
57}
58
59sd_char* endswith_no_case(const sd_char *s, const sd_char *postfix) {
60 sd_size_t sl, pl;
61
62 assert(s);
63 assert(postfix);
64
65 sl = strlen(s);
66 pl = strlen(postfix);
67
68 if (pl == 0)
69 return (sd_char*) s + sl;
70
71 if (sl < pl)
72 return NULL;
73
74 if (strcasecmp(s + sl - pl, postfix) != 0)
75 return NULL;
76
77 return (sd_char*) s + sl - pl;
78}
79
80#ifdef SD_BOOT
81static sd_bool isdigit(sd_char a) {
82 return a >= '0' && a <= '9';
83}
84#endif
85
86static sd_bool is_alpha(sd_char a) {
87 /* Locale independent version of isalpha(). */
88 return (a >= 'a' && a <= 'z') || (a >= 'A' && a <= 'Z');
89}
90
91static sd_bool is_valid_version_char(sd_char a) {
92 return isdigit(a) || is_alpha(a) || IN_SET(a, '~', '-', '^', '.');
93}
94
95sd_int strverscmp_improved(const sd_char *a, const sd_char *b) {
96
97 /* This is based on RPM's rpmvercmp(). But this explicitly handles '-' and '.', as we usually
98 * want to directly compare strings which contain both version and release; e.g.
99 * '247.2-3.1.fc33.x86_64' or '5.11.0-0.rc5.20210128git76c057c84d28.137.fc34'.
100 * Unlike rpmvercmp(), this distiguishes e.g. 123a and 123.a, and 123a is newer.
101 *
102 * This splits the input strings into segments. Each segment is numeric or alpha, and may be
103 * prefixed with the following:
104 * '~' : used for pre-releases, a segment prefixed with this is the oldest,
105 * '-' : used for the separator between version and release,
106 * '^' : used for patched releases, a segment with this is newer than one with '-'.
107 * '.' : used for point releases.
108 * Note, no prefix segment is the newest. All non-supported characters are dropped, and
109 * handled as a separator of segments, e.g., 123_a is equivalent to 123a.
110 *
111 * By using this, version strings can be sorted like following:
112 * (older) 122.1
113 * ^ 123~rc1-1
114 * | 123
115 * | 123-a
116 * | 123-a.1
117 * | 123-1
118 * | 123-1.1
119 * | 123^post1
120 * | 123.a-1
121 * | 123.1-1
122 * v 123a-1
123 * (newer) 124-1
124 */
125
126 if (isempty(a) || isempty(b))
127 return strcmp_ptr(a, b);
128
129 for (;;) {
130 const sd_char *aa, *bb;
131 sd_int r;
132
133 /* Drop leading invalid characters. */
134 while (*a != '\0' && !is_valid_version_char(*a))
135 a++;
136 while (*b != '\0' && !is_valid_version_char(*b))
137 b++;
138
139 /* Handle '~'. Used for pre-releases, e.g. 123~rc1, or 4.5~alpha1 */
140 if (*a == '~' || *b == '~') {
141 /* The string prefixed with '~' is older. */
142 r = CMP(*a != '~', *b != '~');
143 if (r != 0)
144 return r;
145
146 /* Now both strings are prefixed with '~'. Compare remaining strings. */
147 a++;
148 b++;
149 }
150
151 /* If at least one string reaches the end, then longer is newer.
152 * Note that except for '~' prefixed segments, a string has more segments is newer.
153 * So, this check must be after the '~' check. */
154 if (*a == '\0' || *b == '\0')
8b3d4ff0 155 return CMP(*a, *b);
3a6ce677
BR
156
157 /* Handle '-', which separates version and release, e.g 123.4-3.1.fc33.x86_64 */
158 if (*a == '-' || *b == '-') {
159 /* The string prefixed with '-' is older (e.g., 123-9 vs 123.1-1) */
160 r = CMP(*a != '-', *b != '-');
161 if (r != 0)
162 return r;
163
164 a++;
165 b++;
166 }
167
168 /* Handle '^'. Used for patched release. */
169 if (*a == '^' || *b == '^') {
170 r = CMP(*a != '^', *b != '^');
171 if (r != 0)
172 return r;
173
174 a++;
175 b++;
176 }
177
178 /* Handle '.'. Used for point releases. */
179 if (*a == '.' || *b == '.') {
180 r = CMP(*a != '.', *b != '.');
181 if (r != 0)
182 return r;
183
184 a++;
185 b++;
186 }
187
188 if (isdigit(*a) || isdigit(*b)) {
189 /* Skip leading '0', to make 00123 equivalent to 123. */
190 while (*a == '0')
191 a++;
192 while (*b == '0')
193 b++;
194
195 /* Find the leading numeric segments. One may be an empty string. So,
196 * numeric segments are always newer than alpha segments. */
8b3d4ff0 197 for (aa = a; isdigit(*aa); aa++)
3a6ce677 198 ;
8b3d4ff0 199 for (bb = b; isdigit(*bb); bb++)
3a6ce677
BR
200 ;
201
202 /* To compare numeric segments without parsing their values, first compare the
203 * lengths of the segments. Eg. 12345 vs 123, longer is newer. */
204 r = CMP(aa - a, bb - b);
205 if (r != 0)
206 return r;
207
208 /* Then, compare them as strings. */
209 r = strncmp(a, b, aa - a);
210 if (r != 0)
211 return r;
212 } else {
213 /* Find the leading non-numeric segments. */
8b3d4ff0 214 for (aa = a; is_alpha(*aa); aa++)
3a6ce677 215 ;
8b3d4ff0 216 for (bb = b; is_alpha(*bb); bb++)
3a6ce677
BR
217 ;
218
219 /* Note that the segments are usually not NUL-terminated. */
220 r = strncmp(a, b, MIN(aa - a, bb - b));
221 if (r != 0)
222 return r;
223
224 /* Longer is newer, e.g. abc vs abcde. */
225 r = CMP(aa - a, bb - b);
226 if (r != 0)
227 return r;
228 }
229
230 /* The current segments are equivalent. Let's compare the next one. */
231 a = aa;
232 b = bb;
233 }
234}