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