Merge pull request #491 from finit-project/devman

Finit 5: udev/eudev replacement

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
This commit is contained in:
Joachim Wiberg authored and GitHub committed 2026-08-18 10:37:14 +02:00
commit 48e85efb88
94 files changed
+9744 -605

No files matched your search

+2 -9
View File
@@ -13,9 +13,6 @@ pkglibexec_PROGRAMS = getty logit runparts tmpfiles
if SULOGIN
pkglibexec_PROGRAMS += sulogin
endif
if KEVENTD
pkglibexec_PROGRAMS += keventd
endif
if WATCHDOGD
pkglibexec_PROGRAMS += watchdogd
endif
@@ -30,11 +27,6 @@ getty_CFLAGS = -W -Wall -Wextra -std=gnu99
getty_CFLAGS += $(lite_CFLAGS)
getty_LDADD = $(lite_LIBS)
keventd_SOURCES = keventd.c iwatch.c iwatch.h util.c util.h
keventd_CFLAGS = -W -Wall -Wextra -std=gnu99
keventd_CFLAGS += $(lite_CFLAGS)
keventd_LDADD = $(lite_LIBS)
runparts_SOURCES = runparts.c
runparts_CFLAGS = -W -Wall -Wextra -std=gnu99
runparts_CFLAGS += $(lite_CFLAGS)
@@ -105,11 +97,12 @@ endif
initctl_SOURCES = initctl.c initctl.h cgutil.c cgutil.h \
client.c client.h cond.c cond.h reboot.c \
serv.c serv.h svc.h util.c util.h log.h
initctl_CPPFLAGS = $(AM_CPPFLAGS)
initctl_CFLAGS = -W -Wall -Wextra -Wno-unused-parameter -std=gnu99
initctl_CFLAGS += $(lite_CFLAGS) $(uev_CFLAGS)
initctl_LDADD = $(lite_LIBS) $(uev_LIBS)
if DBUS
initctl_CPPFLAGS = $(AM_CPPFLAGS) -I$(top_srcdir)/libink
initctl_CPPFLAGS += -I$(top_srcdir)/libink
initctl_LDADD += $(top_builddir)/libink/libink.la
endif
+7 -71
View File
@@ -61,53 +61,19 @@ static int call(int (*action)(svc_t *, void *), char *buf, size_t len)
static int stop(svc_t *svc, void *user_data)
{
(void)user_data;
if (!svc)
return 1;
service_timeout_cancel(svc);
svc_stop(svc);
service_step(svc);
if (!IS_RESERVED_RUNLEVEL(runlevel))
service_step_all(SVC_TYPE_ANY);
return 0;
return service_stop_now(svc);
}
static int start(svc_t *svc, void *user_data)
{
(void)user_data;
if (!svc)
return 1;
service_timeout_cancel(svc);
svc_start(svc);
service_step(svc);
if (!IS_RESERVED_RUNLEVEL(runlevel))
service_step_all(SVC_TYPE_ANY);
return 0;
return service_start_now(svc);
}
/*
* NOTE: this does not wait for svc to be stopped first, that is the
* responsibility of initctl to do. Otherwise we'd block PID 1,
* or introduce some nasty race conditions.
*/
static int restart(svc_t *svc, void *user_data)
{
if (!svc)
return 1;
if (!svc_is_running(svc))
return start(svc, user_data);
service_timeout_cancel(svc);
service_stop(svc);
service_step(svc);
return 0;
(void)user_data;
return service_restart_now(svc);
}
static int reload(svc_t *svc, void *user_data)
@@ -222,16 +188,7 @@ static svc_t *do_find_byc(char *buf, size_t len)
return svc_find_by_cond(input);
}
static void bypass_shutdown(void *);
struct wq emergency = { .cb = bypass_shutdown };
static void bypass_shutdown(void *unused)
{
(void)unused;
cprintf("TIMEOUT TIMEOUT SHUTTING DOWN NOW!!\n");
do_shutdown(halt);
}
/*
* Handle switch_root API command.
@@ -302,10 +259,7 @@ static int do_reboot(int cmd, int timeout, char *buf, size_t len)
return 255;
}
if (timeout > 0) {
emergency.delay = timeout * 1000;
schedule_work(&emergency);
}
shutdown_bypass(timeout);
switch (cmd) {
case INIT_CMD_REBOOT:
@@ -375,8 +329,8 @@ static void api_cb(uev_t *w, void *arg, int events)
{
static svc_t *iter = NULL;
struct init_request rq;
int sd, lvl;
svc_t *svc;
int sd;
(void)arg;
@@ -454,14 +408,6 @@ static void api_cb(uev_t *w, void *arg, int events)
switch (rq.cmd) {
case INIT_CMD_RUNLVL:
/* Allow changing cfglevel in runlevel S */
if (IS_RESERVED_RUNLEVEL(runlevel)) {
if (runlevel != INIT_LEVEL) {
warnx("Cannot abort runlevel 6/0.");
break;
}
}
switch (rq.runlevel) {
case 's':
case 'S':
@@ -470,17 +416,7 @@ static void api_cb(uev_t *w, void *arg, int events)
case '0'...'9':
dbg("Setting new runlevel %c", rq.runlevel);
lvl = rq.runlevel - '0';
if (lvl == 0)
halt = SHUT_OFF;
if (lvl == 6)
halt = SHUT_REBOOT;
/* User requested change in next runlevel */
if (runlevel == INIT_LEVEL)
cfglevel = lvl;
else
sm_runlevel(lvl);
sm_request_runlevel(rq.runlevel - '0');
break;
default:
+4
View File
@@ -7,12 +7,16 @@
#define COND_BASE "finit/cond"
#define COND_DEV "dev/"
#define COND_DRIVER "driver/"
#define COND_CLASS "class/"
#define COND_PID "pid/"
#define COND_SYS "sys/"
#define COND_USR "usr/"
#define _PATH_COND _PATH_VARRUN COND_BASE "/"
#define _PATH_CONDDEV _PATH_COND COND_DEV
#define _PATH_CONDDRIVER _PATH_COND COND_DRIVER
#define _PATH_CONDCLASS _PATH_COND COND_CLASS
#define _PATH_CONDPID _PATH_COND COND_PID
#define _PATH_CONDSYS _PATH_COND COND_SYS
#define _PATH_CONDUSR _PATH_COND COND_USR
+31 -28
View File
@@ -701,9 +701,9 @@ char *conf_escape(const char *str, char *buf, size_t len)
}
/*
* Called by plugins and similar that dynamically generate system
* services. @body holds the block contents, one indented line per
* setting.
* Generates /run/finit/system/<file> for Finit's built-in services,
* which must probe the running system before registering. @fmt holds
* the block contents, one indented line per setting.
*/
void conf_save_service(int type, const char *name, char *file, const char *fmt, ...)
{
@@ -2307,28 +2307,23 @@ int conf_reload(void)
}
/*
* Next, read all *.conf in /lib/finit/system and /etc/finit.d/
* The system files were previously created at runtime by plugins
* but are now regular files that can be overridden by files in
* /etc/finit.d -- similar to how tmfiles.d(5) work. E.g., add
* an override .conf, or an ignore by symlinking to /dev/null
* Next, read all *.conf in /run/finit/system, /lib/finit/system,
* and /etc/finit.d/. The runtime directory holds files Finit
* generates for its built-in services (keventd, watchdogd,
* runparts), read first so later files can reference them in
* if: statements. A same-named file in a later directory
* overrides an earlier one, similar to how tmpfiles.d(5) work,
* so /etc/finit.d, always read last, is how users override any
* built-in or bundled configuration.
*
* The .conf files (and run/task/service stanzas) are parsed and
* started in order. Each directory is sorted alphanumerically
* and then the result is appended to the overall order:
*
* /lib/finit/system/10-hotplug.conf
* /lib/finit/system/90-testserv.conf
* /run/finit/system/dbus.conf
* /run/finit/system/tty.conf
* /etc/finit.d/10-abc.conf
* /etc/finit.d/20-abc.conf
* /etc/finit.d/enabled/1-aaa.conf
* /etc/finit.d/enabled/1-abc.conf
* /etc/finit.d/enabled/2-aaa.conf
* The .conf files (and their run/task/service stanzas) are
* parsed and started in order. Each directory is sorted
* alphanumerically before being appended to the overall order,
* recorded in /run/finit/conf.order at bootstrap. See
* doc/config/files.md for an example.
*/
glob_append(&gl, 0, "%s/*.conf", FINIT_SYSPATH_);
glob_append(&gl, 1, "%s/*.conf", FINIT_RUNPATH_);
glob_append(&gl, 0, "%s/*.conf", FINIT_RUNPATH_);
glob_append(&gl, 1, "%s/*.conf", FINIT_SYSPATH_);
glob_append(&gl, 1, "%s/*.conf", finit_rcsd);
glob_append(&gl, 1, "%s/enabled/*.conf", finit_rcsd);
@@ -2683,15 +2678,23 @@ int conf_init(uev_ctx_t *ctx)
}
#endif
/*
* Start kernel event daemon as soon as possible, if enabled
* Start kernel event daemon as soon as possible, if enabled.
* In passive mode (-p) alongside the hotplug plugin, keventd
* only monitors power supply events (sys/pwr/ac condition).
*/
if (whichp(FINIT_EXECPATH_ "/keventd"))
conf_save_service(SVC_TYPE_SERVICE, "keventd", "keventd.conf",
"\tdescription = \"Finit kernel event daemon\"\n"
"\trunlevel = \"S12345789\"\n"
"\tnotify = \"none\"\n"
"\tdescription = \"kernel event daemon\"\n"
"\trunlevel = \"S0123456789\"\n"
"\tnotify = \"pid\"\n"
"\tcgroup init {}\n"
"\tcommand = \"" FINIT_EXECPATH_ "/keventd\"\n");
"\tcommand = \"" FINIT_EXECPATH_ "/keventd"
#ifdef HAVE_HOTPLUG_PLUGIN
" -p"
#else
" -c"
#endif
"\"\n");
dbg("Allow plugins to register early runlevel 1 run/task/services ...");
plugin_run_hooks(HOOK_SVC_PLUGIN);
+71 -50
View File
@@ -314,46 +314,24 @@ static int manager_get_service(link_call_t *call, void *userdata)
return 0;
}
/* Service-control helpers used by Start/Stop/Restart/Reload. These
* mirror the static helpers in api.c — kept private here so api.c
* stays untouched in this increment. */
/* svc_parse_jobstr-style adapters over the shared service helpers */
static int dbus_apply_stop(svc_t *svc, void *user_data)
{
(void)user_data;
if (!svc)
return 1;
service_timeout_cancel(svc);
svc_stop(svc);
service_step(svc);
if (!IS_RESERVED_RUNLEVEL(runlevel))
service_step_all(SVC_TYPE_ANY);
return 0;
return service_stop_now(svc);
}
static int dbus_apply_start(svc_t *svc, void *user_data)
{
(void)user_data;
if (!svc)
return 1;
service_timeout_cancel(svc);
svc_start(svc);
service_step(svc);
if (!IS_RESERVED_RUNLEVEL(runlevel))
service_step_all(SVC_TYPE_ANY);
return 0;
return service_start_now(svc);
}
static int dbus_apply_restart(svc_t *svc, void *user_data)
{
if (!svc)
return 1;
if (!svc_is_running(svc))
return dbus_apply_start(svc, user_data);
service_timeout_cancel(svc);
service_stop(svc);
service_step(svc);
return 0;
(void)user_data;
return service_restart_now(svc);
}
struct dispatch_ctx {
@@ -454,9 +432,7 @@ static int manager_set_runlevel(link_call_t *call, void *userdata)
"org.freedesktop.DBus.Error.InvalidArgs",
"runlevel must be 0-9 (excluding internal levels)");
if (lvl == 0) halt = SHUT_OFF;
if (lvl == 6) halt = SHUT_REBOOT;
sm_runlevel((int)lvl);
sm_request_runlevel((int)lvl);
(void)link_call_reply(call);
return 0;
@@ -464,11 +440,18 @@ static int manager_set_runlevel(link_call_t *call, void *userdata)
static int dbus_shutdown(link_call_t *call, shutop_t target, int level)
{
uint32_t timeout;
if (link_call_read_u32(call, &timeout) < 0)
return link_call_reply_error(call,
"org.freedesktop.DBus.Error.InvalidArgs",
"expected (u)");
if (IS_RESERVED_RUNLEVEL(runlevel))
return link_call_reply_error(call,
"org.finit.Error.WrongRunlevel",
"Already in shutdown");
halt = target;
shutdown_bypass((int)timeout);
sm_runlevel(level);
(void)link_call_reply(call);
return 0;
@@ -490,11 +473,9 @@ static int signal_one(svc_t *svc, void *udata)
{
int signo = *(int *)udata;
/* Silently skip stopped services -- a multi-match ident
* (e.g. "sshd:*") should not fail the whole call just because
* one of the matches happens to be in a halted state. */
/* Signalling a stopped service is an error, like the legacy API */
if (!svc_is_running(svc))
return 0;
return 1;
return !!kill(svc->pid, signo);
}
@@ -535,6 +516,10 @@ static int manager_signal(link_call_t *call, void *u)
static int manager_suspend(link_call_t *call, void *u)
{
(void)u;
if (IS_RESERVED_RUNLEVEL(runlevel))
return link_call_reply_error(call,
"org.finit.Error.WrongRunlevel",
"Unsupported command in runlevel S and 6/0");
sync();
if (suspend() < 0) {
const char *msg = (errno == EINVAL)
@@ -623,11 +608,11 @@ static const link_method_t manager_methods[] = {
.flags = LINK_METHOD_PRIVILEGED, .handler = manager_reload },
{ .name = "SetRunlevel", .in_sig = "u", .out_sig = "",
.flags = LINK_METHOD_PRIVILEGED, .handler = manager_set_runlevel },
{ .name = "Reboot", .in_sig = "", .out_sig = "",
{ .name = "Reboot", .in_sig = "u", .out_sig = "",
.flags = LINK_METHOD_PRIVILEGED, .handler = manager_reboot },
{ .name = "Poweroff", .in_sig = "", .out_sig = "",
{ .name = "Poweroff", .in_sig = "u", .out_sig = "",
.flags = LINK_METHOD_PRIVILEGED, .handler = manager_poweroff },
{ .name = "Halt", .in_sig = "", .out_sig = "",
{ .name = "Halt", .in_sig = "u", .out_sig = "",
.flags = LINK_METHOD_PRIVILEGED, .handler = manager_halt },
{ .name = "Suspend", .in_sig = "", .out_sig = "",
.flags = LINK_METHOD_PRIVILEGED, .handler = manager_suspend },
@@ -638,10 +623,18 @@ static const link_method_t manager_methods[] = {
{ NULL, NULL, NULL, 0, NULL }
};
static const link_signal_t manager_signals[] = {
{ .name = "ServiceStateChanged", .sig = "sss" },
{ .name = "RunlevelChanged", .sig = "ss" },
{ .name = "ConfigReloaded", .sig = "" },
{ NULL, NULL }
};
static const link_vtable_t manager_vtable = {
.interface = "org.finit.Manager1",
.methods = manager_methods,
.properties = manager_properties,
.signals = manager_signals,
};
/* ---------- org.finit.Service1 (one object per service) ----------
@@ -665,7 +658,11 @@ static int service_action_method(link_call_t *call, void *userdata,
"org.finit.Error.NoSuchService",
"Service object no longer valid");
action(svc, NULL);
if (action(svc, NULL))
return link_call_reply_error(call,
"org.finit.Error.Failed",
"failed on service");
(void)link_call_reply(call);
return 0;
}
@@ -674,18 +671,15 @@ static int service1_start (link_call_t *c, void *u) { return service_action_met
static int service1_stop (link_call_t *c, void *u) { return service_action_method(c, u, dbus_apply_stop); }
static int service1_restart(link_call_t *c, void *u) { return service_action_method(c, u, dbus_apply_restart); }
static int service1_reload(link_call_t *call, void *userdata)
static int dbus_apply_reload(svc_t *svc, void *user_data)
{
svc_t *svc = userdata;
(void)user_data;
return service_reload(svc);
}
if (!svc)
return link_call_reply_error(call,
"org.finit.Error.NoSuchService",
"Service object no longer valid");
service_reload(svc);
(void)link_call_reply(call);
return 0;
static int service1_reload(link_call_t *c, void *u)
{
return service_action_method(c, u, dbus_apply_reload);
}
static const link_method_t service_methods[] = {
@@ -1049,6 +1043,18 @@ void dbus_notify_runlevel_change(int old_level, int new_level)
"RunlevelChanged", "ss", body, (size_t)blen);
}
/*
* Reconfiguration complete: all conditions have been re-asserted by
* their in-Finit owners. External providers whose conditions are
* generation files, rather than the oneshot symlinks keventd uses,
* subscribe to this to re-assert theirs.
*/
void dbus_notify_reload(void)
{
dbus_emit_signal("/org/finit/manager", "org.finit.Manager1",
"ConfigReloaded", "", NULL, 0);
}
/* ---------- org.finit.Cond1 ---------- */
#define COND_PATH_OBJECT "/org/finit/cond"
@@ -1148,7 +1154,7 @@ static int cond1_set_or_clear(link_call_t *call, int do_set)
"org.freedesktop.DBus.Error.InvalidArgs",
"Set/Clear is restricted to usr/* conditions");
if (do_set)
if (do_set) {
/* cond_set_oneshot, not cond_set: a user-asserted condition
* is a symlink to _PATH_RECONF, so it tracks the reconf
* generation automatically and stays "on" across reloads
@@ -1157,8 +1163,17 @@ static int cond1_set_or_clear(link_call_t *call, int do_set)
* semantics for user conditions, and what initctl cond set
* has done forever via the filesystem path. */
cond_set_oneshot(full);
else
if (cond_get(full) != COND_ON)
return link_call_reply_error(call,
"org.finit.Error.Failed",
"failed asserting condition");
} else {
cond_clear(full);
if (cond_get(full) != COND_OFF)
return link_call_reply_error(call,
"org.finit.Error.Failed",
"failed clearing condition");
}
(void)link_call_reply(call);
return 0;
@@ -1256,9 +1271,15 @@ static const link_method_t cond_methods[] = {
{ NULL, NULL, NULL, 0, NULL }
};
static const link_signal_t cond_signals[] = {
{ .name = "ConditionChanged", .sig = "ss" },
{ NULL, NULL }
};
static const link_vtable_t cond_vtable = {
.interface = COND_INTERFACE,
.methods = cond_methods,
.signals = cond_signals,
};
/* ---------- signal emission: ConditionChanged ---------- */
+52 -13
View File
@@ -58,6 +58,30 @@ static struct dev_node *find_node(const char *cond)
return NULL;
}
static int devmon_cond(const char *cond)
{
return !strncmp(cond, COND_DEV, strlen(COND_DEV)) ||
!strncmp(cond, COND_CLASS, strlen(COND_CLASS)) ||
!strncmp(cond, COND_DRIVER, strlen(COND_DRIVER));
}
/*
* keventd asserts class/ and driver/ conditions for devices that never
* get a /dev node, e.g. LEDs and DSA switch ports. For those the
* condition file is the only record of the device.
*/
static int dev_exists(const char *cond)
{
char path[PATH_MAX], tmp[PATH_MAX];
snprintf(path, sizeof(path), "/%s", cond);
if (fexist(path))
return 1;
snprintf(tmp, sizeof(tmp), _PATH_COND "%s", cond);
return fexist(pid_runpath(tmp, path, sizeof(path)));
}
static void drop_node(struct dev_node *node)
{
if (!node)
@@ -75,9 +99,8 @@ static void drop_node(struct dev_node *node)
void devmon_add_cond(const char *cond)
{
struct dev_node *node;
char path[PATH_MAX];
if (!cond || strncmp(cond, "dev/", 4)) {
if (!cond || !devmon_cond(cond)) {
// dbg("no match %s", cond ?: "<NIL>");
return;
}
@@ -105,14 +128,13 @@ void devmon_add_cond(const char *cond)
TAILQ_INSERT_TAIL(&dev_node_list, node, link);
snprintf(path, sizeof(path), "/%s", cond);
if (fexist(path))
if (dev_exists(cond))
cond_set(cond);
}
void devmon_del_cond(const char *cond)
{
if (!cond || strncmp(cond, COND_DEV, strlen(COND_DEV)))
if (!cond || !devmon_cond(cond))
return;
drop_node(find_node(cond));
@@ -121,11 +143,9 @@ void devmon_del_cond(const char *cond)
void devmon_reconf(void)
{
struct dev_node *node, *tmp;
char path[PATH_MAX];
TAILQ_FOREACH_SAFE(node, &dev_node_list, link, tmp) {
snprintf(path, sizeof(path), "/%s", node->name);
if (fexist(path))
if (dev_exists(node->name))
cond_set(node->name);
else
cond_clear(node->name);
@@ -161,6 +181,18 @@ static void devmon_update_conds(char *dir, char *name, uint32_t mask)
char *cond;
paste(fn, sizeof(fn), dir, name);
/*
* Events in the condition tree come from keventd, the file is
* the condition state so only kick affected services, like the
* sys/usr plugins do.
*/
cond = strstr(fn, COND_BASE);
if (cond) {
cond_update(cond + strlen(COND_BASE) + 1);
return;
}
cond = &fn[1];
// dbg("path: %s, mask: %08x, cond: %s", fn, mask, cond);
@@ -273,23 +305,30 @@ static void devmon_cb(uev_t *w, void *arg, int events)
void devmon_init(uev_ctx_t *ctx)
{
const char *cond_dirs[] = { _PATH_CONDDEV, _PATH_CONDCLASS, _PATH_CONDDRIVER };
char dir[MAX_ARG_LEN];
size_t i;
fd = iwatch_init(&iw_devmon);
if (fd < 0)
return;
if (mkpath(pid_runpath(_PATH_CONDDEV, dir, sizeof(dir)), 0755) && errno != EEXIST) {
err(1, "Failed creating %s condition directory, %s", COND_DEV, _PATH_CONDDEV);
return;
}
if (uev_io_init(ctx, &devw, devmon_cb, NULL, fd, UEV_READ)) {
err(1, "Failed setting up I/O callback for /dev watcher");
close(fd);
return;
}
for (i = 0; i < NELEMS(cond_dirs); i++) {
pid_runpath(cond_dirs[i], dir, sizeof(dir));
if (mkpath(dir, 0755) && errno != EEXIST) {
err(1, "Failed creating condition directory %s", dir);
continue;
}
devmon_add_path(&iw_devmon, dir);
}
strlcpy(dir, _PATH_DEV, sizeof(dir));
if (devmon_add_path(&iw_devmon, dir))
iwatch_exit(&iw_devmon);
+16 -3
View File
@@ -1067,13 +1067,26 @@ static int do_cmd(int cmd)
#ifdef HAVE_DBUS
static int do_reboot_dbus(const char *method)
{
int rc = try_dbus_manager(method, "", NULL);
link_client_t *c;
int rc;
if (rc == 0) {
c = link_client_open(FINIT_BUS_SOCKET);
if (!c)
return -1; /* fall back to legacy transport */
/* Timeout in seconds arms the emergency shutdown bypass, 0 = none */
rc = link_client_call_v(c, "/org/finit/manager",
"org.finit.Manager1", method,
"u", (uint32_t)timeout);
if (rc == LINK_CALL_ERROR)
map_dbus_err(c, method, NULL); /* exits */
link_client_close(c);
if (rc == LINK_CALL_OK) {
sleep(5); /* match legacy: wait for finit to shut down */
return 0;
}
return rc; /* 1 = error, -1 = fall back */
return -1; /* fall back */
}
#endif
-319
View File
@@ -1,319 +0,0 @@
/* Listens to kernel events like AC power status and manages sys/ conditions
*
* Copyright (c) 2021-2025 Joachim Wiberg <troglobit@gmail.com>
*
* 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 <errno.h>
#include <dirent.h>
#include <poll.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <syslog.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <linux/types.h>
#include <linux/netlink.h>
#ifdef _LIBITE_LITE
# include <libite/lite.h>
#else
# include <lite/lite.h>
#endif
#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;
static int running = 1;
static int level;
static int logon;
int debug; /* debug in other modules as well */
void logit(int prio, const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
if (logon)
vsyslog(prio, fmt, ap);
else if (prio <= level) {
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 fgetline(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) {
warn("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 (!fgetline(path, buf, sizeof(buf)) && is_ac(buf)) {
num_ac++;
snprintf(path, sizeof(path), "%s/%s/online", _PATH_SYSFS_PWR, nm);
if (!fgetline(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);
}
static void set_logging(int prio)
{
setlogmask(LOG_UPTO(prio));
level = prio;
}
static void toggle_debug(int signo)
{
(void)signo;
debug ^= 1;
set_logging(debug ? LOG_DEBUG : LOG_NOTICE);
}
static void shut_down(int signo)
{
(void)signo;
running = 0;
}
/*
* 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_PID, LOG_DAEMON);
set_logging(LOG_NOTICE);
logon = 1;
}
signal(SIGUSR1, toggle_debug);
signal(SIGTERM, shut_down);
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 (running) {
char *path = NULL;
int ac = 0;
int i, len;
if (-1 == poll(&pfd, 1, -1)) {
if (errno == EINTR)
continue;
break;
}
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");
continue;
}
}
buf[len] = 0;
logit(LOG_DEBUG, "%s", buf);
/* 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:
*/
+1
View File
@@ -54,6 +54,7 @@ void dbus_unregister_service (svc_t *svc);
void dbus_notify_service_state (svc_t *svc, int old_state, int new_state);
void dbus_notify_condition_change(const char *name, const char *state);
void dbus_notify_runlevel_change(int old_level, int new_level);
void dbus_notify_reload (void);
#endif
void conf_flush_events(void);
+13 -1
View File
@@ -193,7 +193,19 @@ int run_parts(char *dir, char *cmd, const char *env[], int progress, int sysv)
#ifndef __FINIT__
static int usage(int rc)
{
warnx("usage: runparts [-bdhps?] DIRECTORY");
fprintf(stderr,
"Usage: runparts [OPTIONS] DIRECTORY\n"
"\n"
"Run all executable scripts in DIRECTORY in alphabetical order.\n"
"Scripts prefixed S<NUM> are called with 'start', K<NUM> with 'stop'.\n"
"\n"
"Options:\n"
" -b Batch mode, disable progress output and ANSI colors\n"
" -d Enable debug output\n"
" -h This help text\n"
" -p Show progress and exit status for each script\n"
" -s SysV compat, only run S<NUM>name and K<NUM>name scripts\n"
"\n");
return rc;
}
+71
View File
@@ -1442,6 +1442,77 @@ int service_stop(svc_t *svc)
return rc;
}
/**
* service_stop_now - Stop a service on user request
* @svc: Service to stop
*
* Shared by the legacy API and the D-Bus methods, like
* service_reload().
*
* Returns:
* POSIX OK(0) on success, non-zero if @svc is NULL.
*/
int service_stop_now(svc_t *svc)
{
if (!svc)
return 1;
service_timeout_cancel(svc);
svc_stop(svc);
service_step(svc);
if (!IS_RESERVED_RUNLEVEL(runlevel))
service_step_all(SVC_TYPE_ANY);
return 0;
}
/**
* service_start_now - Start a service on user request
* @svc: Service to start
*
* Returns:
* POSIX OK(0) on success, non-zero if @svc is NULL.
*/
int service_start_now(svc_t *svc)
{
if (!svc)
return 1;
service_timeout_cancel(svc);
svc_start(svc);
service_step(svc);
if (!IS_RESERVED_RUNLEVEL(runlevel))
service_step_all(SVC_TYPE_ANY);
return 0;
}
/**
* service_restart_now - Restart a service on user request
* @svc: Service to restart
*
* Does not wait for @svc to stop first, that is the caller's
* (initctl) responsibility. Otherwise we'd block PID 1, or
* introduce some nasty race conditions.
*
* Returns:
* POSIX OK(0) on success, non-zero if @svc is NULL.
*/
int service_restart_now(svc_t *svc)
{
if (!svc)
return 1;
if (!svc_is_running(svc))
return service_start_now(svc);
service_timeout_cancel(svc);
service_stop(svc);
service_step(svc);
return 0;
}
/**
* service_reload - Request reload of a service, driven by the state machine
* @svc: Service to reload
+3
View File
@@ -60,6 +60,9 @@ void service_forked (svc_t *svc);
void service_ready (svc_t *svc, int ready);
int service_stop (svc_t *svc);
int service_start_now (svc_t *svc);
int service_stop_now (svc_t *svc);
int service_restart_now (svc_t *svc);
int service_reload (svc_t *svc);
int service_step (svc_t *svc);
void service_step_all (int types);
+50
View File
@@ -94,6 +94,53 @@ static void shutdown_wdt_cb(uev_t *w, void *arg, int events)
do_shutdown(halt);
}
/*
* Emergency shutdown bypass, same job as the shutdown watchdog above
* but armed by the user: `initctl -t SEC reboot`, on either transport.
*/
static void bypass_shutdown(void *unused)
{
(void)unused;
cprintf("TIMEOUT TIMEOUT SHUTTING DOWN NOW!!\n");
do_shutdown(halt);
}
static struct wq emergency = { .cb = bypass_shutdown };
void shutdown_bypass(int timeout)
{
if (timeout <= 0)
return;
emergency.delay = timeout * 1000;
schedule_work(&emergency);
}
/*
* User-requested runlevel change, shared by the legacy API and the
* D-Bus SetRunlevel method. Refused in runlevel 0/6, deferred via
* cfglevel during bootstrap.
*/
void sm_request_runlevel(int lvl)
{
if (IS_RESERVED_RUNLEVEL(runlevel) && runlevel != INIT_LEVEL) {
warnx("Cannot abort runlevel 6/0.");
return;
}
if (lvl == 0)
halt = SHUT_OFF;
if (lvl == 6)
halt = SHUT_REBOOT;
/* Runlevel S: user requested change in next runlevel */
if (runlevel == INIT_LEVEL)
cfglevel = lvl;
else
sm_runlevel(lvl);
}
/*
* Console input callback - handles Ctrl-C during bootstrap wait
*/
@@ -586,6 +633,9 @@ restart:
service_step_all(SVC_TYPE_ANY);
dbg("Reconfiguration done");
#ifdef HAVE_DBUS
dbus_notify_reload();
#endif
sm.state = SM_RUNNING_STATE;
break;
}
+2
View File
@@ -35,6 +35,8 @@ void sm_step (void);
int sm_in_reload (void);
void sm_reload (void);
void sm_runlevel (int newlevel);
void sm_request_runlevel(int lvl);
void shutdown_bypass(int timeout);
#endif /* FINIT_SM_H_ */