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>
30 #include <libubox/avl.h>
31 #include <libubox/avl-cmp.h>
32 #include <libubox/list.h>
33 #include <libubox/uloop.h>
38 #define HOTPLUG_BASEDIR "/etc/hotplug.d"
39 #define HOTPLUG_OBJECT_PREFIX "hotplug."
41 #define INOTIFY_SZ (sizeof(struct inotify_event) + PATH_MAX + 1)
43 struct ubus_context
*ctx
;
44 static char *inotify_buffer
;
45 static struct uloop_fd fd_inotify_read
;
47 static LIST_HEAD(subsystems
);
49 extern char **environ
;
51 struct hotplug_subsys
{
52 struct list_head list
;
53 struct ubus_object ubus
;
61 struct hotplug_process
{
62 struct ubus_object
*ubus
;
64 struct uloop_timeout timeout
;
65 struct uloop_process process
;
71 static void env_free(char **envp
)
81 static void hotplug_free(struct hotplug_process
*pc
)
84 globfree(&pc
->globbuf
);
88 static void hotplug_done(struct uloop_process
*c
, int ret
)
90 struct hotplug_process
*pc
= container_of(c
, struct hotplug_process
, process
);
94 uloop_timeout_set(&pc
->timeout
, 50);
97 static void hotplug_exec(struct uloop_timeout
*t
)
99 struct hotplug_process
*pc
= container_of(t
, struct hotplug_process
, timeout
);
102 /* we have reached the last entry in the globbuf */
103 if (pc
->cnt
== pc
->globbuf
.gl_pathc
) {
108 if (asprintf(&script
, ". /lib/functions.sh\n. %s\n", pc
->globbuf
.gl_pathv
[pc
->cnt
++]) == -1) {
113 /* prepare for execve() */
114 exec_argv
[0] = "/bin/sh";
116 exec_argv
[2] = script
;
119 /* set callback in uloop_process */
120 pc
->process
.cb
= hotplug_done
;
121 pc
->process
.pid
= fork();
122 if (pc
->process
.pid
== 0) {
124 exit(execve(exec_argv
[0], exec_argv
, pc
->envp
));
125 } 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
;
192 blobmsg_parse(hotplug_policy
, __HOTPLUG_MAX
, tb
, blobmsg_data(msg
), blobmsg_len(msg
));
194 if (!tb
[HOTPLUG_ENV
])
195 return UBUS_STATUS_INVALID_ARGUMENT
;
199 /* first adding existing environment to avl_tree */
201 envle
= calloc(1, sizeof(struct envlist
));
202 assert(envle
!= NULL
);
203 envle
->env
= strdup(*tmpenv
);
204 envle
->avl
.key
= envle
->env
;
205 avl_insert(&env
, &envle
->avl
);
209 /* then adding additional variables from ubus call */
210 blobmsg_for_each_attr(cur
, tb
[HOTPLUG_ENV
], rem
) {
211 char *enve
= blobmsg_get_string(cur
);
215 if (!strncmp(enve
, "LD_", 3))
218 if (!strcmp(enve
, "PATH"))
221 if (strlen(enve
) < 3)
224 if (!(tmp
= strchr(enve
, '=')))
228 if (validate_envvarname(enve
))
235 if (!strcmp(enve
, "ASYNC=0"))
238 envle
= calloc(1, sizeof(struct envlist
));
239 assert(envle
!= NULL
);
240 envle
->env
= strdup(enve
);
241 envle
->avl
.key
= envle
->env
;
242 if (avl_insert(&env
, &envle
->avl
)) {
243 free((void*)envle
->env
);
249 /* allocating new environment */
250 avl_for_each_element(&env
, envle
, avl
)
253 envp
= calloc(envz
+ 1, sizeof(char *));
254 assert(envp
!= NULL
);
256 /* populating new environment */
258 avl_for_each_element_safe(&env
, envle
, avl
, p
) {
259 envp
[envz
++] = envle
->env
;
260 avl_delete(&env
, &envle
->avl
);
264 /* glob'ing for hotplug scripts */
265 if (asprintf(&globstr
, "%s/%s/*", HOTPLUG_BASEDIR
, subsys
) == -1) {
267 return UBUS_STATUS_UNKNOWN_ERROR
;
270 /* synchronous calls are unsupported for now */
273 return UBUS_STATUS_NOT_SUPPORTED
;
276 pc
= calloc(1, sizeof(struct hotplug_process
));
278 pc
->timeout
.cb
= hotplug_exec
;
283 if (glob(globstr
, GLOB_DOOFFS
, NULL
, &pc
->globbuf
)) {
286 return UBUS_STATUS_OK
;
291 /* asynchronous call to hotplug_exec() */
292 uloop_timeout_set(&pc
->timeout
, 50);
294 return UBUS_STATUS_OK
;
297 static const struct ubus_method hotplug_methods
[] = {
298 UBUS_METHOD("call", hotplug_call
, hotplug_policy
),
301 static struct ubus_object_type hotplug_object_type
=
302 UBUS_OBJECT_TYPE("hotplug", hotplug_methods
);
304 static void add_subsystem(int nlen
, char *newname
)
306 struct hotplug_subsys
*nh
= calloc(1, sizeof(struct hotplug_subsys
));
309 if (asprintf(&name
, "%s%.*s", HOTPLUG_OBJECT_PREFIX
, nlen
, newname
) == -1)
312 /* prepare and add ubus object */
313 nh
->ubus
.name
= name
;
314 nh
->ubus
.type
= &hotplug_object_type
;
315 nh
->ubus
.methods
= hotplug_object_type
.methods
;
316 nh
->ubus
.n_methods
= hotplug_object_type
.n_methods
;
317 list_add(&nh
->list
, &subsystems
);
318 ubus_add_object(ctx
, &nh
->ubus
);
321 static void remove_subsystem(int nlen
, char *name
)
323 struct hotplug_subsys
*n
, *h
;
325 /* find match subsystem object by name or any if not given */
326 list_for_each_entry_safe(h
, n
, &subsystems
, list
) {
327 if (nlen
&& (strlen(h
->ubus
.name
) != strnlen(name
, nlen
) + strlen(HOTPLUG_OBJECT_PREFIX
)))
329 if (nlen
&& (strncmp(name
, &h
->ubus
.name
[strlen(HOTPLUG_OBJECT_PREFIX
)], nlen
)))
333 ubus_remove_object(ctx
, &h
->ubus
);
334 free((void*)h
->ubus
.name
);
339 static int init_subsystems(void)
342 struct dirent
*dirent
;
344 dir
= opendir(HOTPLUG_BASEDIR
);
348 while ((dirent
= readdir(dir
))) {
349 /* skip everything but directories */
350 if (dirent
->d_type
!= DT_DIR
)
353 /* skip '.' and '..' as well as hidden files */
354 if (dirent
->d_name
[0] == '.')
357 add_subsystem(strlen(dirent
->d_name
), dirent
->d_name
);
364 static void inotify_read_handler(struct uloop_fd
*u
, unsigned int events
)
368 struct inotify_event
*in
;
370 /* read inotify events */
371 while ((rc
= read(u
->fd
, inotify_buffer
, INOTIFY_SZ
)) == -1 && errno
== EINTR
);
376 /* process events from buffer */
377 for (p
= inotify_buffer
;
378 rc
- (p
- inotify_buffer
) >= (int)sizeof(struct inotify_event
);
379 p
+= sizeof(struct inotify_event
) + in
->len
) {
380 in
= (struct inotify_event
*)p
;
382 /* skip everything but directories */
383 if (!(in
->mask
& IN_ISDIR
))
389 /* skip hidden files */
390 if (in
->name
[0] == '.')
393 /* add/remove subsystem objects */
394 if (in
->mask
& (IN_CREATE
| IN_MOVED_TO
))
395 add_subsystem(in
->len
, in
->name
);
396 else if (in
->mask
& (IN_DELETE
| IN_MOVED_FROM
))
397 remove_subsystem(in
->len
, in
->name
);
401 void ubus_init_hotplug(struct ubus_context
*newctx
)
404 remove_subsystem(0, NULL
);
405 if (init_subsystems()) {
406 printf("failed to initialize hotplug subsystems from %s\n", HOTPLUG_BASEDIR
);
409 fd_inotify_read
.fd
= inotify_init1(IN_NONBLOCK
| IN_CLOEXEC
);
410 fd_inotify_read
.cb
= inotify_read_handler
;
411 assert(fd_inotify_read
.fd
!= -1);
413 inotify_buffer
= calloc(1, INOTIFY_SZ
);
414 assert(inotify_buffer
!= NULL
);
415 inotify_add_watch(fd_inotify_read
.fd
, HOTPLUG_BASEDIR
, IN_CREATE
| IN_MOVED_TO
| IN_DELETE
| IN_MOVED_FROM
| IN_ONLYDIR
);
416 uloop_fd_add(&fd_inotify_read
, ULOOP_READ
);