1 1.7 christos /* $NetBSD: devpubd.c,v 1.7 2021/06/21 03:14:12 christos Exp $ */ 2 1.1 mrg 3 1.1 mrg /*- 4 1.1 mrg * Copyright (c) 2011 Jared D. McNeill <jmcneill (at) invisible.ca> 5 1.1 mrg * All rights reserved. 6 1.1 mrg * 7 1.1 mrg * Redistribution and use in source and binary forms, with or without 8 1.1 mrg * modification, are permitted provided that the following conditions 9 1.1 mrg * are met: 10 1.1 mrg * 1. Redistributions of source code must retain the above copyright 11 1.1 mrg * notice, this list of conditions and the following disclaimer. 12 1.1 mrg * 2. Redistributions in binary form must reproduce the above copyright 13 1.1 mrg * notice, this list of conditions and the following disclaimer in the 14 1.1 mrg * documentation and/or other materials provided with the distribution. 15 1.1 mrg * 3. All advertising materials mentioning features or use of this software 16 1.1 mrg * must display the following acknowledgement: 17 1.1 mrg * This product includes software developed by the NetBSD 18 1.1 mrg * Foundation, Inc. and its contributors. 19 1.1 mrg * 4. Neither the name of The NetBSD Foundation nor the names of its 20 1.1 mrg * contributors may be used to endorse or promote products derived 21 1.1 mrg * from this software without specific prior written permission. 22 1.1 mrg * 23 1.1 mrg * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 24 1.1 mrg * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 25 1.1 mrg * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 26 1.1 mrg * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 27 1.1 mrg * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 28 1.1 mrg * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 29 1.1 mrg * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 30 1.1 mrg * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 31 1.1 mrg * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 32 1.1 mrg * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 33 1.1 mrg * POSSIBILITY OF SUCH DAMAGE. 34 1.1 mrg */ 35 1.1 mrg 36 1.1 mrg #include <sys/cdefs.h> 37 1.3 jmcneill __COPYRIGHT("@(#) Copyright (c) 2011-2015\ 38 1.1 mrg Jared D. McNeill <jmcneill (at) invisible.ca>. All rights reserved."); 39 1.7 christos __RCSID("$NetBSD: devpubd.c,v 1.7 2021/06/21 03:14:12 christos Exp $"); 40 1.1 mrg 41 1.3 jmcneill #include <sys/queue.h> 42 1.1 mrg #include <sys/types.h> 43 1.1 mrg #include <sys/ioctl.h> 44 1.1 mrg #include <sys/drvctlio.h> 45 1.1 mrg #include <sys/wait.h> 46 1.1 mrg 47 1.1 mrg #include <assert.h> 48 1.1 mrg #include <err.h> 49 1.1 mrg #include <errno.h> 50 1.1 mrg #include <fcntl.h> 51 1.1 mrg #include <stdio.h> 52 1.1 mrg #include <stdlib.h> 53 1.1 mrg #include <string.h> 54 1.1 mrg #include <syslog.h> 55 1.1 mrg #include <unistd.h> 56 1.1 mrg 57 1.1 mrg static int drvctl_fd = -1; 58 1.2 joerg static const char devpubd_script[] = DEVPUBD_RUN_HOOKS; 59 1.1 mrg 60 1.3 jmcneill struct devpubd_probe_event { 61 1.3 jmcneill char *device; 62 1.3 jmcneill TAILQ_ENTRY(devpubd_probe_event) entries; 63 1.3 jmcneill }; 64 1.3 jmcneill 65 1.3 jmcneill static TAILQ_HEAD(, devpubd_probe_event) devpubd_probe_events; 66 1.3 jmcneill 67 1.1 mrg #define DEVPUBD_ATTACH_EVENT "device-attach" 68 1.1 mrg #define DEVPUBD_DETACH_EVENT "device-detach" 69 1.1 mrg 70 1.2 joerg __dead static void 71 1.3 jmcneill devpubd_exec(const char *path, char * const *argv) 72 1.1 mrg { 73 1.1 mrg int error; 74 1.1 mrg 75 1.3 jmcneill error = execv(path, argv); 76 1.1 mrg if (error) { 77 1.3 jmcneill syslog(LOG_ERR, "couldn't exec '%s': %m", path); 78 1.1 mrg exit(EXIT_FAILURE); 79 1.1 mrg } 80 1.1 mrg 81 1.1 mrg exit(EXIT_SUCCESS); 82 1.1 mrg } 83 1.1 mrg 84 1.1 mrg static void 85 1.3 jmcneill devpubd_eventhandler(const char *event, const char **device) 86 1.1 mrg { 87 1.3 jmcneill char **argv; 88 1.1 mrg pid_t pid; 89 1.4 christos int status; 90 1.4 christos size_t i, ndevs; 91 1.3 jmcneill 92 1.3 jmcneill for (ndevs = 0, i = 0; device[i] != NULL; i++) { 93 1.3 jmcneill ++ndevs; 94 1.3 jmcneill syslog(LOG_DEBUG, "event = '%s', device = '%s'", event, 95 1.3 jmcneill device[i]); 96 1.3 jmcneill } 97 1.1 mrg 98 1.4 christos argv = calloc(3 + ndevs, sizeof(*argv)); 99 1.3 jmcneill argv[0] = __UNCONST(devpubd_script); 100 1.3 jmcneill argv[1] = __UNCONST(event); 101 1.3 jmcneill for (i = 0; i < ndevs; i++) { 102 1.3 jmcneill argv[2 + i] = __UNCONST(device[i]); 103 1.3 jmcneill } 104 1.3 jmcneill argv[2 + i] = NULL; 105 1.1 mrg 106 1.1 mrg pid = fork(); 107 1.1 mrg switch (pid) { 108 1.1 mrg case -1: 109 1.1 mrg syslog(LOG_ERR, "fork failed: %m"); 110 1.1 mrg break; 111 1.1 mrg case 0: 112 1.3 jmcneill devpubd_exec(devpubd_script, argv); 113 1.1 mrg /* NOTREACHED */ 114 1.1 mrg default: 115 1.1 mrg if (waitpid(pid, &status, 0) == -1) { 116 1.1 mrg syslog(LOG_ERR, "waitpid(%d) failed: %m", pid); 117 1.1 mrg break; 118 1.1 mrg } 119 1.1 mrg if (WIFEXITED(status) && WEXITSTATUS(status)) { 120 1.1 mrg syslog(LOG_WARNING, "%s exited with status %d", 121 1.1 mrg devpubd_script, WEXITSTATUS(status)); 122 1.1 mrg } else if (WIFSIGNALED(status)) { 123 1.1 mrg syslog(LOG_WARNING, "%s received signal %d", 124 1.1 mrg devpubd_script, WTERMSIG(status)); 125 1.1 mrg } 126 1.1 mrg break; 127 1.1 mrg } 128 1.3 jmcneill 129 1.3 jmcneill free(argv); 130 1.1 mrg } 131 1.1 mrg 132 1.2 joerg __dead static void 133 1.1 mrg devpubd_eventloop(void) 134 1.1 mrg { 135 1.3 jmcneill const char *event, *device[2]; 136 1.1 mrg prop_dictionary_t ev; 137 1.1 mrg int res; 138 1.1 mrg 139 1.1 mrg assert(drvctl_fd != -1); 140 1.1 mrg 141 1.3 jmcneill device[1] = NULL; 142 1.3 jmcneill 143 1.1 mrg for (;;) { 144 1.1 mrg res = prop_dictionary_recv_ioctl(drvctl_fd, DRVGETEVENT, &ev); 145 1.1 mrg if (res) 146 1.1 mrg err(EXIT_FAILURE, "DRVGETEVENT failed"); 147 1.7 christos prop_dictionary_get_string(ev, "event", &event); 148 1.7 christos prop_dictionary_get_string(ev, "device", &device[0]); 149 1.1 mrg 150 1.1 mrg printf("%s: event='%s', device='%s'\n", __func__, 151 1.4 christos event, device[0]); 152 1.1 mrg 153 1.1 mrg devpubd_eventhandler(event, device); 154 1.1 mrg 155 1.1 mrg prop_object_release(ev); 156 1.1 mrg } 157 1.1 mrg } 158 1.1 mrg 159 1.1 mrg static void 160 1.1 mrg devpubd_probe(const char *device) 161 1.1 mrg { 162 1.1 mrg struct devlistargs laa; 163 1.1 mrg size_t len, children, n; 164 1.1 mrg void *p; 165 1.1 mrg int error; 166 1.1 mrg 167 1.1 mrg assert(drvctl_fd != -1); 168 1.1 mrg 169 1.1 mrg memset(&laa, 0, sizeof(laa)); 170 1.1 mrg if (device) 171 1.1 mrg strlcpy(laa.l_devname, device, sizeof(laa.l_devname)); 172 1.1 mrg 173 1.1 mrg /* Get the child device count for this device */ 174 1.1 mrg error = ioctl(drvctl_fd, DRVLISTDEV, &laa); 175 1.1 mrg if (error) { 176 1.1 mrg syslog(LOG_ERR, "DRVLISTDEV failed: %m"); 177 1.1 mrg return; 178 1.1 mrg } 179 1.1 mrg 180 1.1 mrg child_count_changed: 181 1.1 mrg /* If this device has no children, return */ 182 1.1 mrg if (laa.l_children == 0) 183 1.1 mrg return; 184 1.1 mrg 185 1.1 mrg /* Allocate a buffer large enough to hold the child device names */ 186 1.1 mrg p = laa.l_childname; 187 1.1 mrg children = laa.l_children; 188 1.1 mrg 189 1.1 mrg len = children * sizeof(laa.l_childname[0]); 190 1.1 mrg laa.l_childname = realloc(laa.l_childname, len); 191 1.1 mrg if (laa.l_childname == NULL) { 192 1.1 mrg syslog(LOG_ERR, "couldn't allocate %zu bytes", len); 193 1.1 mrg laa.l_childname = p; 194 1.1 mrg goto done; 195 1.1 mrg } 196 1.1 mrg 197 1.1 mrg /* Get a list of child devices */ 198 1.1 mrg error = ioctl(drvctl_fd, DRVLISTDEV, &laa); 199 1.1 mrg if (error) { 200 1.1 mrg syslog(LOG_ERR, "DRVLISTDEV failed: %m"); 201 1.1 mrg goto done; 202 1.1 mrg } 203 1.1 mrg 204 1.1 mrg /* If the child count changed between DRVLISTDEV calls, retry */ 205 1.1 mrg if (children != laa.l_children) 206 1.1 mrg goto child_count_changed; 207 1.1 mrg 208 1.1 mrg /* 209 1.3 jmcneill * For each child device, queue an attach event and 210 1.1 mrg * then scan each one for additional devices. 211 1.1 mrg */ 212 1.3 jmcneill for (n = 0; n < laa.l_children; n++) { 213 1.3 jmcneill struct devpubd_probe_event *ev = calloc(1, sizeof(*ev)); 214 1.3 jmcneill ev->device = strdup(laa.l_childname[n]); 215 1.3 jmcneill TAILQ_INSERT_TAIL(&devpubd_probe_events, ev, entries); 216 1.3 jmcneill } 217 1.1 mrg for (n = 0; n < laa.l_children; n++) 218 1.1 mrg devpubd_probe(laa.l_childname[n]); 219 1.1 mrg 220 1.1 mrg done: 221 1.1 mrg free(laa.l_childname); 222 1.1 mrg return; 223 1.1 mrg } 224 1.1 mrg 225 1.3 jmcneill static void 226 1.3 jmcneill devpubd_init(void) 227 1.3 jmcneill { 228 1.3 jmcneill struct devpubd_probe_event *ev; 229 1.3 jmcneill const char **devs; 230 1.4 christos size_t ndevs, i; 231 1.3 jmcneill 232 1.3 jmcneill TAILQ_INIT(&devpubd_probe_events); 233 1.3 jmcneill devpubd_probe(NULL); 234 1.3 jmcneill ndevs = 0; 235 1.3 jmcneill TAILQ_FOREACH(ev, &devpubd_probe_events, entries) { 236 1.3 jmcneill ++ndevs; 237 1.3 jmcneill } 238 1.3 jmcneill devs = calloc(ndevs + 1, sizeof(*devs)); 239 1.3 jmcneill i = 0; 240 1.3 jmcneill TAILQ_FOREACH(ev, &devpubd_probe_events, entries) { 241 1.3 jmcneill devs[i++] = ev->device; 242 1.3 jmcneill } 243 1.3 jmcneill devpubd_eventhandler(DEVPUBD_ATTACH_EVENT, devs); 244 1.3 jmcneill free(devs); 245 1.4 christos while ((ev = TAILQ_FIRST(&devpubd_probe_events)) != NULL) { 246 1.3 jmcneill TAILQ_REMOVE(&devpubd_probe_events, ev, entries); 247 1.3 jmcneill free(ev->device); 248 1.3 jmcneill free(ev); 249 1.3 jmcneill } 250 1.3 jmcneill } 251 1.3 jmcneill 252 1.2 joerg __dead static void 253 1.1 mrg usage(void) 254 1.1 mrg { 255 1.6 mlelstv fprintf(stderr, "usage: %s [-1f]\n", getprogname()); 256 1.1 mrg exit(EXIT_FAILURE); 257 1.1 mrg } 258 1.1 mrg 259 1.1 mrg int 260 1.1 mrg main(int argc, char *argv[]) 261 1.1 mrg { 262 1.1 mrg bool fflag = false; 263 1.6 mlelstv bool once = false; 264 1.1 mrg int ch; 265 1.1 mrg 266 1.1 mrg setprogname(argv[0]); 267 1.1 mrg 268 1.6 mlelstv while ((ch = getopt(argc, argv, "1fh")) != -1) { 269 1.1 mrg switch (ch) { 270 1.6 mlelstv case '1': 271 1.6 mlelstv fflag = true; 272 1.6 mlelstv once = true; 273 1.6 mlelstv break; 274 1.1 mrg case 'f': 275 1.1 mrg fflag = true; 276 1.1 mrg break; 277 1.1 mrg case 'h': 278 1.1 mrg default: 279 1.1 mrg usage(); 280 1.1 mrg /* NOTREACHED */ 281 1.1 mrg } 282 1.1 mrg } 283 1.1 mrg argc -= optind; 284 1.1 mrg argv += optind; 285 1.1 mrg 286 1.1 mrg if (argc) 287 1.1 mrg usage(); 288 1.1 mrg /* NOTREACHED */ 289 1.1 mrg 290 1.1 mrg drvctl_fd = open(DRVCTLDEV, O_RDWR); 291 1.1 mrg if (drvctl_fd == -1) 292 1.1 mrg err(EXIT_FAILURE, "couldn't open " DRVCTLDEV); 293 1.1 mrg 294 1.3 jmcneill /* Look for devices that are already present */ 295 1.3 jmcneill devpubd_init(); 296 1.3 jmcneill 297 1.1 mrg if (!fflag) { 298 1.1 mrg if (daemon(0, 0) == -1) { 299 1.1 mrg err(EXIT_FAILURE, "couldn't fork"); 300 1.1 mrg } 301 1.1 mrg } 302 1.1 mrg 303 1.6 mlelstv if (!once) 304 1.6 mlelstv devpubd_eventloop(); 305 1.1 mrg 306 1.1 mrg return EXIT_SUCCESS; 307 1.1 mrg } 308