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
))
247 if (!strcmp(enve
, "ASYNC=0"))
250 envle
= calloc(1, sizeof(struct envlist
));
254 envle
->env
= strdup(enve
);
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
);
267 /* synchronous calls are unsupported for now */
269 err
= UBUS_STATUS_NOT_SUPPORTED
;
273 /* allocating new environment */
274 avl_for_each_element(&env
, envle
, avl
)
277 envp
= calloc(envz
+ 1, sizeof(char *));
281 /* populating new environment */
283 avl_for_each_element_safe(&env
, envle
, avl
, p
) {
284 envp
[envz
++] = envle
->env
;
285 avl_delete(&env
, &envle
->avl
);
289 pc
= calloc(1, sizeof(struct hotplug_process
));
292 return UBUS_STATUS_UNKNOWN_ERROR
;
294 pc
->timeout
.cb
= hotplug_exec
;
299 /* glob'ing for hotplug scripts */
300 if (asprintf(&globstr
, "%s/%s/*", HOTPLUG_BASEDIR
, subsys
) == -1) {
302 return UBUS_STATUS_UNKNOWN_ERROR
;
305 if (glob(globstr
, GLOB_DOOFFS
, NULL
, &pc
->globbuf
)) {
308 return UBUS_STATUS_OK
;
313 /* asynchronous call to hotplug_exec() */
314 uloop_timeout_set(&pc
->timeout
, 50);
316 return UBUS_STATUS_OK
;
319 avl_for_each_element_safe(&env
, envle
, avl
, p
) {
323 avl_delete(&env
, &envle
->avl
);
330 static const struct ubus_method hotplug_methods
[] = {
331 UBUS_METHOD("call", hotplug_call
, hotplug_policy
),
334 static struct ubus_object_type hotplug_object_type
=
335 UBUS_OBJECT_TYPE("hotplug", hotplug_methods
);
337 static void add_subsystem(int nlen
, char *newname
)
339 struct hotplug_subsys
*nh
= calloc(1, sizeof(struct hotplug_subsys
));
342 if (asprintf(&name
, "%s%.*s", HOTPLUG_OBJECT_PREFIX
, nlen
, newname
) == -1)
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
);
354 static void remove_subsystem(int nlen
, char *name
)
356 struct hotplug_subsys
*n
, *h
;
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
)))
362 if (nlen
&& (strncmp(name
, &h
->ubus
.name
[strlen(HOTPLUG_OBJECT_PREFIX
)], nlen
)))
366 ubus_remove_object(ctx
, &h
->ubus
);
367 free((void*)h
->ubus
.name
);
372 static int init_subsystems(void)
375 struct dirent
*dirent
;
377 dir
= opendir(HOTPLUG_BASEDIR
);
381 while ((dirent
= readdir(dir
))) {
382 /* skip everything but directories */
383 if (dirent
->d_type
!= DT_DIR
)
386 /* skip '.' and '..' as well as hidden files */
387 if (dirent
->d_name
[0] == '.')
390 add_subsystem(strlen(dirent
->d_name
), dirent
->d_name
);
397 static void inotify_read_handler(struct uloop_fd
*u
, unsigned int events
)
401 struct inotify_event
*in
;
403 /* read inotify events */
404 while ((rc
= read(u
->fd
, inotify_buffer
, INOTIFY_SZ
)) == -1 && errno
== EINTR
);
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
;
415 /* skip everything but directories */
416 if (!(in
->mask
& IN_ISDIR
))
422 /* skip hidden files */
423 if (in
->name
[0] == '.')
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
);
434 void ubus_init_hotplug(struct ubus_context
*newctx
)
437 remove_subsystem(0, NULL
);
438 if (init_subsystems()) {
439 printf("failed to initialize hotplug subsystems from %s\n", HOTPLUG_BASEDIR
);
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
);
449 inotify_buffer
= calloc(1, INOTIFY_SZ
);
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)
457 uloop_fd_add(&fd_inotify_read
, ULOOP_READ
);