178977Sroam/*
278977Sroam * Copyright (c) 2001 Peter Pentchev
378977Sroam * All rights reserved.
478977Sroam *
578977Sroam * Redistribution and use in source and binary forms, with or without
678977Sroam * modification, are permitted provided that the following conditions
778977Sroam * are met:
878977Sroam * 1. Redistributions of source code must retain the above copyright
978977Sroam *    notice, this list of conditions and the following disclaimer.
1078977Sroam * 2. Redistributions in binary form must reproduce the above copyright
1178977Sroam *    notice, this list of conditions and the following disclaimer in the
1278977Sroam *    documentation and/or other materials provided with the distribution.
1378977Sroam *
1478977Sroam * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
1578977Sroam * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
1678977Sroam * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
1778977Sroam * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
1878977Sroam * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
1978977Sroam * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
2078977Sroam * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2178977Sroam * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
2278977Sroam * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
2378977Sroam * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
2478977Sroam * SUCH DAMAGE.
2578977Sroam */
2678977Sroam
27114589Sobrien#include <sys/cdefs.h>
28114589Sobrien__FBSDID("$FreeBSD$");
2978977Sroam
3078977Sroam#include <sys/param.h>
3178977Sroam#include <sys/types.h>
3278977Sroam#include <sys/queue.h>
3378977Sroam#include <sys/sysctl.h>
3478977Sroam
3578977Sroam#include <err.h>
3678977Sroam#include <errno.h>
3778977Sroam#include <limits.h>
3878977Sroam#include <stdio.h>
3978977Sroam#include <stdlib.h>
4078977Sroam#include <string.h>
4178977Sroam#include <unistd.h>
4278977Sroam
4378977Sroam#if defined(__FreeBSD_version)
4478977Sroam#if __FreeBSD_version < 500000
4578977Sroam#define NEED_SLASHTERM
4678977Sroam#endif /* < 500000 */
4778977Sroam#else  /* defined(__FreeBSD_version) */
4878977Sroam/* just in case.. */
4978977Sroam#define NEED_SLASHTERM
5078977Sroam#endif /* defined(__FreeBSD_version) */
5178977Sroam
5278977Sroam/* the default sysctl name */
5378977Sroam#define PATHCTL	"kern.module_path"
5478977Sroam
5578977Sroam/* queue structure for the module path broken down into components */
5678977SroamTAILQ_HEAD(pathhead, pathentry);
5778977Sroamstruct pathentry {
5878977Sroam	char			*path;
5978977Sroam	TAILQ_ENTRY(pathentry)	next;
6078977Sroam};
6178977Sroam
6278977Sroam/* the Management Information Base entries for the search path sysctl */
6378977Sroamstatic int	 mib[5];
6478977Sroamstatic size_t	 miblen;
6578977Sroam/* the sysctl name, defaults to PATHCTL */
6678977Sroamstatic char	*pathctl;
6778977Sroam/* the sysctl value - the current module search path */
6878977Sroamstatic char	*modpath;
6978977Sroam/* flag whether user actions require changing the sysctl value */
7078977Sroamstatic int	 changed;
7178977Sroam
7278977Sroam/* Top-level path management functions */
7378977Sroamstatic void	 addpath(struct pathhead *, char *, int, int);
7478977Sroamstatic void	 rempath(struct pathhead *, char *, int, int);
7578977Sroamstatic void	 showpath(struct pathhead *);
7678977Sroam
7778977Sroam/* Low-level path management functions */
7878977Sroamstatic char	*qstring(struct pathhead *);
7978977Sroam
8078977Sroam/* sysctl-related functions */
8178977Sroamstatic void	 getmib(void);
8278977Sroamstatic void	 getpath(void);
8378977Sroamstatic void	 parsepath(struct pathhead *, char *, int);
8478977Sroamstatic void	 setpath(struct pathhead *);
8578977Sroam
8678977Sroamstatic void	 usage(void);
8778977Sroam
8878977Sroam/* Get the MIB entry for our sysctl */
8978977Sroamstatic void
9078977Sroamgetmib(void)
9178977Sroam{
9278977Sroam
9378977Sroam	/* have we already fetched it? */
9478977Sroam	if (miblen != 0)
9578977Sroam		return;
9678977Sroam
9778977Sroam	miblen = sizeof(mib) / sizeof(mib[0]);
9878977Sroam	if (sysctlnametomib(pathctl, mib, &miblen) != 0)
9978977Sroam		err(1, "sysctlnametomib(%s)", pathctl);
10078977Sroam}
10178977Sroam
10278977Sroam/* Get the current module search path */
10378977Sroamstatic void
10478977Sroamgetpath(void)
10578977Sroam{
10678977Sroam	char *path;
10778977Sroam	size_t sz;
10878977Sroam
10978977Sroam	if (modpath != NULL) {
11078977Sroam		free(modpath);
11178977Sroam		modpath = NULL;
11278977Sroam	}
11378977Sroam
11478977Sroam	if (miblen == 0)
11578977Sroam		getmib();
116126643Smarkm	if (sysctl(mib, miblen, NULL, &sz, NULL, 0) == -1)
11778977Sroam		err(1, "getting path: sysctl(%s) - size only", pathctl);
11878977Sroam	if ((path = malloc(sz + 1)) == NULL) {
11978977Sroam		errno = ENOMEM;
12079002Sroam		err(1, "allocating %lu bytes for the path",
12179002Sroam		    (unsigned long)sz+1);
12278977Sroam	}
123126643Smarkm	if (sysctl(mib, miblen, path, &sz, NULL, 0) == -1)
12478977Sroam		err(1, "getting path: sysctl(%s)", pathctl);
12578977Sroam	modpath = path;
12678977Sroam}
12778977Sroam
12878977Sroam/* Set the module search path after changing it */
12978977Sroamstatic void
13078977Sroamsetpath(struct pathhead *pathq)
13178977Sroam{
13278977Sroam	char *newpath;
13378977Sroam
13478977Sroam	if (miblen == 0)
13578977Sroam		getmib();
13678977Sroam	if ((newpath = qstring(pathq)) == NULL) {
13778977Sroam		errno = ENOMEM;
13878977Sroam		err(1, "building path string");
13978977Sroam	}
14078977Sroam	if (sysctl(mib, miblen, NULL, NULL, newpath, strlen(newpath)+1) == -1)
14178977Sroam		err(1, "setting path: sysctl(%s)", pathctl);
14278977Sroam
143113936Sjohan	if (modpath != NULL)
14478977Sroam		free(modpath);
14578977Sroam	modpath = newpath;
14678977Sroam}
14778977Sroam
14878977Sroam/* Add/insert a new component to the module search path */
14978977Sroamstatic void
15078977Sroamaddpath(struct pathhead *pathq, char *path, int force, int insert)
15178977Sroam{
15278977Sroam	struct pathentry *pe, *pskip;
15378977Sroam	char pathbuf[MAXPATHLEN+1];
15478977Sroam	size_t len;
15578977Sroam	static unsigned added = 0;
15678977Sroam	unsigned i;
15778977Sroam
15878977Sroam	/*
15978977Sroam	 * If the path exists, use it; otherwise, take the user-specified
16078977Sroam	 * path at face value - may be a removed directory.
16178977Sroam	 */
16278977Sroam	if (realpath(path, pathbuf) == NULL)
16378977Sroam		strlcpy(pathbuf, path, sizeof(pathbuf));
16478977Sroam
16578977Sroam	len = strlen(pathbuf);
16678977Sroam#ifdef NEED_SLASHTERM
16778977Sroam	/* slash-terminate, because the kernel linker said so. */
16878977Sroam	if ((len == 0) || (pathbuf[len-1] != '/')) {
16978977Sroam		if (len == sizeof(pathbuf) - 1)
17078977Sroam			errx(1, "path too long: %s", pathbuf);
17178977Sroam		pathbuf[len] = '/';
17278977Sroam	}
17378977Sroam#else  /* NEED_SLASHTERM */
17478977Sroam	/* remove a terminating slash if present */
17578977Sroam	if ((len > 0) && (pathbuf[len-1] == '/'))
17678977Sroam		pathbuf[--len] = '\0';
17778977Sroam#endif /* NEED_SLASHTERM */
17878977Sroam
17978977Sroam	/* is it already in there? */
18078977Sroam	TAILQ_FOREACH(pe, pathq, next)
18178977Sroam		if (!strcmp(pe->path, pathbuf))
18278977Sroam			break;
18378977Sroam	if (pe != NULL) {
18478977Sroam		if (force)
18578977Sroam			return;
18678977Sroam		errx(1, "already in the module search path: %s", pathbuf);
18778977Sroam	}
18878977Sroam
18978977Sroam	/* OK, allocate and add it. */
19078977Sroam	if (((pe = malloc(sizeof(*pe))) == NULL) ||
19178977Sroam	    ((pe->path = strdup(pathbuf)) == NULL)) {
19278977Sroam		errno = ENOMEM;
19378977Sroam		err(1, "allocating path component");
19478977Sroam	}
19578977Sroam	if (!insert) {
19678977Sroam		TAILQ_INSERT_TAIL(pathq, pe, next);
19778977Sroam	} else {
19878977Sroam		for (i = 0, pskip = TAILQ_FIRST(pathq); i < added; i++)
19978977Sroam			pskip = TAILQ_NEXT(pskip, next);
20078977Sroam		if (pskip != NULL)
20178977Sroam			TAILQ_INSERT_BEFORE(pskip, pe, next);
20278977Sroam		else
20378977Sroam			TAILQ_INSERT_TAIL(pathq, pe, next);
20478977Sroam		added++;
20578977Sroam	}
20678977Sroam	changed = 1;
20778977Sroam}
20878977Sroam
20978977Sroam/* Remove a path component from the module search path */
21078977Sroamstatic void
21178977Sroamrempath(struct pathhead *pathq, char *path, int force, int insert __unused)
21278977Sroam{
21378977Sroam	char pathbuf[MAXPATHLEN+1];
21478977Sroam	struct pathentry *pe;
21578977Sroam	size_t len;
21678977Sroam
21778977Sroam	/* same logic as in addpath() */
21878977Sroam	if (realpath(path, pathbuf) == NULL)
21978977Sroam		strlcpy(pathbuf, path, sizeof(pathbuf));
22078977Sroam
22178977Sroam	len = strlen(pathbuf);
22278977Sroam#ifdef NEED_SLASHTERM
22378977Sroam	/* slash-terminate, because the kernel linker said so. */
22478977Sroam	if ((len == 0) || (pathbuf[len-1] != '/')) {
22578977Sroam		if (len == sizeof(pathbuf) - 1)
22678977Sroam			errx(1, "path too long: %s", pathbuf);
22778977Sroam		pathbuf[len] = '/';
22878977Sroam	}
22978977Sroam#else  /* NEED_SLASHTERM */
23078977Sroam	/* remove a terminating slash if present */
23178977Sroam	if ((len > 0) && (pathbuf[len-1] == '/'))
23278977Sroam		pathbuf[--len] = '\0';
23378977Sroam#endif /* NEED_SLASHTERM */
23478977Sroam
23578977Sroam	/* Is it in there? */
23678977Sroam	TAILQ_FOREACH(pe, pathq, next)
23778977Sroam		if (!strcmp(pe->path, pathbuf))
23878977Sroam			break;
23978977Sroam	if (pe == NULL) {
24078977Sroam		if (force)
24178977Sroam			return;
24278977Sroam		errx(1, "not in module search path: %s", pathbuf);
24378977Sroam	}
24478977Sroam
24578977Sroam	/* OK, remove it now.. */
24678977Sroam	TAILQ_REMOVE(pathq, pe, next);
24778977Sroam	changed = 1;
24878977Sroam}
24978977Sroam
25078977Sroam/* Display the retrieved module search path */
25178977Sroamstatic void
25278977Sroamshowpath(struct pathhead *pathq)
25378977Sroam{
25478977Sroam	char *s;
25578977Sroam
25678977Sroam	if ((s = qstring(pathq)) == NULL) {
25778977Sroam		errno = ENOMEM;
25878977Sroam		err(1, "building path string");
25978977Sroam	}
26078977Sroam	printf("%s\n", s);
26178977Sroam	free(s);
26278977Sroam}
26378977Sroam
26478977Sroam/* Break a string down into path components, store them into a queue */
26578977Sroamstatic void
26678977Sroamparsepath(struct pathhead *pathq, char *path, int uniq)
26778977Sroam{
26878977Sroam	char *p;
26978977Sroam	struct pathentry *pe;
27078977Sroam
27178977Sroam	while ((p = strsep(&path, ";")) != NULL)
27278977Sroam		if (!uniq) {
273152169Sru			if (((pe = malloc(sizeof(*pe))) == NULL) ||
27478977Sroam			    ((pe->path = strdup(p)) == NULL)) {
27578977Sroam				errno = ENOMEM;
27678977Sroam				err(1, "allocating path element");
27778977Sroam			}
27878977Sroam			TAILQ_INSERT_TAIL(pathq, pe, next);
27978977Sroam		} else {
28078977Sroam			addpath(pathq, p, 1, 0);
28178977Sroam		}
28278977Sroam}
28378977Sroam
28478977Sroam/* Recreate a path string from a components queue */
28578977Sroamstatic char *
28678977Sroamqstring(struct pathhead *pathq)
28778977Sroam{
28878977Sroam	char *s, *p;
28978977Sroam	struct pathentry *pe;
29078977Sroam
29178977Sroam	s = strdup("");
29278977Sroam	TAILQ_FOREACH(pe, pathq, next) {
29378977Sroam		asprintf(&p, "%s%s%s",
29478977Sroam		    s, pe->path, (TAILQ_NEXT(pe, next) != NULL? ";": ""));
29578977Sroam		free(s);
29678977Sroam		if (p == NULL)
29778977Sroam			return (NULL);
29878977Sroam		s = p;
29978977Sroam	}
30078977Sroam
30178977Sroam	return (s);
30278977Sroam}
30378977Sroam
30478977Sroam/* Usage message */
30578977Sroamstatic void
30678977Sroamusage(void)
30778977Sroam{
30878977Sroam
30978977Sroam	fprintf(stderr, "%s\n%s\n",
310141611Sru	    "usage:\tkldconfig [-dfimnUv] [-S sysctlname] [path ...]",
31178977Sroam	    "\tkldconfig -r");
31278977Sroam	exit(1);
31378977Sroam}
31478977Sroam
31578977Sroam/* Main function */
31678977Sroamint
31778977Sroammain(int argc, char *argv[])
31878977Sroam{
31978977Sroam	/* getopt() iterator */
32078977Sroam	int c;
32178977Sroam	/* iterator over argv[] path components */
32278977Sroam	int i;
32378977Sroam	/* Command-line flags: */
32478977Sroam	/* "-f" - no diagnostic messages */
32578977Sroam	int fflag;
32678977Sroam	/* "-i" - insert before the first element */
32778977Sroam	int iflag;
32878977Sroam	/* "-m" - merge into the existing path, do not replace it */
32978977Sroam	int mflag;
33078977Sroam	/* "-n" - do not actually set the new module path */
33178977Sroam	int nflag;
33278977Sroam	/* "-r" - print out the current search path */
33378977Sroam	int rflag;
33478977Sroam	/* "-U" - remove duplicate values from the path */
33578977Sroam	int uniqflag;
33678977Sroam	/* "-v" - verbose operation (currently a no-op) */
33778977Sroam	int vflag;
33878977Sroam	/* The higher-level function to call - add/remove */
33978977Sroam	void (*act)(struct pathhead *, char *, int, int);
34078977Sroam	/* The original path */
34178977Sroam	char *origpath;
34278977Sroam	/* The module search path broken down into components */
34378977Sroam	struct pathhead pathq;
34478977Sroam
34578977Sroam	fflag = iflag = mflag = nflag = rflag = uniqflag = vflag = 0;
34678977Sroam	act = addpath;
34778977Sroam	origpath = NULL;
34878977Sroam	if ((pathctl = strdup(PATHCTL)) == NULL) {
34978977Sroam		/* this is just too paranoid ;) */
35078977Sroam		errno = ENOMEM;
35178977Sroam		err(1, "initializing sysctl name %s", PATHCTL);
35278977Sroam	}
35378977Sroam
35478977Sroam	/* If no arguments and no options are specified, force '-m' */
35578977Sroam	if (argc == 1)
35678977Sroam		mflag = 1;
35778977Sroam
35878977Sroam	while ((c = getopt(argc, argv, "dfimnrS:Uv")) != -1)
35978977Sroam		switch (c) {
36078977Sroam			case 'd':
36178977Sroam				if (iflag || mflag)
36278977Sroam					usage();
36378977Sroam				act = rempath;
36478977Sroam				break;
36578977Sroam			case 'f':
36678977Sroam				fflag = 1;
36778977Sroam				break;
36878977Sroam			case 'i':
36978977Sroam				if (act != addpath)
37078977Sroam					usage();
37178977Sroam				iflag = 1;
37278977Sroam				break;
37378977Sroam			case 'm':
37478977Sroam				if (act != addpath)
37578977Sroam					usage();
37678977Sroam				mflag = 1;
37778977Sroam				break;
37878977Sroam			case 'n':
37978977Sroam				nflag = 1;
38078977Sroam				break;
38178977Sroam			case 'r':
38278977Sroam				rflag = 1;
38378977Sroam				break;
38478977Sroam			case 'S':
38578977Sroam				free(pathctl);
38678977Sroam				if ((pathctl = strdup(optarg)) == NULL) {
38778977Sroam					errno = ENOMEM;
38878977Sroam					err(1, "sysctl name %s", optarg);
38978977Sroam				}
39078977Sroam				break;
39178977Sroam			case 'U':
39278977Sroam				uniqflag = 1;
39378977Sroam				break;
39478977Sroam			case 'v':
39578977Sroam				vflag++;
39678977Sroam				break;
39778977Sroam			default:
39878977Sroam				usage();
39978977Sroam		}
40078977Sroam
40178977Sroam	argc -= optind;
40278977Sroam	argv += optind;
40378977Sroam
40478977Sroam	/* The '-r' flag cannot be used when paths are also specified */
40578977Sroam	if (rflag && (argc > 0))
40678977Sroam		usage();
40778977Sroam
40878977Sroam	TAILQ_INIT(&pathq);
40978977Sroam
41078977Sroam	/* Retrieve and store the path from the sysctl value */
41178977Sroam	getpath();
41278977Sroam	if ((origpath = strdup(modpath)) == NULL) {
41378977Sroam		errno = ENOMEM;
41478977Sroam		err(1, "saving the original search path");
41578977Sroam	}
41678977Sroam
41778977Sroam	/*
41878977Sroam	 * Break down the path into the components queue if:
41978977Sroam	 * - we are NOT adding paths, OR
42078977Sroam	 * - the 'merge' flag is specified, OR
42178977Sroam	 * - the 'print only' flag is specified, OR
42278977Sroam	 * - the 'unique' flag is specified.
42378977Sroam	 */
42478977Sroam	if ((act != addpath) || mflag || rflag || uniqflag)
42578977Sroam		parsepath(&pathq, modpath, uniqflag);
42678977Sroam	else if (modpath[0] != '\0')
42778977Sroam		changed = 1;
42878977Sroam
42978977Sroam	/* Process the path arguments */
43078977Sroam	for (i = 0; i < argc; i++)
43178977Sroam		act(&pathq, argv[i], fflag, iflag);
43278977Sroam
43378977Sroam	if (changed && !nflag)
43478977Sroam		setpath(&pathq);
43578977Sroam
43678977Sroam	if (rflag || (changed && vflag)) {
43778977Sroam		if (changed && (vflag > 1))
43878977Sroam			printf("%s -> ", origpath);
43978977Sroam		showpath(&pathq);
44078977Sroam	}
44178977Sroam
44278977Sroam	return (0);
44378977Sroam}
444