aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSergey Poznyakoff <gray@gnu.org>2020-12-07 10:53:05 +0200
committerSergey Poznyakoff <gray@gnu.org>2020-12-07 10:53:05 +0200
commit217efcb974309cd373e9ab9e11573de8270e74e0 (patch)
treee291e493565e52c86a57d31d4401dc939f0bf581
parentb3a58380b93512b86fe02749425410d89a5655d4 (diff)
downloadpies-217efcb974309cd373e9ab9e11573de8270e74e0.tar.gz
pies-217efcb974309cd373e9ab9e11573de8270e74e0.tar.bz2
New API endpoint /alive. Improve documentation.
* doc/Makefile.am: Add ctl.texi * doc/ctl.texi: New file. * doc/pies.texi: Add anchors for ctl.texi * src/ctl.c (json_error_reply_create): Set status depending on the HTTP response code. Use "message", instead of "error_message". New endpoint: /alive * src/inetd.c: Use grecs_getline instead of getline. * src/sysvinit.c: Likewise. * src/piesctl.c: Inspect "message", instead of "error_message".
-rw-r--r--doc/Makefile.am1
-rw-r--r--doc/ctl.texi448
-rw-r--r--doc/pies.texi10
-rw-r--r--src/ctl.c65
-rw-r--r--src/inetd.c2
-rw-r--r--src/piesctl.c14
-rw-r--r--src/sysvinit.c2
7 files changed, 528 insertions, 14 deletions
diff --git a/doc/Makefile.am b/doc/Makefile.am
index 33ae9cf..5283a88 100644
--- a/doc/Makefile.am
+++ b/doc/Makefile.am
@@ -16,6 +16,7 @@
info_TEXINFOS=pies.texi
pies_TEXINFOS=\
+ ctl.texi\
fdl.texi\
inetd.texi\
macros.texi\
diff --git a/doc/ctl.texi b/doc/ctl.texi
new file mode 100644
index 0000000..588cebe
--- /dev/null
+++ b/doc/ctl.texi
@@ -0,0 +1,448 @@
+@c This is part of the GNU Pies manual.
+@c Copyright (C) 2009--2020 Sergey Poznyakoff
+@c This file is distributed under GFDL 1.3 or any later version
+@c published by the Free Software Foundation.
+
+ This appendix describes @dfn{control API} used to communicate with the
+running @command{pies} daemon via the control interface
+(@pxref{control}). This API is used by @command{piesctl} (@pxref{piesctl}).
+
+The API is designed as a REST service and uses HTTP. Queries are sent to
+pies @dfn{endpoints}, each of which serves a distinct purpose. Data
+are serialized using the JSON format.
+
+The sections below describe in detail each endpoint and associated
+with it request types.
+
+@node /instance
+@appendixsec /instance
+ This endpoint controls the state of the running @command{pies}
+instance and accepts the following HTTP requests: @code{GET},
+@code{DELETE}, @code{POST} (or @code{PUT}).
+
+@deffn {Request} GET /instance
+ Retrieves information about the current instance. The response body
+is a JSON object with the following attributes:
+
+@table @samp
+@item PID
+PID of the running daemon.
+
+@item argv
+Array of the command line arguments. @samp{argv[0]} is the program
+name.
+
+@item binary
+Name of the @command{pies} binary.
+
+@item instance
+The instance name. @xref{instances}.
+
+@item package
+Package name (the string @samp{GNU Pies}).
+
+@item version
+Package version
+@end table
+
+Any of these can be used in the URI to request the information about
+that particular attribute, e.g.:
+
+@example
+GET /instance/argv @result{} @{"argv":["pies", "-x2"]@}
+@end example
+@end deffn
+
+@deffn {Request} DELETE /instance/PID
+Stops the current @command{pies} instance.
+@end deffn
+
+@deffn {Request} PUT /instance/PID
+@deffnx {Request} POST /instance/PID
+Restarts the current @command{pies} instance.
+@end deffn
+
+@node /conf
+@appendixsec /conf
+
+@node /conf/files
+@appendixsubsec /conf/files
+
+@deffn {Request} GET /conf/files
+Return list of configuration files. On success, a JSON array is
+returned. Each array element is an object with two attributes:
+
+@defvr {Attr} string file
+Pathname of the configuration file.
+@end defvr
+
+@defvr {Attr} string syntax
+Configuration file syntax (@pxref{Syntax}).
+@end defvr
+
+For example:
+
+@example
+GET /conf/files @result{}
+[@{"file":"/etc/pies.conf", "syntax":"pies"@},
+ @{"file":"/etc/inetd.conf", "syntax":"inetd"@}]
+@end example
+@end deffn
+
+@deffn {Request} POST /conf/files
+Adds a new configuration file. The body must be a JSON object with
+@samp{file} and @samp{syntax} attributes, as described above. The
+@samp{file} value must contain a pathname of a configuration file
+written in a syntax supplied by the @samp{syntax} attribute
+(@pxref{Syntax}).
+
+This request returns 201 code on success. To actually parse and load
+the added configuration file, send a @samp{PUT} request to
+@samp{/conf/runtime} (@pxref{/conf/runtime}).
+@end deffn
+
+@deffn {Request} DELETE /conf/files/true
+Clears all previously configured configuration files. Responds with:
+
+@example
+@{ "message":"file list cleared", "status":"OK" @}
+@end example
+@end deffn
+
+@deffn {Request} DELETE /conf/files/[@var{list}]
+Removes files named in the @var{list} from the list of configuration files.
+
+The @samp{DELETE} response is 200 on success. To actually update the
+configuration of the running process, send a @samp{PUT} request to
+@samp{/conf/runtime} (@pxref{/conf/runtime}).
+@end deffn
+
+@node /conf/runtime
+@appendixsubsec /conf/runime
+
+This is a write-only URI. The only request supported is
+@samp{PUT /conf/runtime}. It initiates reloading of the
+@command{pies} configuration. Usually, this request is sent after one
+or more @samp{POST} and/or @samp{DELETE} requests to
+@samp{/conf/files}, in order to finalize the changes applied to the
+configuration.
+
+@node /programs
+@appendixsec /programs
+
+ A request sent to this URI selects one or more components and
+applies operation defined by the request type to all of them.
+
+ Components are selected using a query in the form of JSON object
+(a @dfn{selector}). Valid selectors are:
+
+@deffn {Selector} null
+@deffnx {Selector} false
+Matches nothing.
+@end deffn
+
+@deffn {Selector} true
+Matches all components.
+@end deffn
+
+@deffn {Selector} @{ "op": "component", "arg": @var{tag} @}
+Matches component with the given @var{tag} (@pxref{tag}).
+@end deffn
+
+@deffn {Selector} @{ "op": "type", "arg": "component" @}
+Matches all components.
+@end deffn
+
+@deffn {Selector} @{ "op": "type", "arg": "command" @}
+Matches all commands.
+@end deffn
+
+@deffn {Selector} @{ "op": "mode", "arg": @var{mode} @}
+Matches all components with the given @var{mode}. @xref{component
+mode}.
+@end deffn
+
+@deffn {Selector} @{ "op": "active" @}
+Matches all active components.
+@end deffn
+
+@deffn {Selector} @{ "op": "status", "arg": @var{status} @}
+Matches all components with the given @var{status} (one of
+@samp{stopped}, @samp{running}, @samp{listener}, @samp{sleeping},
+@samp{stopping}, @samp{finished}). @xref{component status} for a
+discussion of these values.
+@end deffn
+
+@deffn {Selector} @{ "op: "not", "arg": @var{condition} @}
+Negates @var{condition}, which is any valid selector.
+@end deffn
+
+@deffn {Selector} @{ "op": "and", "arg": @var{array} @}
+Returns the result of logical conjunction on the @var{array} of selectors.
+@end deffn
+
+@deffn {Selector} @{ "op": "or", "arg": @var{array} @}
+Returns the result of logical disjunction on the @var{array} of selectors.
+@end deffn
+
+ For example, the following selector matches all components that are
+in @samp{running} state, excepting components of @samp{inetd} mode:
+
+@example
+@{ "op": "and",
+ "arg": [ @{ "op": "type", "arg": "component" @},
+ @{ "op": "not", "arg": @{ "op": "mode", "arg": "inetd" @}
+ ]
+@}
+@end example
+
+The following requests are supported:
+
+@deffn {Request} GET /programs?@var{selector}
+@deffnx {Request} GET /programs/@var{tag}
+ This request returns information about components matched by
+@var{selector} (see below for the @samp{/programs/@var{tag} variant}.
+The response is a JSON array of descriptions. If no component matches
+the @var{selector}, empty array is returned. Each description is a
+JSON object with the following attributes:
+
+@deftypevr {Attr} string type
+Type of the described entity: @samp{component} for an instance of a
+configured component, and @samp{command} for a command run as a part
+of exit action (@pxref{Exit Actions}), including mailer invocations
+(@pxref{Notification}).
+@end deftypevr
+
+@deftypevr {Attr} string mode
+Mode of the entity. @xref{component mode}.
+@end deftypevr
+
+@anchor{component status}
+@deftypevr {Attr} string status
+Entity status. Possible values are:
+
+@table @code
+@item finished
+A @samp{once} or @samp{startup} component has finished.
+
+@item listener
+Component is an inetd listener.
+
+@item running
+Component is running.
+
+@item sleeping
+Component has been put to sleep because of excessive number of
+failures (@pxref{respawn}).
+
+@item stopped
+Component is stopped.
+
+@item stopping
+Component is being stopped (a @code{SIGTERM} was sent).
+@end table
+@end deftypevr
+
+@deftypevr {Attr} boolean active
+Whether this component is active. By default, all components are
+active, unless marked with a @samp{disable} flag (@pxref{flags}) or
+administratively stopped.
+@end deftypevr
+
+@deftypevr {Attr} integer PID
+PID of the running process.
+@end deftypevr
+
+@deftypevr {Attr} string URL
+(for @samp{inetd} components) URL of the socket the component is
+listening on.
+@end deftypevr
+
+@deftypevr {Attr} string service
+(for @samp{tcpmux} components) TCPMUX service name. @xref{TCPMUX}.
+@end deftypevr
+
+@deftypevr {Attr} string master
+(for @samp{tcpmux} components) Tag of master TCPMUX component.
+@xref{TCPMUX}.
+@end deftypevr
+
+@deftypevr {Attr} string runlevels
+For inittab components, the string of runlevels this component is
+configured to run in. @xref{Init Process}.
+@end deftypevr
+
+@deftypevr {Attr} integer wakeup-time
+If component is in the @samp{sleeping} state, this attribute gives the
+number of seconds after which an attempt will be made to restart it.
+@end deftypevr
+
+@deftypevr {Attr} array argv
+Component command line split into words.
+@end deftypevr
+
+@deftypevr {Attr} string command
+Component command.
+@end deftypevr
+@end deffn
+
+@deffn {Request} DELETE /programs?@var{selector}
+@deffnx {Request} DELETE /programs/@var{tag}
+Stop components matched by the @var{selector}. On success returns:
+
+@example
+@{ "status":"OK" @}
+@end example
+
+@noindent
+On failure, returns
+
+@example
+@{ "status":"ER", "error_message": @var{text} @}
+@end example
+
+@noindent
+where @var{text} is a textual human-readable description of the failure.
+@end deffn
+
+@deffn {Request} PUT /programs?@var{selector}
+@deffnx {Request} PUT /programs/@var{tag}
+Start components matched by @var{selector}.
+@end deffn
+
+@deffn {Request} POST /programs
+Restart components. The selector is supplied in the request content.
+@end deffn
+
+Wherever a selector is passed via query parameters, a simplified form
+with component tag passed as query path is also allowed. For example:
+
+@example
+ GET /programs/@var{tag}
+@end example
+
+@noindent
+is a shortcut for:
+
+@example
+@{ "op":"and",
+ "arg":[ @{"op":"type", "arg":"component"@},
+ @{"op":"component", "arg":@var{tag} @} ] @}
+@end example
+
+@node /alive
+@appendixsec /alive
+
+ This entry point accepts only @samp{GET} requests. The URI must not
+be empty and must not include sub-directories (parts separated with
+slashes). It is treated as the name of the component to return the
+status of. E.g. querying @samp{/alive/foo} returns the status of the
+component named @samp{foo}. The status is returned as HTTP status
+code:
+
+@table @asis
+@item 200
+The component is up and running. For regular components that means
+that the corresponding program is running. For @samp{inetd}
+components that means that the listener is listening on the configured
+socket.
+
+@item 403
+No component specified.
+
+@item 404
+There is no such component.
+
+@item 503
+The component is not running. This means that it has failed, or has
+been stopped administratively or (for @samp{once} and @samp{startup}
+components) that it has run once and finished.
+
+If the component has failed, the @samp{Retry-After:} HTTP header
+contains the number of seconds after which @command{pies} will retry
+starting this component.
+@end table
+
+@node /runlevel
+@appendixsec /runlevel
+ This URI is active when @command{pies} runs as init process
+(@pxref{Init Process}). It supports two requests:
+
+@deffn {Request} GET /runlevel
+Returns the current state of the program as a JSON object with the
+following attributes:
+
+@defvr {Attr} string runlevel
+Current runlevel. @xref{Runlevels}.
+@end defvr
+
+@defvr {Attr} string prevlevel
+Previous runlevel (@samp{N} if none).
+@end defvr
+
+@defvr {Attr} string bootstate
+Boot state. @xref{startup states}.
+@end defvr
+
+@defvr {Attr} string initdefault
+Default runlevel.
+@end defvr
+@end deffn
+
+@deffn {Request} PUT /runlevel/@{"runlevel":@var{L}@}
+Initiates transition from the current runlevel to runlevel @var{L}
+(@pxref{Runlevels}).
+@end deffn
+
+@node /environ
+@appendixsec /environ
+ This URI is active when @command{pies} runs as init process
+(@pxref{Init Process}). It manipulates the program initial
+environment, i.e. the environment that all programs inherit.
+@xref{Init Environment}.
+
+@deffn {Request} GET /environ/
+Returns entire environment formatted as a JSON array of strings. On
+success, the 200 response is returned:
+
+@example
+["RUNLEVEL=3", "CONSOLE=/dev/tty", ...]
+@end example
+@end deffn
+
+@deffn {Request} GET /environ/@var{var}
+Returns the value of the environment variable @var{var}, if such is
+defined. On success, the 200 response carries the object:
+
+@example
+@{ "status":"OK", "value":@var{string} @}
+@end example
+
+@noindent
+If the variable @var{var} is not defined, a 404 response is returned.
+On error, a 403 response is returned. In both cases, the response
+body is the usual @command{pies} diagnostics object:
+
+@example
+@{ "status":"ER", "error_message":@var{text} @}
+@end example
+@end deffn
+
+@deffn {Request} DELETE /environ/@var{var}
+Deletes from the environment the variable @var{var}. On success,
+responds with HTTP 200:
+
+@example
+@{ "status":"OK" @}
+@end example
+
+Error responses are the same as for @samp{GET}.
+@end deffn
+
+@deffn {Request} PUT /environ/@var{name}=@var{value}
+Initializes environment variable @var{name} to @var{value}. See
+@samp{GET} for the possible responses.
+@end deffn
+
+
+
diff --git a/doc/pies.texi b/doc/pies.texi
index 1d65cf4..502b513 100644
--- a/doc/pies.texi
+++ b/doc/pies.texi
@@ -93,6 +93,7 @@ Appendices
* inetd configuration:: @file{Inetd.conf} Format.
* User-Group ACLs::
+* REST API::
* Copying This Manual:: The GNU Free Documentation License.
* Concept Index:: Index of Concepts.
@@ -729,6 +730,9 @@ component @var{tag} @{
@}
@end example
+@cindex component tag
+@cindex tag (component)
+@anchor{tag}
The component is identified by its @dfn{tag}, which is given as
argument to the @code{component} keyword. Component declarations with
the same tags are merged into a single declaration.
@@ -736,6 +740,7 @@ the same tags are merged into a single declaration.
The following are the basic statements which are allowed within the
@code{component} block:
+@anchor{component mode}
@deffn {Config: component} mode @var{mode}
Declare the type (style) of the component. Following are the basic
values for @var{mode}:
@@ -3458,6 +3463,7 @@ with another @samp{init} daemons, and the latter is in native
@command{pies} format (@pxref{Syntax}). Either of the files or even
both of them can be missing.
+@anchor{startup states}
The startup process passes through several states. Transition between
states is controlled by @dfn{runlevel}, which also defines the set of
components that must be executed. Startup states are:
@@ -4514,6 +4520,10 @@ line options used).
@appendix User-Group ACLs
@include usr-acl.texi
+@node REST API
+@appendix REST API
+@include ctl.texi
+
@node Copying This Manual
@appendix GNU Free Documentation License
@include fdl.texi
diff --git a/src/ctl.c b/src/ctl.c
index 9e3ae61..21b0642 100644
--- a/src/ctl.c
+++ b/src/ctl.c
@@ -479,13 +479,13 @@ json_object_set_bool (struct json_value *obj, char const *name, int val)
}
static struct json_value *
-json_error_reply_create (const char *msg)
+json_error_reply_create (int http_code, const char *msg)
{
struct json_value *val;
val = json_reply_create ();
- json_object_set_string (val, "status", "ER");
- json_object_set_string (val, "error_message", "%s", msg);
+ json_object_set_string (val, "status", http_code < 300 ? "OK" : "ER");
+ json_object_set_string (val, "message", "%s", msg);
return val;
}
@@ -607,11 +607,11 @@ ctlio_reply (struct ctlio *io, int code, const char *fmt, ...)
grecs_vasprintf (&str, &len, fmt, ap);
va_end (ap);
- io->output.reply = json_error_reply_create (str);
+ io->output.reply = json_error_reply_create (code, str);
grecs_free (str);
}
else
- io->output.reply = json_error_reply_create (http_text (code));
+ io->output.reply = json_error_reply_create (code, http_text (code));
}
static void
@@ -918,6 +918,8 @@ static void res_instance (struct ctlio *, enum http_method, char const *,
struct json_value *);
static void res_programs (struct ctlio *, enum http_method, char const *,
struct json_value *);
+static void res_alive (struct ctlio *, enum http_method, char const *,
+ struct json_value *);
static void res_conf (struct ctlio *, enum http_method, char const *,
struct json_value *);
@@ -946,6 +948,8 @@ static struct ctlio_resource restab[] = {
{ S(/conf), CTL_ADMIN_STATE, NULL, res_conf },
{ S(/programs), CTL_ADMIN_STATE|CTL_USER_STATE, NULL,
res_programs },
+ { S(/alive), CTL_ADMIN_STATE|CTL_USER_STATE, NULL,
+ res_alive },
#if PIES_SYSVINIT_ENABLED
{ S(/runlevel), CTL_ADMIN_STATE, pred_sysvinit, res_runlevel },
{ S(/environ), CTL_ADMIN_STATE, pred_sysvinit, res_environ },
@@ -2113,6 +2117,57 @@ res_programs (struct ctlio *io, enum http_method meth,
else
res_programs_select (io, meth, uri, json);
}
+
+static void
+res_alive (struct ctlio *io, enum http_method meth, char const *uri,
+ struct json_value *json)
+{
+ struct prog *prog;
+
+ if (!uri)
+ {
+ ctlio_reply (io, 403, NULL);
+ return;
+ }
+
+ prog = progman_locate (uri + 1);
+
+ if (prog && IS_COMPONENT (prog))
+ {
+ switch (prog->v.p.status)
+ {
+ case status_running:
+ case status_listener:
+ /* FIXME: Presumably these three fall into that category too:
+ status_stopped -- Component is stopped administratively
+ status_stopping -- Component is being stopped
+ status_finished -- A "once" or "startup" component has finished
+ */
+ ctlio_reply (io, 200, NULL);
+ break;
+
+ case status_sleeping:
+ {
+ time_t t;
+ char buf[80];
+
+ t = time (NULL) - prog->v.p.timestamp;
+ if (t <= SLEEPTIME)
+ {
+ snprintf (buf, sizeof buf, "%lu", SLEEPTIME - t);
+ output_set_header (&io->output, "Retry-After", buf);
+ }
+ }
+ /* fall through */
+
+ default:
+ ctlio_reply (io, 503, NULL);
+ break;
+ }
+ }
+ else
+ ctlio_reply (io, 404, NULL);
+}
#if PIES_SYSVINIT_ENABLED
static int
diff --git a/src/inetd.c b/src/inetd.c
index 55bc2f1..b7ae0eb 100644
--- a/src/inetd.c
+++ b/src/inetd.c
@@ -96,7 +96,7 @@ inetd_conf_file (const char *file)
}
wsflags = WRDSF_NOVAR | WRDSF_NOCMD | WRDSF_WS | WRDSF_SQUEEZE_DELIMS;
- while (getline (&buf, &size, fp) >= 0)
+ while (grecs_getline (&buf, &size, fp) >= 0)
{
char *p;
struct component *comp;
diff --git a/src/piesctl.c b/src/piesctl.c
index 58f9ad2..969d0e5 100644
--- a/src/piesctl.c
+++ b/src/piesctl.c
@@ -744,7 +744,7 @@ shttp_print_error (struct shttp_connection *conn)
{
struct json_value *jv;
- if (conn->result && (jv = json_value_lookup (conn->result, "error_message")))
+ if (conn->result && (jv = json_value_lookup (conn->result, "message")))
{
if (jv->type == json_string)
grecs_error (NULL, 0, "%s", jv->v.s);
@@ -1477,7 +1477,7 @@ shttp_print_response_status (struct shttp_connection *conn)
{
if (strcmp (v->v.s, "OK") == 0)
fputs (v->v.s, stdout);
- else if (json_object_get_type (elt, "error_message",
+ else if (json_object_get_type (elt, "message",
json_string, &v) == 0)
fputs (v->v.s, stdout);
else
@@ -1756,7 +1756,7 @@ telinit_format_environ (struct shttp_connection *conn)
}
else if (strcmp (val->v.s, "ER") == 0)
{
- if (json_object_get_type (conn->result, "error_message",
+ if (json_object_get_type (conn->result, "message",
json_string, &val) == 0)
fputs (val->v.s, stderr);
else
@@ -1957,7 +1957,7 @@ conf_reload (struct cmdline_parser_state *state)
val = json_object_require_type (conn->result, "status", json_string);
if (strcmp (val->v.s, "ER") == 0)
{
- if (json_object_get_type (conn->result, "error_message",
+ if (json_object_get_type (conn->result, "message",
json_string, &val) == 0)
fputs (val->v.s, stdout);
else
@@ -2032,7 +2032,7 @@ conf_file_clear (struct cmdline_parser_state *state)
val = json_object_require_type (conn->result, "status", json_string);
if (strcmp (val->v.s, "OK"))
{
- if (json_object_get_type (conn->result, "error_message",
+ if (json_object_get_type (conn->result, "message",
json_string, &val) == 0)
fputs (val->v.s, stderr);
else
@@ -2084,7 +2084,7 @@ conf_file_del (struct cmdline_parser_state *state)
val = json_object_require_type (conn->result, "status", json_string);
if (strcmp (val->v.s, "OK") == 0)
{
- if (json_object_get_type (conn->result, "error_message",
+ if (json_object_get_type (conn->result, "message",
json_string, &val) == 0)
{
fputs (val->v.s, stderr);
@@ -2109,7 +2109,7 @@ conf_file_del (struct cmdline_parser_state *state)
}
else
{
- if (json_object_get_type (conn->result, "error_message",
+ if (json_object_get_type (conn->result, "message",
json_string, &val) == 0)
fputs (val->v.s, stderr);
else
diff --git a/src/sysvinit.c b/src/sysvinit.c
index f63687e..a9681e0 100644
--- a/src/sysvinit.c
+++ b/src/sysvinit.c
@@ -1167,7 +1167,7 @@ inittab_parse (const char *file)
fp = fopen (file, "r");
if (fp)
{
- while (getline (&ctx.buf, &ctx.size, fp) >= 0)
+ while (grecs_getline (&ctx.buf, &ctx.size, fp) >= 0)
{
enum inittab_status st = inittab_parse_line (&ctx);
if (st == inittab_err)

Return to:

Send suggestions and report system problems to the System administrator.