xref: /freebsd/usr.sbin/pkg/config.c (revision fcb560670601b2a4d87bb31d7531c8dcc37ee71b)
1 /*-
2  * Copyright (c) 2014 Baptiste Daroussin <bapt@FreeBSD.org>
3  * Copyright (c) 2013 Bryan Drewery <bdrewery@FreeBSD.org>
4  * All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25  * SUCH DAMAGE.
26  */
27 
28 #include <sys/cdefs.h>
29 __FBSDID("$FreeBSD$");
30 
31 #include <sys/param.h>
32 #include <sys/queue.h>
33 #include <sys/sbuf.h>
34 #include <sys/types.h>
35 #include <sys/utsname.h>
36 #include <sys/sysctl.h>
37 
38 #include <assert.h>
39 #include <dirent.h>
40 #include <ucl.h>
41 #include <ctype.h>
42 #include <err.h>
43 #include <errno.h>
44 #include <fcntl.h>
45 #include <inttypes.h>
46 #include <paths.h>
47 #include <stdbool.h>
48 #include <string.h>
49 #include <unistd.h>
50 
51 #include "config.h"
52 
53 #define roundup2(x, y)	(((x)+((y)-1))&(~((y)-1))) /* if y is powers of two */
54 
55 struct config_value {
56        char *value;
57        STAILQ_ENTRY(config_value) next;
58 };
59 
60 struct config_entry {
61 	uint8_t type;
62 	const char *key;
63 	const char *val;
64 	char *value;
65 	STAILQ_HEAD(, config_value) *list;
66 	bool envset;
67 	bool main_only;				/* Only set in pkg.conf. */
68 };
69 
70 static struct config_entry c[] = {
71 	[PACKAGESITE] = {
72 		PKG_CONFIG_STRING,
73 		"PACKAGESITE",
74 		URL_SCHEME_PREFIX "http://pkg.FreeBSD.org/${ABI}/latest",
75 		NULL,
76 		NULL,
77 		false,
78 		false,
79 	},
80 	[ABI] = {
81 		PKG_CONFIG_STRING,
82 		"ABI",
83 		NULL,
84 		NULL,
85 		NULL,
86 		false,
87 		true,
88 	},
89 	[MIRROR_TYPE] = {
90 		PKG_CONFIG_STRING,
91 		"MIRROR_TYPE",
92 		"SRV",
93 		NULL,
94 		NULL,
95 		false,
96 		false,
97 	},
98 	[ASSUME_ALWAYS_YES] = {
99 		PKG_CONFIG_BOOL,
100 		"ASSUME_ALWAYS_YES",
101 		"NO",
102 		NULL,
103 		NULL,
104 		false,
105 		true,
106 	},
107 	[SIGNATURE_TYPE] = {
108 		PKG_CONFIG_STRING,
109 		"SIGNATURE_TYPE",
110 		NULL,
111 		NULL,
112 		NULL,
113 		false,
114 		false,
115 	},
116 	[FINGERPRINTS] = {
117 		PKG_CONFIG_STRING,
118 		"FINGERPRINTS",
119 		NULL,
120 		NULL,
121 		NULL,
122 		false,
123 		false,
124 	},
125 	[REPOS_DIR] = {
126 		PKG_CONFIG_LIST,
127 		"REPOS_DIR",
128 		NULL,
129 		NULL,
130 		NULL,
131 		false,
132 		true,
133 	},
134 };
135 
136 static int
137 pkg_get_myabi(char *dest, size_t sz)
138 {
139 	struct utsname uts;
140 	char machine_arch[255];
141 	size_t len;
142 	int error;
143 
144 	error = uname(&uts);
145 	if (error)
146 		return (errno);
147 
148 	len = sizeof(machine_arch);
149 	error = sysctlbyname("hw.machine_arch", machine_arch, &len, NULL, 0);
150 	if (error)
151 		return (errno);
152 	machine_arch[len] = '\0';
153 
154 	/*
155 	 * Use __FreeBSD_version rather than kernel version (uts.release) for
156 	 * use in jails. This is equivalent to the value of uname -U.
157 	 */
158 	snprintf(dest, sz, "%s:%d:%s", uts.sysname, __FreeBSD_version/100000,
159 	    machine_arch);
160 
161 	return (error);
162 }
163 
164 static void
165 subst_packagesite(const char *abi)
166 {
167 	struct sbuf *newval;
168 	const char *variable_string;
169 	const char *oldval;
170 
171 	if (c[PACKAGESITE].value != NULL)
172 		oldval = c[PACKAGESITE].value;
173 	else
174 		oldval = c[PACKAGESITE].val;
175 
176 	if ((variable_string = strstr(oldval, "${ABI}")) == NULL)
177 		return;
178 
179 	newval = sbuf_new_auto();
180 	sbuf_bcat(newval, oldval, variable_string - oldval);
181 	sbuf_cat(newval, abi);
182 	sbuf_cat(newval, variable_string + strlen("${ABI}"));
183 	sbuf_finish(newval);
184 
185 	free(c[PACKAGESITE].value);
186 	c[PACKAGESITE].value = strdup(sbuf_data(newval));
187 }
188 
189 static int
190 boolstr_to_bool(const char *str)
191 {
192 	if (str != NULL && (strcasecmp(str, "true") == 0 ||
193 	    strcasecmp(str, "yes") == 0 || strcasecmp(str, "on") == 0 ||
194 	    str[0] == '1'))
195 		return (true);
196 
197 	return (false);
198 }
199 
200 static void
201 config_parse(const ucl_object_t *obj, pkg_conf_file_t conftype)
202 {
203 	struct sbuf *buf = sbuf_new_auto();
204 	const ucl_object_t *cur, *seq;
205 	ucl_object_iter_t it = NULL, itseq = NULL;
206 	struct config_entry *temp_config;
207 	struct config_value *cv;
208 	const char *key;
209 	int i;
210 	size_t j;
211 
212 	/* Temporary config for configs that may be disabled. */
213 	temp_config = calloc(CONFIG_SIZE, sizeof(struct config_entry));
214 
215 	while ((cur = ucl_iterate_object(obj, &it, true))) {
216 		key = ucl_object_key(cur);
217 		if (key == NULL)
218 			continue;
219 		sbuf_clear(buf);
220 
221 		if (conftype == CONFFILE_PKG) {
222 			for (j = 0; j < strlen(key); ++j)
223 				sbuf_putc(buf, key[j]);
224 			sbuf_finish(buf);
225 		} else if (conftype == CONFFILE_REPO) {
226 			if (strcasecmp(key, "url") == 0)
227 				sbuf_cpy(buf, "PACKAGESITE");
228 			else if (strcasecmp(key, "mirror_type") == 0)
229 				sbuf_cpy(buf, "MIRROR_TYPE");
230 			else if (strcasecmp(key, "signature_type") == 0)
231 				sbuf_cpy(buf, "SIGNATURE_TYPE");
232 			else if (strcasecmp(key, "fingerprints") == 0)
233 				sbuf_cpy(buf, "FINGERPRINTS");
234 			else if (strcasecmp(key, "enabled") == 0) {
235 				if ((cur->type != UCL_BOOLEAN) ||
236 				    !ucl_object_toboolean(cur))
237 					goto cleanup;
238 			} else
239 				continue;
240 			sbuf_finish(buf);
241 		}
242 
243 		for (i = 0; i < CONFIG_SIZE; i++) {
244 			if (strcmp(sbuf_data(buf), c[i].key) == 0)
245 				break;
246 		}
247 
248 		/* Silently skip unknown keys to be future compatible. */
249 		if (i == CONFIG_SIZE)
250 			continue;
251 
252 		/* env has priority over config file */
253 		if (c[i].envset)
254 			continue;
255 
256 		/* Parse sequence value ["item1", "item2"] */
257 		switch (c[i].type) {
258 		case PKG_CONFIG_LIST:
259 			if (cur->type != UCL_ARRAY) {
260 				warnx("Skipping invalid array "
261 				    "value for %s.\n", c[i].key);
262 				continue;
263 			}
264 			temp_config[i].list =
265 			    malloc(sizeof(*temp_config[i].list));
266 			STAILQ_INIT(temp_config[i].list);
267 
268 			while ((seq = ucl_iterate_object(cur, &itseq, true))) {
269 				if (seq->type != UCL_STRING)
270 					continue;
271 				cv = malloc(sizeof(struct config_value));
272 				cv->value =
273 				    strdup(ucl_object_tostring(seq));
274 				STAILQ_INSERT_TAIL(temp_config[i].list, cv,
275 				    next);
276 			}
277 			break;
278 		case PKG_CONFIG_BOOL:
279 			temp_config[i].value =
280 			    strdup(ucl_object_toboolean(cur) ? "yes" : "no");
281 			break;
282 		default:
283 			/* Normal string value. */
284 			temp_config[i].value = strdup(ucl_object_tostring(cur));
285 			break;
286 		}
287 	}
288 
289 	/* Repo is enabled, copy over all settings from temp_config. */
290 	for (i = 0; i < CONFIG_SIZE; i++) {
291 		if (c[i].envset)
292 			continue;
293 		/* Prevent overriding ABI, ASSUME_ALWAYS_YES, etc. */
294 		if (conftype != CONFFILE_PKG && c[i].main_only == true)
295 			continue;
296 		switch (c[i].type) {
297 		case PKG_CONFIG_LIST:
298 			c[i].list = temp_config[i].list;
299 			break;
300 		default:
301 			c[i].value = temp_config[i].value;
302 			break;
303 		}
304 	}
305 
306 cleanup:
307 	free(temp_config);
308 	sbuf_delete(buf);
309 }
310 
311 /*-
312  * Parse new repo style configs in style:
313  * Name:
314  *   URL:
315  *   MIRROR_TYPE:
316  * etc...
317  */
318 static void
319 parse_repo_file(ucl_object_t *obj)
320 {
321 	ucl_object_iter_t it = NULL;
322 	const ucl_object_t *cur;
323 	const char *key;
324 
325 	while ((cur = ucl_iterate_object(obj, &it, true))) {
326 		key = ucl_object_key(cur);
327 
328 		if (key == NULL)
329 			continue;
330 
331 		if (cur->type != UCL_OBJECT)
332 			continue;
333 
334 		config_parse(cur, CONFFILE_REPO);
335 	}
336 }
337 
338 
339 static int
340 read_conf_file(const char *confpath, pkg_conf_file_t conftype)
341 {
342 	struct ucl_parser *p;
343 	ucl_object_t *obj = NULL;
344 
345 	p = ucl_parser_new(0);
346 
347 	if (!ucl_parser_add_file(p, confpath)) {
348 		if (errno != ENOENT)
349 			errx(EXIT_FAILURE, "Unable to parse configuration "
350 			    "file %s: %s", confpath, ucl_parser_get_error(p));
351 		ucl_parser_free(p);
352 		/* no configuration present */
353 		return (1);
354 	}
355 
356 	obj = ucl_parser_get_object(p);
357 	if (obj->type != UCL_OBJECT)
358 		warnx("Invalid configuration format, ignoring the "
359 		    "configuration file %s", confpath);
360 	else {
361 		if (conftype == CONFFILE_PKG)
362 			config_parse(obj, conftype);
363 		else if (conftype == CONFFILE_REPO)
364 			parse_repo_file(obj);
365 	}
366 
367 	ucl_object_unref(obj);
368 	ucl_parser_free(p);
369 
370 	return (0);
371 }
372 
373 static int
374 load_repositories(const char *repodir)
375 {
376 	struct dirent *ent;
377 	DIR *d;
378 	char *p;
379 	size_t n;
380 	char path[MAXPATHLEN];
381 	int ret;
382 
383 	ret = 0;
384 
385 	if ((d = opendir(repodir)) == NULL)
386 		return (1);
387 
388 	while ((ent = readdir(d))) {
389 		/* Trim out 'repos'. */
390 		if ((n = strlen(ent->d_name)) <= 5)
391 			continue;
392 		p = &ent->d_name[n - 5];
393 		if (strcmp(p, ".conf") == 0) {
394 			snprintf(path, sizeof(path), "%s%s%s",
395 			    repodir,
396 			    repodir[strlen(repodir) - 1] == '/' ? "" : "/",
397 			    ent->d_name);
398 			if (access(path, F_OK) == 0 &&
399 			    read_conf_file(path, CONFFILE_REPO)) {
400 				ret = 1;
401 				goto cleanup;
402 			}
403 		}
404 	}
405 
406 cleanup:
407 	closedir(d);
408 
409 	return (ret);
410 }
411 
412 int
413 config_init(void)
414 {
415 	char *val;
416 	int i;
417 	const char *localbase;
418 	char *env_list_item;
419 	char confpath[MAXPATHLEN];
420 	struct config_value *cv;
421 	char abi[BUFSIZ];
422 
423 	for (i = 0; i < CONFIG_SIZE; i++) {
424 		val = getenv(c[i].key);
425 		if (val != NULL) {
426 			c[i].envset = true;
427 			switch (c[i].type) {
428 			case PKG_CONFIG_LIST:
429 				/* Split up comma-separated items from env. */
430 				c[i].list = malloc(sizeof(*c[i].list));
431 				STAILQ_INIT(c[i].list);
432 				for (env_list_item = strtok(val, ",");
433 				    env_list_item != NULL;
434 				    env_list_item = strtok(NULL, ",")) {
435 					cv =
436 					    malloc(sizeof(struct config_value));
437 					cv->value =
438 					    strdup(env_list_item);
439 					STAILQ_INSERT_TAIL(c[i].list, cv,
440 					    next);
441 				}
442 				break;
443 			default:
444 				c[i].val = val;
445 				break;
446 			}
447 		}
448 	}
449 
450 	/* Read LOCALBASE/etc/pkg.conf first. */
451 	localbase = getenv("LOCALBASE") ? getenv("LOCALBASE") : _LOCALBASE;
452 	snprintf(confpath, sizeof(confpath), "%s/etc/pkg.conf",
453 	    localbase);
454 
455 	if (access(confpath, F_OK) == 0 && read_conf_file(confpath,
456 	    CONFFILE_PKG))
457 		goto finalize;
458 
459 	/* Then read in all repos from REPOS_DIR list of directories. */
460 	if (c[REPOS_DIR].list == NULL) {
461 		c[REPOS_DIR].list = malloc(sizeof(*c[REPOS_DIR].list));
462 		STAILQ_INIT(c[REPOS_DIR].list);
463 		cv = malloc(sizeof(struct config_value));
464 		cv->value = strdup("/etc/pkg");
465 		STAILQ_INSERT_TAIL(c[REPOS_DIR].list, cv, next);
466 		cv = malloc(sizeof(struct config_value));
467 		if (asprintf(&cv->value, "%s/etc/pkg/repos", localbase) < 0)
468 			goto finalize;
469 		STAILQ_INSERT_TAIL(c[REPOS_DIR].list, cv, next);
470 	}
471 
472 	STAILQ_FOREACH(cv, c[REPOS_DIR].list, next)
473 		if (load_repositories(cv->value))
474 			goto finalize;
475 
476 finalize:
477 	if (c[ABI].val == NULL && c[ABI].value == NULL) {
478 		if (pkg_get_myabi(abi, BUFSIZ) != 0)
479 			errx(EXIT_FAILURE, "Failed to determine the system "
480 			    "ABI");
481 		c[ABI].val = abi;
482 	}
483 
484 	subst_packagesite(c[ABI].value != NULL ? c[ABI].value : c[ABI].val);
485 
486 	return (0);
487 }
488 
489 int
490 config_string(pkg_config_key k, const char **val)
491 {
492 	if (c[k].type != PKG_CONFIG_STRING)
493 		return (-1);
494 
495 	if (c[k].value != NULL)
496 		*val = c[k].value;
497 	else
498 		*val = c[k].val;
499 
500 	return (0);
501 }
502 
503 int
504 config_bool(pkg_config_key k, bool *val)
505 {
506 	const char *value;
507 
508 	if (c[k].type != PKG_CONFIG_BOOL)
509 		return (-1);
510 
511 	*val = false;
512 
513 	if (c[k].value != NULL)
514 		value = c[k].value;
515 	else
516 		value = c[k].val;
517 
518 	if (boolstr_to_bool(value))
519 		*val = true;
520 
521 	return (0);
522 }
523 
524 void
525 config_finish(void) {
526 	int i;
527 
528 	for (i = 0; i < CONFIG_SIZE; i++)
529 		free(c[i].value);
530 }
531