2 * Copyright (C) 2021 Daniel Golle <daniel@makrotopia.org>
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
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.
16 #include <sys/inotify.h>
17 #include <sys/types.h>
29 #include <libubox/avl.h>
30 #include <libubox/avl-cmp.h>
31 #include <libubox/list.h>
32 #include <libubox/uloop.h>
37 #define HOTPLUG_BASEDIR "/etc/hotplug.d"
38 #define HOTPLUG_OBJECT_PREFIX "hotplug."
40 #define INOTIFY_SZ (sizeof(struct inotify_event) + PATH_MAX + 1)
42 struct ubus_context
*ctx
;
43 static char *inotify_buffer
;
44 static struct uloop_fd fd_inotify_read
;
46 static LIST_HEAD(subsystems
);
48 extern char **environ
;
50 struct hotplug_subsys
{
51 struct list_head list
;
52 struct ubus_object ubus
;
60 struct hotplug_process
{
61 struct ubus_object
*ubus
;
63 struct uloop_timeout timeout
;
64 struct uloop_process process
;
70 static void env_free(char **envp
)
80 static void hotplug_free(struct hotplug_process
*pc
)
83 globfree(&pc
->globbuf
);
87 static void hotplug_done(struct uloop_process
*c
, int ret
)
89 struct hotplug_process
*pc
= container_of(c
, struct hotplug_process
, process
);
93 uloop_timeout_set(&pc
->timeout
, 50);
96 static void hotplug_exec(struct uloop_timeout
*t
)
98 struct hotplug_process
*pc
= container_of(t
, struct hotplug_process
, timeout
);
101 /* we have reached the last entry in the globbuf */
102 if (pc
->cnt
== pc
->globbuf
.gl_pathc
) {
107 if (asprintf(&script
, ". /lib/functions.sh\n. %s\n", pc
->globbuf
.gl_pathv
[pc
->cnt
++]) == -1) {
112 /* prepare for execve() */
113 exec_argv
[0] = "/bin/sh";
115 exec_argv
[2] = script
;
118 /* set callback in uloop_process */
119 pc
->process
.cb
= hotplug_done
;
120 pc
->process
.pid
= fork();
121 if (pc
->process
.pid
== 0) {
123 exit(execve(exec_argv
[0], exec_argv
, pc
->envp
));
124 } else if (pc
->process
.pid
< 0) {
132 uloop_process_add(&pc
->process
);
135 static int avl_envcmp(const void *k1
, const void *k2
, void *ptr
)
139 tmp
= strchr(k1
, '=');
144 * compare the variable name only, ie. limit strncmp to check
145 * only up to and including the '=' sign
147 return strncmp(k1
, k2
, (tmp
- (char *)k1
) + 1);
150 /* validate NULL-terminated environment variable name */
151 static int validate_envvarname(const char *envvarname
)
153 const char *tmp
= envvarname
;
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') ||
160 /* allow numbers unless they are at the first character */
161 ((tmp
!= envvarname
) && tmp
[0] >= '0' && tmp
[0] <= '9')))
174 static const struct blobmsg_policy hotplug_policy
[__HOTPLUG_MAX
] = {
175 [HOTPLUG_ENV
] = { .name
= "env", .type
= BLOBMSG_TYPE_ARRAY
},
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
)
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
;
187 char **envp
, *globstr
, *tmp
, **tmpenv
;
189 struct hotplug_process
*pc
;
191 int err
= UBUS_STATUS_UNKNOWN_ERROR
;
193 blobmsg_parse(hotplug_policy
, __HOTPLUG_MAX
, tb
, blobmsg_data(msg
), blobmsg_len(msg
));
195 if (!tb
[HOTPLUG_ENV
])
196 return UBUS_STATUS_INVALID_ARGUMENT
;
200 /* first adding existing environment to avl_tree */
202 envle
= calloc(1, sizeof(struct envlist
));
206 envle
->env
= strdup(*tmpenv
);
211 envle
->avl
.key
= envle
->env
;
212 if (avl_insert(&env
, &envle
->avl
) == -1) {
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
);
227 if (!strncmp(enve
, "LD_", 3))
230 if (!strcmp(enve
, "PATH"))
233 if (strlen(enve
) < 3)
236 if (!(tmp
= strchr(enve
, '=')))
240 if (validate_envvarname(enve
))
244 if (!strcmp(enve
, "ASYNC=0"))
247 envle
= calloc(1, sizeof(struct envlist
));
251 envle
->env
= strdup(enve
);
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
);
264 /* synchronous calls are unsupported for now */
266 err
= UBUS_STATUS_NOT_SUPPORTED
;
270 /* allocating new environment */
271 avl_for_each_element(&env
, envle
, avl
)
274 envp
= calloc(envz
+ 1, sizeof(char *));
278 /* populating new environment */
280 avl_for_each_element_safe(&env
, envle
, avl
, p
) {
281 envp
[envz
++] = envle
->env
;
282 avl_delete(&env
, &envle
->avl
);
286 pc
= calloc(1, sizeof(struct hotplug_process
));
289 return UBUS_STATUS_UNKNOWN_ERROR
;
291 pc
->timeout
.cb
= hotplug_exec
;
296 /* glob'ing for hotplug scripts */
297 if (asprintf(&globstr
, "%s/%s/*", HOTPLUG_BASEDIR
, subsys
) == -1) {
299 return UBUS_STATUS_UNKNOWN_ERROR
;
302 if (glob(globstr
, GLOB_DOOFFS
, NULL
, &pc
->globbuf
)) {
305 return UBUS_STATUS_OK
;
310 /* asynchronous call to hotplug_exec() */
311 uloop_timeout_set(&pc
->timeout
, 50);
313 return UBUS_STATUS_OK
;
316 avl_for_each_element_safe(&env
, envle
, avl
, p
) {
320 avl_delete(&env
, &envle
->avl
);
327 static const struct ubus_method hotplug_methods
[] = {
328 UBUS_METHOD("call", hotplug_call
, hotplug_policy
),
331 static struct ubus_object_type hotplug_object_type
=
332 UBUS_OBJECT_TYPE("hotplug", hotplug_methods
);
334 static void add_subsystem(int nlen
, char *newname
)
336 struct hotplug_subsys
*nh
= calloc(1, sizeof(struct hotplug_subsys
));
339 if (asprintf(&name
, "%s%.*s", HOTPLUG_OBJECT_PREFIX
, nlen
, newname
) == -1)
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
);
351 static void remove_subsystem(int nlen
, char *name
)
353 struct hotplug_subsys
*n
, *h
;
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
)))
359 if (nlen
&& (strncmp(name
, &h
->ubus
.name
[strlen(HOTPLUG_OBJECT_PREFIX
)], nlen
)))
363 ubus_remove_object(ctx
, &h
->ubus
);
364 free((void*)h
->ubus
.name
);
369 static int init_subsystems(void)
372 struct dirent
*dirent
;
374 dir
= opendir(HOTPLUG_BASEDIR
);
378 while ((dirent
= readdir(dir
))) {
379 /* skip everything but directories */
380 if (dirent
->d_type
!= DT_DIR
)
383 /* skip '.' and '..' as well as hidden files */
384 if (dirent
->d_name
[0] == '.')
387 add_subsystem(strlen(dirent
->d_name
), dirent
->d_name
);
394 static void inotify_read_handler(struct uloop_fd
*u
, unsigned int events
)
398 struct inotify_event
*in
;
400 /* read inotify events */
401 while ((rc
= read(u
->fd
, inotify_buffer
, INOTIFY_SZ
)) == -1 && errno
== EINTR
);
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
;
412 /* skip everything but directories */
413 if (!(in
->mask
& IN_ISDIR
))
419 /* skip hidden files */
420 if (in
->name
[0] == '.')
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
);
431 void ubus_init_hotplug(struct ubus_context
*newctx
)
434 remove_subsystem(0, NULL
);
435 if (init_subsystems()) {
436 printf("failed to initialize hotplug subsystems from %s\n", HOTPLUG_BASEDIR
);
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
);
446 inotify_buffer
= calloc(1, INOTIFY_SZ
);
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)
454 uloop_fd_add(&fd_inotify_read
, ULOOP_READ
);