mirror of
https://github.com/troglobit/finit.git
synced 2026-10-27 12:20:54 +00:00
libink: a brokerless D-Bus implementation for Finit
Finit had no way to answer the question every service manager gets asked: what is running, and change it. D-Bus is how the rest of userspace asks, but linking libdbus, sd-bus or GIO into PID 1 buys a dependency, an allocator and a main loop we do not control. So libink: the wire format, an object tree, and a bus of Finit's own at /run/finit/bus, gated like INIT_SOCKET. It speaks the standard org.freedesktop.DBus, .Peer, .Introspectable interfaces, and Finit's own Manager1, Service1 and Cond1 on top. Methods that change something are marked privileged and answered only for a caller the kernel vouched for, via SO_PEERCRED. Server and client both, since initctl is the first thing that needs to talk to it, and its Start/Stop/Restart/Reload now go over the bus rather than the legacy socket. Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
This commit is contained in:
1 parent
ae9a24f44f
commit
0a269f3298
39 files changed
+5530
-49
No files matched your search
@@ -81,6 +81,9 @@ finit_SOURCES = api.c cgroup.c cgroup.h \
|
||||
if LOGROTATE
|
||||
finit_SOURCES += logrotate.c
|
||||
endif
|
||||
if DBUS
|
||||
finit_SOURCES += dbus.c
|
||||
endif
|
||||
|
||||
pkginclude_HEADERS = cgroup.h cond.h conf.h finit.h helpers.h log.h \
|
||||
plugin.h svc.h service.h
|
||||
@@ -94,6 +97,10 @@ finit_LDADD += ../plugins/libplug.la
|
||||
else
|
||||
finit_LDADD += -ldl
|
||||
endif
|
||||
if DBUS
|
||||
finit_CPPFLAGS += -I$(top_srcdir)/libink
|
||||
finit_LDADD += $(top_builddir)/libink/libink.la
|
||||
endif
|
||||
|
||||
initctl_SOURCES = initctl.c initctl.h cgutil.c cgutil.h \
|
||||
client.c client.h cond.c cond.h reboot.c \
|
||||
@@ -101,6 +108,10 @@ initctl_SOURCES = initctl.c initctl.h cgutil.c cgutil.h \
|
||||
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_LDADD += $(top_builddir)/libink/libink.la
|
||||
endif
|
||||
|
||||
INIT_LNKS = init telinit
|
||||
REBOOT_LNKS = reboot shutdown halt poweroff suspend
|
||||
|
||||
@@ -113,38 +113,7 @@ static int restart(svc_t *svc, void *user_data)
|
||||
static int reload(svc_t *svc, void *user_data)
|
||||
{
|
||||
(void)user_data;
|
||||
|
||||
if (!svc)
|
||||
return 1;
|
||||
|
||||
if (svc_is_blocked(svc))
|
||||
svc_start(svc);
|
||||
else
|
||||
service_timeout_cancel(svc);
|
||||
|
||||
/*
|
||||
* Clear conditions before reload to ensure dependent services
|
||||
* are properly updated. Only needed when the service does NOT
|
||||
* support SIGHUP (noreload), because then it will be stopped
|
||||
* and restarted, so conditions genuinely go away. When the
|
||||
* service handles SIGHUP, its PID and pidfile persist, so the
|
||||
* condition stays valid and dependents should not be disrupted.
|
||||
*
|
||||
* Note: only clear 'ready' for services where the pidfile
|
||||
* inotify handler reasserts it (pid/none). For s6/systemd
|
||||
* services readiness relies on their respective notification
|
||||
* mechanism which may not re-trigger on SIGHUP.
|
||||
*/
|
||||
if (svc_is_noreload(svc)) {
|
||||
svc_cond_clear(svc);
|
||||
if (svc->notify == SVC_NOTIFY_PID || svc->notify == SVC_NOTIFY_NONE)
|
||||
service_ready(svc, 0);
|
||||
}
|
||||
|
||||
svc_mark_dirty(svc);
|
||||
service_step(svc);
|
||||
|
||||
return 0;
|
||||
return service_reload(svc);
|
||||
}
|
||||
|
||||
static int do_stop (char *buf, size_t len) { return call(stop, buf, len); }
|
||||
|
||||
@@ -37,6 +37,13 @@
|
||||
#include "service.h"
|
||||
#include "sm.h"
|
||||
|
||||
#ifdef HAVE_DBUS
|
||||
/* Forward-declared locally to keep cond-w.c independent of the
|
||||
* daemon's private.h (which pulls in svc/plugin headers). The full
|
||||
* prototype lives in private.h for callers in finit's main loop. */
|
||||
void dbus_notify_condition_change(const char *name, const char *state);
|
||||
#endif
|
||||
|
||||
struct cond_boot {
|
||||
TAILQ_ENTRY(cond_boot) link;
|
||||
char *name;
|
||||
@@ -353,6 +360,9 @@ void cond_set(const char *name)
|
||||
if (cond_set_noupdate(name))
|
||||
return;
|
||||
|
||||
#ifdef HAVE_DBUS
|
||||
dbus_notify_condition_change(name, "on");
|
||||
#endif
|
||||
cond_update(name);
|
||||
}
|
||||
|
||||
@@ -383,6 +393,9 @@ void cond_set_oneshot(const char *name)
|
||||
if (cond_set_oneshot_noupdate(name))
|
||||
return;
|
||||
|
||||
#ifdef HAVE_DBUS
|
||||
dbus_notify_condition_change(name, "on");
|
||||
#endif
|
||||
cond_update(name);
|
||||
}
|
||||
|
||||
@@ -404,6 +417,9 @@ void cond_clear(const char *name)
|
||||
if (cond_clear_noupdate(name))
|
||||
return;
|
||||
|
||||
#ifdef HAVE_DBUS
|
||||
dbus_notify_condition_change(name, "off");
|
||||
#endif
|
||||
cond_update(name);
|
||||
}
|
||||
|
||||
|
||||
+869
@@ -0,0 +1,869 @@
|
||||
/* Finit-side glue between the event loop and libink.
|
||||
*
|
||||
* Owns the libink server, accepts new peers, drives each peer's
|
||||
* state machine, and registers the Finit-specific D-Bus object
|
||||
* tree (org.finit.Manager1 et al). Nothing in libink/ depends on
|
||||
* finit-internal types: the boundary lives in this file, by design.
|
||||
*
|
||||
* Copyright (c) 2026 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 "config.h"
|
||||
|
||||
#ifdef HAVE_DBUS
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/queue.h>
|
||||
#include <uev/uev.h>
|
||||
|
||||
#include <ftw.h>
|
||||
|
||||
#include "link.h"
|
||||
#include "path.h"
|
||||
|
||||
#include "finit.h"
|
||||
#include "cond.h"
|
||||
#include "conf.h"
|
||||
#include "log.h"
|
||||
#include "private.h"
|
||||
#include "service.h"
|
||||
#include "sig.h"
|
||||
#include "sm.h"
|
||||
#include "svc.h"
|
||||
|
||||
#define DBUS_MAX_PEERS 64
|
||||
|
||||
struct peer {
|
||||
uev_t watcher;
|
||||
link_connection_t *conn;
|
||||
TAILQ_ENTRY(peer) link;
|
||||
};
|
||||
|
||||
static TAILQ_HEAD(, peer) peers = TAILQ_HEAD_INITIALIZER(peers);
|
||||
static link_server_t *server;
|
||||
static uev_t accept_watcher;
|
||||
static size_t peer_count;
|
||||
|
||||
static void peer_drop(struct peer *p)
|
||||
{
|
||||
uev_io_stop(&p->watcher);
|
||||
link_connection_close(p->conn);
|
||||
TAILQ_REMOVE(&peers, p, link);
|
||||
peer_count--;
|
||||
free(p);
|
||||
}
|
||||
|
||||
static void peer_cb(uev_t *w, void *arg, int events)
|
||||
{
|
||||
struct peer *p = arg;
|
||||
|
||||
(void)w;
|
||||
|
||||
if (UEV_ERROR == events) {
|
||||
peer_drop(p);
|
||||
return;
|
||||
}
|
||||
|
||||
if (link_connection_process(p->conn) < 0)
|
||||
peer_drop(p);
|
||||
}
|
||||
|
||||
static void accept_cb(uev_t *w, void *arg, int events)
|
||||
{
|
||||
(void)arg;
|
||||
|
||||
if (UEV_ERROR == events) {
|
||||
err(1, "D-Bus accept watcher error");
|
||||
return;
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
link_connection_t *conn = NULL;
|
||||
struct peer *p;
|
||||
|
||||
if (link_server_accept(server, &conn) < 0) {
|
||||
if (errno != EAGAIN && errno != EWOULDBLOCK)
|
||||
err(1, "Failed accepting D-Bus client");
|
||||
break;
|
||||
}
|
||||
|
||||
if (peer_count >= DBUS_MAX_PEERS) {
|
||||
logit(LOG_WARNING, "D-Bus peer cap reached (%zu), dropping",
|
||||
peer_count);
|
||||
link_connection_close(conn);
|
||||
continue;
|
||||
}
|
||||
|
||||
p = calloc(1, sizeof(*p));
|
||||
if (!p) {
|
||||
link_connection_close(conn);
|
||||
err(1, "Out of memory accepting D-Bus client");
|
||||
break;
|
||||
}
|
||||
|
||||
p->conn = conn;
|
||||
TAILQ_INSERT_TAIL(&peers, p, link);
|
||||
peer_count++;
|
||||
|
||||
if (uev_io_init(w->ctx, &p->watcher, peer_cb, p,
|
||||
link_connection_get_fd(conn), UEV_READ)) {
|
||||
err(1, "Failed registering D-Bus peer watcher");
|
||||
peer_drop(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------- org.finit.Manager1 ---------- */
|
||||
|
||||
/* Forward decl + buffer-size constant — both consumed by Manager1
|
||||
* handlers below, defined in the Service1 block further down. */
|
||||
#define SERVICE_PATH_PREFIX "/org/finit/service/"
|
||||
#define SERVICE_PATH_PREFIX_LEN (sizeof(SERVICE_PATH_PREFIX) - 1)
|
||||
#define FINIT_SVC_PATH_MAX 512
|
||||
static int service_path_for(svc_t *svc, char *buf, size_t bufsz);
|
||||
|
||||
static int manager_list_services(link_call_t *call, void *userdata)
|
||||
{
|
||||
link_writer_t *w;
|
||||
svc_t *iter = NULL;
|
||||
svc_t *svc;
|
||||
|
||||
(void)userdata;
|
||||
|
||||
w = link_call_reply(call);
|
||||
if (!w)
|
||||
return -1;
|
||||
|
||||
link_w_array_begin(w, 's');
|
||||
for (svc = svc_iterator(&iter, 1); svc; svc = svc_iterator(&iter, 0)) {
|
||||
char ident[MAX_IDENT_LEN];
|
||||
|
||||
svc_ident(svc, ident, sizeof(ident));
|
||||
link_w_string(w, ident);
|
||||
}
|
||||
link_w_array_end(w);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int manager_get_service(link_call_t *call, void *userdata)
|
||||
{
|
||||
const char *ident;
|
||||
svc_t *svc;
|
||||
char path[FINIT_SVC_PATH_MAX];
|
||||
link_writer_t *w;
|
||||
|
||||
(void)userdata;
|
||||
|
||||
if (link_call_read_string(call, &ident) < 0)
|
||||
return link_call_reply_error(call,
|
||||
"org.freedesktop.DBus.Error.InvalidArgs",
|
||||
"expected (s)");
|
||||
|
||||
svc = svc_find_by_str(ident);
|
||||
if (!svc)
|
||||
return link_call_reply_error(call,
|
||||
"org.finit.Error.NoSuchService", ident);
|
||||
|
||||
if (service_path_for(svc, path, sizeof(path)) < 0)
|
||||
return link_call_reply_error(call,
|
||||
"org.finit.Error.Failed",
|
||||
"Path encoding overflow");
|
||||
|
||||
w = link_call_reply(call);
|
||||
if (!w)
|
||||
return -1;
|
||||
link_w_path(w, path);
|
||||
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. */
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
struct dispatch_ctx {
|
||||
int (*action)(svc_t *, void *);
|
||||
int matched;
|
||||
};
|
||||
|
||||
static int dispatch_found(svc_t *svc, void *udata)
|
||||
{
|
||||
struct dispatch_ctx *ctx = udata;
|
||||
|
||||
ctx->matched++;
|
||||
return ctx->action(svc, NULL);
|
||||
}
|
||||
|
||||
static int dispatch_missing(char *job, char *id, void *udata)
|
||||
{
|
||||
(void)job; (void)id; (void)udata;
|
||||
return 0; /* don't penalise the return; we'll check ->matched */
|
||||
}
|
||||
|
||||
/* Apply `action` to every service matched by `ident`. Returns 0 if
|
||||
* at least one service matched and the action succeeded on all;
|
||||
* -1 if no service matched the identity (caller sends NoSuchService). */
|
||||
static int dispatch_action(const char *ident,
|
||||
int (*action)(svc_t *, void *))
|
||||
{
|
||||
char buf[MAX_IDENT_LEN];
|
||||
struct dispatch_ctx ctx = { .action = action };
|
||||
int rc;
|
||||
|
||||
if (!ident || !*ident || strlen(ident) >= sizeof(buf))
|
||||
return -1;
|
||||
memcpy(buf, ident, strlen(ident) + 1);
|
||||
rc = svc_parse_jobstr(buf, sizeof(buf), &ctx,
|
||||
dispatch_found, dispatch_missing);
|
||||
if (ctx.matched == 0)
|
||||
return -1;
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int manager_take_string_method(link_call_t *call,
|
||||
int (*action)(svc_t *, void *))
|
||||
{
|
||||
const char *ident;
|
||||
|
||||
if (link_call_read_string(call, &ident) < 0)
|
||||
return link_call_reply_error(call,
|
||||
"org.freedesktop.DBus.Error.InvalidArgs",
|
||||
"expected (s)");
|
||||
if (dispatch_action(ident, action) != 0)
|
||||
return link_call_reply_error(call,
|
||||
"org.finit.Error.NoSuchService", ident);
|
||||
|
||||
(void)link_call_reply(call); /* empty reply */
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int manager_start (link_call_t *call, void *u) { (void)u; return manager_take_string_method(call, dbus_apply_start); }
|
||||
static int manager_stop (link_call_t *call, void *u) { (void)u; return manager_take_string_method(call, dbus_apply_stop); }
|
||||
static int manager_restart(link_call_t *call, void *u) { (void)u; return manager_take_string_method(call, dbus_apply_restart); }
|
||||
|
||||
static int manager_reload(link_call_t *call, void *userdata)
|
||||
{
|
||||
(void)userdata;
|
||||
/*
|
||||
* Same semantics as api.c: harmless no-op during bootstrap
|
||||
* and shutdown, the client still sees success.
|
||||
*/
|
||||
if (IS_RESERVED_RUNLEVEL(runlevel))
|
||||
warnx("Ignoring reload in runlevel S and 6/0.");
|
||||
else
|
||||
sm_reload();
|
||||
(void)link_call_reply(call);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int manager_set_runlevel(link_call_t *call, void *userdata)
|
||||
{
|
||||
uint32_t lvl;
|
||||
|
||||
(void)userdata;
|
||||
if (link_call_read_u32(call, &lvl) < 0)
|
||||
return link_call_reply_error(call,
|
||||
"org.freedesktop.DBus.Error.InvalidArgs",
|
||||
"expected (u)");
|
||||
if (lvl > 9 || lvl == INIT_LEVEL)
|
||||
return link_call_reply_error(call,
|
||||
"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);
|
||||
|
||||
(void)link_call_reply(call);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dbus_shutdown(link_call_t *call, shutop_t target, int level)
|
||||
{
|
||||
if (IS_RESERVED_RUNLEVEL(runlevel))
|
||||
return link_call_reply_error(call,
|
||||
"org.finit.Error.WrongRunlevel",
|
||||
"Already in shutdown");
|
||||
halt = target;
|
||||
sm_runlevel(level);
|
||||
(void)link_call_reply(call);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int manager_reboot (link_call_t *c, void *u) { (void)u; return dbus_shutdown(c, SHUT_REBOOT, 6); }
|
||||
static int manager_poweroff(link_call_t *c, void *u) { (void)u; return dbus_shutdown(c, SHUT_OFF, 0); }
|
||||
static int manager_halt (link_call_t *c, void *u) { (void)u; return dbus_shutdown(c, SHUT_HALT, 0); }
|
||||
|
||||
static const link_method_t manager_methods[] = {
|
||||
{ .name = "ListServices", .in_sig = "", .out_sig = "as",
|
||||
.handler = manager_list_services },
|
||||
{ .name = "GetService", .in_sig = "s", .out_sig = "o",
|
||||
.handler = manager_get_service },
|
||||
{ .name = "Start", .in_sig = "s", .out_sig = "",
|
||||
.flags = LINK_METHOD_PRIVILEGED, .handler = manager_start },
|
||||
{ .name = "Stop", .in_sig = "s", .out_sig = "",
|
||||
.flags = LINK_METHOD_PRIVILEGED, .handler = manager_stop },
|
||||
{ .name = "Restart", .in_sig = "s", .out_sig = "",
|
||||
.flags = LINK_METHOD_PRIVILEGED, .handler = manager_restart },
|
||||
{ .name = "Reload", .in_sig = "", .out_sig = "",
|
||||
.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 = "",
|
||||
.flags = LINK_METHOD_PRIVILEGED, .handler = manager_reboot },
|
||||
{ .name = "Poweroff", .in_sig = "", .out_sig = "",
|
||||
.flags = LINK_METHOD_PRIVILEGED, .handler = manager_poweroff },
|
||||
{ .name = "Halt", .in_sig = "", .out_sig = "",
|
||||
.flags = LINK_METHOD_PRIVILEGED, .handler = manager_halt },
|
||||
{ NULL, NULL, NULL, 0, NULL }
|
||||
};
|
||||
|
||||
static const link_vtable_t manager_vtable = {
|
||||
.interface = "org.finit.Manager1",
|
||||
.methods = manager_methods,
|
||||
};
|
||||
|
||||
/* ---------- org.finit.Service1 (one object per service) ----------
|
||||
*
|
||||
* Per-service object at /org/finit/service/<encoded-identity>.
|
||||
* The vtable's `userdata` is the svc_t * for the specific service.
|
||||
* Registration is driven dynamically from svc_new()/svc_del() via
|
||||
* dbus_register_service() / dbus_unregister_service() below. */
|
||||
|
||||
/* SERVICE_PATH_PREFIX / SERVICE_PATH_PREFIX_LEN / FINIT_SVC_PATH_MAX
|
||||
* defined near the top of the file so Manager1.GetService can refer
|
||||
* to them. */
|
||||
|
||||
static int service_action_method(link_call_t *call, void *userdata,
|
||||
int (*action)(svc_t *, void *))
|
||||
{
|
||||
svc_t *svc = userdata;
|
||||
|
||||
if (!svc)
|
||||
return link_call_reply_error(call,
|
||||
"org.finit.Error.NoSuchService",
|
||||
"Service object no longer valid");
|
||||
|
||||
action(svc, NULL);
|
||||
(void)link_call_reply(call);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int service1_start (link_call_t *c, void *u) { return service_action_method(c, u, dbus_apply_start); }
|
||||
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)
|
||||
{
|
||||
svc_t *svc = userdata;
|
||||
|
||||
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 const link_method_t service_methods[] = {
|
||||
{ .name = "Start", .in_sig = "", .out_sig = "",
|
||||
.flags = LINK_METHOD_PRIVILEGED, .handler = service1_start },
|
||||
{ .name = "Stop", .in_sig = "", .out_sig = "",
|
||||
.flags = LINK_METHOD_PRIVILEGED, .handler = service1_stop },
|
||||
{ .name = "Restart", .in_sig = "", .out_sig = "",
|
||||
.flags = LINK_METHOD_PRIVILEGED, .handler = service1_restart },
|
||||
{ .name = "Reload", .in_sig = "", .out_sig = "",
|
||||
.flags = LINK_METHOD_PRIVILEGED, .handler = service1_reload },
|
||||
{ NULL, NULL, NULL, 0, NULL }
|
||||
};
|
||||
|
||||
static const link_vtable_t service_vtable = {
|
||||
.interface = "org.finit.Service1",
|
||||
.methods = service_methods,
|
||||
};
|
||||
|
||||
/* Build the canonical object path for a service. Identity is
|
||||
* "name" for single-instance services, "name:id" otherwise. */
|
||||
static int service_path_for(svc_t *svc, char *buf, size_t bufsz)
|
||||
{
|
||||
char ident[MAX_IDENT_LEN];
|
||||
size_t plen = SERVICE_PATH_PREFIX_LEN;
|
||||
int enc;
|
||||
|
||||
if (bufsz <= plen)
|
||||
return -1;
|
||||
memcpy(buf, SERVICE_PATH_PREFIX, plen);
|
||||
|
||||
svc_ident(svc, ident, sizeof(ident));
|
||||
enc = link_path_encode(ident, buf + plen, bufsz - plen);
|
||||
if (enc < 0)
|
||||
return -1;
|
||||
return (int)plen + enc;
|
||||
}
|
||||
|
||||
void dbus_register_service(svc_t *svc)
|
||||
{
|
||||
char path[FINIT_SVC_PATH_MAX];
|
||||
|
||||
if (!server || !svc)
|
||||
return;
|
||||
if (service_path_for(svc, path, sizeof(path)) < 0)
|
||||
return;
|
||||
|
||||
if (link_server_add_object(server, path, &service_vtable, svc) < 0)
|
||||
logit(LOG_WARNING, "dbus: failed registering %s", path);
|
||||
}
|
||||
|
||||
void dbus_unregister_service(svc_t *svc)
|
||||
{
|
||||
char path[FINIT_SVC_PATH_MAX];
|
||||
|
||||
if (!server || !svc)
|
||||
return;
|
||||
if (service_path_for(svc, path, sizeof(path)) < 0)
|
||||
return;
|
||||
|
||||
(void)link_server_remove_object(server, path);
|
||||
}
|
||||
|
||||
/* ---------- signal emission: ServiceStateChanged ---------- */
|
||||
|
||||
/*
|
||||
* Coarse svc_state_t -> string. svc_status() in svc.h returns a
|
||||
* richer string that also considers svc->block, but emitting just
|
||||
* the state-machine state is enough for clients to track lifecycle
|
||||
* transitions. Keep the strings stable -- they're a wire-API
|
||||
* commitment once shipped.
|
||||
*
|
||||
* No `default:` on purpose: a new SVC_*_STATE added to svc.h must
|
||||
* also pick a wire name here, and -Wall (-Wswitch) flags the
|
||||
* missing case.
|
||||
*/
|
||||
static const char *state_name(svc_state_t s)
|
||||
{
|
||||
switch (s) {
|
||||
case SVC_HALTED_STATE: return "halted";
|
||||
case SVC_DONE_STATE: return "done";
|
||||
case SVC_DEAD_STATE: return "dead";
|
||||
case SVC_CLEANUP_STATE: return "cleanup";
|
||||
case SVC_TEARDOWN_STATE: return "teardown";
|
||||
case SVC_STOPPING_STATE: return "stopping";
|
||||
case SVC_SETUP_STATE: return "setup";
|
||||
case SVC_PAUSED_STATE: return "paused";
|
||||
case SVC_WAITING_STATE: return "waiting";
|
||||
case SVC_STARTING_STATE: return "starting";
|
||||
case SVC_RUNNING_STATE: return "running";
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
void dbus_notify_service_state(svc_t *svc, int old_state, int new_state)
|
||||
{
|
||||
uint8_t body[256];
|
||||
link_writer_t w;
|
||||
struct peer *p;
|
||||
char ident[MAX_IDENT_LEN];
|
||||
ssize_t blen;
|
||||
svc_state_t o = (svc_state_t)old_state;
|
||||
svc_state_t n = (svc_state_t)new_state;
|
||||
|
||||
if (!server || !svc)
|
||||
return;
|
||||
if (TAILQ_EMPTY(&peers))
|
||||
return; /* nobody could possibly be listening */
|
||||
|
||||
svc_ident(svc, ident, sizeof(ident));
|
||||
|
||||
link_writer_init(&w, body, sizeof(body));
|
||||
link_w_string(&w, ident);
|
||||
link_w_string(&w, state_name(o));
|
||||
link_w_string(&w, state_name(n));
|
||||
blen = link_writer_finish(&w);
|
||||
if (blen < 0)
|
||||
return;
|
||||
|
||||
TAILQ_FOREACH(p, &peers, link)
|
||||
(void)link_connection_emit_signal(p->conn,
|
||||
"/org/finit/manager",
|
||||
"org.finit.Manager1",
|
||||
"ServiceStateChanged",
|
||||
"sss",
|
||||
body, (size_t)blen);
|
||||
}
|
||||
|
||||
/* ---------- org.finit.Cond1 ---------- */
|
||||
|
||||
#define COND_PATH_OBJECT "/org/finit/cond"
|
||||
#define COND_INTERFACE "org.finit.Cond1"
|
||||
|
||||
/* Cond1.Set/Clear refuse anything that isn't a usr/ condition --
|
||||
* pid/, sys/, hook/ are owned by Finit's state machine and giving
|
||||
* clients write access there would let them corrupt service state.
|
||||
* Bare names ("foo") are normalised to "usr/foo" the same way
|
||||
* initctl does. The returned pointer is valid for the duration
|
||||
* of the caller's stack frame (`buf` must be at least 128 bytes). */
|
||||
static const char *normalise_usr_cond(const char *name, char *buf, size_t bufsz)
|
||||
{
|
||||
const char *tail;
|
||||
|
||||
if (!name || !*name)
|
||||
return NULL;
|
||||
if (strchr(name, '.'))
|
||||
return NULL;
|
||||
|
||||
if (strchr(name, '/')) {
|
||||
if (strncmp(name, "usr/", 4) != 0)
|
||||
return NULL;
|
||||
tail = name + 4;
|
||||
/* Match initctl's policy: no further slashes in the tail,
|
||||
* and no empty tail ("usr/" alone). */
|
||||
if (!*tail || strchr(tail, '/'))
|
||||
return NULL;
|
||||
if (strlen(name) >= bufsz)
|
||||
return NULL;
|
||||
memcpy(buf, name, strlen(name) + 1);
|
||||
return buf;
|
||||
}
|
||||
|
||||
if ((size_t)snprintf(buf, bufsz, "usr/%s", name) >= bufsz)
|
||||
return NULL;
|
||||
return buf;
|
||||
}
|
||||
|
||||
/* Reject names that would escape /run/finit/cond/. cond_get(name)
|
||||
* boils down to fopen(_PATH_COND + name), so without this check any
|
||||
* caller can make PID 1 open arbitrary files -- a path traversal
|
||||
* primitive that also stalls PID 1 if pointed at a FIFO or a slow
|
||||
* device. Legal cond names look like "usr/foo", "pid/sshd",
|
||||
* "service/keventd/ready"; no leading slash, no ".." segment. */
|
||||
static int cond_name_valid(const char *name)
|
||||
{
|
||||
const char *p;
|
||||
|
||||
if (!name || !*name || *name == '/')
|
||||
return 0;
|
||||
for (p = name; *p; p++) {
|
||||
if (*p == '.' && p[1] == '.' &&
|
||||
(p[2] == '\0' || p[2] == '/'))
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int cond1_get(link_call_t *call, void *userdata)
|
||||
{
|
||||
const char *name;
|
||||
link_writer_t *w;
|
||||
|
||||
(void)userdata;
|
||||
|
||||
if (link_call_read_string(call, &name) < 0)
|
||||
return link_call_reply_error(call,
|
||||
"org.freedesktop.DBus.Error.InvalidArgs",
|
||||
"expected (s)");
|
||||
if (!cond_name_valid(name))
|
||||
return link_call_reply_error(call,
|
||||
"org.freedesktop.DBus.Error.InvalidArgs",
|
||||
"invalid condition name");
|
||||
|
||||
w = link_call_reply(call);
|
||||
if (!w)
|
||||
return -1;
|
||||
link_w_string(w, condstr(cond_get(name)));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cond1_set_or_clear(link_call_t *call, int do_set)
|
||||
{
|
||||
const char *name;
|
||||
char buf[128];
|
||||
const char *full;
|
||||
|
||||
if (link_call_read_string(call, &name) < 0)
|
||||
return link_call_reply_error(call,
|
||||
"org.freedesktop.DBus.Error.InvalidArgs",
|
||||
"expected (s)");
|
||||
|
||||
full = normalise_usr_cond(name, buf, sizeof(buf));
|
||||
if (!full)
|
||||
return link_call_reply_error(call,
|
||||
"org.freedesktop.DBus.Error.InvalidArgs",
|
||||
"Set/Clear is restricted to usr/* conditions");
|
||||
|
||||
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
|
||||
* and runlevel switches. cond_set() writes a fixed
|
||||
* generation that goes "flux" on the next reload -- wrong
|
||||
* semantics for user conditions, and what initctl cond set
|
||||
* has done forever via the filesystem path. */
|
||||
cond_set_oneshot(full);
|
||||
else
|
||||
cond_clear(full);
|
||||
|
||||
(void)link_call_reply(call);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cond1_set (link_call_t *c, void *u) { (void)u; return cond1_set_or_clear(c, 1); }
|
||||
static int cond1_clear(link_call_t *c, void *u) { (void)u; return cond1_set_or_clear(c, 0); }
|
||||
|
||||
/* nftw() can't pass user data so a single static handle ferries the
|
||||
* writer into the callback. Safe because dispatch is single-threaded. */
|
||||
static link_writer_t *cond_walk_writer;
|
||||
static int cond_walk_dump;
|
||||
|
||||
static int cond_walk_cb(const char *fpath, const struct stat *sb,
|
||||
int tflag, struct FTW *ftwbuf)
|
||||
{
|
||||
const char *name;
|
||||
const char *state;
|
||||
size_t prefix_len;
|
||||
|
||||
(void)sb;
|
||||
(void)ftwbuf;
|
||||
|
||||
if (tflag != FTW_F)
|
||||
return 0;
|
||||
if (!strcmp(fpath, _PATH_RECONF))
|
||||
return 0;
|
||||
|
||||
prefix_len = strlen(_PATH_COND);
|
||||
if (strlen(fpath) <= prefix_len)
|
||||
return 0;
|
||||
name = fpath + prefix_len;
|
||||
|
||||
if (cond_walk_dump) {
|
||||
state = condstr(cond_get_path(fpath));
|
||||
link_w_struct_begin(cond_walk_writer);
|
||||
link_w_string(cond_walk_writer, name);
|
||||
link_w_string(cond_walk_writer, state);
|
||||
link_w_struct_end(cond_walk_writer);
|
||||
} else {
|
||||
link_w_string(cond_walk_writer, name);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cond1_list(link_call_t *call, void *userdata)
|
||||
{
|
||||
link_writer_t *w;
|
||||
|
||||
(void)userdata;
|
||||
|
||||
w = link_call_reply(call);
|
||||
if (!w)
|
||||
return -1;
|
||||
|
||||
link_w_array_begin(w, 's');
|
||||
cond_walk_writer = w;
|
||||
cond_walk_dump = 0;
|
||||
(void)nftw(_PATH_COND, cond_walk_cb, 20, 0);
|
||||
cond_walk_writer = NULL;
|
||||
link_w_array_end(w);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cond1_dump(link_call_t *call, void *userdata)
|
||||
{
|
||||
link_writer_t *w;
|
||||
|
||||
(void)userdata;
|
||||
|
||||
w = link_call_reply(call);
|
||||
if (!w)
|
||||
return -1;
|
||||
|
||||
link_w_array_begin(w, '(');
|
||||
cond_walk_writer = w;
|
||||
cond_walk_dump = 1;
|
||||
(void)nftw(_PATH_COND, cond_walk_cb, 20, 0);
|
||||
cond_walk_writer = NULL;
|
||||
link_w_array_end(w);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const link_method_t cond_methods[] = {
|
||||
{ .name = "Get", .in_sig = "s", .out_sig = "s",
|
||||
.handler = cond1_get },
|
||||
{ .name = "Set", .in_sig = "s", .out_sig = "",
|
||||
.flags = LINK_METHOD_PRIVILEGED, .handler = cond1_set },
|
||||
{ .name = "Clear", .in_sig = "s", .out_sig = "",
|
||||
.flags = LINK_METHOD_PRIVILEGED, .handler = cond1_clear },
|
||||
{ .name = "List", .in_sig = "", .out_sig = "as",
|
||||
.handler = cond1_list },
|
||||
{ .name = "Dump", .in_sig = "", .out_sig = "a(ss)",
|
||||
.handler = cond1_dump },
|
||||
{ NULL, NULL, NULL, 0, NULL }
|
||||
};
|
||||
|
||||
static const link_vtable_t cond_vtable = {
|
||||
.interface = COND_INTERFACE,
|
||||
.methods = cond_methods,
|
||||
};
|
||||
|
||||
/* ---------- signal emission: ConditionChanged ---------- */
|
||||
|
||||
void dbus_notify_condition_change(const char *name, const char *state)
|
||||
{
|
||||
uint8_t body[256];
|
||||
link_writer_t w;
|
||||
struct peer *p;
|
||||
ssize_t blen;
|
||||
|
||||
if (!server || !name || !state)
|
||||
return;
|
||||
if (TAILQ_EMPTY(&peers))
|
||||
return;
|
||||
|
||||
link_writer_init(&w, body, sizeof(body));
|
||||
link_w_string(&w, name);
|
||||
link_w_string(&w, state);
|
||||
blen = link_writer_finish(&w);
|
||||
if (blen < 0)
|
||||
return;
|
||||
|
||||
TAILQ_FOREACH(p, &peers, link)
|
||||
(void)link_connection_emit_signal(p->conn,
|
||||
COND_PATH_OBJECT,
|
||||
COND_INTERFACE,
|
||||
"ConditionChanged",
|
||||
"ss",
|
||||
body, (size_t)blen);
|
||||
}
|
||||
|
||||
/* ---------- init / exit ---------- */
|
||||
|
||||
int dbus_init(uev_ctx_t *ctx)
|
||||
{
|
||||
dbg("Setting up D-Bus listening socket at %s ...", FINIT_BUS_SOCKET);
|
||||
|
||||
if (link_server_new(&server, FINIT_BUS_SOCKET) < 0) {
|
||||
err(1, "Failed binding D-Bus socket %s", FINIT_BUS_SOCKET);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (link_server_add_object(server, "/org/finit/manager",
|
||||
&manager_vtable, NULL) < 0) {
|
||||
err(1, "Failed registering Manager1 object");
|
||||
link_server_free(server);
|
||||
server = NULL;
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (link_server_add_object(server, COND_PATH_OBJECT,
|
||||
&cond_vtable, NULL) < 0) {
|
||||
err(1, "Failed registering Cond1 object");
|
||||
link_server_free(server);
|
||||
server = NULL;
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (uev_io_init(ctx, &accept_watcher, accept_cb, NULL,
|
||||
link_server_get_fd(server), UEV_READ)) {
|
||||
err(1, "Failed registering D-Bus accept watcher");
|
||||
link_server_free(server);
|
||||
server = NULL;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Register Service1 objects for every service already loaded.
|
||||
* Subsequent svc_new()/svc_del() calls into
|
||||
* dbus_register_service()/dbus_unregister_service(). */
|
||||
{
|
||||
svc_t *iter = NULL;
|
||||
svc_t *svc;
|
||||
|
||||
for (svc = svc_iterator(&iter, 1); svc;
|
||||
svc = svc_iterator(&iter, 0))
|
||||
dbus_register_service(svc);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int dbus_exit(void)
|
||||
{
|
||||
struct peer *p;
|
||||
|
||||
uev_io_stop(&accept_watcher);
|
||||
|
||||
while ((p = TAILQ_FIRST(&peers)))
|
||||
peer_drop(p);
|
||||
|
||||
if (server) {
|
||||
link_server_free(server);
|
||||
server = NULL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif /* HAVE_DBUS */
|
||||
|
||||
/**
|
||||
* Local Variables:
|
||||
* indent-tabs-mode: t
|
||||
* c-file-style: "linux"
|
||||
* End:
|
||||
*/
|
||||
@@ -780,6 +780,11 @@ int main(int argc, char *argv[])
|
||||
dbg("Starting initctl API responder ...");
|
||||
api_init(&loop);
|
||||
|
||||
#ifdef HAVE_DBUS
|
||||
dbg("Starting D-Bus listener ...");
|
||||
dbus_init(&loop);
|
||||
#endif
|
||||
|
||||
dbg("Starting service interval monitor ...");
|
||||
service_init(&loop);
|
||||
|
||||
|
||||
@@ -71,6 +71,7 @@
|
||||
#define BUF_SIZE 4096
|
||||
|
||||
#define INIT_SOCKET _PATH_VARRUN "finit/socket"
|
||||
#define FINIT_BUS_SOCKET _PATH_VARRUN "finit/bus"
|
||||
#define INIT_MAGIC 0x03091969
|
||||
#define INIT_LEVEL 10
|
||||
|
||||
|
||||
+130
-6
@@ -268,19 +268,105 @@ static int do_startstop(int cmd, char *arg)
|
||||
return do_svc(cmd, arg);
|
||||
}
|
||||
|
||||
static int do_start (char *arg) { return do_startstop(INIT_CMD_START_SVC, arg); }
|
||||
static int do_stop (char *arg) { return do_startstop(INIT_CMD_STOP_SVC, arg); }
|
||||
#ifdef HAVE_DBUS
|
||||
#include "link.h"
|
||||
|
||||
/* Try the D-Bus path for a Manager1 method. Returns:
|
||||
* 0 succeeded via D-Bus
|
||||
* 1 D-Bus replied with an error -- callers should error out
|
||||
* -1 D-Bus not reachable -- callers should fall back to the
|
||||
* legacy INIT_SOCKET transport
|
||||
*
|
||||
* On D-Bus error replies the function maps the org.* error name to
|
||||
* the same exit code initctl historically printed for that case
|
||||
* (e.g. NoSuchService -> 69 with the legacy message). */
|
||||
static int try_dbus_manager(const char *method, const char *arg_sig,
|
||||
const char *arg)
|
||||
{
|
||||
link_client_t *c;
|
||||
const char *err;
|
||||
int rc;
|
||||
|
||||
c = link_client_open(FINIT_BUS_SOCKET);
|
||||
if (!c)
|
||||
return -1;
|
||||
|
||||
/* "s" methods take the service identity (arg may be NULL ->
|
||||
* empty string); void methods pass no body. */
|
||||
if (arg_sig && !strcmp(arg_sig, "s"))
|
||||
rc = link_client_call_v(c, "/org/finit/manager",
|
||||
"org.finit.Manager1", method,
|
||||
"s", arg ? arg : "");
|
||||
else if (!arg_sig || !*arg_sig)
|
||||
rc = link_client_call_v(c, "/org/finit/manager",
|
||||
"org.finit.Manager1", method, NULL);
|
||||
else
|
||||
rc = LINK_CALL_FAIL;
|
||||
|
||||
if (rc == LINK_CALL_ERROR) {
|
||||
const link_reply_t *r = link_client_reply(c);
|
||||
|
||||
err = (r && r->error_name) ? r->error_name : "";
|
||||
/* Exact match on the fully-qualified error name; substring
|
||||
* matching would misfire on a future name that contains
|
||||
* one of these as a substring. */
|
||||
if (!strcmp(err, "org.finit.Error.NoSuchService")) {
|
||||
link_client_close(c);
|
||||
ERRX(noerr ? 0 : 69, "no such task or service(s): %s",
|
||||
arg ? arg : "");
|
||||
}
|
||||
if (!strcmp(err, "org.freedesktop.DBus.Error.AccessDenied")) {
|
||||
link_client_close(c);
|
||||
ERRX(1, "permission denied: %s requires root", method);
|
||||
}
|
||||
link_client_close(c);
|
||||
ERRX(1, "%s: %s", method, *err ? err : "D-Bus error");
|
||||
}
|
||||
link_client_close(c);
|
||||
if (rc == LINK_CALL_OK)
|
||||
return 0;
|
||||
return -1; /* LINK_CALL_FAIL or anything else: fall back */
|
||||
}
|
||||
#endif /* HAVE_DBUS */
|
||||
|
||||
static int do_start (char *arg)
|
||||
{
|
||||
#ifdef HAVE_DBUS
|
||||
int rc = try_dbus_manager("Start", "s", arg);
|
||||
if (rc >= 0) return rc;
|
||||
#endif
|
||||
return do_startstop(INIT_CMD_START_SVC, arg);
|
||||
}
|
||||
|
||||
static int do_stop (char *arg)
|
||||
{
|
||||
#ifdef HAVE_DBUS
|
||||
int rc = try_dbus_manager("Stop", "s", arg);
|
||||
if (rc >= 0) return rc;
|
||||
#endif
|
||||
return do_startstop(INIT_CMD_STOP_SVC, arg);
|
||||
}
|
||||
|
||||
static int do_reload (char *arg)
|
||||
{
|
||||
if (!arg || !arg[0])
|
||||
if (!arg || !arg[0]) {
|
||||
#ifdef HAVE_DBUS
|
||||
int rc = try_dbus_manager("Reload", "", NULL);
|
||||
if (rc >= 0) return rc;
|
||||
#endif
|
||||
return do_svc(INIT_CMD_RELOAD, NULL);
|
||||
}
|
||||
|
||||
return do_startstop(INIT_CMD_RELOAD_SVC, arg);
|
||||
}
|
||||
|
||||
static int do_restart(char *arg)
|
||||
{
|
||||
#ifdef HAVE_DBUS
|
||||
int rc = try_dbus_manager("Restart", "s", arg);
|
||||
if (rc == 0) return 0;
|
||||
if (rc == 1) ERRX(noerr ? 0 : 7, "failed restarting %s", arg);
|
||||
#endif
|
||||
if (do_startstop(INIT_CMD_RESTART_SVC, arg))
|
||||
ERRX(noerr ? 0 : 7, "failed restarting %s", arg);
|
||||
|
||||
@@ -643,9 +729,47 @@ static int do_cmd(int cmd)
|
||||
return 0;
|
||||
}
|
||||
|
||||
int do_reboot (char *arg) { return do_cmd(INIT_CMD_REBOOT); }
|
||||
int do_halt (char *arg) { return do_cmd(INIT_CMD_HALT); }
|
||||
int do_poweroff(char *arg) { return do_cmd(INIT_CMD_POWEROFF); }
|
||||
#ifdef HAVE_DBUS
|
||||
static int do_reboot_dbus(const char *method)
|
||||
{
|
||||
int rc = try_dbus_manager(method, "", NULL);
|
||||
|
||||
if (rc == 0) {
|
||||
sleep(5); /* match legacy: wait for finit to shut down */
|
||||
return 0;
|
||||
}
|
||||
return rc; /* 1 = error, -1 = fall back */
|
||||
}
|
||||
#endif
|
||||
|
||||
int do_reboot (char *arg)
|
||||
{
|
||||
#ifdef HAVE_DBUS
|
||||
int rc = do_reboot_dbus("Reboot");
|
||||
if (rc >= 0) return rc;
|
||||
#endif
|
||||
return do_cmd(INIT_CMD_REBOOT);
|
||||
}
|
||||
|
||||
int do_halt (char *arg)
|
||||
{
|
||||
#ifdef HAVE_DBUS
|
||||
int rc = do_reboot_dbus("Halt");
|
||||
if (rc >= 0) return rc;
|
||||
#endif
|
||||
return do_cmd(INIT_CMD_HALT);
|
||||
}
|
||||
|
||||
int do_poweroff(char *arg)
|
||||
{
|
||||
#ifdef HAVE_DBUS
|
||||
int rc = do_reboot_dbus("Poweroff");
|
||||
if (rc >= 0) return rc;
|
||||
#endif
|
||||
return do_cmd(INIT_CMD_POWEROFF);
|
||||
}
|
||||
|
||||
/* Suspend has no Manager1 equivalent yet; uses the legacy IPC. */
|
||||
int do_suspend (char *arg) { return do_cmd(INIT_CMD_SUSPEND); }
|
||||
|
||||
/**
|
||||
|
||||
@@ -45,6 +45,15 @@ extern uev_ctx_t *ctx;
|
||||
|
||||
int api_init (uev_ctx_t *ctx);
|
||||
int api_exit (void);
|
||||
|
||||
#ifdef HAVE_DBUS
|
||||
int dbus_init (uev_ctx_t *ctx);
|
||||
int dbus_exit (void);
|
||||
void dbus_register_service (svc_t *svc);
|
||||
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);
|
||||
#endif
|
||||
void conf_flush_events(void);
|
||||
|
||||
void service_monitor (pid_t lost, int status);
|
||||
|
||||
+59
-7
@@ -1443,17 +1443,65 @@ int service_stop(svc_t *svc)
|
||||
}
|
||||
|
||||
/**
|
||||
* service_reload - Reload a service
|
||||
* service_reload - Request reload of a service, driven by the state machine
|
||||
* @svc: Service to reload
|
||||
*
|
||||
* This function does some basic checks of the runtime state of Finit
|
||||
* and a sanity check of the @svc before sending %SIGHUP or calling
|
||||
* the reload:script command.
|
||||
* Mark a service dirty so the state machine will (re-)apply its
|
||||
* configuration, then advance the state machine. For services that
|
||||
* don't handle SIGHUP this also clears the readiness condition and
|
||||
* the pidfile/none-notify readiness flag so dependents are properly
|
||||
* notified once the service comes back.
|
||||
*
|
||||
* Returns:
|
||||
* POSIX OK(0) on success, non-zero if @svc is NULL.
|
||||
*/
|
||||
int service_reload(svc_t *svc)
|
||||
{
|
||||
if (!svc)
|
||||
return 1;
|
||||
|
||||
if (svc_is_blocked(svc))
|
||||
svc_start(svc);
|
||||
else
|
||||
service_timeout_cancel(svc);
|
||||
|
||||
/*
|
||||
* Clear conditions before reload to ensure dependent services
|
||||
* are properly updated. Only needed when the service does NOT
|
||||
* support SIGHUP (noreload), because then it will be stopped
|
||||
* and restarted, so conditions genuinely go away. When the
|
||||
* service handles SIGHUP, its PID and pidfile persist, so the
|
||||
* condition stays valid and dependents should not be disrupted.
|
||||
*
|
||||
* Note: only clear 'ready' for services where the pidfile
|
||||
* inotify handler reasserts it (pid/none). For s6/systemd
|
||||
* services readiness relies on their respective notification
|
||||
* mechanism which may not re-trigger on SIGHUP.
|
||||
*/
|
||||
if (svc_is_noreload(svc)) {
|
||||
svc_cond_clear(svc);
|
||||
if (svc->notify == SVC_NOTIFY_PID || svc->notify == SVC_NOTIFY_NONE)
|
||||
service_ready(svc, 0);
|
||||
}
|
||||
|
||||
svc_mark_dirty(svc);
|
||||
service_step(svc);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* service_reload_apply - Perform the actual reload of a running service
|
||||
* @svc: Service to reload
|
||||
*
|
||||
* Low-level reload step: sends %SIGHUP or runs the reload:script
|
||||
* command. Called by the state machine when a service marked
|
||||
* reload-pending by service_reload() reaches the right state.
|
||||
*
|
||||
* Returns:
|
||||
* POSIX OK(0) or non-zero on error.
|
||||
*/
|
||||
static int service_reload(svc_t *svc)
|
||||
static int service_reload_apply(svc_t *svc)
|
||||
{
|
||||
const char *id = svc_ident(svc, NULL, 0);
|
||||
int do_progress = 1;
|
||||
@@ -3015,6 +3063,10 @@ static void svc_set_state(svc_t *svc, svc_state_t new_state)
|
||||
return;
|
||||
*state = new_state;
|
||||
|
||||
#ifdef HAVE_DBUS
|
||||
dbus_notify_service_state(svc, old_state, new_state);
|
||||
#endif
|
||||
|
||||
/*
|
||||
* The unit has stopped: HALTED comes after any post:/cleanup:
|
||||
* script, DONE is a completed run/task, where remain-after-exit
|
||||
@@ -3384,7 +3436,7 @@ restart:
|
||||
if (sm_in_reload())
|
||||
break;
|
||||
|
||||
service_reload(svc);
|
||||
service_reload_apply(svc);
|
||||
}
|
||||
|
||||
svc_mark_clean(svc);
|
||||
@@ -3422,7 +3474,7 @@ restart:
|
||||
if (svc_is_noreload(svc))
|
||||
service_stop(svc);
|
||||
else
|
||||
service_reload(svc);
|
||||
service_reload_apply(svc);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -59,6 +59,7 @@ void service_forked (svc_t *svc);
|
||||
void service_ready (svc_t *svc, int ready);
|
||||
|
||||
int service_stop (svc_t *svc);
|
||||
int service_reload (svc_t *svc);
|
||||
int service_step (svc_t *svc);
|
||||
void service_step_all (int types);
|
||||
void service_worker (void *unused);
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
|
||||
#include "finit.h"
|
||||
#include "conf.h"
|
||||
#include "private.h"
|
||||
#include "svc.h"
|
||||
#include "helpers.h"
|
||||
#include "pid.h"
|
||||
@@ -153,6 +154,10 @@ svc_t *svc_new(char *cmd, char *name, char *id, int type)
|
||||
|
||||
TAILQ_INSERT_TAIL(&svc_list, svc, link);
|
||||
|
||||
#ifdef HAVE_DBUS
|
||||
dbus_register_service(svc);
|
||||
#endif
|
||||
|
||||
return svc;
|
||||
}
|
||||
|
||||
@@ -170,6 +175,10 @@ static struct wq work = {
|
||||
*/
|
||||
int svc_del(svc_t *svc)
|
||||
{
|
||||
#ifdef HAVE_DBUS
|
||||
dbus_unregister_service(svc);
|
||||
#endif
|
||||
|
||||
TAILQ_REMOVE(&svc_list, svc, link);
|
||||
TAILQ_INSERT_TAIL(&gc_list, svc, link);
|
||||
|
||||
|
||||
Reference in new issue
Block a user