kldconfig.c revision 79002
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
28static const char rcsid[] =
29  "$FreeBSD: head/sbin/kldconfig/kldconfig.c 79002 2001-06-29 22:31:17Z roam $";
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 */
58TAILQ_HEAD(pathhead, pathentry);
59struct pathentry {
60	char			*path;
61	TAILQ_ENTRY(pathentry)	next;
62};
63
64/* the Management Information Base entries for the search path sysctl */
65static int	 mib[5];
66static size_t	 miblen;
67/* the sysctl name, defaults to PATHCTL */
68static char	*pathctl;
69/* the sysctl value - the current module search path */
70static char	*modpath;
71/* flag whether user actions require changing the sysctl value */
72static int	 changed;
73
74/* Top-level path management functions */
75static void	 addpath(struct pathhead *, char *, int, int);
76static void	 rempath(struct pathhead *, char *, int, int);
77static void	 showpath(struct pathhead *);
78
79/* Low-level path management functions */
80static char	*qstring(struct pathhead *);
81
82/* sysctl-related functions */
83static void	 getmib(void);
84static void	 getpath(void);
85static void	 parsepath(struct pathhead *, char *, int);
86static void	 setpath(struct pathhead *);
87
88static void	 usage(void);
89
90/* Get the MIB entry for our sysctl */
91static void
92getmib(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 */
105static void
106getpath(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 */
131static void
132setpath(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 */
151static void
152addpath(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 */
212static void
213rempath(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 */
253static void
254showpath(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 */
267static void
268parsepath(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 */
287static char *
288qstring(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 */
307static void
308usage(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 */
318int
319main(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