freebsd-dev/sbin/kldconfig/kldconfig.c
2003-05-03 18:41:59 +00:00

444 lines
11 KiB
C

/*
* Copyright (c) 2001 Peter Pentchev
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include <sys/param.h>
#include <sys/types.h>
#include <sys/queue.h>
#include <sys/sysctl.h>
#include <err.h>
#include <errno.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#if defined(__FreeBSD_version)
#if __FreeBSD_version < 500000
#define NEED_SLASHTERM
#endif /* < 500000 */
#else /* defined(__FreeBSD_version) */
/* just in case.. */
#define NEED_SLASHTERM
#endif /* defined(__FreeBSD_version) */
/* the default sysctl name */
#define PATHCTL "kern.module_path"
/* queue structure for the module path broken down into components */
TAILQ_HEAD(pathhead, pathentry);
struct pathentry {
char *path;
TAILQ_ENTRY(pathentry) next;
};
/* the Management Information Base entries for the search path sysctl */
static int mib[5];
static size_t miblen;
/* the sysctl name, defaults to PATHCTL */
static char *pathctl;
/* the sysctl value - the current module search path */
static char *modpath;
/* flag whether user actions require changing the sysctl value */
static int changed;
/* Top-level path management functions */
static void addpath(struct pathhead *, char *, int, int);
static void rempath(struct pathhead *, char *, int, int);
static void showpath(struct pathhead *);
/* Low-level path management functions */
static char *qstring(struct pathhead *);
/* sysctl-related functions */
static void getmib(void);
static void getpath(void);
static void parsepath(struct pathhead *, char *, int);
static void setpath(struct pathhead *);
static void usage(void);
/* Get the MIB entry for our sysctl */
static void
getmib(void)
{
/* have we already fetched it? */
if (miblen != 0)
return;
miblen = sizeof(mib) / sizeof(mib[0]);
if (sysctlnametomib(pathctl, mib, &miblen) != 0)
err(1, "sysctlnametomib(%s)", pathctl);
}
/* Get the current module search path */
static void
getpath(void)
{
char *path;
size_t sz;
if (modpath != NULL) {
free(modpath);
modpath = NULL;
}
if (miblen == 0)
getmib();
if (sysctl(mib, miblen, NULL, &sz, NULL, NULL) == -1)
err(1, "getting path: sysctl(%s) - size only", pathctl);
if ((path = malloc(sz + 1)) == NULL) {
errno = ENOMEM;
err(1, "allocating %lu bytes for the path",
(unsigned long)sz+1);
}
if (sysctl(mib, miblen, path, &sz, NULL, NULL) == -1)
err(1, "getting path: sysctl(%s)", pathctl);
modpath = path;
}
/* Set the module search path after changing it */
static void
setpath(struct pathhead *pathq)
{
char *newpath;
if (miblen == 0)
getmib();
if ((newpath = qstring(pathq)) == NULL) {
errno = ENOMEM;
err(1, "building path string");
}
if (sysctl(mib, miblen, NULL, NULL, newpath, strlen(newpath)+1) == -1)
err(1, "setting path: sysctl(%s)", pathctl);
if (modpath != NULL)
free(modpath);
modpath = newpath;
}
/* Add/insert a new component to the module search path */
static void
addpath(struct pathhead *pathq, char *path, int force, int insert)
{
struct pathentry *pe, *pskip;
char pathbuf[MAXPATHLEN+1];
size_t len;
static unsigned added = 0;
unsigned i;
/*
* If the path exists, use it; otherwise, take the user-specified
* path at face value - may be a removed directory.
*/
if (realpath(path, pathbuf) == NULL)
strlcpy(pathbuf, path, sizeof(pathbuf));
len = strlen(pathbuf);
#ifdef NEED_SLASHTERM
/* slash-terminate, because the kernel linker said so. */
if ((len == 0) || (pathbuf[len-1] != '/')) {
if (len == sizeof(pathbuf) - 1)
errx(1, "path too long: %s", pathbuf);
pathbuf[len] = '/';
}
#else /* NEED_SLASHTERM */
/* remove a terminating slash if present */
if ((len > 0) && (pathbuf[len-1] == '/'))
pathbuf[--len] = '\0';
#endif /* NEED_SLASHTERM */
/* is it already in there? */
TAILQ_FOREACH(pe, pathq, next)
if (!strcmp(pe->path, pathbuf))
break;
if (pe != NULL) {
if (force)
return;
errx(1, "already in the module search path: %s", pathbuf);
}
/* OK, allocate and add it. */
if (((pe = malloc(sizeof(*pe))) == NULL) ||
((pe->path = strdup(pathbuf)) == NULL)) {
errno = ENOMEM;
err(1, "allocating path component");
}
if (!insert) {
TAILQ_INSERT_TAIL(pathq, pe, next);
} else {
for (i = 0, pskip = TAILQ_FIRST(pathq); i < added; i++)
pskip = TAILQ_NEXT(pskip, next);
if (pskip != NULL)
TAILQ_INSERT_BEFORE(pskip, pe, next);
else
TAILQ_INSERT_TAIL(pathq, pe, next);
added++;
}
changed = 1;
}
/* Remove a path component from the module search path */
static void
rempath(struct pathhead *pathq, char *path, int force, int insert __unused)
{
char pathbuf[MAXPATHLEN+1];
struct pathentry *pe;
size_t len;
/* same logic as in addpath() */
if (realpath(path, pathbuf) == NULL)
strlcpy(pathbuf, path, sizeof(pathbuf));
len = strlen(pathbuf);
#ifdef NEED_SLASHTERM
/* slash-terminate, because the kernel linker said so. */
if ((len == 0) || (pathbuf[len-1] != '/')) {
if (len == sizeof(pathbuf) - 1)
errx(1, "path too long: %s", pathbuf);
pathbuf[len] = '/';
}
#else /* NEED_SLASHTERM */
/* remove a terminating slash if present */
if ((len > 0) && (pathbuf[len-1] == '/'))
pathbuf[--len] = '\0';
#endif /* NEED_SLASHTERM */
/* Is it in there? */
TAILQ_FOREACH(pe, pathq, next)
if (!strcmp(pe->path, pathbuf))
break;
if (pe == NULL) {
if (force)
return;
errx(1, "not in module search path: %s", pathbuf);
}
/* OK, remove it now.. */
TAILQ_REMOVE(pathq, pe, next);
changed = 1;
}
/* Display the retrieved module search path */
static void
showpath(struct pathhead *pathq)
{
char *s;
if ((s = qstring(pathq)) == NULL) {
errno = ENOMEM;
err(1, "building path string");
}
printf("%s\n", s);
free(s);
}
/* Break a string down into path components, store them into a queue */
static void
parsepath(struct pathhead *pathq, char *path, int uniq)
{
char *p;
struct pathentry *pe;
while ((p = strsep(&path, ";")) != NULL)
if (!uniq) {
if (((pe = malloc(sizeof(pe))) == NULL) ||
((pe->path = strdup(p)) == NULL)) {
errno = ENOMEM;
err(1, "allocating path element");
}
TAILQ_INSERT_TAIL(pathq, pe, next);
} else {
addpath(pathq, p, 1, 0);
}
}
/* Recreate a path string from a components queue */
static char *
qstring(struct pathhead *pathq)
{
char *s, *p;
struct pathentry *pe;
s = strdup("");
TAILQ_FOREACH(pe, pathq, next) {
asprintf(&p, "%s%s%s",
s, pe->path, (TAILQ_NEXT(pe, next) != NULL? ";": ""));
free(s);
if (p == NULL)
return (NULL);
s = p;
}
return (s);
}
/* Usage message */
static void
usage(void)
{
fprintf(stderr, "%s\n%s\n",
"usage:\tkldconfig [-dfimnUv] [-S sysctlname] [path..]",
"\tkldconfig -r");
exit(1);
}
/* Main function */
int
main(int argc, char *argv[])
{
/* getopt() iterator */
int c;
/* iterator over argv[] path components */
int i;
/* Command-line flags: */
/* "-f" - no diagnostic messages */
int fflag;
/* "-i" - insert before the first element */
int iflag;
/* "-m" - merge into the existing path, do not replace it */
int mflag;
/* "-n" - do not actually set the new module path */
int nflag;
/* "-r" - print out the current search path */
int rflag;
/* "-U" - remove duplicate values from the path */
int uniqflag;
/* "-v" - verbose operation (currently a no-op) */
int vflag;
/* The higher-level function to call - add/remove */
void (*act)(struct pathhead *, char *, int, int);
/* The original path */
char *origpath;
/* The module search path broken down into components */
struct pathhead pathq;
fflag = iflag = mflag = nflag = rflag = uniqflag = vflag = 0;
act = addpath;
origpath = NULL;
if ((pathctl = strdup(PATHCTL)) == NULL) {
/* this is just too paranoid ;) */
errno = ENOMEM;
err(1, "initializing sysctl name %s", PATHCTL);
}
/* If no arguments and no options are specified, force '-m' */
if (argc == 1)
mflag = 1;
while ((c = getopt(argc, argv, "dfimnrS:Uv")) != -1)
switch (c) {
case 'd':
if (iflag || mflag)
usage();
act = rempath;
break;
case 'f':
fflag = 1;
break;
case 'i':
if (act != addpath)
usage();
iflag = 1;
break;
case 'm':
if (act != addpath)
usage();
mflag = 1;
break;
case 'n':
nflag = 1;
break;
case 'r':
rflag = 1;
break;
case 'S':
free(pathctl);
if ((pathctl = strdup(optarg)) == NULL) {
errno = ENOMEM;
err(1, "sysctl name %s", optarg);
}
break;
case 'U':
uniqflag = 1;
break;
case 'v':
vflag++;
break;
default:
usage();
}
argc -= optind;
argv += optind;
/* The '-r' flag cannot be used when paths are also specified */
if (rflag && (argc > 0))
usage();
TAILQ_INIT(&pathq);
/* Retrieve and store the path from the sysctl value */
getpath();
if ((origpath = strdup(modpath)) == NULL) {
errno = ENOMEM;
err(1, "saving the original search path");
}
/*
* Break down the path into the components queue if:
* - we are NOT adding paths, OR
* - the 'merge' flag is specified, OR
* - the 'print only' flag is specified, OR
* - the 'unique' flag is specified.
*/
if ((act != addpath) || mflag || rflag || uniqflag)
parsepath(&pathq, modpath, uniqflag);
else if (modpath[0] != '\0')
changed = 1;
/* Process the path arguments */
for (i = 0; i < argc; i++)
act(&pathq, argv[i], fflag, iflag);
if (changed && !nflag)
setpath(&pathq);
if (rflag || (changed && vflag)) {
if (changed && (vflag > 1))
printf("%s -> ", origpath);
showpath(&pathq);
}
return (0);
}