xref: /openbsd/usr.sbin/hotplugd/hotplugd.c (revision 73471bf0)
1 /*	$OpenBSD: hotplugd.c,v 1.17 2021/07/12 15:09:21 beck Exp $	*/
2 /*
3  * Copyright (c) 2004 Alexander Yurchenko <grange@openbsd.org>
4  *
5  * Permission to use, copy, modify, and distribute this software for any
6  * purpose with or without fee is hereby granted, provided that the above
7  * copyright notice and this permission notice appear in all copies.
8  *
9  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16  */
17 
18 /*
19  * Devices hot plugging daemon.
20  */
21 
22 #include <sys/types.h>
23 #include <sys/device.h>
24 #include <sys/hotplug.h>
25 #include <sys/wait.h>
26 
27 #include <err.h>
28 #include <errno.h>
29 #include <fcntl.h>
30 #include <libgen.h>
31 #include <limits.h>
32 #include <signal.h>
33 #include <stdarg.h>
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <string.h>
37 #include <syslog.h>
38 #include <unistd.h>
39 
40 #define _PATH_DEV_HOTPLUG		"/dev/hotplug"
41 #define _PATH_ETC_HOTPLUG		"/etc/hotplug"
42 #define _PATH_ETC_HOTPLUG_ATTACH	_PATH_ETC_HOTPLUG "/attach"
43 #define _PATH_ETC_HOTPLUG_DETACH	_PATH_ETC_HOTPLUG "/detach"
44 #define _LOG_TAG			"hotplugd"
45 #define _LOG_FACILITY			LOG_DAEMON
46 #define _LOG_OPT			(LOG_NDELAY | LOG_PID)
47 
48 volatile sig_atomic_t quit = 0;
49 char *device = _PATH_DEV_HOTPLUG;
50 int devfd = -1;
51 
52 void exec_script(const char *, int, char *);
53 
54 void sigchild(int);
55 void sigquit(int);
56 __dead void usage(void);
57 
58 int
59 main(int argc, char *argv[])
60 {
61 	int ch;
62 	struct sigaction sact;
63 	struct hotplug_event he;
64 
65 	while ((ch = getopt(argc, argv, "d:")) != -1)
66 		switch (ch) {
67 		case 'd':
68 			device = optarg;
69 			break;
70 		case '?':
71 		default:
72 			usage();
73 			/* NOTREACHED */
74 		}
75 
76 	argc -= optind;
77 	argv += optind;
78 	if (argc > 0)
79 		usage();
80 
81 	if (unveil(device, "r") == -1)
82 		err(1, "unveil %s", device);
83 	if (unveil(_PATH_ETC_HOTPLUG_ATTACH, "rx") == -1)
84 		err(1, "unveil %s", _PATH_ETC_HOTPLUG_ATTACH);
85 	if (unveil(_PATH_ETC_HOTPLUG_DETACH, "rx") == -1)
86 		err(1, "unveil %s", _PATH_ETC_HOTPLUG_DETACH);
87 	if (pledge("stdio rpath proc exec", NULL) == -1)
88 		err(1, "pledge");
89 
90 	if ((devfd = open(device, O_RDONLY | O_CLOEXEC)) == -1)
91 		err(1, "%s", device);
92 
93 	bzero(&sact, sizeof(sact));
94 	sigemptyset(&sact.sa_mask);
95 	sact.sa_flags = 0;
96 	sact.sa_handler = sigquit;
97 	sigaction(SIGINT, &sact, NULL);
98 	sigaction(SIGQUIT, &sact, NULL);
99 	sigaction(SIGTERM, &sact, NULL);
100 	sact.sa_handler = SIG_IGN;
101 	sigaction(SIGHUP, &sact, NULL);
102 	sact.sa_handler = sigchild;
103 	sact.sa_flags = SA_NOCLDSTOP;
104 	sigaction(SIGCHLD, &sact, NULL);
105 
106 	openlog(_LOG_TAG, _LOG_OPT, _LOG_FACILITY);
107 	if (daemon(0, 0) == -1)
108 		err(1, "daemon");
109 
110 	syslog(LOG_INFO, "started");
111 
112 	while (!quit) {
113 		if (read(devfd, &he, sizeof(he)) == -1) {
114 			if (errno == EINTR)
115 				/* ignore */
116 				continue;
117 			syslog(LOG_ERR, "read: %m");
118 			exit(1);
119 		}
120 
121 		switch (he.he_type) {
122 		case HOTPLUG_DEVAT:
123 			syslog(LOG_INFO, "%s attached, class %d",
124 			    he.he_devname, he.he_devclass);
125 			exec_script(_PATH_ETC_HOTPLUG_ATTACH, he.he_devclass,
126 			    he.he_devname);
127 			break;
128 		case HOTPLUG_DEVDT:
129 			syslog(LOG_INFO, "%s detached, class %d",
130 			    he.he_devname, he.he_devclass);
131 			exec_script(_PATH_ETC_HOTPLUG_DETACH, he.he_devclass,
132 			    he.he_devname);
133 			break;
134 		default:
135 			syslog(LOG_NOTICE, "unknown event (0x%x)", he.he_type);
136 		}
137 	}
138 
139 	syslog(LOG_INFO, "terminated");
140 
141 	closelog();
142 	close(devfd);
143 
144 	return (0);
145 }
146 
147 void
148 exec_script(const char *file, int class, char *name)
149 {
150 	char strclass[8];
151 	pid_t pid;
152 
153 	snprintf(strclass, sizeof(strclass), "%d", class);
154 
155 	if (access(file, X_OK | R_OK) == -1) {
156 		if (errno != ENOENT)
157 			syslog(LOG_ERR, "%s: %m", file);
158 		return;
159 	}
160 
161 	if ((pid = fork()) == -1) {
162 		syslog(LOG_ERR, "fork: %m");
163 		return;
164 	}
165 	if (pid == 0) {
166 		/* child process */
167 		char filebuf[PATH_MAX];
168 
169 		strlcpy(filebuf, file, sizeof(filebuf));
170 		execl(file, basename(filebuf), strclass, name, (char *)NULL);
171 		syslog(LOG_ERR, "execl %s: %m", file);
172 		_exit(1);
173 		/* NOTREACHED */
174 	}
175 }
176 
177 /* ARGSUSED */
178 void
179 sigchild(int signum)
180 {
181 	struct syslog_data sdata = SYSLOG_DATA_INIT;
182 	int saved_errno, status;
183 	pid_t pid;
184 
185 	saved_errno = errno;
186 
187 	sdata.log_tag = _LOG_TAG;
188 	sdata.log_fac = _LOG_FACILITY;
189 	sdata.log_stat = _LOG_OPT;
190 
191 	while ((pid = waitpid(WAIT_ANY, &status, WNOHANG)) != 0) {
192 		if (pid == -1) {
193 			if (errno == EINTR)
194 				continue;
195 			if (errno != ECHILD)
196 				syslog_r(LOG_ERR, &sdata, "waitpid: %m");
197 			break;
198 		}
199 
200 		if (WIFEXITED(status)) {
201 			if (WEXITSTATUS(status) != 0) {
202 				syslog_r(LOG_NOTICE, &sdata,
203 				    "child exit status: %d",
204 				    WEXITSTATUS(status));
205 			}
206 		} else {
207 			syslog_r(LOG_NOTICE, &sdata,
208 			    "child is terminated abnormally");
209 		}
210 	}
211 
212 	errno = saved_errno;
213 }
214 
215 /* ARGSUSED */
216 void
217 sigquit(int signum)
218 {
219 	quit = 1;
220 }
221 
222 __dead void
223 usage(void)
224 {
225 	extern char *__progname;
226 
227 	fprintf(stderr, "usage: %s [-d device]\n", __progname);
228 	exit(1);
229 }
230