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