mirror of
https://github.com/troglobit/finit.git
synced 2026-10-15 10:29:35 +00:00
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:
1 parent
ebc0ef62e6
commit
6310d9e760
16 files changed
+1271
-315
No files matched your search
@@ -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
@@ -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.
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
@@ -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
@@ -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)
|
||||
|
||||
@@ -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
@@ -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
File diff suppressed because it is too large.
Load diff
+1
-1
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
Executable
+31
@@ -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
@@ -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 \
|
||||
|
||||
@@ -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]);
|
||||
|
||||
Reference in new issue
Block a user