1 /* 2 * Copyright (C) 2021 Daniel Golle <daniel@makrotopia.org> 3 * 4 * This program is free software; you can redistribute it and/or modify 5 * it under the terms of the GNU Lesser General Public License version 2.1 6 * as published by the Free Software Foundation 7 * 8 * This program is distributed in the hope that it will be useful, 9 * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 * GNU General Public License for more details. 12 */ 13 14 #define _GNU_SOURCE 15 16 #include <sys/inotify.h> 17 #include <sys/types.h> 18 19 #include <dirent.h> 20 #include <errno.h> 21 #include <fcntl.h> 22 #include <glob.h> 23 #include <limits.h> 24 #include <stdbool.h> 25 #include <stdlib.h> 26 #include <stdio.h> 27 #include <string.h> 28 #include <unistd.h> 29 30 #include <libubox/avl.h> 31 #include <libubox/avl-cmp.h> 32 #include <libubox/list.h> 33 #include <libubox/uloop.h> 34 #include <libubus.h> 35 36 #include "procd.h" 37 #include "utils/utils.h" 38 39 #define HOTPLUG_BASEDIR "/etc/hotplug.d" 40 #define HOTPLUG_OBJECT_PREFIX "hotplug." 41 42 #define INOTIFY_SZ (sizeof(struct inotify_event) + PATH_MAX + 1) 43 44 struct ubus_context *ctx; 45 static char *inotify_buffer; 46 static struct uloop_fd fd_inotify_read; 47 48 static AVL_TREE(subsystems, avl_strcmp, false, NULL); 49 50 extern char **environ; 51 52 struct hotplug_subsys { 53 struct avl_node node; 54 struct ubus_object ubus; 55 }; 56 57 struct envlist { 58 struct avl_node avl; 59 char *env; 60 }; 61 62 struct hotplug_process { 63 struct ubus_object *ubus; 64 char **envp; 65 struct uloop_timeout timeout; 66 struct uloop_process process; 67 glob_t globbuf; 68 unsigned int cnt; 69 int ret; 70 }; 71 72 static void env_free(char **envp) 73 { 74 char **tmp; 75 76 tmp = envp; 77 while (*tmp) 78 free(*(tmp++)); 79 free(envp); 80 } 81 82 static void hotplug_free(struct hotplug_process *pc) 83 { 84 env_free(pc->envp); 85 globfree(&pc->globbuf); 86 free(pc); 87 } 88 89 static void hotplug_done(struct uloop_process *c, int ret) 90 { 91 struct hotplug_process *pc = container_of(c, struct hotplug_process, process); 92 93 pc->ret = ret; 94 95 uloop_timeout_set(&pc->timeout, 50); 96 } 97 98 static void hotplug_exec(struct uloop_timeout *t) 99 { 100 struct hotplug_process *pc = container_of(t, struct hotplug_process, timeout); 101 char *script; 102 char *exec_argv[4]; 103 /* we have reached the last entry in the globbuf */ 104 if (pc->cnt == pc->globbuf.gl_pathc) { 105 hotplug_free(pc); 106 return; 107 } 108 109 if (asprintf(&script, ". /lib/functions.sh\n. %s\n", pc->globbuf.gl_pathv[pc->cnt++]) == -1) { 110 pc->ret = ENOMEM; 111 return; 112 } 113 114 /* prepare for execve() */ 115 exec_argv[0] = "/bin/sh"; 116 exec_argv[1] = "-c"; 117 exec_argv[2] = script; 118 exec_argv[3] = NULL; 119 120 /* set callback in uloop_process */ 121 pc->process.cb = hotplug_done; 122 pc->process.pid = fork(); 123 if (pc->process.pid == 0) { 124 /* child */ 125 patch_stdio("/dev/null"); 126 execve(exec_argv[0], exec_argv, pc->envp); 127 ERROR("Failed to execute script: %m\n"); 128 exit(EXIT_FAILURE); 129 } else if (pc->process.pid < 0) { 130 /* fork error */ 131 free(script); 132 hotplug_free(pc); 133 return; 134 } 135 /* parent */ 136 free(script); 137 uloop_process_add(&pc->process); 138 } 139 140 static int avl_envcmp(const void *k1, const void *k2, void *ptr) 141 { 142 const char *tmp; 143 144 tmp = strchr(k1, '='); 145 if (!tmp) 146 return -1; 147 148 /* 149 * compare the variable name only, ie. limit strncmp to check 150 * only up to and including the '=' sign 151 */ 152 return strncmp(k1, k2, (tmp - (char *)k1) + 1); 153 } 154 155 /* validate NULL-terminated environment variable name */ 156 static int validate_envvarname(const char *envvarname) 157 { 158 const char *tmp = envvarname; 159 160 /* check for illegal characters in env variable name */ 161 while (tmp[0] != '\0') { 162 if (!((tmp[0] >= 'a' && tmp[0] <= 'z') || 163 (tmp[0] >= 'A' && tmp[0] <= 'Z') || 164 (tmp[0] == '_') || 165 /* allow numbers unless they are at the first character */ 166 ((tmp != envvarname) && tmp[0] >= '' && tmp[0] <= '9'))) 167 return EINVAL; 168 ++tmp; 169 } 170 171 return 0; 172 } 173 174 enum { 175 HOTPLUG_ENV, 176 __HOTPLUG_MAX 177 }; 178 179 static const struct blobmsg_policy hotplug_policy[__HOTPLUG_MAX] = { 180 [HOTPLUG_ENV] = { .name = "env", .type = BLOBMSG_TYPE_ARRAY }, 181 }; 182 183 static int hotplug_call(struct ubus_context *ctx, struct ubus_object *obj, 184 struct ubus_request_data *req, const char *method, 185 struct blob_attr *msg) 186 { 187 const char *subsys = &obj->name[strlen(HOTPLUG_OBJECT_PREFIX)]; 188 struct blob_attr *tb[__HOTPLUG_MAX], *cur; 189 AVL_TREE(env, avl_envcmp, false, NULL); 190 struct envlist *envle, *p; 191 int rem; 192 char **envp, *globstr, *tmp, **tmpenv; 193 size_t envz = 0; 194 struct hotplug_process *pc; 195 bool async = true; 196 int err = UBUS_STATUS_UNKNOWN_ERROR; 197 198 blobmsg_parse(hotplug_policy, __HOTPLUG_MAX, tb, blobmsg_data(msg), blobmsg_len(msg)); 199 200 if (!tb[HOTPLUG_ENV]) 201 return UBUS_STATUS_INVALID_ARGUMENT; 202 203 tmpenv = environ; 204 205 /* first adding existing environment to avl_tree */ 206 while (*tmpenv) { 207 envle = calloc(1, sizeof(struct envlist)); 208 if (!envle) 209 goto err_envle; 210 211 envle->env = strdup(*tmpenv); 212 if (!envle->env) { 213 free(envle); 214 goto err_envle; 215 } 216 envle->avl.key = envle->env; 217 if (avl_insert(&env, &envle->avl) == -1) { 218 free(envle->env); 219 free(envle); 220 goto err_envle; 221 } 222 223 ++tmpenv; 224 } 225 226 /* then adding additional variables from ubus call */ 227 blobmsg_for_each_attr(cur, tb[HOTPLUG_ENV], rem) { 228 char *enve = blobmsg_get_string(cur); 229 if (!enve) 230 continue; 231 232 if (!strncmp(enve, "LD_", 3)) 233 continue; 234 235 if (!strncmp(enve, "PATH=", 5)) 236 continue; 237 238 if (strlen(enve) < 3) 239 continue; 240 241 if (!(tmp = strchr(enve, '='))) 242 continue; 243 244 *tmp = '\0'; 245 if (validate_envvarname(enve)) 246 continue; 247 *tmp = '='; 248 249 if (!strcmp(enve, "ASYNC=0")) 250 async = false; 251 252 envle = calloc(1, sizeof(struct envlist)); 253 if (!envle) 254 goto err_envle; 255 256 envle->env = strdup(enve); 257 if (!envle->env) { 258 free(envle); 259 goto err_envle; 260 } 261 envle->avl.key = envle->env; 262 if (avl_insert(&env, &envle->avl)) { 263 /* do not override existing env values, just skip */ 264 free((void*)envle->env); 265 free(envle); 266 } 267 } 268 269 /* synchronous calls are unsupported for now */ 270 if (!async) { 271 err = UBUS_STATUS_NOT_SUPPORTED; 272 goto err_envle; 273 } 274 275 /* allocating new environment */ 276 avl_for_each_element(&env, envle, avl) 277 ++envz; 278 279 envp = calloc(envz + 1, sizeof(char *)); 280 if (!envp) 281 goto err_envle; 282 283 /* populating new environment */ 284 envz = 0; 285 avl_for_each_element_safe(&env, envle, avl, p) { 286 envp[envz++] = envle->env; 287 avl_delete(&env, &envle->avl); 288 free(envle); 289 } 290 291 pc = calloc(1, sizeof(struct hotplug_process)); 292 if (!pc) { 293 env_free(envp); 294 return UBUS_STATUS_UNKNOWN_ERROR; 295 } 296 pc->timeout.cb = hotplug_exec; 297 pc->envp = envp; 298 pc->cnt = 0; 299 pc->ubus = obj; 300 301 /* glob'ing for hotplug scripts */ 302 if (asprintf(&globstr, "%s/%s/*", HOTPLUG_BASEDIR, subsys) == -1) { 303 hotplug_free(pc); 304 return UBUS_STATUS_UNKNOWN_ERROR; 305 } 306 307 if (glob(globstr, GLOB_DOOFFS, NULL, &pc->globbuf)) { 308 free(globstr); 309 hotplug_free(pc); 310 return UBUS_STATUS_OK; 311 } 312 313 free(globstr); 314 315 /* asynchronous call to hotplug_exec() */ 316 uloop_timeout_set(&pc->timeout, 50); 317 318 return UBUS_STATUS_OK; 319 320 err_envle: 321 avl_for_each_element_safe(&env, envle, avl, p) { 322 if (envle->env) 323 free(envle->env); 324 325 avl_delete(&env, &envle->avl); 326 free(envle); 327 } 328 329 return err; 330 } 331 332 static const struct ubus_method hotplug_methods[] = { 333 UBUS_METHOD("call", hotplug_call, hotplug_policy), 334 }; 335 336 static struct ubus_object_type hotplug_object_type = 337 UBUS_OBJECT_TYPE("hotplug", hotplug_methods); 338 339 static void add_subsystem(const char *name) 340 { 341 struct hotplug_subsys *nh; 342 char *name_buf; 343 344 nh = avl_find_element(&subsystems, name, nh, node); 345 if (nh) 346 return; 347 348 nh = calloc_a(sizeof(struct hotplug_subsys), 349 &name_buf, sizeof(HOTPLUG_OBJECT_PREFIX) + 1 + strlen(name)); 350 if (!nh) 351 return; 352 353 nh->ubus.name = name_buf; 354 name_buf += sprintf(name_buf, "%s", HOTPLUG_OBJECT_PREFIX); 355 nh->node.key = strcpy(name_buf, name); 356 357 nh->ubus.type = &hotplug_object_type; 358 nh->ubus.methods = hotplug_object_type.methods; 359 nh->ubus.n_methods = hotplug_object_type.n_methods; 360 ubus_add_object(ctx, &nh->ubus); 361 avl_insert(&subsystems, &nh->node); 362 } 363 364 static void free_subsystem(struct hotplug_subsys *h) 365 { 366 ubus_remove_object(ctx, &h->ubus); 367 free(h); 368 } 369 370 static void remove_all_subsystems(void) 371 { 372 struct hotplug_subsys *n, *h; 373 374 /* find match subsystem object by name or any if not given */ 375 avl_remove_all_elements(&subsystems, h, node, n) 376 free_subsystem(h); 377 } 378 379 static void remove_subsystem(char *name) 380 { 381 struct hotplug_subsys *h; 382 383 if (!strcmp(name, "button")) 384 return; 385 386 h = avl_find_element(&subsystems, name, h, node); 387 if (!h) 388 return; 389 390 avl_delete(&subsystems, &h->node); 391 free_subsystem(h); 392 } 393 394 static int init_subsystems(void) 395 { 396 DIR *dir; 397 struct dirent *dirent; 398 struct stat st; 399 400 add_subsystem("button"); 401 402 dir = opendir(HOTPLUG_BASEDIR); 403 if (dir == NULL) 404 return ENOENT; 405 406 while ((dirent = readdir(dir))) { 407 /* skip everything but directories */ 408 if (dirent->d_type == DT_UNKNOWN) { 409 if ((fstatat(dirfd(dir), dirent->d_name, &st, AT_SYMLINK_NOFOLLOW) == -1) 410 || !S_ISDIR(st.st_mode)) 411 continue; 412 } else if (dirent->d_type != DT_DIR) 413 continue; 414 415 /* skip '.' and '..' as well as hidden files */ 416 if (dirent->d_name[0] == '.') 417 continue; 418 419 add_subsystem(dirent->d_name); 420 } 421 closedir(dir); 422 423 return 0; 424 } 425 426 static void inotify_read_handler(struct uloop_fd *u, unsigned int events) 427 { 428 int rc; 429 char *p; 430 struct inotify_event *in; 431 432 /* read inotify events */ 433 while ((rc = read(u->fd, inotify_buffer, INOTIFY_SZ)) == -1 && errno == EINTR); 434 435 if (rc <= 0) 436 return; 437 438 /* process events from buffer */ 439 for (p = inotify_buffer; 440 rc - (p - inotify_buffer) >= (int)sizeof(struct inotify_event); 441 p += sizeof(struct inotify_event) + in->len) { 442 in = (struct inotify_event*)p; 443 444 /* skip everything but directories */ 445 if (!(in->mask & IN_ISDIR)) 446 continue; 447 448 if (in->len < 1) 449 continue; 450 451 /* skip hidden files */ 452 if (in->name[0] == '.') 453 continue; 454 455 /* add/remove subsystem objects */ 456 if (in->mask & (IN_CREATE | IN_MOVED_TO)) 457 add_subsystem(in->name); 458 else if (in->mask & (IN_DELETE | IN_MOVED_FROM)) 459 remove_subsystem(in->name); 460 } 461 } 462 463 void hotplug_ubus_event(struct blob_attr *data) 464 { 465 static const struct blobmsg_policy policy = 466 { "SUBSYSTEM", BLOBMSG_TYPE_STRING }; 467 struct hotplug_subsys *h; 468 struct blob_attr *attr; 469 const char *subsys; 470 471 blobmsg_parse_attr(&policy, 1, &attr, data); 472 if (!attr) 473 return; 474 475 subsys = blobmsg_get_string(attr); 476 h = avl_find_element(&subsystems, subsys, h, node); 477 if (!h) 478 return; 479 480 ubus_notify(ctx, &h->ubus, "event", data, -1); 481 } 482 483 void ubus_init_hotplug(struct ubus_context *newctx) 484 { 485 ctx = newctx; 486 remove_all_subsystems(); 487 if (init_subsystems()) { 488 printf("failed to initialize hotplug subsystems from %s\n", HOTPLUG_BASEDIR); 489 return; 490 } 491 fd_inotify_read.fd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC); 492 fd_inotify_read.cb = inotify_read_handler; 493 if (fd_inotify_read.fd == -1) { 494 printf("failed to initialize inotify handler for %s\n", HOTPLUG_BASEDIR); 495 return; 496 } 497 498 inotify_buffer = calloc(1, INOTIFY_SZ); 499 if (!inotify_buffer) 500 return; 501 502 if (inotify_add_watch(fd_inotify_read.fd, HOTPLUG_BASEDIR, 503 IN_CREATE | IN_MOVED_TO | IN_DELETE | IN_MOVED_FROM | IN_ONLYDIR) == -1) 504 return; 505 506 uloop_fd_add(&fd_inotify_read, ULOOP_READ); 507 } 508
This page was automatically generated by LXR 0.3.1. • OpenWrt