#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <strings.h>
#include <unistd.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/modctl.h>
#include <zone.h>
#include <spawn.h>
#include <err.h>
#include <stdbool.h>
static void
usage(void)
{
(void) fprintf(stderr,
"usage: modunload [-e <exec_file>] -i <id> | <name>\n");
exit(EXIT_FAILURE);
}
static void
exec_userfile(char *execfile, const struct modinfo *modinfo, char **envp)
{
char modid[8], mod0[8];
char *child_args[] = {execfile, modid, mod0, NULL};
(void) snprintf(modid, sizeof (modid), "%d", modinfo->mi_id);
(void) snprintf(mod0, sizeof (mod0), "%d",
modinfo->mi_msinfo[0].msi_p0);
const short desired_attrs =
POSIX_SPAWN_NOSIGCHLD_NP | POSIX_SPAWN_WAITPID_NP;
posix_spawnattr_t attr;
int res;
if ((res = posix_spawnattr_init(&attr)) != 0 ||
(res = posix_spawnattr_setflags(&attr, desired_attrs)) != 0) {
errc(EXIT_FAILURE, res, "could not set spawn attrs");
}
pid_t child;
if (posix_spawn(&child, execfile, NULL, &attr, child_args, envp) != 0) {
err(EXIT_FAILURE, "could not exec %s", execfile);
}
(void) posix_spawnattr_destroy(&attr);
int status, error;
do {
error = waitpid(child, &status, 0);
} while (error == -1 && errno == EINTR);
if (error < 0) {
err(EXIT_FAILURE, "error while waiting for child");
}
if (WEXITSTATUS(status) != 0) {
errx(WEXITSTATUS(status),
"%s returned error %d.", execfile, status);
}
}
int
main(int argc, char *argv[], char *envp[])
{
int id = -1;
char *execfile = NULL;
char *unload_name = NULL;
int opt;
bool has_mod_id = false;
if (argc < 2)
usage();
while ((opt = getopt(argc, argv, "i:e:")) != -1) {
switch (opt) {
case 'i':
if (has_mod_id) {
errx(EXIT_FAILURE,
"Only one module id can be specified");
}
if (sscanf(optarg, "%d", &id) != 1 || id < 0)
errx(EXIT_FAILURE, "Invalid id %s", optarg);
has_mod_id = true;
break;
case 'e':
execfile = optarg;
break;
case '?':
usage();
break;
}
}
argc -= optind;
argv += optind;
if (argc == 1) {
unload_name = argv[0];
} else if (argc > 1) {
errx(EXIT_FAILURE, "Only one module name can be specified");
}
if (unload_name == NULL && !has_mod_id) {
(void) fprintf(stderr,
"missing required module id or name\n");
usage();
} else if (unload_name != NULL && has_mod_id) {
(void) fprintf(stderr,
"invalid to specify both module id and name\n");
usage();
}
if (getzoneid() != GLOBAL_ZONEID) {
errx(EXIT_FAILURE,
"modunload can only be run from the global zone");
}
struct modinfo modinfo;
if (execfile != NULL || unload_name != NULL) {
modinfo.mi_id = modinfo.mi_nextid = id;
modinfo.mi_info = MI_INFO_ONE;
if (unload_name != NULL) {
(void) strlcpy(modinfo.mi_name, unload_name,
sizeof (modinfo.mi_name));
modinfo.mi_info |= MI_INFO_BY_NAME;
}
if (modctl(MODINFO, id, &modinfo) < 0) {
err(EXIT_FAILURE, "can't get module information");
}
if (unload_name != NULL) {
id = modinfo.mi_id;
}
}
if (execfile) {
exec_userfile(execfile, &modinfo, envp);
}
if (modctl(MODUNLOAD, id) < 0) {
if (errno == EPERM) {
errx(EXIT_FAILURE,
"Insufficient privileges to unload a module");
} else if (id != 0) {
err(EXIT_FAILURE, "can't unload the module");
}
}
return (EXIT_SUCCESS);
}