xref: /freebsd/sbin/kldconfig/kldconfig.c (revision 7ef62cebc2f965b0f640263e179276928885e33d)
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/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 */
49 TAILQ_HEAD(pathhead, pathentry);
50 struct pathentry {
51 	char			*path;
52 	TAILQ_ENTRY(pathentry)	next;
53 };
54 
55 /* the Management Information Base entries for the search path sysctl */
56 static int	 mib[5];
57 static size_t	 miblen;
58 /* the sysctl name, defaults to PATHCTL */
59 static char	*pathctl;
60 /* the sysctl value - the current module search path */
61 static char	*modpath;
62 /* flag whether user actions require changing the sysctl value */
63 static int	 changed;
64 
65 /* Top-level path management functions */
66 static void	 addpath(struct pathhead *, char *, int, int);
67 static void	 rempath(struct pathhead *, char *, int, int);
68 static void	 showpath(struct pathhead *);
69 
70 /* Low-level path management functions */
71 static char	*qstring(struct pathhead *);
72 
73 /* sysctl-related functions */
74 static void	 getmib(void);
75 static void	 getpath(void);
76 static void	 parsepath(struct pathhead *, char *, int);
77 static void	 setpath(struct pathhead *);
78 
79 static void	 usage(void);
80 
81 /* Get the MIB entry for our sysctl */
82 static void
83 getmib(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 */
96 static void
97 getpath(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 */
122 static void
123 setpath(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 */
142 static void
143 addpath(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 */
194 static void
195 rempath(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 */
226 static void
227 showpath(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 */
240 static void
241 parsepath(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 */
260 static char *
261 qstring(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 */
280 static void
281 usage(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 */
291 int
292 main(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