• source navigation  • diff markup  • identifier search  • freetext search  • 

Sources/procd/hotplug-dispatch.c

  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