]> git.ipfire.org Git - pakfire.git/blob - src/libpakfire/dependencies.c
dependencies: Fix parsing deps that don't have any space
[pakfire.git] / src / libpakfire / dependencies.c
1 /*#############################################################################
2 # #
3 # Pakfire - The IPFire package management system #
4 # Copyright (C) 2022 Pakfire development team #
5 # #
6 # This program is free software: you can redistribute it and/or modify #
7 # it under the terms of the GNU General Public License as published by #
8 # the Free Software Foundation, either version 3 of the License, or #
9 # (at your option) any later version. #
10 # #
11 # This program is distributed in the hope that it will be useful, #
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of #
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
14 # GNU General Public License for more details. #
15 # #
16 # You should have received a copy of the GNU General Public License #
17 # along with this program. If not, see <http://www.gnu.org/licenses/>. #
18 # #
19 #############################################################################*/
20
21 #include <ctype.h>
22 #include <errno.h>
23 #include <stdlib.h>
24 #include <string.h>
25
26 #include <solv/pool.h>
27
28 #include <pakfire/dependencies.h>
29 #include <pakfire/logging.h>
30 #include <pakfire/package.h>
31 #include <pakfire/pakfire.h>
32 #include <pakfire/string.h>
33 #include <pakfire/util.h>
34
35 const struct pakfire_dep pakfire_deps[] = {
36 { PAKFIRE_PKG_PROVIDES, "provides" },
37 { PAKFIRE_PKG_PREREQUIRES, "prerequires" },
38 { PAKFIRE_PKG_REQUIRES, "requires" },
39 { PAKFIRE_PKG_CONFLICTS, "conflicts" },
40 { PAKFIRE_PKG_OBSOLETES, "obsoletes" },
41 { PAKFIRE_PKG_RECOMMENDS, "recommends" },
42 { PAKFIRE_PKG_SUGGESTS, "suggests" },
43 { PAKFIRE_PKG_SUPPLEMENTS, "supplements" },
44 { PAKFIRE_PKG_ENHANCES, "enhances" },
45 { 0, NULL },
46 };
47
48 static const struct pakfire_rich_operation {
49 const char* keyword;
50 const int flags;
51 } pakfire_rich_operations[] = {
52 { "and ", REL_AND },
53 { "or ", REL_OR },
54 { "if ", REL_COND },
55 { "unless ", REL_UNLESS },
56 { "else ", REL_ELSE },
57 { "with ", REL_WITH },
58 { "without ", REL_WITHOUT },
59 { NULL, 0 },
60 };
61
62 const char* pakfire_dep2str(struct pakfire* pakfire, Id id) {
63 Pool* pool = pakfire_get_solv_pool(pakfire);
64 if (!pool)
65 return NULL;
66
67 return pool_dep2str(pool, id);
68 }
69
70 /*
71 Reads the string until the next space is found.
72
73 This function considers opening and closing brackets.
74 */
75 static size_t skip(const char** s, const char** n) {
76 const char* p = *s;
77
78 // Store p in n
79 *n = p;
80
81 int brackets = 0;
82
83 while (*p) {
84 switch (*p) {
85 // End on space, =, >, <, or comma
86 case ' ':
87 case '=':
88 case '>':
89 case '<':
90 case ',':
91 goto END;
92
93 // Increment counter on opening bracket
94 case '(':
95 brackets++;
96 break;
97
98 // End on the last closing bracket
99 case ')':
100 if (brackets-- <= 0)
101 goto END;
102 break;
103 }
104
105 // Move to next character
106 p++;
107 }
108
109 END:
110 *s = p;
111
112 // Return the length of the skipped string
113 return p - *n;
114 }
115
116 /*
117 This function parses any namespaced dependencies
118 */
119 static Id pakfire_parse_namespace(Pool* pool, const char* s) {
120 const char* p = strchr(s, '(');
121 if (!p)
122 return 0;
123
124 // Store the namespace ID
125 Id namespace = pool_strn2id(pool, s, p - s, 1);
126
127 // Find the end of the string
128 s = strrchr(p, ')');
129 if (!s)
130 return 0;
131
132 Id id = pool_strn2id(pool, p + 1, s - p - 1, 1);
133
134 // Bring it all together
135 return pool_rel2id(pool, namespace, id, REL_NAMESPACE, 1);
136 }
137
138 /*
139 This function parses a simple dependency like "foo = 1.2.3"
140 */
141 static Id pakfire_parse_dep(struct pakfire* pakfire, const char** s) {
142 Id id = ID_NULL;
143
144 if (!s) {
145 errno = EINVAL;
146 return 0;
147 }
148
149 const char* p = *s;
150 const char* n = NULL;
151
152 // Ignore empty strings
153 if (!p)
154 return ID_NULL;
155
156 Pool* pool = pakfire_get_solv_pool(pakfire);
157
158 // Consume any leading space
159 if (isspace(*p))
160 p++;
161
162 // Find the first part
163 size_t l = skip(&p, &n);
164
165 // Add name to pool
166 if (pakfire_string_startswith(n, "pakfire("))
167 id = pakfire_parse_namespace(pool, n);
168 else
169 id = pool_strn2id(pool, n, l, 1);
170
171 // Consume any more space
172 if (isspace(*p))
173 p++;
174
175 if (*p == '<' || *p == '=' || *p == '>') {
176 int flags = 0;
177
178 while (1) {
179 if (*p == '<')
180 flags |= REL_LT;
181 else if (*p == '=')
182 flags |= REL_EQ;
183 else if (*p == '>')
184 flags |= REL_GT;
185 else
186 break;
187
188 p++;
189 }
190
191 // Consume any more space
192 if (isspace(*p))
193 p++;
194
195 // Find the length of EVR
196 l = skip(&p, &n);
197
198 // Strip zero epoch
199 if (pakfire_string_startswith(n, "0:"))
200 n += 2;
201
202 // Add EVR to pool
203 Id evr = pool_strn2id(pool, n, l, 1);
204
205 // Combine everything
206 id = pool_rel2id(pool, id, evr, flags, 1);
207 }
208
209 *s = p;
210
211 return id;
212 }
213
214 /*
215 This function parses any rich dependencies
216 */
217 static Id pakfire_parse_rich_dep(struct pakfire* pakfire, const char** dep, int flags) {
218 const char* p = *dep;
219 Id id;
220
221 // Do not try parsing empty strings
222 if (!*p)
223 return ID_NULL;
224
225 // Must be starting with an opening bracket
226 if (!flags && *p++ != '(')
227 return ID_NULL;
228
229 switch (*p) {
230 // A new rich dependency
231 case '(':
232 id = pakfire_parse_rich_dep(pakfire, &p, 0);
233 if (!id)
234 return id;
235 break;
236
237 // End
238 case ')':
239 return ID_NULL;
240
241 // Parse a regular dependency
242 default:
243 id = pakfire_parse_dep(pakfire, &p);
244 if (!id)
245 return id;
246 break;
247 }
248
249 // Consume any space
250 if (isspace(*p))
251 p++;
252
253 // If we have successfully parsed something, we would expect a closing bracket
254 if (*p == ')') {
255 *dep = p + 1;
256 return id;
257 }
258
259 // Search for a keyword
260 for (const struct pakfire_rich_operation* op = pakfire_rich_operations; op->keyword; op++) {
261 if (pakfire_string_startswith(p, op->keyword)) {
262 // Skip the keyword
263 p += strlen(op->keyword);
264
265 // Store the flags
266 flags = op->flags;
267 break;
268 }
269 }
270
271 // If there was no keyword, we are done
272 if (!flags)
273 return ID_NULL;
274
275 // Parse the next bit
276 Id evr = pakfire_parse_rich_dep(pakfire, &p, flags);
277
278 // Abort if there was a problem
279 if (!evr)
280 return ID_NULL;
281
282 // Store until where we have parsed the string
283 *dep = p;
284
285 Pool* pool = pakfire_get_solv_pool(pakfire);
286
287 // Combine everything
288 return pool_rel2id(pool, id, evr, flags, 1);
289 }
290
291 Id pakfire_str2dep(struct pakfire* pakfire, const char* s) {
292 Id id = ID_NULL;
293
294 // Invalid input
295 if (!s) {
296 errno = EINVAL;
297 return id;
298 }
299
300 // Ignore empty strings
301 if (!*s)
302 return id;
303
304 // Consume any leading space
305 if (isspace(*s))
306 s++;
307
308 // Ignore any comments
309 if (*s == '#')
310 return id;
311
312 // Parse any rich dependencies
313 if (*s == '(')
314 id = pakfire_parse_rich_dep(pakfire, &s, 0);
315 else
316 id = pakfire_parse_dep(pakfire, &s);
317
318 // Return nothing if we could not parse the entire string
319 if (id && *s)
320 id = ID_NULL;
321
322 return id;
323 }
324
325 int pakfire_str2deps(struct pakfire* pakfire, struct pakfire_package* pkg,
326 const enum pakfire_package_key key, const char* deps) {
327 char* p = NULL;
328 int r = 0;
329
330 // Check for valid inputs
331 if (!deps) {
332 errno = EINVAL;
333 return 1;
334 }
335
336 // Copy deps into a working buffer
337 char* buffer = strdup(deps);
338 if (!buffer)
339 goto ERROR;
340
341 char* dep = strtok_r(buffer, "\n", &p);
342
343 // Walk through the buffer line by line
344 while (dep) {
345 DEBUG(pakfire, "Found dep '%s'\n", dep);
346
347 // Add the dependency
348 r = pakfire_package_add_dep(pkg, key, dep);
349 if (r)
350 goto ERROR;
351
352 // Move on to next token
353 dep = strtok_r(NULL, "\n", &p);
354 }
355
356 ERROR:
357 if (buffer)
358 free(buffer);
359
360 return r;
361 }