diff options
author | Sergey Poznyakoff <gray@gnu.org.ua> | 2016-02-20 22:33:00 +0200 |
---|---|---|
committer | Sergey Poznyakoff <gray@gnu.org.ua> | 2016-02-20 22:42:46 +0200 |
commit | 46130027d8e6e91951226456174e6ad471ddc50f (patch) | |
tree | f88ce6edd9b078b5f331497bc452525f3c4c9c94 /src | |
parent | e6902abfddb4d7b16dc9a4231a3781f354a08cd5 (diff) | |
download | pies-46130027d8e6e91951226456174e6ad471ddc50f.tar.gz pies-46130027d8e6e91951226456174e6ad471ddc50f.tar.bz2 |
Improve control interface
This commit implements the following operations on the new /conf endpoints:
GET /conf/runtime - List configuration
PUT /conf/runtime - Reload configuration
POST /conf/runtime - Post new configuration
GET /conf/files - List configuration files
DELETE /conf/files - Delete some or all configuration files
from the list
POST /conf/files - Install new configuration file
Piesctl supports the following new commands:
piesctl config reload
piesctl config file clear
piesctl config file add SYNTAX NAME
piesctl config file del[ete] NAME [NAME...]
piesctl config file list
* src/ctl.c (res_conf): New function.
(restab): New endpoint /conf
* src/pies.c (config_file_add): Set free_entry.
(config_file_remove, config_file_remove_all)
(config_file_list_serialize): New functions.
(pies_config_parse): Bail out if tree processing fails.
(pies_reload): Remove unused function.
(main): Redo ACTION_RELOAD handling.
Handle ACTION_COMMIT.
* src/pies.h (ACTION_COMMIT): New action.
(config_file_remove, config_file_remove_all)
(config_file_list_serialize)
(pies_read_config, progman_gc): New protos.
* src/piesctl.c (cmdline_parser_state): New struct
(next_token, peek_token): Take ptr to cmdline_parser_state as argument.
(parse_error, require_token, assert_eol)
(piesctl_format): New functions.
(parse_condition_to_uri): Rewrite using piesctl_format.
New subcommand: config.
Improve help output.
Diffstat (limited to 'src')
-rw-r--r-- | src/ctl.c | 246 | ||||
-rw-r--r-- | src/pies.c | 86 | ||||
-rw-r--r-- | src/pies.h | 10 | ||||
-rw-r--r-- | src/piesctl.c | 568 |
4 files changed, 764 insertions, 146 deletions
@@ -910,6 +910,8 @@ static void res_programs (struct ctlio *, enum http_method, char const *, struct json_value *); static void res_runlevel (struct ctlio *, enum http_method, char const *, struct json_value *); +static void res_conf (struct ctlio *, enum http_method, char const *, + struct json_value *); static int pred_sysvinit (void); @@ -926,6 +928,7 @@ struct ctlio_resource static struct ctlio_resource restab[] = { #define S(s) #s, (sizeof (#s)-1) { S(/instance), CTL_ADMIN_STATE, NULL, res_instance }, + { S(/conf), CTL_ADMIN_STATE, NULL, res_conf }, { S(/programs), CTL_ADMIN_STATE|CTL_USER_STATE, NULL, res_programs }, { S(/runlevel), CTL_ADMIN_STATE, pred_sysvinit, res_runlevel }, @@ -2108,3 +2111,246 @@ res_runlevel (struct ctlio *io, enum http_method meth, ctlio_reply (io, 405, NULL); } +/* GET /conf/runtime - List configuration + * PUT /conf/runtime - Reload configuration + * POST /conf/runtime - Post new configuration + * + * GET /conf/files - List configuration files + * DELETE /conf/files - Delete some or all configuration files + * from the list + * POST /conf/files - Install new list configuration file + */ + +static void +conf_list_files (struct ctlio *io) +{ + io->output.reply = json_new_array (); + io->code = 200; + config_file_list_serialize (io->output.reply); +} + +static void +conf_add_file (struct ctlio *io, struct json_value *json) +{ + struct json_value *v; + struct config_syntax *synt; + + if (json->type != json_object) + { + ctlio_reply (io, 400, NULL); + return; + } + if (json_object_get (json, "syntax", &v) || v->type != json_string) + { + ctlio_reply (io, 400, NULL); + return; + } + synt = str_to_config_syntax (v->v.s); + if (!synt) + { + ctlio_reply (io, 400, NULL); + return; + } + if (json_object_get (json, "file", &v) || v->type != json_string) + { + ctlio_reply (io, 400, NULL); + return; + } + + config_file_add (synt, v->v.s); + ctlio_reply (io, 201, NULL); +} + +static void +conf_delete_files (struct ctlio *io, struct json_value *json) +{ + io->code = 200; + io->output.reply = json_reply_create (); + + if ((json->type == json_bool && json->v.b == 1) + || (json->type == json_arr && json_array_size (json) == 0)) + { + config_file_remove_all (); + json_object_set_string (io->output.reply, "status", "OK"); + json_object_set_string (io->output.reply, "message", + "file list cleared"); + } + else if (json->type == json_arr) + { + size_t i, n, fails = 0; + struct json_value *reply; + + n = json_array_size (json); + + /* Check request */ + for (i = 0; i < n; i++) + { + struct json_value *val; + json_array_get (json, i, &val); + if (val->type != json_string) + { + json_object_set_string (io->output.reply, "status", "ER"); + json_object_set_string (io->output.reply, "error_message", + "malformed request"); + return; + } + } + + reply = json_new_array (); + /* Process request */ + for (i = 0; i < n; i++) + { + struct json_value *val; + int rc; + + json_array_get (json, i, &val); + + rc = config_file_remove (val->v.s); + if (rc) + ++fails; + json_array_append (reply, json_new_bool (!rc)); + } + + if (fails == n) + { + json_object_set_string (io->output.reply, "status", "ER"); + json_object_set_string (io->output.reply, "error_message", + "no matching files found"); + json_value_free (reply); + } + else + { + json_object_set_string (io->output.reply, "status", "OK"); + if (fails) + { + json_object_set_string (io->output.reply, "error_message", + "some files not removed"); + json_object_set (io->output.reply, "result", reply); + } + else + json_value_free (reply); + } + } +} + +static void (*saved_diag_fun)(grecs_locus_t const *, int, int, + const char *msg); +static struct json_value *messages; + +static void +reload_diag_fun (grecs_locus_t const *locus, int err, int errcode, + const char *text) +{ + struct json_value *msg = json_new_object (); + + if (locus) + { + struct json_value *ar = json_new_array (); + json_array_append (ar, json_new_string (locus->beg.file)); + json_array_append (ar, json_new_string (locus->end.file)); + json_object_set (msg, "file", ar); + + ar = json_new_array (); + json_array_append (ar, json_new_number (locus->beg.line)); + json_array_append (ar, json_new_number (locus->end.line)); + json_object_set (msg, "line", ar); + + ar = json_new_array (); + json_array_append (ar, json_new_number (locus->beg.col)); + json_array_append (ar, json_new_number (locus->end.col)); + json_object_set (msg, "col", ar); + + json_object_set (msg, "error", json_new_bool (err)); + + if (errcode) + { + json_object_set (msg, "syserrno", + json_new_number (errno)); + json_object_set (msg, "syserrstr", + json_new_string (strerror (errno))); + } + + json_object_set (msg, "message", json_new_string (text)); + json_array_append (messages, msg); + } + + saved_diag_fun (locus, err, errcode, text); +} + +static void +conf_reload (struct ctlio *io) +{ + io->code = 200; + io->output.reply = json_reply_create (); + + saved_diag_fun = grecs_print_diag_fun; + grecs_print_diag_fun = reload_diag_fun; + messages = json_new_array (); + if (pies_read_config ()) + { + json_object_set_string (io->output.reply, "status", "ER"); + json_object_set_string (io->output.reply, "error_message", + "configuration syntax error"); + } + else + { + pies_schedule_action (ACTION_COMMIT); + json_object_set_string (io->output.reply, "status", "OK"); + json_object_set_string (io->output.reply, "message", + "reload successful"); + } + json_object_set (io->output.reply, "parser_messages", messages); + grecs_print_diag_fun = saved_diag_fun; +} + +static void +res_conf (struct ctlio *io, enum http_method meth, + char const *uri, struct json_value *json) +{ + if (!uri) + { + ctlio_reply (io, 404, NULL); + return; + } + + ++uri; /* skip leading / */ + if (strcmp (uri, "files") == 0) + { + switch (meth) + { + case METH_GET: + conf_list_files (io); + break; + + case METH_POST: + conf_add_file (io, json); + break; + + case METH_DELETE: + conf_delete_files (io, json); + break; + + default: + ctlio_reply (io, 405, NULL); + } + } + else if (strcmp (uri, "runtime") == 0) + { + switch (meth) + { + case METH_GET: + case METH_POST: + ctlio_reply (io, 501, NULL); + break; + + case METH_PUT: + conf_reload (io); + break; + + default: + ctlio_reply (io, 405, NULL); + } + } + else + ctlio_reply (io, 404, NULL); +} @@ -98,6 +98,17 @@ str_to_config_syntax (const char *str) return NULL; } +static void +config_file_free (void *ptr) +{ + if (ptr) + { + struct config_file *file = ptr; + grecs_free (file->name); + grecs_free (file); + } +} + void config_file_add (struct config_syntax *syntax, const char *name) { @@ -105,7 +116,10 @@ config_file_add (struct config_syntax *syntax, const char *name) file->syntax = syntax; file->name = grecs_strdup (name); if (!config_list) + { config_list = grecs_list_create (); + config_list->free_entry = config_file_free; + } grecs_list_append (config_list, file); } @@ -115,6 +129,45 @@ config_file_add_type (enum config_syntax_type syntax, const char *name) config_file_add (&config_syntax_tab[syntax], name); } +int +config_file_remove (const char *name) +{ + struct grecs_list_entry *ep; + + for (ep = config_list->head; ep; ep = ep->next) + { + struct config_file *file = ep->data; + if (strcmp (file->name, name) == 0) + { + grecs_list_remove_entry (config_list, ep); + config_file_free (file); + return 0; + } + } + return 1; +} + +void +config_file_remove_all (void) +{ + grecs_list_clear (config_list); +} + +void +config_file_list_serialize (struct json_value *ar) +{ + struct grecs_list_entry *ep; + + for (ep = config_list->head; ep; ep = ep->next) + { + struct config_file *file = ep->data; + struct json_value *obj = json_new_object (); + json_object_set (obj, "syntax", json_new_string (file->syntax->name)); + json_object_set (obj, "file", json_new_string (file->name)); + json_array_append (ar, obj); + } +} + /* Logging */ static int stderr_closed_p () @@ -1492,6 +1545,9 @@ pies_config_parse (char const *name) grecs_tree_free (tree); + if (grecs_error_count) + return 1; + return 0; } @@ -1528,22 +1584,6 @@ pies_read_config (void) return err; } -int -pies_reload (void) -{ - int rc = pies_read_config (); - if (rc == 0) - { - component_config_commit (); - if (init_process) - sysvinit_runlevel_setup (PIES_COMP_DEFAULT); - progman_create_sockets (); - progman_start (); - } - - return rc; -} - static struct config_syntax *current_syntax = &config_syntax_tab[CONF_PIES]; #include "cmdline.h" @@ -2186,7 +2226,19 @@ main (int argc, char **argv) break; case ACTION_RELOAD: - pies_reload (); + if (pies_read_config ()) + { + action = ACTION_CONT; + break; + } + /* fall through */ + case ACTION_COMMIT: + component_config_commit (); + if (init_process) + sysvinit_runlevel_setup (PIES_COMP_DEFAULT); + progman_create_sockets (); + progman_start (); + pies_schedule_children (PIES_CHLD_WAKEUP); action = ACTION_CONT; break; @@ -271,7 +271,8 @@ enum pies_action { ACTION_RELOAD, ACTION_CTRLALTDEL, ACTION_KBREQUEST, - ACTION_POWER + ACTION_POWER, + ACTION_COMMIT }; extern char *instance; @@ -309,6 +310,10 @@ struct config_syntax; struct config_syntax *str_to_config_syntax (const char *str); void config_file_add (struct config_syntax *syntax, const char *name); void config_file_add_type (enum config_syntax_type syntax, const char *name); +void config_file_list_serialize (struct json_value *ar); + +int config_file_remove (const char *name); +void config_file_remove_all (void); void free_redirector (struct redirector *rp); @@ -322,9 +327,12 @@ void free_action (struct action *act); void pies_schedule_children (int op); +int pies_read_config (void); + void register_prog (struct component *comp); int progman_waiting_p (void); void progman_start (void); +void progman_gc (void); void progman_wake_sleeping (int); void progman_stop (void); void progman_cleanup (int expect_term); diff --git a/src/piesctl.c b/src/piesctl.c index e2e0483..310dcc3 100644 --- a/src/piesctl.c +++ b/src/piesctl.c @@ -24,6 +24,7 @@ #include <locale.h> #include <errno.h> #include <fcntl.h> +#include <assert.h> #include <configmake.h> #include <grecs.h> #include <grecs-locus.h> @@ -1240,14 +1241,15 @@ print_comp (FILE *fp, struct json_value *v, size_t n) fputc ('\n', fp); } -struct pcond_parser_state +struct cmdline_parser_state { int argc; char **argv; + struct shttp_connection *conn; }; -static char * -next_token (struct pcond_parser_state *state) +static char const * +next_token (struct cmdline_parser_state *state) { if (state->argc == 0) return NULL; @@ -1256,13 +1258,79 @@ next_token (struct pcond_parser_state *state) } static char const * -peek_token (struct pcond_parser_state *state) +peek_token (struct cmdline_parser_state *state) { if (state->argc == 0) return NULL; return *state->argv; } +static void +parse_error (char const *fmt, ...) +{ + va_list ap; + va_start (ap, fmt); + fflush (stdout); + fprintf (stderr, "%s: ", program_name); + vfprintf (stderr, fmt, ap); + fputc ('\n', stderr); + va_end (ap); + exit (EX_USAGE); +} + +static char const * +require_token (struct cmdline_parser_state *state) +{ + char const *tok = next_token (state); + if (!tok) + parse_error ("%s", _("unexpected end of statement")); + return tok; +} + +static void +assert_eol (struct cmdline_parser_state *state) +{ + char const *tok = next_token (state); + if (tok) + parse_error (_("expected end of statement, but found \"%s\""), tok); +} + +static void +acc_writer (void *closure, char const *text, size_t len) +{ + grecs_txtacc_grow ((struct grecs_txtacc *)closure, text, len); +} + +static char * +piesctl_format (struct json_value *val, char const *base) +{ + char *ret = NULL; + struct grecs_txtacc *acc; + + acc = grecs_txtacc_create (); + if (base) + { + grecs_txtacc_grow_string (acc, base); + if (val) + grecs_txtacc_grow_char (acc, '?'); + } + if (val) + { + struct json_format fmt = { + .indent = 0, + .precision = 0, + .write = acc_writer, + .data = acc + }; + json_format_value (val, &fmt); + } + grecs_txtacc_grow_char (acc, 0); + + ret = grecs_txtacc_finish (acc, 1); + grecs_txtacc_free (acc); + return ret; +} + static struct json_value * json_encode_op (char const *op, struct json_value *arg) { @@ -1272,10 +1340,10 @@ json_encode_op (char const *op, struct json_value *arg) return ret; } -static void pcond_parse_or (struct pcond_parser_state *, struct json_value **); +static void pcond_parse_or (struct cmdline_parser_state *, struct json_value **); static void -pcond_parse_unary (struct pcond_parser_state *state, struct json_value **ret) +pcond_parse_unary (struct cmdline_parser_state *state, struct json_value **ret) { char const *term = next_token (state); @@ -1292,20 +1360,14 @@ pcond_parse_unary (struct pcond_parser_state *state, struct json_value **ret) else if (is_array_member (arg_terms, term)) { if (!peek_token (state)) - { - grecs_error (NULL, 0, _("%s requires argument"), term); - exit (EX_USAGE); - } + parse_error (_("%s requires argument"), term); *ret = json_encode_op (term, json_new_string (next_token (state))); } else if (strcasecmp (term, "not") == 0) { struct json_value *val; if (!peek_token (state)) - { - grecs_error (NULL, 0, _("%s requires argument"), term); - exit (EX_USAGE); - } + parse_error (_("%s requires argument"), term); pcond_parse_unary (state, &val); *ret = json_encode_op (term, val); } @@ -1314,16 +1376,10 @@ pcond_parse_unary (struct pcond_parser_state *state, struct json_value **ret) pcond_parse_or (state, ret); term = next_token (state); if (!term || term[0] != ')') - { - grecs_error (NULL, 0, _("unbalanced parentesis")); - exit (EX_USAGE); - } + parse_error ("%s", _("unbalanced parentesis")); } else - { - grecs_error (NULL, 0, _("parse error at %s"), term); - exit (EX_USAGE); - } + parse_error (_("parse error at %s"), term); } } @@ -1365,7 +1421,7 @@ binop_append_optimized (struct json_value *ar, struct json_value *val, } static void -pcond_parse_and (struct pcond_parser_state *state, struct json_value **ret) +pcond_parse_and (struct cmdline_parser_state *state, struct json_value **ret) { char const *token; struct json_value *left, *right, *ar; @@ -1386,7 +1442,7 @@ pcond_parse_and (struct pcond_parser_state *state, struct json_value **ret) } static void -pcond_parse_or (struct pcond_parser_state *state, struct json_value **ret) +pcond_parse_or (struct cmdline_parser_state *state, struct json_value **ret) { char const *token; struct json_value *left, *right, *ar; @@ -1413,17 +1469,11 @@ parse_condition (int argc, char **argv) if (argc > 1) { - struct pcond_parser_state state; + struct cmdline_parser_state state; state.argc = argc - 1; state.argv = argv + 1; pcond_parse_or (&state, &val); - if (state.argc) - { - grecs_error (NULL, 0, - _("expected end of statement, but found \"%s\""), - *state.argv); - exit (EX_USAGE); - } + assert_eol (&state); #if 0 print_json (stdout, val); exit (0); @@ -1432,47 +1482,16 @@ parse_condition (int argc, char **argv) return val; } -static void -acc_writer (void *closure, char const *text, size_t len) -{ - grecs_txtacc_grow ((struct grecs_txtacc *)closure, text, len); -} - static char * parse_condition_to_uri (char const *base, int argc, char **argv, int mandatory) { - char *ret = NULL; - struct grecs_txtacc *acc; struct json_value *val; - + char *ret; if (mandatory && argc == 1) - { - grecs_error (NULL, 0, _("condition must be specified")); - exit (EX_USAGE); - } - - acc = grecs_txtacc_create (); - if (base) - grecs_txtacc_grow_string (acc, base); - + parse_error ("%s", _("condition must be specified")); val = parse_condition (argc, argv); - if (val) - { - struct json_format fmt = { - .indent = 0, - .precision = 0, - .write = acc_writer, - .data = acc - }; - if (base) - grecs_txtacc_grow_char (acc, '?'); - json_format_value (val, &fmt); + ret = piesctl_format (val, base); json_value_free (val); - } - grecs_txtacc_grow_char (acc, 0); - - ret = grecs_txtacc_finish (acc, 1); - grecs_txtacc_free (acc); return ret; } @@ -1516,6 +1535,21 @@ json_object_get_type (struct json_value *obj, char const *name, return 0; } +static struct json_value * +json_object_require_type (struct json_value *obj, char const *name, + enum json_value_type type) +{ + struct json_value *val; + + if (json_object_get_type (obj, name, type, &val)) + { + grecs_error (NULL, 0, _("can't get attribute %s"), name); + // shttp_format_result (conn, stderr); + exit (EX_PROTOCOL); + } + return val; +} + static void shttp_print_response_status (struct shttp_connection *conn) { @@ -1728,8 +1762,7 @@ com_reboot (struct shttp_connection *conn, int argc, char **argv) */ struct telinit_parser { - int argc; - char **argv; + struct cmdline_parser_state state; int method; struct json_value *query; char *uri; @@ -1761,19 +1794,10 @@ telinit_format_runlevel (struct shttp_connection *conn) } } -static char * -telinit_get_token (struct telinit_parser *p) -{ - if (p->argc == 0) - return NULL; - --p->argc; - return *p->argv++; -} - static void telinit_parse_runlevel (struct telinit_parser *p) { - char *tok = telinit_get_token (p); + char const *tok = next_token (&p->state); if (!tok) { @@ -1792,15 +1816,12 @@ telinit_parse_runlevel (struct telinit_parser *p) static void telinit_parse (struct telinit_parser *p) { - char *tok = telinit_get_token (p); + char const *tok = next_token (&p->state); if (strcmp (tok, "runlevel") == 0) telinit_parse_runlevel (p); else - { - grecs_error (NULL, 0, _("unrecognized subcommand: %s"), tok); - exit (EX_USAGE); - } + parse_error (_("unrecognized subcommand: %s"), tok); } static void @@ -1809,13 +1830,10 @@ com_telinit (struct shttp_connection *conn, int argc, char **argv) struct telinit_parser parser; if (argc == 1) - { - grecs_error (NULL, 0, _("not enough arguments to telinit")); - exit (EX_USAGE); - } + parse_error ("%s", _("not enough arguments to telinit")); - parser.argc = argc-1; - parser.argv = argv+1; + parser.state.argc = argc-1; + parser.state.argv = argv+1; parser.method = METH_INVALID; parser.query = NULL; telinit_parse (&parser); @@ -1845,8 +1863,7 @@ com_telinit (struct shttp_connection *conn, int argc, char **argv) break; default: - grecs_error (NULL, 0, _("bad syntax")); - exit (EX_USAGE); + parse_error ("%s", _("bad syntax")); } json_value_free (parser.query); @@ -1856,6 +1873,267 @@ com_telinit (struct shttp_connection *conn, int argc, char **argv) parser.format (conn); } +/* + * piesctl config reload + * piesctl config file clear + * piesctl config file add syntax name + * piesctl config file del[ete] name [name...] + * piesctl config file list + */ + +static void +locus_deserialize (struct grecs_locus *loc, struct json_value *msg) +{ + struct json_value *tmp, *x; + + memset (loc, 0, sizeof *loc); + if (json_object_get_type (msg, "file", json_arr, &tmp) == 0 + && json_array_size (tmp) == 2) + { + if (json_array_get (tmp, 0, &x) == 0 && x->type == json_string) + loc->beg.file = x->v.s; + else + loc->beg.file = "<unknown>"; + if (json_array_get (tmp, 1, &x) == 0 && x->type == json_string) + loc->end.file = x->v.s; + else + loc->beg.file = "<unknown>"; + } + + if (json_object_get_type (msg, "line", json_arr, &tmp) == 0 + && json_array_size (tmp) == 2) + { + if (json_array_get (tmp, 0, &x) == 0 && x->type == json_number) + loc->beg.line = x->v.n; + if (json_array_get (tmp, 1, &x) == 0 && x->type == json_number) + loc->end.line = x->v.n; + } + + if (json_object_get_type (msg, "col", json_arr, &tmp) == 0 + && json_array_size (tmp) == 2) + { + if (json_array_get (tmp, 0, &x) == 0 && x->type == json_number) + loc->beg.col = x->v.n; + if (json_array_get (tmp, 1, &x) == 0 && x->type == json_number) + loc->end.col = x->v.n; + } +} + +static void +conf_reload (struct cmdline_parser_state *state) +{ + struct shttp_connection *conn = state->conn; + struct json_value *val; + + assert_eol (state); + shttp_io_init (&conn->req); + shttp_process (conn, METH_PUT, "/conf/runtime"); + if (dump) + return; + assert (conn->result && conn->result->type == json_object); + + 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", + json_string, &val) == 0) + fputs (val->v.s, stdout); + else + printf ("%s", _("unknown error")); + fputc ('\n', stdout); + } + + if (json_object_get_type (conn->result, "parser_messages", json_arr, + &val) == 0) + { + size_t i, n = json_array_size (val); + struct json_value *msg, *tmp; + + for (i = 0; i < n; i++) + { + struct grecs_locus loc; + int err = 1; + int ec; + + if (json_array_get (val, i, &msg) || msg->type != json_object) + continue; + locus_deserialize (&loc, msg); + if (json_object_get_type (msg, "error", json_bool, &tmp) == 0) + err = tmp->v.b; + if (json_object_get_type (msg, "syserrno", json_number, &tmp) == 0) + ec = tmp->v.n; + + tmp = json_object_require_type (msg, "message", json_string); + piesctl_diag (&loc, err, ec, tmp->v.s); + } + } +} + +static void +conf_file_list (struct cmdline_parser_state *state) +{ + struct shttp_connection *conn = state->conn; + struct json_value *val, *tmp; + size_t i, n; + + assert_eol (state); + shttp_io_init (&conn->req); + shttp_process (conn, METH_GET, "/conf/files"); + if (dump) + return; + assert (conn->result && conn->result->type == json_arr); + n = json_array_size (conn->result); + for (i = 0; i < n; i++) + { + char const *syntax, *file; + if (json_array_get (conn->result, i, &val) || val->type != json_object) + continue; + if (json_object_get_type (val, "syntax", json_string, &tmp)) + continue; + syntax = tmp->v.s; + if (json_object_get_type (val, "file", json_string, &tmp)) + continue; + file = tmp->v.s; + printf ("%-16s%s\n", syntax, file); + } +} + +static void +conf_file_clear (struct cmdline_parser_state *state) +{ + struct shttp_connection *conn = state->conn; + struct json_value *val; + + assert_eol (state); + shttp_io_init (&conn->req); + shttp_process (conn, METH_DELETE, "/conf/files"); + 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", + json_string, &val) == 0) + fputs (val->v.s, stderr); + else + fputs (_("unknown error"), stderr); + fputc ('\n', stderr); + } +} + +static void +conf_file_add (struct cmdline_parser_state *state) +{ + struct shttp_connection *conn = state->conn; + struct json_value *val; + char const *name, *syntax; + + syntax = require_token (state); + name = require_token (state); + + val = json_new_object (); + json_object_set (val, "syntax", json_new_string (syntax)); + json_object_set (val, "file", json_new_string (name)); + + shttp_io_init (&conn->req); + conn->req.content = piesctl_format (val, NULL); + conn->req.content_length = strlen (conn->req.content); + shttp_process (conn, METH_POST, "/conf/files"); + // FIXME conn->resp.code == 201? +} + +static void +conf_file_del (struct cmdline_parser_state *state) +{ + struct shttp_connection *conn = state->conn; + struct json_value *val = json_new_array (); + char const *tok; + char *uri; + struct cmdline_parser_state save_state = *state; + + while ((tok = next_token (state))) + json_array_append (val, json_new_string (tok)); + + shttp_io_init (&conn->req); + uri = piesctl_format (val, "/conf/files"); + json_value_free (val); + + shttp_process (conn, METH_DELETE, uri); + free (uri); + + 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", + json_string, &val) == 0) + fputs (val->v.s, stderr); + fputc ('\n', stderr); + if (json_object_get_type (conn->result, "result", json_arr, &val) == 0) + { + size_t i, n = json_array_size (val); + assert (n == save_state.argc); + for (i = 0; i < n; i++) + { + struct json_value *v; + if (json_array_get (val, i, &v) == 0 + && v->type == json_bool + && !v->v.b) + { + fputs (save_state.argv[i], stderr); + fputc ('\n', stderr); + } + } + } + } + else + { + if (json_object_get_type (conn->result, "error_message", + json_string, &val) == 0) + fputs (val->v.s, stderr); + else + fputs (_("unknown error"), stderr); + fputc ('\n', stderr); + } +} + +static void +conf_file (struct cmdline_parser_state *state) +{ + char const *tok = require_token (state); + if (strcmp (tok, "add") == 0) + conf_file_add (state); + else if (strcmp (tok, "del") == 0 || strcmp (tok, "delete") == 0) + conf_file_del (state); + else if (strcmp (tok, "list") == 0) + conf_file_list (state); + else if (strcmp (tok, "clear") == 0) + conf_file_clear (state); + else + parse_error (_("unexpected word: %s"), tok); +} + +static void +conf_parse (struct cmdline_parser_state *state) +{ + char const *tok = require_token (state); + + if (strcmp (tok, "reload") == 0) + conf_reload (state); + else if (strcmp (tok, "file") == 0) + conf_file (state); + else + parse_error (_("unexpected word: %s"), tok); +} + +static void +com_config (struct shttp_connection *conn, int argc, char **argv) +{ + struct cmdline_parser_state state; + + state.argc = argc - 1; + state.argv = argv + 1; + state.conn = conn; + conf_parse (&state); +} + typedef void (*ctlcom_t) (struct shttp_connection *, int, char **); struct comtab @@ -1867,20 +2145,64 @@ struct comtab }; static struct comtab comtab[] = { - { "list", N_("[CONDITION]"), N_("list configured components"), com_list }, - { "stop", N_("CONDITION"), N_("stop components"), com_stop }, - { "start", N_("CONDITION"), N_("start components"), com_start }, - { "restart", N_("CONDITION"), N_("restart components"), com_restart }, + { "list", N_("[CONDITION]"), + N_("list configured components"), com_list }, + { "stop", N_("CONDITION"), + N_("stop components"), com_stop }, + { "start", N_("CONDITION"), + N_("start components"), com_start }, + { "restart", N_("CONDITION"), + N_("restart components"), com_restart }, + { NULL, NULL, + N_("CONDITION is defined as follows:\ +\n\ + <condition> ::= <disjunction>\n\ + <disjunction> ::= <conjunction> | <conjunction> \"or\" <disjunction>\n\ + <conjunction> ::= <unary> | <unary> \"and\" <conjunction>\n\ + <unary> ::= <term> | \"not\" <condition> | \"(\" <condition> \")\"\n\ + <term> ::= \"all\" | <keyword> <value>\n\ + <keyword> ::= \"type\" | \"mode\" | \"status\" | \"component\"\n\ + <value> ::= <word> | <quoted-string>\n\ + <word> ::= <printable> | <word> <printable>\n\ + <printable> ::= \"A\" - \"Z\" | \"a\" - \"z\" | \"0\" - \"9\" |\n\ + \"_\" | \".\" | \"*\" | \":\" | \"@\" | \"[\" | \"]\" | \"-\" | \"/\"\n\ + <quoted-string> ::= \"\"\" <string> \"\"\"\n\ + <string> ::= <char> | <string> <char>\n\ + <char> ::= <any character except \"\\\" and \"\"\"> | \"\\\\\" | \"\\\"\""), + NULL }, + { "id", N_("[KEYWORDS...]"), N_("show info about the running GNU Pies instance"), com_id }, - { "shutdown", NULL, N_("stop running pies instance"), com_shutdown }, - { "reboot", NULL, N_("restart pies instance"), com_reboot }, - { "telinit", "ARGS", N_("communicate with the init process"), + { NULL, NULL, + N_("Available KEYWORDS are: package, version, instance, binary, argv, PID"), NULL }, + { "shutdown", "", + N_("stop running pies instance"), com_shutdown }, + { "reboot", "", + N_("restart pies instance"), com_reboot }, + + { "config", "reload", + N_("reload configuration"), com_config }, + { NULL, "file clear", + N_("clear configuration file list"), NULL }, + { NULL, "file add SYNTAX FILE", + N_("add FILE of given SYNTAX to the list of configuration files"), NULL }, + { NULL, "file del[ete] NAME [NAME...]", + N_("remove listed names from the list of configuration files"), NULL }, + { NULL, "file list", + N_("list configuration files"), NULL }, + + { "telinit", "runlevel [N]", N_("list or change the runlevel"), |