xref: /freebsd/sbin/kldconfig/kldconfig.c (revision 4b9d6057)
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 */
46 TAILQ_HEAD(pathhead, pathentry);
47 struct pathentry {
48 	char			*path;
49 	TAILQ_ENTRY(pathentry)	next;
50 };
51 
52 /* the Management Information Base entries for the search path sysctl */
53 static int	 mib[5];
54 static size_t	 miblen;
55 /* the sysctl name, defaults to PATHCTL */
56 static char	*pathctl;
57 /* the sysctl value - the current module search path */
58 static char	*modpath;
59 /* flag whether user actions require changing the sysctl value */
60 static int	 changed;
61 
62 /* Top-level path management functions */
63 static void	 addpath(struct pathhead *, char *, int, int);
64 static void	 rempath(struct pathhead *, char *, int, int);
65 static void	 showpath(struct pathhead *);
66 
67 /* Low-level path management functions */
68 static char	*qstring(struct pathhead *);
69 
70 /* sysctl-related functions */
71 static void	 getmib(void);
72 static void	 getpath(void);
73 static void	 parsepath(struct pathhead *, char *, int);
74 static void	 setpath(struct pathhead *);
75 
76 static void	 usage(void);
77 
78 /* Get the MIB entry for our sysctl */
79 static void
80 getmib(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 */
93 static void
94 getpath(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 */
119 static void
120 setpath(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 */
139 static void
140 addpath(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 */
191 static void
192 rempath(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 */
223 static void
224 showpath(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 */
237 static void
238 parsepath(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 */
257 static char *
258 qstring(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 */
277 static void
278 usage(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 */
288 int
289 main(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