1 /* $OpenBSD: hotplugd.c,v 1.19 2023/03/08 04:43:13 guenther 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
main(int argc,char * argv[])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 default:
71 usage();
72 /* NOTREACHED */
73 }
74
75 argc -= optind;
76 argv += optind;
77 if (argc > 0)
78 usage();
79
80 if (unveil(device, "r") == -1)
81 err(1, "unveil %s", device);
82 if (unveil(_PATH_ETC_HOTPLUG_ATTACH, "rx") == -1)
83 err(1, "unveil %s", _PATH_ETC_HOTPLUG_ATTACH);
84 if (unveil(_PATH_ETC_HOTPLUG_DETACH, "rx") == -1)
85 err(1, "unveil %s", _PATH_ETC_HOTPLUG_DETACH);
86 if (pledge("stdio rpath proc exec", NULL) == -1)
87 err(1, "pledge");
88
89 if ((devfd = open(device, O_RDONLY | O_CLOEXEC)) == -1)
90 err(1, "%s", device);
91
92 bzero(&sact, sizeof(sact));
93 sigemptyset(&sact.sa_mask);
94 sact.sa_flags = 0;
95 sact.sa_handler = sigquit;
96 sigaction(SIGINT, &sact, NULL);
97 sigaction(SIGQUIT, &sact, NULL);
98 sigaction(SIGTERM, &sact, NULL);
99 sact.sa_handler = SIG_IGN;
100 sigaction(SIGHUP, &sact, NULL);
101 sact.sa_handler = sigchild;
102 sact.sa_flags = SA_NOCLDSTOP;
103 sigaction(SIGCHLD, &sact, NULL);
104
105 openlog(_LOG_TAG, _LOG_OPT, _LOG_FACILITY);
106 if (daemon(0, 0) == -1)
107 err(1, "daemon");
108
109 syslog(LOG_INFO, "started");
110
111 while (!quit) {
112 if (read(devfd, &he, sizeof(he)) == -1) {
113 if (errno == EINTR)
114 /* ignore */
115 continue;
116 syslog(LOG_ERR, "read: %m");
117 exit(1);
118 }
119
120 switch (he.he_type) {
121 case HOTPLUG_DEVAT:
122 syslog(LOG_INFO, "%s attached, class %d",
123 he.he_devname, he.he_devclass);
124 exec_script(_PATH_ETC_HOTPLUG_ATTACH, he.he_devclass,
125 he.he_devname);
126 break;
127 case HOTPLUG_DEVDT:
128 syslog(LOG_INFO, "%s detached, class %d",
129 he.he_devname, he.he_devclass);
130 exec_script(_PATH_ETC_HOTPLUG_DETACH, he.he_devclass,
131 he.he_devname);
132 break;
133 default:
134 syslog(LOG_NOTICE, "unknown event (0x%x)", he.he_type);
135 }
136 }
137
138 syslog(LOG_INFO, "terminated");
139
140 closelog();
141 close(devfd);
142
143 return (0);
144 }
145
146 void
exec_script(const char * file,int class,char * name)147 exec_script(const char *file, int class, char *name)
148 {
149 char strclass[8];
150 pid_t pid;
151
152 snprintf(strclass, sizeof(strclass), "%d", class);
153
154 if (access(file, X_OK | R_OK) == -1) {
155 if (errno != ENOENT)
156 syslog(LOG_ERR, "%s: %m", file);
157 return;
158 }
159
160 if ((pid = fork()) == -1) {
161 syslog(LOG_ERR, "fork: %m");
162 return;
163 }
164 if (pid == 0) {
165 /* child process */
166 char filebuf[PATH_MAX];
167
168 strlcpy(filebuf, file, sizeof(filebuf));
169 execl(file, basename(filebuf), strclass, name, (char *)NULL);
170 syslog(LOG_ERR, "execl %s: %m", file);
171 _exit(1);
172 /* NOTREACHED */
173 }
174 }
175
176 void
sigchild(int signum)177 sigchild(int signum)
178 {
179 struct syslog_data sdata = SYSLOG_DATA_INIT;
180 int saved_errno, status;
181 pid_t pid;
182
183 saved_errno = errno;
184
185 sdata.log_tag = _LOG_TAG;
186 sdata.log_fac = _LOG_FACILITY;
187 sdata.log_stat = _LOG_OPT;
188
189 while ((pid = waitpid(WAIT_ANY, &status, WNOHANG)) != 0) {
190 if (pid == -1) {
191 if (errno == EINTR)
192 continue;
193 if (errno != ECHILD)
194 syslog_r(LOG_ERR, &sdata, "waitpid: %m");
195 break;
196 }
197
198 if (WIFEXITED(status)) {
199 if (WEXITSTATUS(status) != 0) {
200 syslog_r(LOG_NOTICE, &sdata,
201 "child exit status: %d",
202 WEXITSTATUS(status));
203 }
204 } else {
205 syslog_r(LOG_NOTICE, &sdata,
206 "child is terminated abnormally");
207 }
208 }
209
210 errno = saved_errno;
211 }
212
213 void
sigquit(int signum)214 sigquit(int signum)
215 {
216 quit = 1;
217 }
218
219 __dead void
usage(void)220 usage(void)
221 {
222 extern char *__progname;
223
224 fprintf(stderr, "usage: %s [-d device]\n", __progname);
225 exit(1);
226 }
227