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 LIST_HEAD(subsystems); 47 48 extern char **environ; 49 50 struct hotplug_subsys { 51 struct list_head list; 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 (!strlen(++tmp)) 245 continue; 246 247 if (!strcmp(enve, "ASYNC=0")) 248 async = false; 249 250 envle = calloc(1, sizeof(struct envlist)); 251 if (!envle) 252 goto err_envle; 253 254 envle->env = strdup(enve); 255 if (!envle->env) { 256 free(envle); 257 goto err_envle; 258 } 259 envle->avl.key = envle->env; 260 if (avl_insert(&env, &envle->avl)) { 261 /* do not override existing env values, just skip */ 262 free((void*)envle->env); 263 free(envle); 264 } 265 } 266 267 /* synchronous calls are unsupported for now */ 268 if (!async) { 269 err = UBUS_STATUS_NOT_SUPPORTED; 270 goto err_envle; 271 } 272 273 /* allocating new environment */ 274 avl_for_each_element(&env, envle, avl) 275 ++envz; 276 277 envp = calloc(envz + 1, sizeof(char *)); 278 if (!envp) 279 goto err_envle; 280 281 /* populating new environment */ 282 envz = 0; 283 avl_for_each_element_safe(&env, envle, avl, p) { 284 envp[envz++] = envle->env; 285 avl_delete(&env, &envle->avl); 286 free(envle); 287 } 288 289 pc = calloc(1, sizeof(struct hotplug_process)); 290 if (!pc) { 291 env_free(envp); 292 return UBUS_STATUS_UNKNOWN_ERROR; 293 } 294 pc->timeout.cb = hotplug_exec; 295 pc->envp = envp; 296 pc->cnt = 0; 297 pc->ubus = obj; 298 299 /* glob'ing for hotplug scripts */ 300 if (asprintf(&globstr, "%s/%s/*", HOTPLUG_BASEDIR, subsys) == -1) { 301 hotplug_free(pc); 302 return UBUS_STATUS_UNKNOWN_ERROR; 303 } 304 305 if (glob(globstr, GLOB_DOOFFS, NULL, &pc->globbuf)) { 306 free(globstr); 307 hotplug_free(pc); 308 return UBUS_STATUS_OK; 309 } 310 311 free(globstr); 312 313 /* asynchronous call to hotplug_exec() */ 314 uloop_timeout_set(&pc->timeout, 50); 315 316 return UBUS_STATUS_OK; 317 318 err_envle: 319 avl_for_each_element_safe(&env, envle, avl, p) { 320 if (envle->env) 321 free(envle->env); 322 323 avl_delete(&env, &envle->avl); 324 free(envle); 325 } 326 327 return err; 328 } 329 330 static const struct ubus_method hotplug_methods[] = { 331 UBUS_METHOD("call", hotplug_call, hotplug_policy), 332 }; 333 334 static struct ubus_object_type hotplug_object_type = 335 UBUS_OBJECT_TYPE("hotplug", hotplug_methods); 336 337 static void add_subsystem(int nlen, char *newname) 338 { 339 struct hotplug_subsys *nh = calloc(1, sizeof(struct hotplug_subsys)); 340 char *name; 341 342 if (asprintf(&name, "%s%.*s", HOTPLUG_OBJECT_PREFIX, nlen, newname) == -1) 343 exit(ENOMEM); 344 345 /* prepare and add ubus object */ 346 nh->ubus.name = name; 347 nh->ubus.type = &hotplug_object_type; 348 nh->ubus.methods = hotplug_object_type.methods; 349 nh->ubus.n_methods = hotplug_object_type.n_methods; 350 list_add(&nh->list, &subsystems); 351 ubus_add_object(ctx, &nh->ubus); 352 } 353 354 static void remove_subsystem(int nlen, char *name) 355 { 356 struct hotplug_subsys *n, *h; 357 358 /* find match subsystem object by name or any if not given */ 359 list_for_each_entry_safe(h, n, &subsystems, list) { 360 if (nlen && (strlen(h->ubus.name) != strnlen(name, nlen) + strlen(HOTPLUG_OBJECT_PREFIX))) 361 continue; 362 if (nlen && (strncmp(name, &h->ubus.name[strlen(HOTPLUG_OBJECT_PREFIX)], nlen))) 363 continue; 364 365 list_del(&h->list); 366 ubus_remove_object(ctx, &h->ubus); 367 free((void*)h->ubus.name); 368 free(h); 369 } 370 } 371 372 static int init_subsystems(void) 373 { 374 DIR *dir; 375 struct dirent *dirent; 376 377 dir = opendir(HOTPLUG_BASEDIR); 378 if (dir == NULL) 379 return ENOENT; 380 381 while ((dirent = readdir(dir))) { 382 /* skip everything but directories */ 383 if (dirent->d_type != DT_DIR) 384 continue; 385 386 /* skip '.' and '..' as well as hidden files */ 387 if (dirent->d_name[0] == '.') 388 continue; 389 390 add_subsystem(strlen(dirent->d_name), dirent->d_name); 391 } 392 closedir(dir); 393 394 return 0; 395 } 396 397 static void inotify_read_handler(struct uloop_fd *u, unsigned int events) 398 { 399 int rc; 400 char *p; 401 struct inotify_event *in; 402 403 /* read inotify events */ 404 while ((rc = read(u->fd, inotify_buffer, INOTIFY_SZ)) == -1 && errno == EINTR); 405 406 if (rc <= 0) 407 return; 408 409 /* process events from buffer */ 410 for (p = inotify_buffer; 411 rc - (p - inotify_buffer) >= (int)sizeof(struct inotify_event); 412 p += sizeof(struct inotify_event) + in->len) { 413 in = (struct inotify_event*)p; 414 415 /* skip everything but directories */ 416 if (!(in->mask & IN_ISDIR)) 417 continue; 418 419 if (in->len < 1) 420 continue; 421 422 /* skip hidden files */ 423 if (in->name[0] == '.') 424 continue; 425 426 /* add/remove subsystem objects */ 427 if (in->mask & (IN_CREATE | IN_MOVED_TO)) 428 add_subsystem(in->len, in->name); 429 else if (in->mask & (IN_DELETE | IN_MOVED_FROM)) 430 remove_subsystem(in->len, in->name); 431 } 432 } 433 434 void ubus_init_hotplug(struct ubus_context *newctx) 435 { 436 ctx = newctx; 437 remove_subsystem(0, NULL); 438 if (init_subsystems()) { 439 printf("failed to initialize hotplug subsystems from %s\n", HOTPLUG_BASEDIR); 440 return; 441 } 442 fd_inotify_read.fd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC); 443 fd_inotify_read.cb = inotify_read_handler; 444 if (fd_inotify_read.fd == -1) { 445 printf("failed to initialize inotify handler for %s\n", HOTPLUG_BASEDIR); 446 return; 447 } 448 449 inotify_buffer = calloc(1, INOTIFY_SZ); 450 if (!inotify_buffer) 451 return; 452 453 if (inotify_add_watch(fd_inotify_read.fd, HOTPLUG_BASEDIR, 454 IN_CREATE | IN_MOVED_TO | IN_DELETE | IN_MOVED_FROM | IN_ONLYDIR) == -1) 455 return; 456 457 uloop_fd_add(&fd_inotify_read, ULOOP_READ); 458 } 459
This page was automatically generated by LXR 0.3.1. • OpenWrt