jcs's openbsd hax
openbsd
1/*
2 * tuple.c
3 * management of key->value tuples
4 *
5 * Copyright (c) 2011, 2012 pkgconf authors (see AUTHORS).
6 *
7 * Permission to use, copy, modify, and/or distribute this software for any
8 * purpose with or without fee is hereby granted, provided that the above
9 * copyright notice and this permission notice appear in all copies.
10 *
11 * This software is provided 'as is' and without any warranty, express or
12 * implied. In no event shall the authors be liable for any damages arising
13 * from the use of this software.
14 */
15
16#include <libpkgconf/stdinc.h>
17#include <libpkgconf/libpkgconf.h>
18
19/*
20 * !doc
21 *
22 * libpkgconf `tuple` module
23 * =========================
24 *
25 * The `tuple` module provides key-value mappings backed by a linked list. The key-value
26 * mapping is mainly used for variable substitution when parsing .pc files.
27 *
28 * There are two sets of mappings: a ``pkgconf_pkg_t`` specific mapping, and a `global` mapping.
29 * The `tuple` module provides convenience wrappers for managing the `global` mapping, which is
30 * attached to a given client object.
31 */
32
33/*
34 * !doc
35 *
36 * .. c:function:: void pkgconf_tuple_add_global(pkgconf_client_t *client, const char *key, const char *value)
37 *
38 * Defines a global variable, replacing the previous declaration if one was set.
39 *
40 * :param pkgconf_client_t* client: The pkgconf client object to modify.
41 * :param char* key: The key for the mapping (variable name).
42 * :param char* value: The value for the mapped entry.
43 * :return: nothing
44 */
45void
46pkgconf_tuple_add_global(pkgconf_client_t *client, const char *key, const char *value)
47{
48 pkgconf_tuple_add(client, &client->global_vars, key, value, false, 0);
49}
50
51static pkgconf_tuple_t *
52lookup_global_tuple(const pkgconf_client_t *client, const char *key)
53{
54 pkgconf_node_t *node;
55
56 PKGCONF_FOREACH_LIST_ENTRY(client->global_vars.head, node)
57 {
58 pkgconf_tuple_t *tuple = node->data;
59
60 if (!strcmp(tuple->key, key))
61 return tuple;
62 }
63
64 return NULL;
65}
66
67/*
68 * !doc
69 *
70 * .. c:function:: void pkgconf_tuple_find_global(const pkgconf_client_t *client, const char *key)
71 *
72 * Looks up a global variable.
73 *
74 * :param pkgconf_client_t* client: The pkgconf client object to access.
75 * :param char* key: The key or variable name to look up.
76 * :return: the contents of the variable or ``NULL``
77 * :rtype: char *
78 */
79char *
80pkgconf_tuple_find_global(const pkgconf_client_t *client, const char *key)
81{
82 pkgconf_tuple_t *tuple;
83
84 tuple = lookup_global_tuple(client, key);
85 if (tuple == NULL)
86 return NULL;
87
88 return tuple->value;
89}
90
91/*
92 * !doc
93 *
94 * .. c:function:: void pkgconf_tuple_free_global(pkgconf_client_t *client)
95 *
96 * Delete all global variables associated with a pkgconf client object.
97 *
98 * :param pkgconf_client_t* client: The pkgconf client object to modify.
99 * :return: nothing
100 */
101void
102pkgconf_tuple_free_global(pkgconf_client_t *client)
103{
104 pkgconf_tuple_free(&client->global_vars);
105}
106
107/*
108 * !doc
109 *
110 * .. c:function:: void pkgconf_tuple_define_global(pkgconf_client_t *client, const char *kv)
111 *
112 * Parse and define a global variable.
113 *
114 * :param pkgconf_client_t* client: The pkgconf client object to modify.
115 * :param char* kv: The variable in the form of ``key=value``.
116 * :return: nothing
117 */
118void
119pkgconf_tuple_define_global(pkgconf_client_t *client, const char *kv)
120{
121 char *workbuf = strdup(kv);
122 char *value;
123 pkgconf_tuple_t *tuple;
124
125 value = strchr(workbuf, '=');
126 if (value == NULL)
127 goto out;
128
129 *value++ = '\0';
130
131 tuple = pkgconf_tuple_add(client, &client->global_vars, workbuf, value, false, 0);
132 if (tuple != NULL)
133 tuple->flags = PKGCONF_PKG_TUPLEF_OVERRIDE;
134
135out:
136 free(workbuf);
137}
138
139static void
140pkgconf_tuple_find_delete(pkgconf_list_t *list, const char *key)
141{
142 pkgconf_node_t *node, *next;
143
144 PKGCONF_FOREACH_LIST_ENTRY_SAFE(list->head, next, node)
145 {
146 pkgconf_tuple_t *tuple = node->data;
147
148 if (!strcmp(tuple->key, key))
149 {
150 pkgconf_tuple_free_entry(tuple, list);
151 return;
152 }
153 }
154}
155
156static char *
157dequote(const char *value)
158{
159 char *buf = calloc(1, (strlen(value) + 1) * 2);
160 char *bptr = buf;
161 const char *i;
162 char quote = 0;
163
164 if (*value == '\'' || *value == '"')
165 quote = *value;
166
167 for (i = value; *i != '\0'; i++)
168 {
169 if (*i == '\\' && quote && *(i + 1) == quote)
170 {
171 i++;
172 *bptr++ = *i;
173 }
174 else if (*i != quote)
175 *bptr++ = *i;
176 }
177
178 return buf;
179}
180
181static const char *
182find_sysroot(const pkgconf_client_t *client, pkgconf_list_t *vars)
183{
184 const char *sysroot_dir;
185
186 sysroot_dir = pkgconf_tuple_find(client, vars, "pc_sysrootdir");
187 if (sysroot_dir == NULL)
188 sysroot_dir = client->sysroot_dir;
189
190 return sysroot_dir;
191}
192
193static bool
194should_rewrite_sysroot(const pkgconf_client_t *client, pkgconf_list_t *vars, const char *buf, unsigned int flags)
195{
196 const char *sysroot_dir;
197
198 if (flags & PKGCONF_PKG_PROPF_UNINSTALLED && !(client->flags & PKGCONF_PKG_PKGF_FDO_SYSROOT_RULES))
199 return false;
200
201 sysroot_dir = find_sysroot(client, vars);
202 if (sysroot_dir == NULL)
203 return false;
204
205 if (*buf != '/')
206 return false;
207
208 if (!strcmp(sysroot_dir, "/"))
209 return false;
210
211 if (strlen(buf) <= strlen(sysroot_dir))
212 return false;
213
214 if (strstr(buf + strlen(sysroot_dir), sysroot_dir) == NULL)
215 return false;
216
217 return true;
218}
219
220/*
221 * !doc
222 *
223 * .. c:function:: pkgconf_tuple_t *pkgconf_tuple_add(const pkgconf_client_t *client, pkgconf_list_t *list, const char *key, const char *value, bool parse)
224 *
225 * Optionally parse and then define a variable.
226 *
227 * :param pkgconf_client_t* client: The pkgconf client object to access.
228 * :param pkgconf_list_t* list: The variable list to add the new variable to.
229 * :param char* key: The name of the variable being added.
230 * :param char* value: The value of the variable being added.
231 * :param bool parse: Whether or not to parse the value for variable substitution.
232 * :return: a variable object
233 * :rtype: pkgconf_tuple_t *
234 */
235pkgconf_tuple_t *
236pkgconf_tuple_add(const pkgconf_client_t *client, pkgconf_list_t *list, const char *key, const char *value, bool parse, unsigned int flags)
237{
238 char *dequote_value;
239 pkgconf_tuple_t *tuple = calloc(1, sizeof(pkgconf_tuple_t));
240
241 pkgconf_tuple_find_delete(list, key);
242
243 dequote_value = dequote(value);
244
245 tuple->key = strdup(key);
246 if (parse)
247 tuple->value = pkgconf_tuple_parse(client, list, dequote_value, flags);
248 else
249 tuple->value = strdup(dequote_value);
250
251 PKGCONF_TRACE(client, "adding tuple to @%p: %s => %s (parsed? %d)", list, key, tuple->value, parse);
252
253 pkgconf_node_insert(&tuple->iter, tuple, list);
254
255 free(dequote_value);
256
257 return tuple;
258}
259
260/*
261 * !doc
262 *
263 * .. c:function:: char *pkgconf_tuple_find(const pkgconf_client_t *client, pkgconf_list_t *list, const char *key)
264 *
265 * Look up a variable in a variable list.
266 *
267 * :param pkgconf_client_t* client: The pkgconf client object to access.
268 * :param pkgconf_list_t* list: The variable list to search.
269 * :param char* key: The variable name to search for.
270 * :return: the value of the variable or ``NULL``
271 * :rtype: char *
272 */
273char *
274pkgconf_tuple_find(const pkgconf_client_t *client, pkgconf_list_t *list, const char *key)
275{
276 pkgconf_node_t *node;
277 pkgconf_tuple_t *global_tuple;
278
279 global_tuple = lookup_global_tuple(client, key);
280 if (global_tuple != NULL && global_tuple->flags & PKGCONF_PKG_TUPLEF_OVERRIDE)
281 return global_tuple->value;
282
283 PKGCONF_FOREACH_LIST_ENTRY(list->head, node)
284 {
285 pkgconf_tuple_t *tuple = node->data;
286
287 if (!strcmp(tuple->key, key))
288 return tuple->value;
289 }
290
291 if (global_tuple != NULL)
292 return global_tuple->value;
293
294 return NULL;
295}
296
297/*
298 * !doc
299 *
300 * .. c:function:: char *pkgconf_tuple_parse(const pkgconf_client_t *client, pkgconf_list_t *vars, const char *value, unsigned int flags)
301 *
302 * Parse an expression for variable substitution.
303 *
304 * :param pkgconf_client_t* client: The pkgconf client object to access.
305 * :param pkgconf_list_t* list: The variable list to search for variables (along side the global variable list).
306 * :param char* value: The ``key=value`` string to parse.
307 * :param uint flags: Any flags to consider while parsing.
308 * :return: the variable data with any variables substituted
309 * :rtype: char *
310 */
311char *
312pkgconf_tuple_parse(const pkgconf_client_t *client, pkgconf_list_t *vars, const char *value, unsigned int flags)
313{
314 char buf[PKGCONF_BUFSIZE];
315 const char *ptr;
316 char *bptr = buf;
317
318 if (!(client->flags & PKGCONF_PKG_PKGF_FDO_SYSROOT_RULES) &&
319 (!(flags & PKGCONF_PKG_PROPF_UNINSTALLED) || (client->flags & PKGCONF_PKG_PKGF_PKGCONF1_SYSROOT_RULES)))
320 {
321 if (*value == '/' && client->sysroot_dir != NULL && strncmp(value, client->sysroot_dir, strlen(client->sysroot_dir)))
322 bptr += pkgconf_strlcpy(buf, client->sysroot_dir, sizeof buf);
323 }
324
325 for (ptr = value; *ptr != '\0' && bptr - buf < PKGCONF_BUFSIZE; ptr++)
326 {
327 if (*ptr != '$' || (*ptr == '$' && *(ptr + 1) != '{'))
328 *bptr++ = *ptr;
329 else if (*(ptr + 1) == '{')
330 {
331 char varname[PKGCONF_ITEM_SIZE];
332 char *vend = varname + PKGCONF_ITEM_SIZE - 1;
333 char *vptr = varname;
334 const char *pptr;
335 char *kv, *parsekv;
336
337 *vptr = '\0';
338
339 for (pptr = ptr + 2; *pptr != '\0'; pptr++)
340 {
341 if (*pptr != '}')
342 {
343 if (vptr < vend)
344 *vptr++ = *pptr;
345 else
346 {
347 *vptr = '\0';
348 break;
349 }
350 }
351 else
352 {
353 *vptr = '\0';
354 break;
355 }
356 }
357
358 PKGCONF_TRACE(client, "lookup tuple %s", varname);
359
360 size_t remain = PKGCONF_BUFSIZE - (bptr - buf);
361 ptr += (pptr - ptr);
362 kv = pkgconf_tuple_find_global(client, varname);
363 if (kv != NULL)
364 {
365 size_t nlen = pkgconf_strlcpy(bptr, kv, remain);
366 if (nlen > remain)
367 {
368 pkgconf_warn(client, "warning: truncating very long variable to 64KB\n");
369
370 bptr = buf + (PKGCONF_BUFSIZE - 1);
371 break;
372 }
373
374 bptr += nlen;
375 }
376 else
377 {
378 kv = pkgconf_tuple_find(client, vars, varname);
379
380 if (kv != NULL)
381 {
382 size_t nlen;
383
384 parsekv = pkgconf_tuple_parse(client, vars, kv, flags);
385 nlen = pkgconf_strlcpy(bptr, parsekv, remain);
386 free(parsekv);
387
388 if (nlen > remain)
389 {
390 pkgconf_warn(client, "warning: truncating very long variable to 64KB\n");
391
392 bptr = buf + (PKGCONF_BUFSIZE - 1);
393 break;
394 }
395
396 bptr += nlen;
397 }
398 }
399 }
400 }
401
402 *bptr = '\0';
403
404 /*
405 * Sigh. Somebody actually attempted to use freedesktop.org pkg-config's broken sysroot support,
406 * which was written by somebody who did not understand how sysroots are supposed to work. This
407 * results in an incorrect path being built as the sysroot will be prepended twice, once explicitly,
408 * and once by variable expansion (the pkgconf approach). We could simply make ${pc_sysrootdir} blank,
409 * but sometimes it is necessary to know the explicit sysroot path for other reasons, so we can't really
410 * do that.
411 *
412 * As a result, we check to see if ${pc_sysrootdir} is prepended as a duplicate, and if so, remove the
413 * prepend. This allows us to handle both our approach and the broken freedesktop.org implementation's
414 * approach. Because a path can be shorter than ${pc_sysrootdir}, we do some checks first to ensure it's
415 * safe to skip ahead in the string to scan for our sysroot dir.
416 *
417 * Finally, we call pkgconf_path_relocate() to clean the path of spurious elements.
418 *
419 * New in 1.9: Only attempt to rewrite the sysroot if we are not processing an uninstalled package.
420 */
421 if (should_rewrite_sysroot(client, vars, buf, flags))
422 {
423 char cleanpath[PKGCONF_ITEM_SIZE];
424 const char *sysroot_dir = find_sysroot(client, vars);
425
426 pkgconf_strlcpy(cleanpath, buf + strlen(sysroot_dir), sizeof cleanpath);
427 pkgconf_path_relocate(cleanpath, sizeof cleanpath);
428
429 return strdup(cleanpath);
430 }
431
432 return strdup(buf);
433}
434
435/*
436 * !doc
437 *
438 * .. c:function:: void pkgconf_tuple_free_entry(pkgconf_tuple_t *tuple, pkgconf_list_t *list)
439 *
440 * Deletes a variable object, removing it from any variable lists and releasing any memory associated
441 * with it.
442 *
443 * :param pkgconf_tuple_t* tuple: The variable object to release.
444 * :param pkgconf_list_t* list: The variable list the variable object is attached to.
445 * :return: nothing
446 */
447void
448pkgconf_tuple_free_entry(pkgconf_tuple_t *tuple, pkgconf_list_t *list)
449{
450 pkgconf_node_delete(&tuple->iter, list);
451
452 free(tuple->key);
453 free(tuple->value);
454 free(tuple);
455}
456
457/*
458 * !doc
459 *
460 * .. c:function:: void pkgconf_tuple_free(pkgconf_list_t *list)
461 *
462 * Deletes a variable list and any variables attached to it.
463 *
464 * :param pkgconf_list_t* list: The variable list to delete.
465 * :return: nothing
466 */
467void
468pkgconf_tuple_free(pkgconf_list_t *list)
469{
470 pkgconf_node_t *node, *next;
471
472 PKGCONF_FOREACH_LIST_ENTRY_SAFE(list->head, next, node)
473 pkgconf_tuple_free_entry(node->data, list);
474
475 pkgconf_list_zero(list);
476}