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 */
45 void
pkgconf_tuple_add_global(pkgconf_client_t * client,const char * key,const char * value)46 pkgconf_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);
49 }
50
51 /*
52 * !doc
53 *
54 * .. c:function:: void pkgconf_tuple_find_global(const pkgconf_client_t *client, const char *key)
55 *
56 * Looks up a global variable.
57 *
58 * :param pkgconf_client_t* client: The pkgconf client object to access.
59 * :param char* key: The key or variable name to look up.
60 * :return: the contents of the variable or ``NULL``
61 * :rtype: char *
62 */
63 char *
pkgconf_tuple_find_global(const pkgconf_client_t * client,const char * key)64 pkgconf_tuple_find_global(const pkgconf_client_t *client, const char *key)
65 {
66 pkgconf_node_t *node;
67
68 PKGCONF_FOREACH_LIST_ENTRY(client->global_vars.head, node)
69 {
70 pkgconf_tuple_t *tuple = node->data;
71
72 if (!strcmp(tuple->key, key))
73 return tuple->value;
74 }
75
76 return NULL;
77 }
78
79 /*
80 * !doc
81 *
82 * .. c:function:: void pkgconf_tuple_free_global(pkgconf_client_t *client)
83 *
84 * Delete all global variables associated with a pkgconf client object.
85 *
86 * :param pkgconf_client_t* client: The pkgconf client object to modify.
87 * :return: nothing
88 */
89 void
pkgconf_tuple_free_global(pkgconf_client_t * client)90 pkgconf_tuple_free_global(pkgconf_client_t *client)
91 {
92 pkgconf_tuple_free(&client->global_vars);
93 }
94
95 /*
96 * !doc
97 *
98 * .. c:function:: void pkgconf_tuple_define_global(pkgconf_client_t *client, const char *kv)
99 *
100 * Parse and define a global variable.
101 *
102 * :param pkgconf_client_t* client: The pkgconf client object to modify.
103 * :param char* kv: The variable in the form of ``key=value``.
104 * :return: nothing
105 */
106 void
pkgconf_tuple_define_global(pkgconf_client_t * client,const char * kv)107 pkgconf_tuple_define_global(pkgconf_client_t *client, const char *kv)
108 {
109 char *workbuf = strdup(kv);
110 char *value;
111
112 value = strchr(workbuf, '=');
113 if (value == NULL)
114 goto out;
115
116 *value++ = '\0';
117 pkgconf_tuple_add_global(client, workbuf, value);
118 out:
119 free(workbuf);
120 }
121
122 static void
pkgconf_tuple_find_delete(pkgconf_list_t * list,const char * key)123 pkgconf_tuple_find_delete(pkgconf_list_t *list, const char *key)
124 {
125 pkgconf_node_t *node, *next;
126
127 PKGCONF_FOREACH_LIST_ENTRY_SAFE(list->head, next, node)
128 {
129 pkgconf_tuple_t *tuple = node->data;
130
131 if (!strcmp(tuple->key, key))
132 {
133 pkgconf_tuple_free_entry(tuple, list);
134 return;
135 }
136 }
137 }
138
139 static char *
dequote(const char * value)140 dequote(const char *value)
141 {
142 char *buf = calloc((strlen(value) + 1) * 2, 1);
143 char *bptr = buf;
144 const char *i;
145 char quote = 0;
146
147 if (*value == '\'' || *value == '"')
148 quote = *value;
149
150 for (i = value; *i != '\0'; i++)
151 {
152 if (*i == '\\' && quote && *(i + 1) == quote)
153 {
154 i++;
155 *bptr++ = *i;
156 }
157 else if (*i != quote)
158 *bptr++ = *i;
159 }
160
161 return buf;
162 }
163
164 /*
165 * !doc
166 *
167 * .. 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)
168 *
169 * Optionally parse and then define a variable.
170 *
171 * :param pkgconf_client_t* client: The pkgconf client object to access.
172 * :param pkgconf_list_t* list: The variable list to add the new variable to.
173 * :param char* key: The name of the variable being added.
174 * :param char* value: The value of the variable being added.
175 * :param bool parse: Whether or not to parse the value for variable substitution.
176 * :return: a variable object
177 * :rtype: pkgconf_tuple_t *
178 */
179 pkgconf_tuple_t *
pkgconf_tuple_add(const pkgconf_client_t * client,pkgconf_list_t * list,const char * key,const char * value,bool parse)180 pkgconf_tuple_add(const pkgconf_client_t *client, pkgconf_list_t *list, const char *key, const char *value, bool parse)
181 {
182 char *dequote_value;
183 pkgconf_tuple_t *tuple = calloc(sizeof(pkgconf_tuple_t), 1);
184
185 pkgconf_tuple_find_delete(list, key);
186
187 dequote_value = dequote(value);
188
189 PKGCONF_TRACE(client, "adding tuple to @%p: %s => %s (parsed? %d)", list, key, dequote_value, parse);
190
191 tuple->key = strdup(key);
192 if (parse)
193 tuple->value = pkgconf_tuple_parse(client, list, dequote_value);
194 else
195 tuple->value = strdup(dequote_value);
196
197 pkgconf_node_insert(&tuple->iter, tuple, list);
198
199 free(dequote_value);
200
201 return tuple;
202 }
203
204 /*
205 * !doc
206 *
207 * .. c:function:: char *pkgconf_tuple_find(const pkgconf_client_t *client, pkgconf_list_t *list, const char *key)
208 *
209 * Look up a variable in a variable list.
210 *
211 * :param pkgconf_client_t* client: The pkgconf client object to access.
212 * :param pkgconf_list_t* list: The variable list to search.
213 * :param char* key: The variable name to search for.
214 * :return: the value of the variable or ``NULL``
215 * :rtype: char *
216 */
217 char *
pkgconf_tuple_find(const pkgconf_client_t * client,pkgconf_list_t * list,const char * key)218 pkgconf_tuple_find(const pkgconf_client_t *client, pkgconf_list_t *list, const char *key)
219 {
220 pkgconf_node_t *node;
221 char *res;
222
223 if ((res = pkgconf_tuple_find_global(client, key)) != NULL)
224 return res;
225
226 PKGCONF_FOREACH_LIST_ENTRY(list->head, node)
227 {
228 pkgconf_tuple_t *tuple = node->data;
229
230 if (!strcmp(tuple->key, key))
231 return tuple->value;
232 }
233
234 return NULL;
235 }
236
237 /*
238 * !doc
239 *
240 * .. c:function:: char *pkgconf_tuple_parse(const pkgconf_client_t *client, pkgconf_list_t *vars, const char *value)
241 *
242 * Parse an expression for variable substitution.
243 *
244 * :param pkgconf_client_t* client: The pkgconf client object to access.
245 * :param pkgconf_list_t* list: The variable list to search for variables (along side the global variable list).
246 * :param char* value: The ``key=value`` string to parse.
247 * :return: the variable data with any variables substituted
248 * :rtype: char *
249 */
250 char *
pkgconf_tuple_parse(const pkgconf_client_t * client,pkgconf_list_t * vars,const char * value)251 pkgconf_tuple_parse(const pkgconf_client_t *client, pkgconf_list_t *vars, const char *value)
252 {
253 char buf[PKGCONF_BUFSIZE];
254 const char *ptr;
255 char *bptr = buf;
256
257 if (!(client->flags & PKGCONF_PKG_PKGF_FDO_SYSROOT_RULES))
258 {
259 if (*value == '/' && client->sysroot_dir != NULL && strncmp(value, client->sysroot_dir, strlen(client->sysroot_dir)))
260 bptr += pkgconf_strlcpy(buf, client->sysroot_dir, sizeof buf);
261 }
262
263 for (ptr = value; *ptr != '\0' && bptr - buf < PKGCONF_BUFSIZE; ptr++)
264 {
265 if (*ptr != '$' || (*ptr == '$' && *(ptr + 1) != '{'))
266 *bptr++ = *ptr;
267 else if (*(ptr + 1) == '{')
268 {
269 char varname[PKGCONF_ITEM_SIZE];
270 char *vend = varname + PKGCONF_ITEM_SIZE - 1;
271 char *vptr = varname;
272 const char *pptr;
273 char *kv, *parsekv;
274
275 *vptr = '\0';
276
277 for (pptr = ptr + 2; *pptr != '\0'; pptr++)
278 {
279 if (*pptr != '}')
280 {
281 if (vptr < vend)
282 *vptr++ = *pptr;
283 else
284 {
285 *vptr = '\0';
286 break;
287 }
288 }
289 else
290 {
291 *vptr = '\0';
292 break;
293 }
294 }
295
296 ptr += (pptr - ptr);
297 kv = pkgconf_tuple_find_global(client, varname);
298 if (kv != NULL)
299 {
300 strncpy(bptr, kv, PKGCONF_BUFSIZE - (bptr - buf));
301 bptr += strlen(kv);
302 }
303 else
304 {
305 kv = pkgconf_tuple_find(client, vars, varname);
306
307 if (kv != NULL)
308 {
309 parsekv = pkgconf_tuple_parse(client, vars, kv);
310
311 strncpy(bptr, parsekv, PKGCONF_BUFSIZE - (bptr - buf));
312 bptr += strlen(parsekv);
313
314 free(parsekv);
315 }
316 }
317 }
318 }
319
320 *bptr = '\0';
321
322 /*
323 * Sigh. Somebody actually attempted to use freedesktop.org pkg-config's broken sysroot support,
324 * which was written by somebody who did not understand how sysroots are supposed to work. This
325 * results in an incorrect path being built as the sysroot will be prepended twice, once explicitly,
326 * and once by variable expansion (the pkgconf approach). We could simply make ${pc_sysrootdir} blank,
327 * but sometimes it is necessary to know the explicit sysroot path for other reasons, so we can't really
328 * do that.
329 *
330 * As a result, we check to see if ${pc_sysrootdir} is prepended as a duplicate, and if so, remove the
331 * prepend. This allows us to handle both our approach and the broken freedesktop.org implementation's
332 * approach. Because a path can be shorter than ${pc_sysrootdir}, we do some checks first to ensure it's
333 * safe to skip ahead in the string to scan for our sysroot dir.
334 *
335 * Finally, we call pkgconf_path_relocate() to clean the path of spurious elements.
336 */
337 if (*buf == '/' &&
338 client->sysroot_dir != NULL &&
339 strcmp(client->sysroot_dir, "/") != 0 &&
340 strlen(buf) > strlen(client->sysroot_dir) &&
341 strstr(buf + strlen(client->sysroot_dir), client->sysroot_dir) != NULL)
342 {
343 char cleanpath[PKGCONF_ITEM_SIZE];
344
345 pkgconf_strlcpy(cleanpath, buf + strlen(client->sysroot_dir), sizeof cleanpath);
346 pkgconf_path_relocate(cleanpath, sizeof cleanpath);
347
348 return strdup(cleanpath);
349 }
350
351 return strdup(buf);
352 }
353
354 /*
355 * !doc
356 *
357 * .. c:function:: void pkgconf_tuple_free_entry(pkgconf_tuple_t *tuple, pkgconf_list_t *list)
358 *
359 * Deletes a variable object, removing it from any variable lists and releasing any memory associated
360 * with it.
361 *
362 * :param pkgconf_tuple_t* tuple: The variable object to release.
363 * :param pkgconf_list_t* list: The variable list the variable object is attached to.
364 * :return: nothing
365 */
366 void
pkgconf_tuple_free_entry(pkgconf_tuple_t * tuple,pkgconf_list_t * list)367 pkgconf_tuple_free_entry(pkgconf_tuple_t *tuple, pkgconf_list_t *list)
368 {
369 pkgconf_node_delete(&tuple->iter, list);
370
371 free(tuple->key);
372 free(tuple->value);
373 free(tuple);
374 }
375
376 /*
377 * !doc
378 *
379 * .. c:function:: void pkgconf_tuple_free(pkgconf_list_t *list)
380 *
381 * Deletes a variable list and any variables attached to it.
382 *
383 * :param pkgconf_list_t* list: The variable list to delete.
384 * :return: nothing
385 */
386 void
pkgconf_tuple_free(pkgconf_list_t * list)387 pkgconf_tuple_free(pkgconf_list_t *list)
388 {
389 pkgconf_node_t *node, *next;
390
391 PKGCONF_FOREACH_LIST_ENTRY_SAFE(list->head, next, node)
392 pkgconf_tuple_free_entry(node->data, list);
393 }
394