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