1/*-
2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3 *
4 * Copyright (c) 2001 Peter Pentchev
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 *    notice, this list of conditions and the following disclaimer in the
14 *    documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26 * SUCH DAMAGE.
27 */
28
29#include <sys/cdefs.h>
30__FBSDID("$FreeBSD$");
31
32#include <sys/param.h>
33#include <sys/types.h>
34#include <sys/queue.h>
35#include <sys/sysctl.h>
36
37#include <err.h>
38#include <errno.h>
39#include <limits.h>
40#include <stdio.h>
41#include <stdlib.h>
42#include <string.h>
43#include <unistd.h>
44
45/* the default sysctl name */
46#define PATHCTL	"kern.module_path"
47
48/* queue structure for the module path broken down into components */
49TAILQ_HEAD(pathhead, pathentry);
50struct pathentry {
51	char			*path;
52	TAILQ_ENTRY(pathentry)	next;
53};
54
55/* the Management Information Base entries for the search path sysctl */
56static int	 mib[5];
57static size_t	 miblen;
58/* the sysctl name, defaults to PATHCTL */
59static char	*pathctl;
60/* the sysctl value - the current module search path */
61static char	*modpath;
62/* flag whether user actions require changing the sysctl value */
63static int	 changed;
64
65/* Top-level path management functions */
66static void	 addpath(struct pathhead *, char *, int, int);
67static void	 rempath(struct pathhead *, char *, int, int);
68static void	 showpath(struct pathhead *);
69
70/* Low-level path management functions */
71static char	*qstring(struct pathhead *);
72
73/* sysctl-related functions */
74static void	 getmib(void);
75static void	 getpath(void);
76static void	 parsepath(struct pathhead *, char *, int);
77static void	 setpath(struct pathhead *);
78
79static void	 usage(void);
80
81/* Get the MIB entry for our sysctl */
82static void
83getmib(void)
84{
85
86	/* have we already fetched it? */
87	if (miblen != 0)
88		return;
89
90	miblen = nitems(mib);
91	if (sysctlnametomib(pathctl, mib, &miblen) != 0)
92		err(1, "sysctlnametomib(%s)", pathctl);
93}
94
95/* Get the current module search path */
96static void
97getpath(void)
98{
99	char *path;
100	size_t sz;
101
102	if (modpath != NULL) {
103		free(modpath);
104		modpath = NULL;
105	}
106
107	if (miblen == 0)
108		getmib();
109	if (sysctl(mib, miblen, NULL, &sz, NULL, 0) == -1)
110		err(1, "getting path: sysctl(%s) - size only", pathctl);
111	if ((path = malloc(sz + 1)) == NULL) {
112		errno = ENOMEM;
113		err(1, "allocating %lu bytes for the path",
114		    (unsigned long)sz+1);
115	}
116	if (sysctl(mib, miblen, path, &sz, NULL, 0) == -1)
117		err(1, "getting path: sysctl(%s)", pathctl);
118	modpath = path;
119}
120
121/* Set the module search path after changing it */
122static void
123setpath(struct pathhead *pathq)
124{
125	char *newpath;
126
127	if (miblen == 0)
128		getmib();
129	if ((newpath = qstring(pathq)) == NULL) {
130		errno = ENOMEM;
131		err(1, "building path string");
132	}
133	if (sysctl(mib, miblen, NULL, NULL, newpath, strlen(newpath)+1) == -1)
134		err(1, "setting path: sysctl(%s)", pathctl);
135
136	if (modpath != NULL)
137		free(modpath);
138	modpath = newpath;
139}
140
141/* Add/insert a new component to the module search path */
142static void
143addpath(struct pathhead *pathq, char *path, int force, int insert)
144{
145	struct pathentry *pe, *pskip;
146	char pathbuf[MAXPATHLEN+1];
147	size_t len;
148	static unsigned added = 0;
149	unsigned i;
150
151	/*
152	 * If the path exists, use it; otherwise, take the user-specified
153	 * path at face value - may be a removed directory.
154	 */
155	if (realpath(path, pathbuf) == NULL)
156		strlcpy(pathbuf, path, sizeof(pathbuf));
157
158	len = strlen(pathbuf);
159	/* remove a terminating slash if present */
160	if ((len > 0) && (pathbuf[len-1] == '/'))
161		pathbuf[--len] = '\0';
162
163	/* is it already in there? */
164	TAILQ_FOREACH(pe, pathq, next)
165		if (!strcmp(pe->path, pathbuf))
166			break;
167	if (pe != NULL) {
168		if (force)
169			return;
170		errx(1, "already in the module search path: %s", pathbuf);
171	}
172
173	/* OK, allocate and add it. */
174	if (((pe = malloc(sizeof(*pe))) == NULL) ||
175	    ((pe->path = strdup(pathbuf)) == NULL)) {
176		errno = ENOMEM;
177		err(1, "allocating path component");
178	}
179	if (!insert) {
180		TAILQ_INSERT_TAIL(pathq, pe, next);
181	} else {
182		for (i = 0, pskip = TAILQ_FIRST(pathq); i < added; i++)
183			pskip = TAILQ_NEXT(pskip, next);
184		if (pskip != NULL)
185			TAILQ_INSERT_BEFORE(pskip, pe, next);
186		else
187			TAILQ_INSERT_TAIL(pathq, pe, next);
188		added++;
189	}
190	changed = 1;
191}
192
193/* Remove a path component from the module search path */
194static void
195rempath(struct pathhead *pathq, char *path, int force, int insert __unused)
196{
197	char pathbuf[MAXPATHLEN+1];
198	struct pathentry *pe;
199	size_t len;
200
201	/* same logic as in addpath() */
202	if (realpath(path, pathbuf) == NULL)
203		strlcpy(pathbuf, path, sizeof(pathbuf));
204
205	len = strlen(pathbuf);
206	/* remove a terminating slash if present */
207	if ((len > 0) && (pathbuf[len-1] == '/'))
208		pathbuf[--len] = '\0';
209
210	/* Is it in there? */
211	TAILQ_FOREACH(pe, pathq, next)
212		if (!strcmp(pe->path, pathbuf))
213			break;
214	if (pe == NULL) {
215		if (force)
216			return;
217		errx(1, "not in module search path: %s", pathbuf);
218	}
219
220	/* OK, remove it now.. */
221	TAILQ_REMOVE(pathq, pe, next);
222	changed = 1;
223}
224
225/* Display the retrieved module search path */
226static void
227showpath(struct pathhead *pathq)
228{
229	char *s;
230
231	if ((s = qstring(pathq)) == NULL) {
232		errno = ENOMEM;
233		err(1, "building path string");
234	}
235	printf("%s\n", s);
236	free(s);
237}
238
239/* Break a string down into path components, store them into a queue */
240static void
241parsepath(struct pathhead *pathq, char *path, int uniq)
242{
243	char *p;
244	struct pathentry *pe;
245
246	while ((p = strsep(&path, ";")) != NULL)
247		if (!uniq) {
248			if (((pe = malloc(sizeof(*pe))) == NULL) ||
249			    ((pe->path = strdup(p)) == NULL)) {
250				errno = ENOMEM;
251				err(1, "allocating path element");
252			}
253			TAILQ_INSERT_TAIL(pathq, pe, next);
254		} else {
255			addpath(pathq, p, 1, 0);
256		}
257}
258
259/* Recreate a path string from a components queue */
260static char *
261qstring(struct pathhead *pathq)
262{
263	char *s, *p;
264	struct pathentry *pe;
265
266	s = strdup("");
267	TAILQ_FOREACH(pe, pathq, next) {
268		asprintf(&p, "%s%s%s",
269		    s, pe->path, (TAILQ_NEXT(pe, next) != NULL? ";": ""));
270		free(s);
271		if (p == NULL)
272			return (NULL);
273		s = p;
274	}
275
276	return (s);
277}
278
279/* Usage message */
280static void
281usage(void)
282{
283
284	fprintf(stderr, "%s\n%s\n",
285	    "usage:\tkldconfig [-dfimnUv] [-S sysctlname] [path ...]",
286	    "\tkldconfig -r");
287	exit(1);
288}
289
290/* Main function */
291int
292main(int argc, char *argv[])
293{
294	/* getopt() iterator */
295	int c;
296	/* iterator over argv[] path components */
297	int i;
298	/* Command-line flags: */
299	/* "-f" - no diagnostic messages */
300	int fflag;
301	/* "-i" - insert before the first element */
302	int iflag;
303	/* "-m" - merge into the existing path, do not replace it */
304	int mflag;
305	/* "-n" - do not actually set the new module path */
306	int nflag;
307	/* "-r" - print out the current search path */
308	int rflag;
309	/* "-U" - remove duplicate values from the path */
310	int uniqflag;
311	/* "-v" - verbose operation (currently a no-op) */
312	int vflag;
313	/* The higher-level function to call - add/remove */
314	void (*act)(struct pathhead *, char *, int, int);
315	/* The original path */
316	char *origpath;
317	/* The module search path broken down into components */
318	struct pathhead pathq;
319
320	fflag = iflag = mflag = nflag = rflag = uniqflag = vflag = 0;
321	act = addpath;
322	origpath = NULL;
323	if ((pathctl = strdup(PATHCTL)) == NULL) {
324		/* this is just too paranoid ;) */
325		errno = ENOMEM;
326		err(1, "initializing sysctl name %s", PATHCTL);
327	}
328
329	/* If no arguments and no options are specified, force '-m' */
330	if (argc == 1)
331		mflag = 1;
332
333	while ((c = getopt(argc, argv, "dfimnrS:Uv")) != -1)
334		switch (c) {
335			case 'd':
336				if (iflag || mflag)
337					usage();
338				act = rempath;
339				break;
340			case 'f':
341				fflag = 1;
342				break;
343			case 'i':
344				if (act != addpath)
345					usage();
346				iflag = 1;
347				break;
348			case 'm':
349				if (act != addpath)
350					usage();
351				mflag = 1;
352				break;
353			case 'n':
354				nflag = 1;
355				break;
356			case 'r':
357				rflag = 1;
358				break;
359			case 'S':
360				free(pathctl);
361				if ((pathctl = strdup(optarg)) == NULL) {
362					errno = ENOMEM;
363					err(1, "sysctl name %s", optarg);
364				}
365				break;
366			case 'U':
367				uniqflag = 1;
368				break;
369			case 'v':
370				vflag++;
371				break;
372			default:
373				usage();
374		}
375
376	argc -= optind;
377	argv += optind;
378
379	/* The '-r' flag cannot be used when paths are also specified */
380	if (rflag && (argc > 0))
381		usage();
382
383	TAILQ_INIT(&pathq);
384
385	/* Retrieve and store the path from the sysctl value */
386	getpath();
387	if ((origpath = strdup(modpath)) == NULL) {
388		errno = ENOMEM;
389		err(1, "saving the original search path");
390	}
391
392	/*
393	 * Break down the path into the components queue if:
394	 * - we are NOT adding paths, OR
395	 * - the 'merge' flag is specified, OR
396	 * - the 'print only' flag is specified, OR
397	 * - the 'unique' flag is specified.
398	 */
399	if ((act != addpath) || mflag || rflag || uniqflag)
400		parsepath(&pathq, modpath, uniqflag);
401	else if (modpath[0] != '\0')
402		changed = 1;
403
404	/* Process the path arguments */
405	for (i = 0; i < argc; i++)
406		act(&pathq, argv[i], fflag, iflag);
407
408	if (changed && !nflag)
409		setpath(&pathq);
410
411	if (rflag || (changed && vflag)) {
412		if (changed && (vflag > 1))
413			printf("%s -> ", origpath);
414		showpath(&pathq);
415	}
416
417	return (0);
418}
419