xref: /illumos-gate/usr/src/cmd/modload/modunload.c (revision 896f4973f52689c3618f0c953a6f47ca310523b8)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  * Copyright 2025 Oxide Computer Company
25  */
26 
27 #include <sys/types.h>
28 #include <sys/stat.h>
29 #include <sys/wait.h>
30 #include <strings.h>
31 #include <unistd.h>
32 #include <errno.h>
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <sys/modctl.h>
36 #include <zone.h>
37 #include <spawn.h>
38 #include <err.h>
39 #include <stdbool.h>
40 
41 
42 static void
43 usage(void)
44 {
45 	(void) fprintf(stderr,
46 	    "usage:  modunload [-e <exec_file>] -i <id> | <name>\n");
47 	exit(EXIT_FAILURE);
48 }
49 
50 /*
51  * Execute the user file.
52  */
53 static void
54 exec_userfile(char *execfile, const struct modinfo *modinfo, char **envp)
55 {
56 	char modid[8], mod0[8];
57 	char *child_args[] = {execfile, modid, mod0, NULL};
58 
59 	(void) snprintf(modid, sizeof (modid), "%d", modinfo->mi_id);
60 	(void) snprintf(mod0, sizeof (mod0), "%d",
61 	    modinfo->mi_msinfo[0].msi_p0);
62 
63 	const short desired_attrs =
64 	    POSIX_SPAWN_NOSIGCHLD_NP | POSIX_SPAWN_WAITPID_NP;
65 	posix_spawnattr_t attr;
66 	int res;
67 	if ((res = posix_spawnattr_init(&attr)) != 0 ||
68 	    (res = posix_spawnattr_setflags(&attr, desired_attrs)) != 0) {
69 		errc(EXIT_FAILURE, res, "could not set spawn attrs");
70 	}
71 
72 	pid_t child;
73 	if (posix_spawn(&child, execfile, NULL, &attr, child_args, envp) != 0) {
74 		err(EXIT_FAILURE, "could not exec %s", execfile);
75 	}
76 	(void) posix_spawnattr_destroy(&attr);
77 
78 	int status, error;
79 	do {
80 		error = waitpid(child, &status, 0);
81 	} while (error == -1 && errno == EINTR);
82 	if (error < 0) {
83 		err(EXIT_FAILURE, "error while waiting for child");
84 	}
85 
86 	if (WEXITSTATUS(status) != 0) {
87 		errx(WEXITSTATUS(status),
88 		    "%s returned error %d.", execfile, status);
89 	}
90 }
91 
92 /*
93  * Unload a loaded module.
94  */
95 int
96 main(int argc, char *argv[], char *envp[])
97 {
98 	int id = -1;
99 	char *execfile = NULL;
100 	char *unload_name = NULL;
101 	int opt;
102 	bool has_mod_id = false;
103 
104 	if (argc < 2)
105 		usage();
106 
107 	while ((opt = getopt(argc, argv, "i:e:")) != -1) {
108 		switch (opt) {
109 		case 'i':
110 			if (has_mod_id) {
111 				errx(EXIT_FAILURE,
112 				    "Only one module id can be specified");
113 			}
114 			if (sscanf(optarg, "%d", &id) != 1 || id < 0)
115 				errx(EXIT_FAILURE, "Invalid id %s", optarg);
116 			has_mod_id = true;
117 			break;
118 		case 'e':
119 			execfile = optarg;
120 			break;
121 		case '?':
122 			usage();
123 			break;
124 		}
125 	}
126 	argc -= optind;
127 	argv += optind;
128 
129 	if (argc == 1) {
130 		unload_name = argv[0];
131 	} else if (argc > 1) {
132 		errx(EXIT_FAILURE, "Only one module name can be specified");
133 	}
134 
135 	/* One must specify a name (x)or an ID, not both */
136 	if (unload_name == NULL && !has_mod_id) {
137 		(void) fprintf(stderr,
138 		    "missing required module id or name\n");
139 		usage();
140 	} else if (unload_name != NULL && has_mod_id) {
141 		(void) fprintf(stderr,
142 		    "invalid to specify both module id and name\n");
143 		usage();
144 	}
145 
146 	if (getzoneid() != GLOBAL_ZONEID) {
147 		errx(EXIT_FAILURE,
148 		    "modunload can only be run from the global zone");
149 	}
150 
151 	struct modinfo modinfo;
152 	if (execfile != NULL || unload_name != NULL) {
153 		modinfo.mi_id = modinfo.mi_nextid = id;
154 		modinfo.mi_info = MI_INFO_ONE;
155 		if (unload_name != NULL) {
156 			(void) strlcpy(modinfo.mi_name, unload_name,
157 			    sizeof (modinfo.mi_name));
158 			modinfo.mi_info |= MI_INFO_BY_NAME;
159 		}
160 		if (modctl(MODINFO, id, &modinfo) < 0) {
161 			err(EXIT_FAILURE, "can't get module information");
162 		}
163 		if (unload_name != NULL) {
164 			id = modinfo.mi_id;
165 		}
166 	}
167 
168 	if (execfile) {
169 		exec_userfile(execfile, &modinfo, envp);
170 	}
171 
172 	/*
173 	 * Unload the module.
174 	 */
175 	if (modctl(MODUNLOAD, id) < 0) {
176 		if (errno == EPERM) {
177 			errx(EXIT_FAILURE,
178 			    "Insufficient privileges to unload a module");
179 		} else if (id != 0) {
180 			err(EXIT_FAILURE, "can't unload the module");
181 		}
182 	}
183 
184 	return (EXIT_SUCCESS);
185 }
186