xref: /freebsd/contrib/pkgconf/libpkgconf/personality.c (revision f9bc005b8ef3b507a5ecbc2d7fe4411ef70eff38)
1 /*
2  * personality.c
3  * libpkgconf cross-compile personality database
4  *
5  * SPDX-License-Identifier: pkgconf
6  *
7  * Copyright (c) 2018 pkgconf authors (see AUTHORS).
8  *
9  * Permission to use, copy, modify, and/or distribute this software for any
10  * purpose with or without fee is hereby granted, provided that the above
11  * copyright notice and this permission notice appear in all copies.
12  *
13  * This software is provided 'as is' and without any warranty, express or
14  * implied.  In no event shall the authors be liable for any damages arising
15  * from the use of this software.
16  */
17 
18 #include <libpkgconf/config.h>
19 #include <libpkgconf/stdinc.h>
20 #include <libpkgconf/libpkgconf.h>
21 
22 #ifdef __FreeBSD__
23 # include <sys/param.h>
24 # include <sys/sysctl.h>
25 # include <err.h>
26 #endif
27 
28 /*
29  * !doc
30  *
31  * libpkgconf `personality` module
32  * =========================
33  */
34 
35 /*
36  * Increment each time the default personality is inited, decrement each time
37  * it's deinited. Whenever it is 0, then the deinit frees the personality. In
38  * that case an additional call to init will create it anew.
39  */
40 static unsigned default_personality_init = 0;
41 
42 static pkgconf_cross_personality_t default_personality = {
43 	.name = "default",
44 };
45 
46 static inline void
47 build_default_search_path(pkgconf_list_t* dirlist)
48 {
49 #ifdef _WIN32
50 	char namebuf[MAX_PATH];
51 	pkgconf_buffer_t pathbuf = PKGCONF_BUFFER_INITIALIZER;
52 	char *p;
53 
54 	/* Reserve one byte for the NUL: GetModuleFileName returns the size passed
55 	 * to it (nSize) when the path is truncated, so passing sizeof namebuf could
56 	 * yield sizepath == sizeof namebuf and overflow namebuf[] by one byte below.
57 	 *
58 	 * See the GetModuleFileNameA return-value documentation:
59 	 * https://learn.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-getmodulefilenamea
60 	 */
61 	int sizepath = GetModuleFileName(NULL, namebuf, sizeof namebuf - 1);
62 	char * winslash;
63 	namebuf[sizepath] = '\0';
64 
65 	while ((winslash = strchr(namebuf, '\\')) != NULL)
66 		*winslash = '/';
67 
68 	p = strrchr(namebuf, '/');
69 	if (p == NULL)
70 	{
71 		pkgconf_path_split(PKG_DEFAULT_PATH, dirlist, true);
72 		return;
73 	}
74 	*p = '\0';
75 
76 	pkgconf_buffer_append_fmt(&pathbuf, "%s/../lib/pkgconfig", namebuf);
77 	pkgconf_path_add(pkgconf_buffer_str(&pathbuf), dirlist, true);
78 	pkgconf_buffer_reset(&pathbuf);
79 
80 	pkgconf_buffer_append_fmt(&pathbuf, "%s/../share/pkgconfig", namebuf);
81 	pkgconf_path_add(pkgconf_buffer_str(&pathbuf), dirlist, true);
82 	pkgconf_buffer_finalize(&pathbuf);
83 #elif __HAIKU__
84 	char **paths;
85 	size_t count;
86 	if (find_paths(B_FIND_PATH_DEVELOP_LIB_DIRECTORY, "pkgconfig", &paths, &count) == B_OK) {
87 		for (size_t i = 0; i < count; i++)
88 			pkgconf_path_add(paths[i], dirlist, true);
89 		free(paths);
90 		paths = NULL;
91 	}
92 	if (find_paths(B_FIND_PATH_DATA_DIRECTORY, "pkgconfig", &paths, &count) == B_OK) {
93 		for (size_t i = 0; i < count; i++)
94 			pkgconf_path_add(paths[i], dirlist, true);
95 		free(paths);
96 		paths = NULL;
97 	}
98 #elif defined(__FreeBSD__)
99 	pkgconf_buffer_t path1 = PKGCONF_BUFFER_INITIALIZER;
100 	pkgconf_buffer_t path2 = PKGCONF_BUFFER_INITIALIZER;
101 	char const *localbase;
102 	char buf[MAXPATHLEN];
103 	size_t len;
104 
105 	localbase = getenv("LOCALBASE");
106 
107 	if (localbase == NULL) {
108 		len = sizeof(buf);
109 		if (sysctlbyname("user.localbase", &buf, &len, NULL, 0) < 0)
110 			err(1, "sysctl(user.localbase)");
111 
112 		localbase = buf;
113 	}
114 
115 	if (!pkgconf_buffer_append(&path1, localbase) ||
116 			!pkgconf_buffer_append(&path1, "/libdata/pkgconfig"))
117 		err(1, NULL);
118 	pkgconf_path_add(pkgconf_buffer_str(&path1), dirlist, false);
119 
120 	pkgconf_path_add("/usr/libdata/pkgconfig", dirlist, false);
121 
122 	if (!pkgconf_buffer_append(&path2, localbase) ||
123 			!pkgconf_buffer_append(&path2, "/share/pkgconfig"))
124 		err(1, NULL);
125 	pkgconf_path_add(pkgconf_buffer_str(&path2), dirlist, false);
126 #else
127 	pkgconf_path_split(PKG_DEFAULT_PATH, dirlist, false);
128 #endif
129 }
130 
131 /*
132  * !doc
133  *
134  * .. c:function:: const pkgconf_cross_personality_t *pkgconf_cross_personality_default(void)
135  *
136  *    Returns the default cross-compile personality.
137  *
138  *    Not thread safe.
139  *
140  *    :rtype: pkgconf_cross_personality_t*
141  *    :return: the default cross-compile personality
142  */
143 pkgconf_cross_personality_t *
144 pkgconf_cross_personality_default(void)
145 {
146 	if (default_personality_init) {
147 		++default_personality_init;
148 		return &default_personality;
149 	}
150 
151 	build_default_search_path(&default_personality.dir_list);
152 
153 	pkgconf_path_split(SYSTEM_LIBDIR, &default_personality.filter_libdirs, false);
154 	pkgconf_path_split(SYSTEM_INCLUDEDIR, &default_personality.filter_includedirs, false);
155 
156 	++default_personality_init;
157 	return &default_personality;
158 }
159 
160 /*
161  * !doc
162  *
163  * .. c:function:: void pkgconf_cross_personality_deinit(pkgconf_cross_personality_t *)
164  *
165  *    Destroys a cross personality object and/or decreases the reference count on the
166  *    default cross personality object.
167  *
168  *    Not thread safe.
169  *
170  *    :rtype: void
171  */
172 void
173 pkgconf_cross_personality_deinit(pkgconf_cross_personality_t *personality)
174 {
175 	/* allow NULL parameter for API backwards compatibility */
176 	if (personality == NULL)
177 		return;
178 
179 	/* XXX: this hack is rather ugly, but it works for now... */
180 	if (personality == &default_personality && --default_personality_init > 0)
181 		return;
182 
183 	pkgconf_path_free(&personality->dir_list);
184 	pkgconf_path_free(&personality->filter_libdirs);
185 	pkgconf_path_free(&personality->filter_includedirs);
186 
187 	if (personality->sysroot_dir != NULL)
188 		free(personality->sysroot_dir);
189 
190 	if (personality == &default_personality)
191 		return;
192 
193 	if (personality->name != NULL)
194 		free(personality->name);
195 
196 	free(personality);
197 }
198 
199 #ifndef PKGCONF_LITE
200 static bool
201 valid_triplet(const char *triplet)
202 {
203 	const char *c = triplet;
204 
205 	for (; *c; c++)
206 		if (!isalnum((unsigned char)*c) && *c != '-' && *c != '_')
207 			return false;
208 
209 	return true;
210 }
211 
212 typedef void (*personality_keyword_func_t)(pkgconf_cross_personality_t *p, const char *keyword, const char *warnprefix, const ptrdiff_t offset, const char *value);
213 typedef struct {
214 	const char *keyword;
215 	const personality_keyword_func_t func;
216 	const ptrdiff_t offset;
217 } personality_keyword_pair_t;
218 
219 static void
220 personality_bool_func(pkgconf_cross_personality_t *p, const char *keyword, const char *warnprefix, const ptrdiff_t offset, const char *value)
221 {
222 	(void) keyword;
223 	(void) warnprefix;
224 
225 	bool *dest = (bool *)((char *) p + offset);
226 	*dest = strcasecmp(value, "true") == 0 || strcasecmp(value, "yes") == 0 || strcasecmp(value, "1") == 0;
227 }
228 
229 static void
230 personality_copy_func(pkgconf_cross_personality_t *p, const char *keyword, const char *warnprefix, const ptrdiff_t offset, const char *value)
231 {
232 	(void) keyword;
233 	(void) warnprefix;
234 
235 	char **dest = (char **)((char *) p + offset);
236 
237 	if (*dest != NULL)
238 		free(*dest);
239 
240 	*dest = strdup(value);
241 }
242 
243 static void
244 personality_fragment_func(pkgconf_cross_personality_t *p, const char *keyword, const char *warnprefix, const ptrdiff_t offset, const char *value)
245 {
246 	(void) keyword;
247 	(void) warnprefix;
248 
249 	pkgconf_list_t *dest = (pkgconf_list_t *)((char *) p + offset);
250 	pkgconf_path_split(value, dest, false);
251 }
252 
253 /* keep in alphabetical order! */
254 static const personality_keyword_pair_t personality_keyword_pairs[] = {
255 	{"DefaultSearchPaths", personality_fragment_func, offsetof(pkgconf_cross_personality_t, dir_list)},
256 	{"SysrootDir", personality_copy_func, offsetof(pkgconf_cross_personality_t, sysroot_dir)},
257 	{"SystemIncludePaths", personality_fragment_func, offsetof(pkgconf_cross_personality_t, filter_includedirs)},
258 	{"SystemLibraryPaths", personality_fragment_func, offsetof(pkgconf_cross_personality_t, filter_libdirs)},
259 	{"Triplet", personality_copy_func, offsetof(pkgconf_cross_personality_t, name)},
260 	{"WantDefaultPure", personality_bool_func, offsetof(pkgconf_cross_personality_t, want_default_pure)},
261 	{"WantDefaultStatic", personality_bool_func, offsetof(pkgconf_cross_personality_t, want_default_static)},
262 };
263 
264 static int
265 personality_keyword_pair_cmp(const void *key, const void *ptr)
266 {
267 	const personality_keyword_pair_t *pair = ptr;
268 	return strcasecmp(key, pair->keyword);
269 }
270 
271 static void
272 personality_keyword_set(void *data, const char *warnprefix, const char *keyword, const char *value)
273 {
274 	pkgconf_cross_personality_t *p = data;
275 	const personality_keyword_pair_t *pair = bsearch(keyword,
276 		personality_keyword_pairs, PKGCONF_ARRAY_SIZE(personality_keyword_pairs),
277 		sizeof(personality_keyword_pair_t), personality_keyword_pair_cmp);
278 
279 	if (pair == NULL || pair->func == NULL)
280 		return;
281 
282 	pair->func(p, keyword, warnprefix, pair->offset, value);
283 }
284 
285 static const pkgconf_parser_operand_func_t personality_parser_ops[256] = {
286 	[':'] = (pkgconf_parser_operand_func_t) personality_keyword_set
287 };
288 
289 static void personality_warn_func(void *p, const char *fmt, ...) PRINTFLIKE(2, 3);
290 
291 static void
292 personality_warn_func(void *p, const char *fmt, ...)
293 {
294 	va_list va;
295 
296 	(void) p;
297 
298 	va_start(va, fmt);
299 	pkgconf_output_file_vfmt(stderr, fmt, va);
300 	va_end(va);
301 }
302 
303 static pkgconf_cross_personality_t *
304 load_personality_with_path(const char *path, const char *triplet, bool datadir)
305 {
306 	pkgconf_buffer_t pathbuf = PKGCONF_BUFFER_INITIALIZER;
307 	FILE *f;
308 	pkgconf_cross_personality_t *p;
309 
310 	/* if triplet is null, assume that path is a direct path to the personality file */
311 	if (triplet == NULL)
312 		pkgconf_buffer_append(&pathbuf, path);
313 	else if (datadir)
314 		pkgconf_buffer_append_fmt(&pathbuf, "%s/pkgconfig/personality.d/%s.personality", path, triplet);
315 	else
316 		pkgconf_buffer_append_fmt(&pathbuf, "%s/%s.personality", path, triplet);
317 
318 	p = calloc(1, sizeof(pkgconf_cross_personality_t));
319 	if (p == NULL)
320 	{
321 		pkgconf_buffer_finalize(&pathbuf);
322 		return NULL;
323 	}
324 
325 	if (triplet != NULL)
326 		p->name = strdup(triplet);
327 
328 	f = fopen(pkgconf_buffer_str(&pathbuf), "rb");
329 	if (f == NULL)
330 	{
331 		pkgconf_buffer_finalize(&pathbuf);
332 		pkgconf_cross_personality_deinit(p);
333 		return NULL;
334 	}
335 
336 	pkgconf_parser_parse(f, p, personality_parser_ops, personality_warn_func, pkgconf_buffer_str(&pathbuf));
337 	pkgconf_buffer_finalize(&pathbuf);
338 
339 	return p;
340 }
341 
342 /*
343  * !doc
344  *
345  * .. c:function:: pkgconf_cross_personality_t *pkgconf_cross_personality_find(const char *triplet)
346  *
347  *    Attempts to find a cross-compile personality given a triplet.
348  *
349  *    :rtype: pkgconf_cross_personality_t*
350  *    :return: the default cross-compile personality
351  */
352 pkgconf_cross_personality_t *
353 pkgconf_cross_personality_find(const char *triplet)
354 {
355 	pkgconf_list_t plist = PKGCONF_LIST_INITIALIZER;
356 	pkgconf_node_t *n;
357 	pkgconf_cross_personality_t *out = NULL;
358 #if ! defined(_WIN32) && ! defined(__HAIKU__)
359 	const char *envvar;
360 #endif
361 #if defined(__FreeBSD__)
362 	pkgconf_buffer_t path = PKGCONF_BUFFER_INITIALIZER;
363 	char const *localbase;
364 	char buf[MAXPATHLEN];
365 	size_t len;
366 #endif
367 
368 	out = load_personality_with_path(triplet, NULL, false);
369 	if (out != NULL)
370 		return out;
371 
372 	if (!valid_triplet(triplet))
373 		return NULL;
374 
375 #if ! defined(_WIN32) && ! defined(__HAIKU__)
376 	envvar = getenv("XDG_DATA_HOME");
377 	if (envvar != NULL)
378 		pkgconf_path_add(envvar, &plist, true);
379 	else
380 	{
381 		envvar = getenv("HOME");
382 		if (envvar != NULL)
383 		{
384 			pkgconf_buffer_t pathbuf = PKGCONF_BUFFER_INITIALIZER;
385 
386 			pkgconf_buffer_append_fmt(&pathbuf, "%s/.local/share", envvar);
387 			pkgconf_path_add(pkgconf_buffer_str(&pathbuf), &plist, true);
388 			pkgconf_buffer_finalize(&pathbuf);
389 		}
390 	}
391 
392 	pkgconf_path_build_from_environ(NULL, "XDG_DATA_DIRS", "/usr/local/share" PKG_CONFIG_PATH_SEP_S "/usr/share", &plist, true);
393 
394 	PKGCONF_FOREACH_LIST_ENTRY(plist.head, n)
395 	{
396 		pkgconf_path_t *pn = n->data;
397 
398 		out = load_personality_with_path(pn->path, triplet, true);
399 		if (out != NULL)
400 			goto finish;
401 	}
402 	pkgconf_path_free(&plist);
403 #endif
404 
405 #ifdef __FreeBSD__
406 	localbase = getenv("LOCALBASE");
407 
408 	if (localbase == NULL) {
409 		len = sizeof(buf);
410 		if (sysctlbyname("user.localbase", &buf, &len, NULL, 0) < 0)
411 			err(1, "sysctl(user.localbase)");
412 
413 		localbase = buf;
414 	}
415 
416 	pkgconf_path_add("/usr/share/pkgconfig/personality.d", &plist, true);
417 	pkgconf_path_add("/etc/pkgconfig/personality.d", &plist, true);
418 
419 	if (!pkgconf_buffer_append(&path, localbase) ||
420 			!pkgconf_buffer_append(&path,
421 				"/etc/pkgconfig/personality.d"))
422 		err(1, NULL);
423 	pkgconf_path_add(pkgconf_buffer_str(&path), &plist, true);
424 #else
425 	pkgconf_path_split(PERSONALITY_PATH, &plist, true);
426 #endif
427 
428 	PKGCONF_FOREACH_LIST_ENTRY(plist.head, n)
429 	{
430 		pkgconf_path_t *pn = n->data;
431 
432 		out = load_personality_with_path(pn->path, triplet, false);
433 		if (out != NULL)
434 			goto finish;
435 	}
436 
437 finish:
438 	pkgconf_path_free(&plist);
439 	return out != NULL ? out : pkgconf_cross_personality_default();
440 }
441 #endif
442