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