]>
Commit | Line | Data |
---|---|---|
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 | ||
10 | sd_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 | |
24 | sd_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 | ||
38 | sd_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 | ||
59 | sd_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 | |
81 | static sd_bool isdigit(sd_char a) { | |
82 | return a >= '0' && a <= '9'; | |
83 | } | |
84 | #endif | |
85 | ||
86 | static 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 | ||
91 | static sd_bool is_valid_version_char(sd_char a) { | |
92 | return isdigit(a) || is_alpha(a) || IN_SET(a, '~', '-', '^', '.'); | |
93 | } | |
94 | ||
95 | sd_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 | } |