initctl: the status views over D-Bus

The summary table, the per-service detail, JSON and the quiet and
ident forms all read state Finit already publishes, so they read it
from the bus like everything else rather than through a second path
that has to be kept in step.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
This commit is contained in:
Joachim Wiberg committed 2026-08-13 09:28:26 +02:00
1 parent ebc0ef62e6
commit 6310d9e760
16 files changed
+1271 -315

No files matched your search

+5 -5
View File
@@ -62,16 +62,16 @@ jobs:
tree /tmp || true
- name: Check dependencies
run: |
ldd /tmp/sbin/finit
LD_LIBRARY_PATH=/tmp/lib ldd /tmp/sbin/finit
size /tmp/sbin/finit
ldd /tmp/sbin/initctl
LD_LIBRARY_PATH=/tmp/lib ldd /tmp/sbin/initctl
size /tmp/sbin/initctl
ldd /tmp/sbin/reboot
LD_LIBRARY_PATH=/tmp/lib ldd /tmp/sbin/reboot
size /tmp/sbin/reboot
- name: Verify starting and showing usage text
run: |
sudo /tmp/sbin/finit -h
sudo /tmp/sbin/initctl -h
sudo env LD_LIBRARY_PATH=/tmp/lib /tmp/sbin/finit -h
sudo env LD_LIBRARY_PATH=/tmp/lib /tmp/sbin/initctl -h
- name: Enable unprivileged userns (unshare)
run: |
sudo sysctl kernel.apparmor_restrict_unprivileged_userns=0
+35
View File
@@ -127,6 +127,41 @@ look up the exact path rather than constructing it by hand.
| `Restart` | — | — | yes | … |
| `Reload` | — | — | yes | Reload (SIGHUP if supported, else restart). |
### Properties
All read-only; observable via `Properties.Get` and `Properties.GetAll`.
| Property | Type | Returns |
| -------------- | ---- | ------- |
| `Identity` | `s` | Service identity, `name` or `name:id`. |
| `Name` | `s` | Program name (basename of the command). |
| `State` | `s` | Current status, same vocabulary as `initctl status` (richer than the coarse `ServiceStateChanged` strings). |
| `Pid` | `u` | Current PID, 0 when not running. |
| `RestartCount` | `u` | Restarts since the last stable run. |
| `Runlevels` | `u` | Allowed runlevels as a bitmask, bit N = runlevel N, bit 10 = S. |
| `Description` | `s` | The service's `description` string. |
| `Command` | `s` | Full command line, arguments included. |
| `Conditions` | `s` | Declared conditions, raw `.conf` form. |
| `Type` | `s` | Unit type: `service`, `task`, `run`, `sysv`, `tty`, `free`. |
| `Origin` | `s` | Source `.conf` file, empty for built-ins. |
| `Environment` | `s` | The service's `env` setting, raw. |
| `PidFile` | `s` | Declared PID file, raw (`!` prefix included). |
| `User` | `s` | User the service runs as. |
| `Group` | `s` | Group the service runs as. |
| `Uptime` | `u` | Seconds since start, 0 when not running. |
| `ExitStatus` | `u` | Raw `waitpid(2)` status from the last exit. |
| `RestartsTotal`| `u` | Restarts over the service's lifetime. |
| `RestartMax` | `u` | Restart limit before the service is blocked. |
| `Starts` | `u` | Times started, for `manual-start` units. |
| `ManualStart` | `b` | `manual-start` set in the `.conf`. |
| `Forking` | `b` | Daemon forks to background. |
| `Started` | `b` | Run/task completed successfully. |
On every state transition the object also emits the standard
`org.freedesktop.DBus.Properties.PropertiesChanged` signal: `State`
in the changed dictionary, `Pid` and `RestartCount` invalidated (call
`Get` for fresh values).
The per-service surface lets generic tooling supply an object handle
once and then invoke methods on it, instead of repeatedly passing the
identity string.
+2
View File
@@ -333,6 +333,7 @@ static int handle_properties_get(link_connection_t *conn, const struct link_msg
if (!p->getter)
break;
__w_init(&w, conn->txbuf, sizeof(conn->txbuf));
__w_sig(&w, p->sig ? p->sig : "s");
if (p->getter(&w, e->userdata) != 0 || (blen = __w_finish(&w)) < 0)
return __send_error(conn, m,
"org.freedesktop.DBus.Error.Failed",
@@ -380,6 +381,7 @@ static int handle_properties_get_all(link_connection_t *conn, const struct link_
continue;
__w_struct_begin(&w);
__w_string(&w, p->name);
__w_sig(&w, p->sig ? p->sig : "s");
if (p->getter(&w, e->userdata) != 0)
return __send_error(conn, m,
"org.freedesktop.DBus.Error.Failed",
+2
View File
@@ -312,6 +312,8 @@ int link_r_bool (link_reader_t *r, int *o) { return __r_bool (r, o);
int link_r_u32 (link_reader_t *r, uint32_t *o) { return __r_u32 (r, o); }
int link_r_string(link_reader_t *r, const char **o) { return __r_string(r, o); }
int link_r_path (link_reader_t *r, const char **o) { return __r_path (r, o); }
int link_r_variant_begin (link_reader_t *r, char *type) { return __r_variant_begin(r, type); }
int link_r_skip_basic (link_reader_t *r, char type) { return __r_skip_basic(r, type); }
int link_r_variant_string(link_reader_t *r, const char **o) { return __r_variant_string(r, o); }
int link_r_align (link_reader_t *r, size_t n) { return __r_align (r, n); }
int link_r_array_begin(link_reader_t *r, size_t *e) { return __r_array_begin(r, e); }
+7 -3
View File
@@ -129,9 +129,11 @@ typedef struct {
} link_method_t;
/* A read-only property descriptor. Set via the Properties.Set side
* is not yet implemented; only Get and GetAll are. The getter writes
* the property's value as a D-Bus variant (use link_w_variant_string
* for "s"-typed properties) into the provided writer. */
* is not yet implemented; only Get and GetAll are. The framework
* emits the variant signature from `sig`; the getter writes only the
* bare value into the provided writer (link_w_string for "s",
* link_w_u32 for "u", ...), so the declared type is the single
* source of truth. */
typedef int (*link_property_getter_fn)(link_writer_t *w, void *userdata);
typedef struct {
@@ -329,6 +331,8 @@ int link_r_bool (link_reader_t *r, int *out);
int link_r_u32 (link_reader_t *r, uint32_t *out);
int link_r_string (link_reader_t *r, const char **out); /* "s" */
int link_r_path (link_reader_t *r, const char **out); /* "o" */
int link_r_variant_begin (link_reader_t *r, char *type); /* sig header, cursor at value */
int link_r_skip_basic (link_reader_t *r, char type); /* skip one basic value */
int link_r_variant_string(link_reader_t *r, const char **out); /* "v" containing "s" */
int link_r_align (link_reader_t *r, size_t n); /* skip to next n-byte boundary */
int link_r_done (const link_reader_t *r);
+73 -16
View File
@@ -110,6 +110,7 @@ void __w_variant_string(struct link_writer *w, const char *s)
__w_string(w, s);
}
static size_t element_align(char c)
{
switch (c) {
@@ -262,32 +263,88 @@ static int read_string_like(struct link_reader *r, const char **out)
int __r_string(struct link_reader *r, const char **out) { return read_string_like(r, out); }
int __r_path (struct link_reader *r, const char **out) { return read_string_like(r, out); }
/*
* Parse a variant's signature header, i.e. "g" wire form: 1-byte
* length, bytes, NUL. Only single-character inner signatures are
* supported. On success the cursor sits at the value and the type
* code is returned in *type.
*/
int __r_variant_begin(struct link_reader *r, char *type)
{
uint8_t sig_len;
if (r_skip_align(r, 1) < 0 || r->off + 1 > r->cap)
goto fail;
sig_len = r->base[r->off++];
if (sig_len != 1 || r->off + 2 > r->cap)
goto fail;
if (r->base[r->off + 1] != 0)
goto fail;
*type = (char)r->base[r->off];
r->off += 2;
return 0;
fail:
r->err = 1;
return -1;
}
/* Read a variant "v" expected to contain a string. Fails if the
* inner signature is anything other than "s" (returns -1, *out set
* to NULL). */
int __r_variant_string(struct link_reader *r, const char **out)
{
uint8_t sig_len;
uint32_t slen;
char type;
*out = NULL;
/* signature is "g" wire form: 1-byte length, bytes, NUL */
if (r_skip_align(r, 1) < 0 || r->off + 1 > r->cap) { r->err = 1; return -1; }
sig_len = r->base[r->off++];
if (sig_len != 1 || r->off + 2 > r->cap) { r->err = 1; return -1; }
if (r->base[r->off] != 's' || r->base[r->off + 1] != 0) { r->err = 1; return -1; }
r->off += 2;
/* now a normal string */
if (__r_u32(r, &slen) < 0) return -1;
if (r->off + (size_t)slen + 1 > r->cap || r->base[r->off + slen] != 0) {
if (__r_variant_begin(r, &type) < 0)
return -1;
if (type != 's') {
r->err = 1;
return -1;
}
*out = (const char *)(r->base + r->off);
r->off += (size_t)slen + 1;
return 0;
return read_string_like(r, out);
}
/*
* Skip one basic value of the given type code, as returned by
* __r_variant_begin(). Lets a{sv} consumers tolerate value types
* they don't know. Returns -1 on non-basic types.
*/
int __r_skip_basic(struct link_reader *r, char type)
{
const char *s;
uint32_t u;
uint8_t y;
switch (type) {
case 's':
case 'o':
return read_string_like(r, &s);
case 'b':
case 'u':
case 'i':
return __r_u32(r, &u);
case 'y':
return __r_byte(r, &y);
default:
r->err = 1;
return -1;
}
}
/* Read a variant "v" expected to contain a uint32. */
int __r_variant_u32(struct link_reader *r, uint32_t *out)
{
char type;
if (__r_variant_begin(r, &type) < 0)
return -1;
if (type != 'u') {
r->err = 1;
return -1;
}
return __r_u32(r, out);
}
int __r_done(const struct link_reader *r)
+3
View File
@@ -44,7 +44,10 @@ int __r_bool (struct link_reader *r, int *out);
int __r_u32 (struct link_reader *r, uint32_t *out);
int __r_string(struct link_reader *r, const char **out); /* "s" */
int __r_path (struct link_reader *r, const char **out); /* "o" */
int __r_variant_begin (struct link_reader *r, char *type); /* sig header, cursor at value */
int __r_skip_basic (struct link_reader *r, char type); /* skip one basic value */
int __r_variant_string(struct link_reader *r, const char **out); /* "v" containing "s" */
int __r_variant_u32 (struct link_reader *r, uint32_t *out); /* "v" containing "u" */
int __r_align (struct link_reader *r, size_t n); /* skip to n-byte boundary */
int __r_array_begin(struct link_reader *r, size_t *out_end);
int __r_done (const struct link_reader *r);
+240 -13
View File
@@ -65,14 +65,23 @@ 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 struct peer *sysbus_peer;
static void sysbus_probe(void);
static void peer_drop(struct peer *p)
{
int was_sysbus = p == sysbus_peer;
uev_io_stop(&p->watcher);
link_connection_close(p->conn);
TAILQ_REMOVE(&peers, p, link);
peer_count--;
free(p);
/* broker gone; the notify paths probe for its return */
if (was_sysbus)
sysbus_peer = NULL;
}
static void peer_cb(uev_t *w, void *arg, int events)
@@ -456,7 +465,7 @@ static int prop_runlevel(link_writer_t *w, void *u)
(void)u;
snprintf(buf, sizeof(buf), "%d", runlevel);
link_w_variant_string(w, buf);
link_w_string(w, buf);
return 0;
}
@@ -466,14 +475,14 @@ static int prop_prevrunlevel(link_writer_t *w, void *u)
(void)u;
snprintf(buf, sizeof(buf), "%d", prevlevel);
link_w_variant_string(w, buf);
link_w_string(w, buf);
return 0;
}
static int prop_version(link_writer_t *w, void *u)
{
(void)u;
link_w_variant_string(w, PACKAGE_VERSION);
link_w_string(w, PACKAGE_VERSION);
return 0;
}
@@ -576,9 +585,146 @@ static const link_method_t service_methods[] = {
{ NULL, NULL, NULL, 0, NULL }
};
/*
* Getters write the bare value; the framework emits the variant
* signature from the table below. `State` deliberately uses the
* initctl status vocabulary from svc_status(), not the coarser
* ServiceStateChanged strings -- a client that only tracks edges
* has the signal, a client that asks gets the full story.
*/
#define SVC_PROP_STR(fn, field) \
static int fn(link_writer_t *w, void *arg) \
{ \
link_w_string(w, ((svc_t *)arg)->field); \
return 0; \
}
#define SVC_PROP_U32(fn, field) \
static int fn(link_writer_t *w, void *arg) \
{ \
link_w_u32(w, (uint32_t)((svc_t *)arg)->field); \
return 0; \
}
#define SVC_PROP_BOOL(fn, field) \
static int fn(link_writer_t *w, void *arg) \
{ \
link_w_bool(w, ((svc_t *)arg)->field); \
return 0; \
}
static int svc_prop_identity(link_writer_t *w, void *arg)
{
link_w_string(w, svc_ident(arg, NULL, 0));
return 0;
}
static int svc_prop_state(link_writer_t *w, void *arg)
{
link_w_string(w, svc_status(arg));
return 0;
}
static int svc_prop_type(link_writer_t *w, void *arg)
{
link_w_string(w, svc_typestr((svc_t *)arg));
return 0;
}
static int svc_prop_command(link_writer_t *w, void *arg)
{
svc_t *svc = arg;
char buf[512];
compose_cmdline(svc, buf, sizeof(buf));
if (svc_is_sysv(svc)) {
strlcat(buf, " ", sizeof(buf));
strlcat(buf, svc->state == SVC_HALTED_STATE
? "stop" : "start", sizeof(buf));
}
link_w_string(w, buf);
return 0;
}
static int svc_prop_pid(link_writer_t *w, void *arg)
{
svc_t *svc = arg;
link_w_u32(w, svc->pid > 0 ? (uint32_t)svc->pid : 0);
return 0;
}
static int svc_prop_restarts(link_writer_t *w, void *arg)
{
svc_t *svc = arg;
link_w_u32(w, svc->restart_cnt > 0 ? (uint32_t)svc->restart_cnt : 0);
return 0;
}
static int svc_prop_uptime(link_writer_t *w, void *arg)
{
svc_t *svc = arg;
long up = 0;
if (svc->pid > 0) {
up = jiffies() - svc->start_time;
if (up < 0)
up = 0;
}
link_w_u32(w, (uint32_t)up);
return 0;
}
SVC_PROP_STR (svc_prop_name, name)
SVC_PROP_STR (svc_prop_desc, desc)
SVC_PROP_STR (svc_prop_conditions, cond)
SVC_PROP_STR (svc_prop_origin, file)
SVC_PROP_STR (svc_prop_environ, env)
SVC_PROP_STR (svc_prop_pidfile, pidfile)
SVC_PROP_STR (svc_prop_user, username)
SVC_PROP_STR (svc_prop_group, group)
SVC_PROP_U32 (svc_prop_runlevels, runlevels)
SVC_PROP_U32 (svc_prop_exitstatus, status)
SVC_PROP_U32 (svc_prop_restarts_tot, restart_tot)
SVC_PROP_U32 (svc_prop_restart_max, restart_max)
SVC_PROP_U32 (svc_prop_starts, once)
SVC_PROP_BOOL(svc_prop_manual, manual)
SVC_PROP_BOOL(svc_prop_forking, forking)
SVC_PROP_BOOL(svc_prop_started, started)
static const link_property_t service_properties[] = {
{ .name = "Identity", .sig = "s", .getter = svc_prop_identity },
{ .name = "Name", .sig = "s", .getter = svc_prop_name },
{ .name = "State", .sig = "s", .getter = svc_prop_state },
{ .name = "Pid", .sig = "u", .getter = svc_prop_pid },
{ .name = "RestartCount", .sig = "u", .getter = svc_prop_restarts },
{ .name = "Runlevels", .sig = "u", .getter = svc_prop_runlevels },
{ .name = "Description", .sig = "s", .getter = svc_prop_desc },
{ .name = "Command", .sig = "s", .getter = svc_prop_command },
{ .name = "Conditions", .sig = "s", .getter = svc_prop_conditions },
{ .name = "Type", .sig = "s", .getter = svc_prop_type },
{ .name = "Origin", .sig = "s", .getter = svc_prop_origin },
{ .name = "Environment", .sig = "s", .getter = svc_prop_environ },
{ .name = "PidFile", .sig = "s", .getter = svc_prop_pidfile },
{ .name = "User", .sig = "s", .getter = svc_prop_user },
{ .name = "Group", .sig = "s", .getter = svc_prop_group },
{ .name = "Uptime", .sig = "u", .getter = svc_prop_uptime },
{ .name = "ExitStatus", .sig = "u", .getter = svc_prop_exitstatus },
{ .name = "RestartsTotal",.sig = "u", .getter = svc_prop_restarts_tot },
{ .name = "RestartMax", .sig = "u", .getter = svc_prop_restart_max },
{ .name = "Starts", .sig = "u", .getter = svc_prop_starts },
{ .name = "ManualStart", .sig = "b", .getter = svc_prop_manual },
{ .name = "Forking", .sig = "b", .getter = svc_prop_forking },
{ .name = "Started", .sig = "b", .getter = svc_prop_started },
{ NULL, NULL, NULL }
};
static const link_vtable_t service_vtable = {
.interface = "org.finit.Service1",
.methods = service_methods,
.interface = "org.finit.Service1",
.methods = service_methods,
.properties = service_properties,
};
/* Build the canonical object path for a service. Identity is
@@ -693,10 +839,14 @@ void dbus_notify_service_state(svc_t *svc, int old_state, int new_state)
uint8_t body[256];
link_writer_t w;
char ident[MAX_IDENT_LEN];
char path[FINIT_SVC_PATH_MAX];
ssize_t blen;
if (!svc)
return;
sysbus_probe();
svc_ident(svc, ident, sizeof(ident));
link_writer_init(&w, body, sizeof(body));
@@ -709,6 +859,35 @@ void dbus_notify_service_state(svc_t *svc, int old_state, int new_state)
dbus_emit_signal("/org/finit/manager", "org.finit.Manager1",
"ServiceStateChanged", "sss", body, (size_t)blen);
/*
* Dual emission: Properties-aware clients track one object via
* the standard PropertiesChanged instead of filtering the
* manager-wide signal. Volatile numerics are invalidated, not
* marshalled -- interested clients re-Get.
*/
if (service_path_for(svc, path, sizeof(path)) < 0)
return;
link_writer_init(&w, body, sizeof(body));
link_w_string(&w, "org.finit.Service1");
link_w_array_begin(&w, '{');
link_w_struct_begin(&w);
link_w_string(&w, "State");
link_w_variant_string(&w, svc_status(svc));
link_w_struct_end(&w);
link_w_array_end(&w);
link_w_array_begin(&w, 's');
link_w_string(&w, "Pid");
link_w_string(&w, "RestartCount");
link_w_array_end(&w);
blen = link_writer_finish(&w);
if (blen < 0)
return;
dbus_emit_signal(path, "org.freedesktop.DBus.Properties",
"PropertiesChanged", "sa{sv}as",
body, (size_t)blen);
}
/* ---------- signal emission: RunlevelChanged ----------
@@ -960,6 +1139,8 @@ void dbus_notify_condition_change(const char *name, const char *state)
if (!name || !state)
return;
sysbus_probe();
link_writer_init(&w, body, sizeof(body));
link_w_string(&w, name);
link_w_string(&w, state);
@@ -1018,17 +1199,18 @@ static int sysbus_request_name(link_client_t *c)
return (result == 1) ? 0 : -1;
}
static void try_attach_system_bus(uev_ctx_t *ctx)
static int try_attach_system_bus(uev_ctx_t *ctx)
{
link_client_t *c;
link_connection_t *conn;
struct peer *p;
int rc;
c = link_client_open_timeout(SYSTEM_BUS_PATH, SYSTEM_BUS_TIMEOUT_MS);
if (!c) {
dbg("System bus unavailable at %s; skipping registration",
SYSTEM_BUS_PATH);
return;
return -1;
}
rc = link_client_call_v(c, "/org/freedesktop/DBus",
@@ -1036,13 +1218,13 @@ static void try_attach_system_bus(uev_ctx_t *ctx)
if (rc != LINK_CALL_OK) {
dbg("System-bus Hello failed (rc=%d); skipping", rc);
link_client_close(c);
return;
return -1;
}
if (sysbus_request_name(c) < 0) {
logit(LOG_WARNING, "Failed to claim %s on system bus", FINIT_BUS_NAME);
link_client_close(c);
return;
return -1;
}
/* link_server_attach owns the fd from this point on whether it
@@ -1050,14 +1232,55 @@ static void try_attach_system_bus(uev_ctx_t *ctx)
* window. */
conn = link_server_attach(server, link_client_steal_fd(c), (uid_t)-1);
if (!conn)
return;
return -1;
if (!peer_register(ctx, conn)) {
p = peer_register(ctx, conn);
if (!p) {
logit(LOG_WARNING, "Failed registering system-bus peer");
return;
return -1;
}
sysbus_peer = p;
logit(LOG_NOTICE, "Registered %s on system bus", FINIT_BUS_NAME);
return 0;
}
/*
* The broker is usually not up yet when dbus_init() runs -- it is
* typically a finit service itself -- and it may restart at any
* time. The service and condition notify paths call sysbus_probe()
* on every event: when org.finit is unclaimed and the broker's
* socket exists, one coalesced attach attempt is scheduled. This
* stays daemon-agnostic -- only the socket is probed, never a
* service name -- and the broker's own service transitions are what
* trigger it.
*/
static uev_t sysbus_tmr;
static int sysbus_tmr_up;
static void sysbus_probe_cb(uev_t *w, void *arg, int events)
{
(void)arg;
(void)events;
if (!sysbus_peer)
(void)try_attach_system_bus(w->ctx);
}
static void sysbus_probe(void)
{
if (sysbus_peer || !server)
return;
if (access(SYSTEM_BUS_PATH, F_OK))
return;
/* coalesce bursts to a single probe */
if (!sysbus_tmr_up)
sysbus_tmr_up = !uev_timer_init(ctx, &sysbus_tmr,
sysbus_probe_cb, NULL,
200, 0);
else
uev_timer_set(&sysbus_tmr, 200, 0);
}
/* ---------- init / exit ---------- */
@@ -1107,7 +1330,7 @@ int dbus_init(uev_ctx_t *ctx)
dbus_register_service(svc);
}
try_attach_system_bus(ctx);
(void)try_attach_system_bus(ctx);
return 0;
}
@@ -1116,6 +1339,10 @@ int dbus_exit(void)
{
struct peer *p;
if (sysbus_tmr_up) {
uev_timer_stop(&sysbus_tmr);
sysbus_tmr_up = 0;
}
uev_io_stop(&accept_watcher);
while ((p = TAILQ_FIRST(&peers)))
+754 -274
View File
File diff suppressed because it is too large. Load diff
+1 -1
View File
@@ -520,7 +520,7 @@ static int is_norespawn(void)
fexist("/tmp/norespawn");
}
static void compose_cmdline(svc_t *svc, char *buf, size_t len)
void compose_cmdline(svc_t *svc, char *buf, size_t len)
{
size_t i;
+1
View File
@@ -54,6 +54,7 @@ void service_ready_script (svc_t *svc); /* XXX: only for pidfile plugin b
int service_timeout_after (svc_t *svc, int timeout, void (*cb)(svc_t *svc));
int service_timeout_cancel (svc_t *svc);
void compose_cmdline (svc_t *svc, char *buf, size_t len);
void service_forked (svc_t *svc);
void service_ready (svc_t *svc, int ready);
+2
View File
@@ -77,6 +77,7 @@ EXTRA_DIST += dbus-manager.sh
EXTRA_DIST += dbus-service.sh
EXTRA_DIST += dbus-cond.sh
EXTRA_DIST += dbus-initctl.sh
EXTRA_DIST += dbus-introspect.sh
AM_TESTS_ENVIRONMENT = SYSROOT='$(abs_builddir)/sysroot/';
AM_TESTS_ENVIRONMENT += export SYSROOT;
@@ -139,6 +140,7 @@ TESTS += dbus-manager.sh
TESTS += dbus-service.sh
TESTS += dbus-cond.sh
TESTS += dbus-initctl.sh
TESTS += dbus-introspect.sh
endif
check-recursive: setup-chroot
+48
View File
@@ -94,3 +94,51 @@ say "initctl reload <svc> routes through Service1.Reload"
texec initctl reload keventd >/dev/null \
|| fail "initctl reload keventd returned non-zero"
assert "Per-service reload ok" 0 -eq 0
say "initctl status views render over D-Bus alone"
# hide the legacy socket: everything below must come over the bus
texec mv /run/finit/socket /run/finit/socket.hidden
set +e
out=$(texec initctl -p status)
status_rc=$?
one=$(texec initctl -p status keventd)
one_rc=$?
texec initctl -q status keventd
quiet_rc=$?
jout=$(texec initctl -j status)
json_rc=$?
idout=$(texec initctl ident)
ident_rc=$?
set -e
texec mv /run/finit/socket.hidden /run/finit/socket
assert "status table ok without legacy socket (rc=$status_rc)" \
"$status_rc" -eq 0
case "$out" in
*keventd*running*) assert "keventd running in table" 0 -eq 0 ;;
*) fail "keventd/running missing from status table" ;;
esac
assert "status detail view ok (rc=$one_rc)" "$one_rc" -eq 0
case "$one" in
*"Status : running"*"Identity : keventd"*"Restarts :"*)
assert "detail view fields present" 0 -eq 0 ;;
*) fail "detail view incomplete: $one" ;;
esac
assert "quiet mode exit code (rc=$quiet_rc)" "$quiet_rc" -eq 0
assert "json mode ok (rc=$json_rc)" "$json_rc" -eq 0
case "$jout" in
*'"identity": "keventd"'*'"status": "running"'*)
assert "json fields present" 0 -eq 0 ;;
*) fail "json output incomplete: $jout" ;;
esac
assert "ident ok (rc=$ident_rc)" "$ident_rc" -eq 0
case "$idout" in
*keventd*) assert "ident lists keventd" 0 -eq 0 ;;
*) fail "ident missing keventd" ;;
esac
+31
View File
@@ -0,0 +1,31 @@
#!/bin/sh
# libink: introspection XML well-formedness.
#
# Covers compound type signatures in generated introspection XML:
# Cond1.Dump declares out_sig "a(ss)", which must appear as a single
# <arg>, not one <arg> per signature character.
set -eu
TEST_DIR=$(dirname "$0")
# shellcheck source=/dev/null
. "$TEST_DIR/lib/setup.sh"
# shellcheck source=/dev/null
. "$TEST_DIR/lib/dbus-setup.sh"
say "Introspect on /org/finit/cond"
xml=$(texec "$CLIENT" introspect "$BUS" /org/finit/cond)
say "Cond1.Dump advertises a single a(ss) out arg"
case "$xml" in
*'<arg type="a(ss)" direction="out"/>'*) assert "a(ss) intact" 0 -eq 0 ;;
*) fail "a(ss) not found as a single arg" ;;
esac
say "No per-character type fragments in the XML"
case "$xml" in
*'type="("'* | *'type=")"'* | *'type="{"'* | *'type="}"'* | *'type="a"'*)
fail "per-character <arg> fragment leaked" ;;
*) assert "no bracket/array fragments" 0 -eq 0 ;;
esac
+25 -3
View File
@@ -2,9 +2,10 @@
# libink: org.finit.Service1 vtable + ServiceStateChanged signal.
#
# Covers per-service objects exposed at /org/finit/service/<encoded>:
# GetService lookup, Introspect on a service object, Service1.Restart,
# authorization (non-root rejected), and the Manager1.ServiceStateChanged
# signal that Service1.Restart triggers.
# GetService lookup, Introspect on a service object, Service1
# properties via Properties.Get, Service1.Restart, authorization
# (non-root rejected), and the Manager1.ServiceStateChanged signal
# that Service1.Restart triggers.
set -eu
@@ -29,6 +30,27 @@ case "$xml" in
fail "Service1 not visible on /org/finit/service/keventd: $xml" ;;
esac
say "Service1 properties: Identity, State, Pid"
ident=$(texec "$CLIENT" getprop "$BUS" /org/finit/service/keventd \
org.finit.Service1 Identity)
assert "Identity is keventd (got: $ident)" "$ident" = "keventd"
state=$(texec "$CLIENT" getprop "$BUS" /org/finit/service/keventd \
org.finit.Service1 State)
assert "State is running (got: $state)" "$state" = "running"
pid=$(texec "$CLIENT" getprop "$BUS" /org/finit/service/keventd \
org.finit.Service1 Pid)
assert "Pid is non-zero (got: $pid)" "$pid" -gt 0
say "Service1 properties are advertised in introspection XML"
case "$xml" in
*'<property name="Identity" type="s"'*'<property name="Pid" type="u"'*)
assert "Identity and Pid declared with types" 0 -eq 0 ;;
*)
fail "Property declarations missing from service XML" ;;
esac
say "Service1.Restart on /org/finit/service/keventd succeeds"
texec "$CLIENT" call-void "$BUS" /org/finit/service/keventd \
org.finit.Service1 Restart >/dev/null \
+42
View File
@@ -338,6 +338,47 @@ static int mode_get_service(int argc, char *argv[])
return rc;
}
/*
* getprop BUS PATH IFACE PROP -- Properties.Get, prints the variant
* value; dispatches on the wire signature, "s" and "u" supported.
*/
static int mode_getprop(int argc, char *argv[])
{
const link_reply_t *r;
link_client_t *c;
link_reader_t reader;
const char *s;
uint32_t u;
int rc;
if (argc != 6) return 2;
c = link_client_open(argv[2]);
if (!c) return 2;
rc = link_client_call_v(c, argv[3],
"org.freedesktop.DBus.Properties", "Get",
"ss", argv[4], argv[5]);
rc = report_rc(c, rc);
if (rc == 0) {
char type = 0;
r = link_client_reply(c);
link_reader_init(&reader, r->body, r->body_len);
if (link_r_variant_begin(&reader, &type) != 0)
rc = 2;
else if (type == 's' && link_r_string(&reader, &s) == 0)
printf("%s\n", s);
else if (type == 'u' && link_r_u32(&reader, &u) == 0)
printf("%u\n", u);
else {
fprintf(stderr, "unsupported variant type '%c'\n", type);
rc = 2;
}
}
link_client_close(c);
return rc;
}
static int mode_monitor_signal(int argc, char *argv[])
{
link_client_t *c;
@@ -441,6 +482,7 @@ int main(int argc, char *argv[])
if (!strcmp(argv[1], "call-s-as-uid")) return mode_call_s_as_uid (argc, argv);
if (!strcmp(argv[1], "call-void-as-uid")) return mode_call_void_as_uid(argc, argv);
if (!strcmp(argv[1], "get-service")) return mode_get_service (argc, argv);
if (!strcmp(argv[1], "getprop")) return mode_getprop (argc, argv);
if (!strcmp(argv[1], "monitor-signal")) return mode_monitor_signal (argc, argv);
if (!strcmp(argv[1], "unknown")) return mode_unknown (argc, argv);
fprintf(stderr, "%s: unknown mode '%s'\n", argv[0], argv[1]);