Initial import from FreeBSD RELENG_4:
[dragonfly.git] / sbin / kldconfig / kldconfig.c
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: src/sbin/kldconfig/kldconfig.c,v 1.3.2.1 2001/08/01 05:52:36 obrien Exp $";
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 }