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 (!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(int nlen, char *newname) 335 { 336 struct hotplug_subsys *nh = calloc(1, sizeof(struct hotplug_subsys)); 337 char *name; 338 339 if (asprintf(&name, "%s%.*s", HOTPLUG_OBJECT_PREFIX, nlen, newname) == -1) 340 exit(ENOMEM); 341 342 /* prepare and add ubus object */ 343 nh->ubus.name = name; 344 nh->ubus.type = &hotplug_object_type; 345 nh->ubus.methods = hotplug_object_type.methods; 346 nh->ubus.n_methods = hotplug_object_type.n_methods; 347 list_add(&nh->list, &subsystems); 348 ubus_add_object(ctx, &nh->ubus); 349 } 350 351 static void remove_subsystem(int nlen, char *name) 352 { 353 struct hotplug_subsys *n, *h; 354 355 /* find match subsystem object by name or any if not given */ 356 list_for_each_entry_safe(h, n, &subsystems, list) { 357 if (nlen && (strlen(h->ubus.name) != strnlen(name, nlen) + strlen(HOTPLUG_OBJECT_PREFIX))) 358 continue; 359 if (nlen && (strncmp(name, &h->ubus.name[strlen(HOTPLUG_OBJECT_PREFIX)], nlen))) 360 continue; 361 362 list_del(&h->list); 363 ubus_remove_object(ctx, &h->ubus); 364 free((void*)h->ubus.name); 365 free(h); 366 } 367 } 368 369 static int init_subsystems(void) 370 { 371 DIR *dir; 372 struct dirent *dirent; 373 374 dir = opendir(HOTPLUG_BASEDIR); 375 if (dir == NULL) 376 return ENOENT; 377 378 while ((dirent = readdir(dir))) { 379 /* skip everything but directories */ 380 if (dirent->d_type != DT_DIR) 381 continue; 382 383 /* skip '.' and '..' as well as hidden files */ 384 if (dirent->d_name[0] == '.') 385 continue; 386 387 add_subsystem(strlen(dirent->d_name), dirent->d_name); 388 } 389 closedir(dir); 390 391 return 0; 392 } 393 394 static void inotify_read_handler(struct uloop_fd *u, unsigned int events) 395 { 396 int rc; 397 char *p; 398 struct inotify_event *in; 399 400 /* read inotify events */ 401 while ((rc = read(u->fd, inotify_buffer, INOTIFY_SZ)) == -1 && errno == EINTR); 402 403 if (rc <= 0) 404 return; 405 406 /* process events from buffer */ 407 for (p = inotify_buffer; 408 rc - (p - inotify_buffer) >= (int)sizeof(struct inotify_event); 409 p += sizeof(struct inotify_event) + in->len) { 410 in = (struct inotify_event*)p; 411 412 /* skip everything but directories */ 413 if (!(in->mask & IN_ISDIR)) 414 continue; 415 416 if (in->len < 1) 417 continue; 418 419 /* skip hidden files */ 420 if (in->name[0] == '.') 421 continue; 422 423 /* add/remove subsystem objects */ 424 if (in->mask & (IN_CREATE | IN_MOVED_TO)) 425 add_subsystem(in->len, in->name); 426 else if (in->mask & (IN_DELETE | IN_MOVED_FROM)) 427 remove_subsystem(in->len, in->name); 428 } 429 } 430 431 void ubus_init_hotplug(struct ubus_context *newctx) 432 { 433 ctx = newctx; 434 remove_subsystem(0, NULL); 435 if (init_subsystems()) { 436 printf("failed to initialize hotplug subsystems from %s\n", HOTPLUG_BASEDIR); 437 return; 438 } 439 fd_inotify_read.fd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC); 440 fd_inotify_read.cb = inotify_read_handler; 441 if (fd_inotify_read.fd == -1) { 442 printf("failed to initialize inotify handler for %s\n", HOTPLUG_BASEDIR); 443 return; 444 } 445 446 inotify_buffer = calloc(1, INOTIFY_SZ); 447 if (!inotify_buffer) 448 return; 449 450 if (inotify_add_watch(fd_inotify_read.fd, HOTPLUG_BASEDIR, 451 IN_CREATE | IN_MOVED_TO | IN_DELETE | IN_MOVED_FROM | IN_ONLYDIR) == -1) 452 return; 453 454 uloop_fd_add(&fd_inotify_read, ULOOP_READ); 455 } 456
This page was automatically generated by LXR 0.3.1. • OpenWrt