diff --git a/plugins/sys.c b/plugins/sys.c index 9759e99e..ad048f5b 100644 --- a/plugins/sys.c +++ b/plugins/sys.c @@ -33,40 +33,71 @@ #include "plugin.h" #include "iwatch.h" -#define _PATH_SYSFS_PWR "/sys/class/power_supply" - static struct iwatch iw_sys; -static int num_ac_online; -static int num_ac; -static void sys_cond(int set) + +static int sys_add_path(struct iwatch *iw, char *path) { - char cond[MAX_COND_LEN] = COND_SYS; - - strlcat(cond, "pwr/ac", sizeof(cond)); - if (set) - cond_set_oneshot(cond); - else - cond_clear(cond); + return iwatch_add(iw, path, IN_ONLYDIR | IN_CLOSE_WRITE); } -static int check_online(char *ac) +static void sys_update_conds(char *dir, char *name, uint32_t mask) { - char path[strlen(ac) + 1]; - char *ptr; - int val; + char fn[PATH_MAX]; + char *cond; - strlcpy(path, ac, sizeof(path)); - ptr = strrchr(path, '/'); - if (!ptr) - return 0; - ptr[1] = 0; - strlcat(path, "online", sizeof(path)); + paste(fn, sizeof(fn), dir, name); + cond = strstr(fn, COND_BASE); + if (!cond) + return; - if (fngetint(path, &val)) - return 0; + cond += strlen(COND_BASE) + 1; + _d("cond: %s set: %d", cond, mask & IN_CREATE ? 1 : 0); + cond_update(cond); +} - return val; +/* synthesize events in case of new run dirs */ +static void sys_scandir(struct iwatch *iw, char *dir, int len) +{ + glob_t gl; + size_t i; + char path[len + 6]; + int rc; + + snprintf(path, sizeof(path), "%s/*", dir); + rc = glob(path, GLOB_NOSORT, NULL, &gl); + if (rc && rc != GLOB_NOMATCH) + return; + + for (i = 0; i < gl.gl_pathc; i++) { + _d("scan found %s", gl.gl_pathv[i]); + sys_update_conds(dir, gl.gl_pathv[i], IN_CREATE); + } + globfree(&gl); +} + +/* + * create/remove sub-directory in monitored directory + */ +static void sys_handle_dir(struct iwatch *iw, char *dir, char *name, int mask) +{ + char path[strlen(dir) + strlen(name) + 2]; + struct iwatch_path *iwp; + + paste(path, sizeof(path), dir, name); + _d("path: %s", path); + + iwp = iwatch_find_by_path(iw, path); + + if (mask & IN_CREATE) { + if (!iwp) { + if (!sys_add_path(iw, path)) + sys_scandir(iw, path, sizeof(path)); + } + } else if (mask & IN_DELETE) { + if (iwp) + iwatch_del(&iw_sys, iwp); + } } static void sys_callback(void *arg, int fd, int events) @@ -102,99 +133,34 @@ static void sys_callback(void *arg, int fd, int events) if (!iwp) continue; - if (check_online(iwp->path)) { - if (!num_ac_online) - sys_cond(1); - - if (num_ac_online < num_ac) - num_ac_online++; - } else { - if (num_ac_online > 0) - num_ac_online--; - - if (!num_ac_online) - sys_cond(0); + if (ev->mask & IN_ISDIR) { + sys_handle_dir(&iw_sys, iwp->path, ev->name, ev->mask); + continue; } + + if (ev->mask & (IN_CREATE | IN_DELETE)) + sys_update_conds(iwp->path, ev->name, ev->mask); } } -static int is_ac(char *path, size_t len) -{ - char type[32] = { 0 }; - char *types[] = { - "Mains", - "USB", - "BrickID", - "Wireless", - NULL - }; - FILE *fp; - int i; - - strlcat(path, "/type", len); - fp = fopen(path, "r"); - if (!fp) - return 0; - - if (!fgets(type, sizeof(type), fp)) { - fclose(fp); - return 0; - } - fclose(fp); - - for (i = 0; types[i]; i++) { - if (!strncmp(type, types[i], strlen(types[i]))) - return 1; - } - - return 0; -} - -/* - * Called when base filesystem is up, modules have been probed, - * or insmodded from /etc/finit.conf, so by now we should have - * /sys/class/power_supply/ available for probing. If none is - * found we assert /sys/pwr/ac condition anyway, this is what - * systemd does (ConditionACPower) and also makes most sense. - */ static void sys_init(void *arg) { - struct dirent **d = NULL; - char path[384]; - int i, n; + char sysdir[MAX_ARG_LEN]; + char *path; - if (mkpath(pid_runpath(_PATH_CONDSYS, path, sizeof(path)), 0755) && errno != EEXIST) { + if (mkpath(pid_runpath(_PATH_CONDSYS, sysdir, sizeof(sysdir)), 0755) && errno != EEXIST) { _pe("Failed creating %s condition directory, %s", COND_SYS, _PATH_CONDSYS); return; } - n = scandir(_PATH_SYSFS_PWR, &d, NULL, alphasort); - if (n <= 0) { - iwatch_exit(&iw_sys); + path = realpath(_PATH_CONDSYS, NULL); + if (!path) { + _pe("Cannot figure out real path to %s, aborting", _PATH_CONDSYS); return; } - - for (i = 0; i < n; i++) { - char *nm = d[i]->d_name; - snprintf(path, sizeof(path), "%s/%s", _PATH_SYSFS_PWR, nm); - if (is_ac(path, sizeof(path))) { - num_ac++; - - snprintf(path, sizeof(path), "%s/%s/uevent", _PATH_SYSFS_PWR, nm); - if (check_online(path)) - num_ac_online++; - - if (iwatch_add(&iw_sys, path, IN_CLOSE_NOWRITE)) - _pe("Failed setting up pwr monitor for %s", path); - } - free(d[i]); - } - free(d); - - /* if any power_supply is online, or none can be found */ - if (num_ac == 0 || num_ac_online > 0) - sys_cond(1); + if (iwatch_add(&iw_sys, path, IN_ONLYDIR)) + iwatch_exit(&iw_sys); } static plugin_t plugin = { @@ -206,11 +172,6 @@ PLUGIN_INIT(plugin_init) { int fd; - if (!fisdir(_PATH_SYSFS_PWR)) { - _w("System does not have %s, disabling AC power monitor.", _PATH_SYSFS_PWR); - return; - } - fd = iwatch_init(&iw_sys); if (fd < 0) return; diff --git a/src/.gitignore b/src/.gitignore index c34fc93d..dd3cb9ef 100644 --- a/src/.gitignore +++ b/src/.gitignore @@ -1,6 +1,7 @@ .deps/* finit initctl +keventd logit reboot sulogin diff --git a/src/Makefile.am b/src/Makefile.am index aa3a7b11..7158b8c1 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -8,18 +8,22 @@ AM_LDFLAGS = -export-dynamic endif sbin_PROGRAMS = finit initctl -pkglibexec_PROGRAMS = logit sulogin +pkglibexec_PROGRAMS = keventd logit sulogin +if WATCHDOGD +pkglibexec_PROGRAMS += watchdogd +endif + finit_pkglibdir = $(pkglibdir) finit_pkglib_SCRIPTS = rescue.conf sample.conf +keventd_SOURCES = keventd.c iwatch.c iwatch.h util.c util.h +keventd_CFLAGS = -W -Wall -Wextra -std=gnu99 +keventd_LDADD = $(lite_LIBS) + sulogin_SOURCES = sulogin.c sulogin_CFLAGS = -W -Wall -Wextra -std=gnu99 sulogin_LDADD = -lcrypt -if WATCHDOGD -pkglibexec_PROGRAMS += watchdogd -endif - logit_SOURCES = logit.c logrotate.c logit_CFLAGS = -W -Wall -Wextra -Wno-unused-parameter -std=gnu99 logit_CFLAGS += $(lite_CFLAGS) diff --git a/src/finit.c b/src/finit.c index dfbc0c6c..46c34368 100644 --- a/src/finit.c +++ b/src/finit.c @@ -594,6 +594,12 @@ int main(int argc, char *argv[]) wdog = svc_find_by_nameid("watchdog", "finit"); } + /* + * Start kernel event daemon as soon as possible, if enabled + */ + if (whichp(FINIT_LIBPATH_ "/keventd")) + service_register(SVC_TYPE_SERVICE, "[123456789] cgroup.init " FINIT_LIBPATH_ "/keventd -- Finit kernel event daemon", global_rlimit, NULL); + /* Base FS up, enable standard SysV init signals */ sig_setup(&loop); diff --git a/src/keventd.c b/src/keventd.c new file mode 100644 index 00000000..43b80beb --- /dev/null +++ b/src/keventd.c @@ -0,0 +1,278 @@ +/* Listens to kernel events like AC power status and manages sys/ conditions + * + * Copyright (c) 2021 Joachim Wiberg + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include "lite/lite.h" + +#include "cond.h" +#include "pid.h" +#include "util.h" + +#define _PATH_SYSFS_PWR "/sys/class/power_supply" + +static int num_ac_online; +static int num_ac; + +int debug; /* debug in other modules as well */ + +void logit(int prio, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + if (!debug) + vsyslog(prio, fmt, ap); + else { + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); + } + va_end(ap); +} +#define panic(fmt, args...) { logit(LOG_CRIT, fmt ":%s", ##args, strerror(errno)); exit(1); } +#define warn(fmt, args...) { logit(LOG_WARNING, fmt ":%s", ##args, strerror(errno)); } + +static void sys_cond(char *cond, int set) +{ + char oneshot[256]; + + snprintf(oneshot, sizeof(oneshot), "%s/%s", _PATH_CONDSYS, cond); + if (set) { + if (symlink(_PATH_RECONF, oneshot) && errno != EEXIST) + warn("failed asserting sys/%s", cond); + } else { + if (erase(oneshot) && errno != ENOENT) + warn("failed asserting sys/%s", cond); + } +} + +static int fgetln(char *path, char *buf, size_t len) +{ + FILE *fp; + + fp = fopen(path, "r"); + if (!fp) + return -1; + + if (!fgets(buf, len, fp)) { + fclose(fp); + return -1; + } + + chomp(buf); + fclose(fp); + + return 0; +} + +static int check_online(char *online) +{ + int val; + + if (!online) + return 0; + + val = atoi(online); + logit(LOG_INFO, "AC %s", val ? "connected" : "disconnected"); + + return val; +} + +static int is_ac(char *type) +{ + char *types[] = { + "Mains", + "USB", + "BrickID", + "Wireless", + NULL + }; + int i; + + for (i = 0; types[i]; i++) { + if (!strncmp(type, types[i], strlen(types[i]))) + return 1; + } + + return 0; +} + +static void init(void) +{ + struct dirent **d = NULL; + char *cond_dirs[] = { + _PATH_CONDSYS, + _PATH_CONDSYS "/pwr", + }; + char path[384]; + int i, n; + + for (i = 0; i < (int)NELEMS(cond_dirs); i++) { + if (mkpath(cond_dirs[i], 0755) && errno != EEXIST) { + _pe("Failed creating %s condition directory, %s", COND_SYS, + cond_dirs[i]); + return; + } + } + + n = scandir(_PATH_SYSFS_PWR, &d, NULL, alphasort); + for (i = 0; i < n; i++) { + char *nm = d[i]->d_name; + char buf[10]; + + snprintf(path, sizeof(path), "%s/%s/type", _PATH_SYSFS_PWR, nm); + if (!fgetln(path, buf, sizeof(buf)) && is_ac(buf)) { + num_ac++; + + snprintf(path, sizeof(path), "%s/%s/online", _PATH_SYSFS_PWR, nm); + if (!fgetln(path, buf, sizeof(buf))) { + if (check_online(buf)) + num_ac_online++; + } + } + free(d[i]); + } + + if (n > 0) + free(d); + + /* if any power_supply is online, or none can be found */ + if (num_ac == 0 || num_ac_online > 0) + sys_cond("pwr/ac", 1); +} + +/* + * Started by Finit as soon as possible when base filesystem is up, + * modules have been probed, or insmodded from /etc/finit.conf, so by + * now we should have /sys/class/power_supply/ available for probing. + * If none is found we assert /sys/pwr/ac condition anyway, this is what + * systemd does (ConditionACPower) and also makes most sense. + */ +int main(int argc, char *argv[]) +{ + struct sockaddr_nl nls = { 0 }; + struct pollfd pfd; + char buf[1024]; + + if (argc > 1) { + if (!strcmp(argv[1], "-d")) + debug = 1; + } + + if (!debug) { + openlog("keventd", LOG_CONS | LOG_PID, LOG_DAEMON); + setlogmask(LOG_UPTO(LOG_NOTICE)); + } + + init(); + + pfd.events = POLLIN; + pfd.fd = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT); + if (pfd.fd == -1) + panic("failed creating netlink socket"); + + nls.nl_family = AF_NETLINK; + nls.nl_pid = 0; + nls.nl_groups = -1; + if (bind(pfd.fd, (void *)&nls, sizeof(struct sockaddr_nl))) + panic("bind failed"); + + logit(LOG_DEBUG, "Waiting for events ..."); + while (-1 != poll(&pfd, 1, -1)) { + char *path = NULL; + int ac = 0; + int i, len; + + len = recv(pfd.fd, buf, sizeof(buf), MSG_DONTWAIT); + if (len == -1) { + switch (errno) { + case EINTR: + continue; + case ENOBUFS: + warn("lost events"); + continue; + default: + panic("unhandled"); + break; + } + } + + /* skip libusb events, focus on kernel events/changes */ + if (strncmp(buf, "change@", 7)) + continue; + + /* XXX: currently limited to monitoring this subsystem */ + if (!strstr(buf, "power_supply")) + continue; + + i = 0; + while (i < len) { + char *line = buf + i; + + logit(LOG_DEBUG, "%s", line); + if (!strncmp(line, "DEVPATH=", 8)) { + path = &line[8]; + logit(LOG_DEBUG, "Got path %s", path); + } else if (!strncmp(line, "POWER_SUPPLY_TYPE=", 18)) { + ac = is_ac(&line[18]); + } else if (!strncmp(line, "POWER_SUPPLY_ONLINE=", 20) && ac) { + if (check_online(&line[20])) { + if (!num_ac_online) + sys_cond("pwr/ac", 1); + num_ac_online++; + } else { + if (num_ac_online > 0) + num_ac_online--; + if (!num_ac_online) + sys_cond("pwr/ac", 0); + } + } + + i += strlen(line) + 1; + } + } + close(pfd.fd); + + return 0; +} + +/** + * Local Variables: + * indent-tabs-mode: t + * c-file-style: "linux" + * End: + */