Skip to content

Management API (sig_mgmt)

sig_mgmt (include/sig/mgmt/sig_mgmt.h, built by -DSIG_MGMT=ON) is the supported API for managing a running libsig instance: inspecting services, configurations and capsules, editing configurations and services, and starting, stopping and restarting services. It is a set of plain C operations that take and return sig_value trees and sig_status_t codes. It knows nothing about any transport or response format (configuration and service definition documents are JSON text, which edits accept and results return parsed). REST is one transport over it (sig_mgmt_http); a REPL, an ssh text tool or an IPC executable would be another.

The rule: nothing outside sig_mgmt should mutate a running project. Go through these operations so every client gets the same validation, the same locking and the same errors. This is a documented rule, not an enforced one: the core headers (sig_runtime, sig_project, …) are not hidden.

Register the module before anything that depends on it, then find the instance with sig_mgmt_from_runtime:

sig_runtime_register_module(rt, sig_mgmt_module(), NULL);
/* ... register transports after it ... */
sig_mgmt *m;
sig_mgmt_from_runtime(rt, &m); /* from a module's create hook or later */

sig_mgmt_from_runtime returns SIG_ERR_NOT_FOUND if the module is not registered and SIG_ERR_STATE if it is registered but not yet created. A SIG_MGMT-only build (no SIG_NET, no SIG_HTTP_SERVER) builds and tests everything on this page.

Every operation returns a sig_status_t.

  • On SIG_OK, an operation that has an out (or out_state) parameter has filled it with an owned sig_value tree. The caller releases it with sig_value_dtor. On failure the out value is left untouched (do not dtor it).
  • A tree owns all its memory: it is a deep copy with no pointers into runtime state, so it stays valid after the call returns and after the runtime stops.
  • Lists are sorted by name. Field names in the trees below are part of the contract.
  • Configuration documents in results (config) are parsed trees, not the stored text. Number spelling and key order may differ from the file.
  • A property value is copied into the tree as it is, whatever its kind (including bytes, maps and lists). Deciding how to show a kind a transport cannot express is the transport’s business.
  • Edit operations take an optional errmsg buffer (char *errmsg, size_t errmsg_len, may be NULL). It receives a NUL-terminated reason of at most errmsg_len - 1 bytes, and is empty unless stated below.
Status Meaning
SIG_ERR_INVALID_ARG A NULL argument. For edits also: a name or new name the project refuses, a new name of 80 bytes or more, an empty or NULL document (errmsg is document required). For start/restart also: the service could not start because a capsule configuration is invalid (the host log has the detail).
SIG_ERR_NOT_FOUND The object does not exist. This includes any name that could not name one (empty, over 64 characters, or containing a character other than letters, digits, _ and -, such as .. or /).
SIG_ERR_EXISTS Start of a running service; delete of a configuration a service uses (empty errmsg); rename onto a taken name, including its own (errmsg names it); a restart that lost a race to a concurrent start.
SIG_ERR_PARSE The document failed validation. errmsg is the project’s reason for configurations; empty for services (the reason is only in the host log).
SIG_ERR_STATE There is no project (the runtime is not running), or a service vanished before its state could be read, or a start was overtaken by concurrent stops or edits 8 times in a row.
SIG_ERR_ALLOC Out of memory. An edit that succeeded in the project but could not build its result returns this with the edit already applied.
anything else A failure inside the runtime or project, passed through.

On any failure of an edit, nothing was changed (except the SIG_ERR_ALLOC case above).

Each reads a snapshot and fills *out with a map or list tree.

Operation Result tree
sig_mgmt_system(m, out) {version, uptime_seconds, modules: [name]}
sig_mgmt_list_services(m, out) {services: [{name, state, dirty, instances}]}
sig_mgmt_get_service(m, name, out) {name, state, dirty, instances: [{name, configuration, type, inputs: [name], outputs: [name]}], links: [{from: {instance, terminal}, to: {instance, terminal}}]}
sig_mgmt_get_instance(m, service, instance, out) {name, service, configuration, type, properties: {key: value}}
sig_mgmt_list_capsules(m, out) {capsules: [{name, category?, inputs, outputs, properties: [{name, type, required, default?, min?, max?, choices?, description}]}]}
sig_mgmt_list_configurations(m, out) {configurations: [{name, type}]}
sig_mgmt_get_configuration(m, name, out) {name, type, config: <definition>, used_by: [service]}
sig_mgmt_get_service_config(m, name, out) {name, config: <definition>, dirty}

state is "running" or "stopped". dirty is true when the stored definition has been edited since the service was built (see Editing and the dirty flag). Fields marked ? are present only when set. uptime_seconds is measured from mgmt’s own start time.

Errors: SIG_ERR_INVALID_ARG (NULL argument), SIG_ERR_NOT_FOUND (no such object, or a name that could not name one; not returned by sig_mgmt_system or sig_mgmt_list_capsules), SIG_ERR_STATE (no project; not returned by sig_mgmt_system or sig_mgmt_list_capsules, which need no project), SIG_ERR_ALLOC.

Operation Effect
sig_mgmt_start_service(m, name, out_state) Starts a stopped service.
sig_mgmt_stop_service(m, name, out_state) Stops a service. Stopping a stopped service is SIG_OK.
sig_mgmt_restart_service(m, name, out_state) Stop, then start.
sig_mgmt_request_shutdown(m) Asks the runtime to shut down and returns SIG_OK (SIG_ERR_INVALID_ARG for NULL). It does not wait.

On SIG_OK, *out_state is {name, state}. start, stop and restart block until the service’s hooks have run. Errors: SIG_ERR_NOT_FOUND, SIG_ERR_EXISTS (start of a running service), SIG_ERR_STATE, SIG_ERR_INVALID_ARG (NULL, or an invalid capsule configuration), anything else the runtime returns.

A start (or the start half of a restart) of a stopped, dirty service first rebuilds it from its edited definition. The rebuild frees the old service, so mgmt does it under its write lock, then starts with sig_runtime_service_start_prebuilt, which returns SIG_ERR_STALE instead of rebuilding if the service went stale again in between. mgmt then repeats, up to 8 times, and gives up with SIG_ERR_STATE.

Restart is therefore not atomic: a concurrent start can win between its two halves (SIG_ERR_EXISTS), and if the rebuild or the start fails the service is left stopped.

An edit takes the document as text (doc, doc_len bytes, JSON, exactly as the project layer’s put and patch functions take it). out receives an owned tree on SIG_OK.

Operation Result tree
sig_mgmt_put_configuration(m, name, doc, doc_len, &created, out, errmsg, errmsg_len) {name, config}
sig_mgmt_patch_configuration(m, name, doc, doc_len, out, errmsg, errmsg_len) {name, config}
sig_mgmt_delete_configuration(m, name) none
sig_mgmt_rename_configuration(m, name, new_name, out, errmsg, errmsg_len) {name: <new name>, config}
sig_mgmt_put_service(m, name, doc, doc_len, &created, out, errmsg, errmsg_len) {name, config, dirty}
sig_mgmt_patch_service(m, name, doc, doc_len, out, errmsg, errmsg_len) {name, config, dirty}
sig_mgmt_delete_service(m, name) none

put creates or replaces; *created (put only) says whether the object is new. patch takes a JSON Merge Patch (RFC 7386) against the stored document. Rename takes the new name as a plain string. Errors are in the table above: put never returns SIG_ERR_NOT_FOUND (it creates), so only patch, delete and rename do.

Edits change the stored definition and mark affected services dirty. A stopped service is rebuilt at once; a running one keeps its old instances until it is restarted.

Each sig_mgmt has one reader/writer lock (sig_rwlock).

  • Reads take it shared, build the snapshot, and release it. Readers do not block one another.
  • Edits (put, patch, delete, rename) take it exclusive, and only while the project is edited. They wait for in-flight readers to finish and readers wait for them. The result tree is built afterwards from text copied out under the lock, so no project pointer outlives it.
  • start, stop, restart and shutdown do not hold the lock while the service’s hooks run (they may block for long). Only the short lookup that builds out_state takes it shared, plus the short exclusive rebuild of a stopped, dirty service described above.
  • Residual stall: an edit or rebuild that waits on the runtime’s service-control lock behind a slow start of another service holds the write lock while it waits, so reads stall until that start returns. This is inherent in sig_runtime_edit.
  • An operation never calls another operation while holding the lock, and the lock is not recursive.
  • Never call these operations from a service or capsule thread (a handler, a job, a start or stop hook): start, stop and restart wait for service threads to be joined and can deadlock. Call them from host, module or transport threads. They need the runtime to be running (SIG_ERR_STATE otherwise).

A transport is a runtime module that finds sig_mgmt in its create hook and maps its own framing onto the operations. Register sig_mgmt_module() first. This compiles against the header:

#include <stdio.h>
#include "sig/mgmt/sig_mgmt.h"
static sig_status_t text_create(sig_runtime *rt, const char *config, void **state) {
(void)config;
sig_mgmt *m; /* the mgmt module must be registered before this one */
sig_status_t st = sig_mgmt_from_runtime(rt, &m);
*state = (st == SIG_OK) ? m : NULL;
return st;
}
/* Called by your transport (a REPL line, an ssh command) on its own thread. */
static void text_list_services(sig_mgmt *m) {
sig_value out;
if (sig_mgmt_list_services(m, &out) != SIG_OK) {
puts("error");
return;
}
const sig_value *list, *svc, *name, *state;
const char *n, *s;
size_t nl, sl;
sig_value_map_get(&out, "services", &list);
for (size_t i = 0; sig_value_list_get(list, i, &svc) == SIG_OK; i++) {
sig_value_map_get(svc, "name", &name);
sig_value_map_get(svc, "state", &state);
sig_value_get_string(name, &n, &nl);
sig_value_get_string(state, &s, &sl);
printf("%s %s\n", n, s);
}
sig_value_dtor(&out);
}
static const sig_runtime_module text_module = {.name = "text", .create = text_create};

Register it with sig_runtime_register_module(rt, &text_module, NULL) after sig_mgmt_module(); the state pointer it keeps is the sig_mgmt * to pass to each operation. Error handling on the sig_value_* getters is omitted for brevity. To print a whole tree as JSON use sig_value_to_json (sig/value/sig_value_json.h), which sorts map keys; the result is a heap string that the caller releases with sig_free().

What a transport owns, and sig_mgmt does not: parsing requests, choosing how results are rendered, mapping sig_status_t to its own error vocabulary, and authorization. sig_mgmt_http is the worked example: see src/mgmt/sig_mgmt_rest.c and the status table in rest-api.md.