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