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 asprintf(&script
, ". /lib/functions.sh\n. %s\n", pc
->globbuf
.gl_pathv
[pc
->cnt
++]);
109 /* prepare for execve() */
110 exec_argv
[0] = "/bin/sh";
112 exec_argv
[2] = script
;
115 /* set callback in uloop_process */
116 pc
->process
.cb
= hotplug_done
;
117 pc
->process
.pid
= fork();
118 if (pc
->process
.pid
== 0) {
120 exit(execve(exec_argv
[0], exec_argv
, pc
->envp
));
121 } else if (pc
->process
.pid
< 0) {
128 uloop_process_add(&pc
->process
);
131 static int avl_envcmp(const void *k1
, const void *k2
, void *ptr
)
135 tmp
= strchr(k1
, '=');
140 * compare the variable name only, ie. limit strncmp to check
141 * only up to and including the '=' sign
143 return strncmp(k1
, k2
, (tmp
- (char *)k1
) + 1);
146 /* validate NULL-terminated environment variable name */
147 static int validate_envvarname(const char *envvarname
)
149 const char *tmp
= envvarname
;
151 /* check for illegal characters in env variable name */
152 while (tmp
[0] != '\0') {
153 if (!((tmp
[0] >= 'a' && tmp
[0] <= 'z') ||
154 (tmp
[0] >= 'A' && tmp
[0] <= 'Z') ||
156 /* allow numbers unless they are at the first character */
157 ((tmp
!= envvarname
) && tmp
[0] >= '0' && tmp
[0] <= '9')))
170 static const struct blobmsg_policy hotplug_policy
[__HOTPLUG_MAX
] = {
171 [HOTPLUG_ENV
] = { .name
= "env", .type
= BLOBMSG_TYPE_ARRAY
},
174 static int hotplug_call(struct ubus_context
*ctx
, struct ubus_object
*obj
,
175 struct ubus_request_data
*req
, const char *method
,
176 struct blob_attr
*msg
)
178 const char *subsys
= &obj
->name
[strlen(HOTPLUG_OBJECT_PREFIX
)];
179 struct blob_attr
*tb
[__HOTPLUG_MAX
], *cur
;
180 AVL_TREE(env
, avl_envcmp
, false, NULL
);
181 struct envlist
*envle
, *p
;
183 char **envp
, *globstr
, *tmp
, **tmpenv
;
185 struct hotplug_process
*pc
;
188 blobmsg_parse(hotplug_policy
, __HOTPLUG_MAX
, tb
, blobmsg_data(msg
), blobmsg_len(msg
));
190 if (!tb
[HOTPLUG_ENV
])
191 return UBUS_STATUS_INVALID_ARGUMENT
;
195 /* first adding existing environment to avl_tree */
197 envle
= calloc(1, sizeof(struct envlist
));
198 assert(envle
!= NULL
);
199 envle
->env
= strdup(*tmpenv
);
200 envle
->avl
.key
= envle
->env
;
201 avl_insert(&env
, &envle
->avl
);
205 /* then adding additional variables from ubus call */
206 blobmsg_for_each_attr(cur
, tb
[HOTPLUG_ENV
], rem
) {
207 char *enve
= blobmsg_get_string(cur
);
211 if (!strncmp(enve
, "LD_", 3))
214 if (!strcmp(enve
, "PATH"))
217 if (strlen(enve
) < 3)
220 if (!(tmp
= strchr(enve
, '=')))
224 if (validate_envvarname(enve
))
231 if (!strcmp(enve
, "ASYNC=0"))
234 envle
= calloc(1, sizeof(struct envlist
));
235 assert(envle
!= NULL
);
236 envle
->env
= strdup(enve
);
237 envle
->avl
.key
= envle
->env
;
238 if (avl_insert(&env
, &envle
->avl
)) {
239 free((void*)envle
->env
);
245 /* allocating new environment */
246 avl_for_each_element(&env
, envle
, avl
)
249 envp
= calloc(envz
+ 1, sizeof(char *));
250 assert(envp
!= NULL
);
252 /* populating new environment */
254 avl_for_each_element_safe(&env
, envle
, avl
, p
) {
255 envp
[envz
++] = envle
->env
;
256 avl_delete(&env
, &envle
->avl
);
260 /* glob'ing for hotplug scripts */
261 if (asprintf(&globstr
, "%s/%s/*", HOTPLUG_BASEDIR
, subsys
) == -1) {
263 return UBUS_STATUS_UNKNOWN_ERROR
;
266 /* synchronous calls are unsupported for now */
269 return UBUS_STATUS_NOT_SUPPORTED
;
272 pc
= calloc(1, sizeof(struct hotplug_process
));
274 pc
->timeout
.cb
= hotplug_exec
;
279 if (glob(globstr
, GLOB_DOOFFS
, NULL
, &pc
->globbuf
)) {
282 return UBUS_STATUS_OK
;
287 /* asynchronous call to hotplug_exec() */
288 uloop_timeout_set(&pc
->timeout
, 50);
290 return UBUS_STATUS_OK
;
293 static const struct ubus_method hotplug_methods
[] = {
294 UBUS_METHOD("call", hotplug_call
, hotplug_policy
),
297 static struct ubus_object_type hotplug_object_type
=
298 UBUS_OBJECT_TYPE("hotplug", hotplug_methods
);
300 static void add_subsystem(int nlen
, char *newname
)
302 struct hotplug_subsys
*nh
= calloc(1, sizeof(struct hotplug_subsys
));
305 asprintf(&name
, "%s%.*s", HOTPLUG_OBJECT_PREFIX
, nlen
, newname
);
307 /* prepare and add ubus object */
308 nh
->ubus
.name
= name
;
309 nh
->ubus
.type
= &hotplug_object_type
;
310 nh
->ubus
.methods
= hotplug_object_type
.methods
;
311 nh
->ubus
.n_methods
= hotplug_object_type
.n_methods
;
312 list_add(&nh
->list
, &subsystems
);
313 ubus_add_object(ctx
, &nh
->ubus
);
316 static void remove_subsystem(int nlen
, char *name
)
318 struct hotplug_subsys
*n
, *h
;
320 /* find match subsystem object by name or any if not given */
321 list_for_each_entry_safe(h
, n
, &subsystems
, list
) {
322 if (nlen
&& (strlen(h
->ubus
.name
) != strnlen(name
, nlen
) + strlen(HOTPLUG_OBJECT_PREFIX
)))
324 if (nlen
&& (strncmp(name
, &h
->ubus
.name
[strlen(HOTPLUG_OBJECT_PREFIX
)], nlen
)))
328 ubus_remove_object(ctx
, &h
->ubus
);
329 free((void*)h
->ubus
.name
);
334 static int init_subsystems(void)
337 struct dirent
*dirent
;
339 dir
= opendir(HOTPLUG_BASEDIR
);
343 while ((dirent
= readdir(dir
))) {
344 /* skip everything but directories */
345 if (dirent
->d_type
!= DT_DIR
)
348 /* skip '.' and '..' as well as hidden files */
349 if (dirent
->d_name
[0] == '.')
352 add_subsystem(strlen(dirent
->d_name
), dirent
->d_name
);
359 static void inotify_read_handler(struct uloop_fd
*u
, unsigned int events
)
363 struct inotify_event
*in
;
365 /* read inotify events */
366 while ((rc
= read(u
->fd
, inotify_buffer
, INOTIFY_SZ
)) == -1 && errno
== EINTR
);
371 /* process events from buffer */
372 for (p
= inotify_buffer
;
373 rc
- (p
- inotify_buffer
) >= (int)sizeof(struct inotify_event
);
374 p
+= sizeof(struct inotify_event
) + in
->len
) {
375 in
= (struct inotify_event
*)p
;
377 /* skip everything but directories */
378 if (!(in
->mask
& IN_ISDIR
))
384 /* skip hidden files */
385 if (in
->name
[0] == '.')
388 /* add/remove subsystem objects */
389 if (in
->mask
& (IN_CREATE
| IN_MOVED_TO
))
390 add_subsystem(in
->len
, in
->name
);
391 else if (in
->mask
& (IN_DELETE
| IN_MOVED_FROM
))
392 remove_subsystem(in
->len
, in
->name
);
396 void ubus_init_hotplug(struct ubus_context
*newctx
)
399 remove_subsystem(0, NULL
);
400 if (init_subsystems()) {
401 printf("failed to initialize hotplug subsystems from %s\n", HOTPLUG_BASEDIR
);
404 fd_inotify_read
.fd
= inotify_init1(IN_NONBLOCK
| IN_CLOEXEC
);
405 fd_inotify_read
.cb
= inotify_read_handler
;
406 assert(fd_inotify_read
.fd
!= -1);
408 inotify_buffer
= calloc(1, INOTIFY_SZ
);
409 assert(inotify_buffer
!= NULL
);
410 inotify_add_watch(fd_inotify_read
.fd
, HOTPLUG_BASEDIR
, IN_CREATE
| IN_MOVED_TO
| IN_DELETE
| IN_MOVED_FROM
| IN_ONLYDIR
);
411 uloop_fd_add(&fd_inotify_read
, ULOOP_READ
);