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
48volatile sig_atomic_t quit = 0;
49char *device = _PATH_DEV_HOTPLUG;
50int devfd = -1;
51
52void exec_script(const char *, int, char *);
53
54void sigchild(int);
55void sigquit(int);
56__dead void usage(void);
57
58int
59main(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
146void
147exec_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
176void
177sigchild(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
213void
214sigquit(int signum)
215{
216	quit = 1;
217}
218
219__dead void
220usage(void)
221{
222	extern char *__progname;
223
224	fprintf(stderr, "usage: %s [-d device]\n", __progname);
225	exit(1);
226}
227