NetBSD/sbin/modload/modload.c
mrg 72a64c5437 add ELF support, mostly from joda@pdc.kth.se (Johan Danielsson) in PR#6149,
updated for the symtab support for a.out (non for ELF yet) and other cleanup
by myself.  should work on the alpha as well as other ELF ports...
remove the ld(1) output by default, rather than leaving little binary turds
across the filesystem...
1999-06-13 12:54:40 +00:00

456 lines
10 KiB
C

/* $NetBSD: modload.c,v 1.25 1999/06/13 12:54:40 mrg Exp $ */
/*
* Copyright (c) 1993 Terrence R. Lambert.
* 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.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by Terrence R. Lambert.
* 4. The name Terrence R. Lambert may not be used to endorse or promote
* products derived from this software without specific prior written
* permission.
*
* THIS SOFTWARE IS PROVIDED BY TERRENCE R. LAMBERT ``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 TERRENCE R. LAMBERT 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>
#ifndef lint
__RCSID("$NetBSD: modload.c,v 1.25 1999/06/13 12:54:40 mrg Exp $");
#endif /* not lint */
#include <sys/param.h>
#include <sys/ioctl.h>
#include <sys/conf.h>
#include <sys/mount.h>
#include <sys/lkm.h>
#include <sys/stat.h>
#include <sys/file.h>
#include <err.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <nlist.h>
#include "pathnames.h"
#define TRUE 1
#define FALSE 0
#include "modload.h"
#ifndef DFLT_ENTRY
#define DFLT_ENTRY "xxxinit"
#endif /* !DFLT_ENTRY */
#ifndef DFLT_ENTRYEXT
#define DFLT_ENTRYEXT "_lkmentry"
#endif /* !DFLT_ENTRYEXT */
int debug = 0;
int verbose = 0;
char *out = NULL;
int symtab = 0;
int Sflag;
static void cleanup __P((void));
/* prelink the module */
static int
prelink(const char *kernel,
const char *entry,
const char *outfile,
const void *address,
const char *object)
{
char cmdbuf[1024];
int error = 0;
linkcmd(cmdbuf, sizeof(cmdbuf),
kernel, entry, outfile, address, object);
if (debug)
fprintf(stderr, "%s\n", cmdbuf);
switch (system(cmdbuf)) {
case 0: /* SUCCESS! */
break;
case 1: /* uninformitive error */
/*
* Someone needs to fix the return values from the NetBSD
* ld program -- it's totally uninformative.
*
* No such file (4 on SunOS)
* Can't write output (2 on SunOS)
* Undefined symbol (1 on SunOS)
* etc.
*/
case 127: /* can't load shell */
case 32512:
default:
error = 1;
break;
}
return error;
}
static void
usage(void)
{
fprintf(stderr, "usage:\n");
fprintf(stderr, "modload [-d] [-v] [-n] [-A <kernel>] [-e <entry>]\n");
fprintf(stderr,
" [-p <postinstall>] [-o <output file>] <input file>\n");
exit(1);
}
int fileopen = 0;
#define DEV_OPEN 0x01
#define MOD_OPEN 0x02
#define PART_RESRV 0x04
#define OUTFILE_CREAT 0x08
int devfd, modfd;
struct lmc_resrv resrv;
static void
cleanup(void)
{
if (fileopen & PART_RESRV) {
/*
* Free up kernel memory
*/
if (ioctl(devfd, LMUNRESRV, 0) == -1)
warn("can't release slot 0x%08x memory", resrv.slot);
}
if (fileopen & DEV_OPEN)
close(devfd);
if (fileopen & MOD_OPEN)
close(modfd);
if (fileopen & OUTFILE_CREAT)
unlink(out);
}
static int
verify_entry(char *entry, char *filename)
{
struct nlist names[2];
int n;
char *s;
memset(names, 0, sizeof(names));
s = malloc(strlen(entry) + 2);
s[0] = '_';
strcpy(s + 1, entry);
#ifdef _AOUT_INCLUDE_
names[0].n_un.n_name = s;
#else
names[0].n_name = s;
#endif
n = nlist(filename, names);
if (n == -1)
err(1, "nlist %s", filename);
return n;
}
/*
* Transfer data to kernel memory in chunks
* of MODIOBUF size at a time.
*/
void
loadbuf(void *buf, size_t len)
{
struct lmc_loadbuf ldbuf;
size_t n;
char *p = buf;
while(len) {
n = MIN(len, MODIOBUF);
ldbuf.cnt = n;
ldbuf.data = p;
if(ioctl(devfd, LMLOADBUF, &ldbuf) == -1)
err(11, "error loading buffer");
len -= n;
p += n;
}
}
/* Transfer some empty space. */
void
loadspace(size_t len)
{
char buf[MODIOBUF];
size_t n;
memset(buf, 0, sizeof(buf));
while(len) {
n = MIN(len, sizeof(buf));
loadbuf(buf, n);
len -= n;
}
}
int
main(int argc, char **argv)
{
int c;
char *kname = _PATH_UNIX;
char *entry = DFLT_ENTRY;
char *post = NULL;
char *modobj;
char modout[80], *p;
struct stat stb;
int strtablen;
size_t modsize; /* XXX */
void* modentry; /* XXX */
int noready = 0, old = 0;
while ((c = getopt(argc, argv, "dnvsAS:e:p:o:")) != -1) {
switch (c) {
case 'd':
debug = 1;
break; /* debug */
case 'v':
verbose = 1;
break; /* verbose */
case 'A':
kname = optarg;
break; /* kernel */
case 'e':
entry = optarg;
break; /* entry point */
case 'p':
post = optarg;
break; /* postinstall */
case 'o':
out = optarg;
break; /* output file */
case 'n':
noready = 1;
break;
case 's':
symtab = 1;
break;
case 'S':
Sflag = 1;
break;
case '?':
usage();
/* NOTREACHED */
default:
printf("default!\n");
break;
}
}
argc -= optind;
argv += optind;
if (argc != 1)
usage();
modobj = argv[0];
atexit(cleanup);
/*
* Open the virtual device device driver for exclusive use (needed
* to write the new module to it as our means of getting it in the
* kernel).
*/
if ((devfd = open(_PATH_LKM, O_RDWR, 0)) == -1)
err(3, _PATH_LKM);
fileopen |= DEV_OPEN;
strncpy(modout, modobj, sizeof(modout) - 1);
modout[sizeof(modout) - 1] = '\0';
p = strrchr(modout, '.');
if (!p || strcmp(p, ".o"))
errx(2, "module object must end in .o");
*p = '\0';
if (out == NULL)
out = modout;
/*
* Verify that the entry point for the module exists.
*/
if (verify_entry(entry, modobj)) {
/*
* Try <modobj>_init if entry is DFLT_ENTRY.
*/
if (entry == DFLT_ENTRY) {
if ((p = strrchr(modout, '/')))
p++;
else
p = modout;
entry = malloc(strlen(p) +
strlen(DFLT_ENTRYEXT) + 1);
strcpy(entry, p);
strcat(entry, DFLT_ENTRYEXT);
if (verify_entry(entry, modobj))
errx(1, "entry point _%s not found in %s",
entry, modobj);
} else
errx(1, "entry point _%s not found in %s", entry,
modobj);
}
/*
* Prelink to get file size
*/
if (prelink(kname, entry, out, 0, modobj))
errx(1, "can't prelink `%s' creating `%s'", modobj, out);
if (Sflag == 0)
fileopen |= OUTFILE_CREAT;
/*
* Pre-open the 0-linked module to get the size information
*/
if ((modfd = open(out, O_RDONLY, 0)) == -1)
err(4, "%s", out);
fileopen |= MOD_OPEN;
/*
* stat for filesize to figure out string table size
*/
if (fstat(modfd, &stb) == -1)
err(3, "fstat `%s'", out);
/*
* work out various sizes and fill in resrv bits
*/
if (mod_sizes(modfd, &modsize, &strtablen, &resrv, &stb) != 0)
err(1, "can't get module sizes");
/*
* Close the dummy module -- we have our sizing information.
*/
close(modfd);
fileopen &= ~MOD_OPEN;
/*
* Reserve the required amount of kernel memory -- this may fail
* to be successful.
*/
resrv.size = modsize; /* size in bytes */
resrv.name = modout; /* objname w/o ".o" */
resrv.slot = -1; /* returned */
resrv.addr = 0; /* returned */
if (verbose)
warnx("reserving %lu bytes of memory", (unsigned long)modsize);
if (ioctl(devfd, LMRESERV, &resrv) == -1) {
if (symtab)
warn("not loading symbols: kernel does not support symbol table loading");
doold:
symtab = 0;
if (ioctl(devfd, LMRESERV_O, &resrv) == -1)
err(9, "can't reserve memory");
old = TRUE;
}
fileopen |= PART_RESRV;
/*
* Relink at kernel load address
*/
if (prelink(kname, entry, out, (void*)resrv.addr, modobj))
errx(1, "can't link `%s' creating `%s' bound to %p",
modobj, out, (void*)resrv.addr);
/*
* Open the relinked module to load it...
*/
if ((modfd = open(out, O_RDONLY, 0)) == -1)
err(4, "%s", out);
fileopen |= MOD_OPEN;
modentry = mod_load(modfd);
if (debug)
fprintf(stderr, "modentry = %p\n", modentry);
if (symtab)
mod_symload(strtablen);
/*
* Save ourselves before disaster (potentitally) strikes...
*/
sync();
if (noready)
return 0;
/*
* Trigger the module as loaded by calling the entry procedure;
* this will do all necessary table fixup to ensure that state
* is maintained on success, or blow everything back to ground
* zero on failure.
*/
if (ioctl(devfd, LMREADY, &modentry) == -1) {
if (errno == EINVAL && !old) {
if (fileopen & MOD_OPEN)
close(modfd);
/* PART_RESRV is not true since the kernel cleans up
after a failed LMREADY */
fileopen &= ~(MOD_OPEN|PART_RESRV);
/* try using oldstyle */
warn("module failed to load using new version; trying old version");
goto doold;
} else
err(14, "error initializing module");
}
/*
* Success!
*/
fileopen &= ~PART_RESRV; /* loaded */
printf("Module loaded as ID %d\n", resrv.slot);
/*
* Execute the post-install program, if specified.
*/
if (post) {
struct lmc_stat sbuf;
char id[16], type[16], offset[16];
sbuf.id = resrv.slot;
if (ioctl(devfd, LMSTAT, &sbuf) == -1)
err(15, "error fetching module stats for post-install");
(void)snprintf(id, sizeof(id), "%d", sbuf.id);
(void)snprintf(type, sizeof(type), "0x%x", sbuf.type);
(void)snprintf(offset, sizeof(offset), "%ld",
(long)sbuf.offset);
/*
* XXX
* The modload docs say that drivers can install bdevsw &
* cdevsw, but the interface only supports one at a time.
*/
execl(post, post, id, type, offset, 0);
err(16, "can't exec `%s'", post);
}
exit (0);
}