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