/* This file is part of GNU Pies. Copyright (C) 2015, 2016 Sergey Poznyakoff GNU Pies is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. GNU Pies is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with GNU Pies. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "base64.h" #include "progname.h" #include "libpies.h" #include "grecsasrt.h" #include "fprintftime.h" struct pies_url *default_url; /* Control socket URL */ struct grecs_sockaddr *source_addr; struct client_conn { struct pies_url *url; struct grecs_sockaddr *source_addr; } client; char const *instance = "pies"; char *config_file; char default_config_file[] = SYSCONFDIR "/piesctl.conf"; int preprocess_only; int verbose; int dump; struct grecs_sockaddr_hints hints = { .flags = GRECS_AH_PASSIVE }; #define EX_OK 0 #define EX_NOTFOUND 1 int exit_status = EX_OK; static void config_help (void); #include "piesctl-cl.h" static struct grecs_keyword instance_keywords[] = { {"socket", N_("url: string"), N_("Socket URL for that instance."), grecs_type_string, GRECS_DFLT, NULL, offsetof(struct client_conn,url), conf_callback_url}, {"source", N_("arg"), N_("Source IP address."), grecs_type_sockaddr, GRECS_DFLT, NULL, offsetof(struct client_conn,source_addr) }, { NULL } }; static int callback_instance (enum grecs_callback_command cmd, grecs_locus_t *locus, void *varptr, grecs_value_t *value, void *cb_data) { switch (cmd) { case grecs_callback_section_begin: if (GRECS_VALUE_EMPTY_P (value)) { grecs_error (locus, 0, _("missing tag")); return 1; } if (assert_grecs_value_type (&value->locus, value, GRECS_TYPE_STRING)) return 1; if (strcmp (value->v.string, instance) == 0) *(struct client_conn **) cb_data = &client; else *(struct client_conn **) cb_data = NULL; break; case grecs_callback_section_end: break; case grecs_callback_set_value: grecs_error (locus, 0, _("invalid use of block statement")); } return 0; } struct grecs_keyword piesctl_keywords[] = { {"socket", N_("url: string"), N_("Default socket URL."), grecs_type_string, GRECS_DFLT, &default_url, 0, conf_callback_url}, {"source", N_("arg"), N_("Source IP address."), grecs_type_sockaddr, GRECS_DFLT, &source_addr }, {"instance", N_("name"), N_("Configure connection to a pies instance"), grecs_type_section, GRECS_DFLT, NULL, 0, callback_instance, NULL, instance_keywords }, { NULL } }; static void parse_config (void) { char *file_name; struct grecs_node *tree; grecs_include_path_setup (DEFAULT_VERSION_INCLUDE_DIR, DEFAULT_INCLUDE_DIR, NULL); grecs_log_to_stderr = 1; grecs_preprocessor = pp_command_line (); grecs_sockaddr_hints = &hints; if (config_file) file_name = config_file; else if (access (default_config_file, F_OK) == 0) file_name = default_config_file; else file_name = NULL; if (file_name) { if (preprocess_only) exit (grecs_preproc_run (file_name, grecs_preprocessor) ? EX_CONFIG : EX_OK); if (verbose) printf (_("%s: reading configuration from %s\n"), program_name, file_name); tree = grecs_parse (file_name); if (!tree) exit (EX_CONFIG); if (grecs_tree_process (tree, piesctl_keywords)) exit (EX_CONFIG); if (!client.url && verbose) printf (_("%s: URL not found in %s\n"), program_name, file_name); } if (!client.url) { /* Try local instance configuration */ file_name = mkfilename (SYSCONFDIR, instance, ".conf"); if (access (file_name, F_OK) == 0) { struct grecs_node *node; if (verbose) printf (_("%s: reading configuration from %s\n"), program_name, file_name); if (preprocess_only) exit (grecs_preproc_run (file_name, grecs_preprocessor) ? EX_CONFIG : EX_OK); tree = grecs_parse (file_name); node = grecs_find_node (tree, "/control/socket"); if (node) { assert_grecs_value_type (&node->locus, node->v.value, GRECS_TYPE_STRING); if (pies_url_create (&client.url, node->v.value->v.string)) { grecs_error (&node->locus, 0, _("%s: cannot create URL: %s"), node->v.value->v.string, strerror (errno)); exit (EX_CONFIG); } } else if (verbose) printf (_("%s: URL not found in %s\n"), program_name, file_name); } free (file_name); } if (!client.url) { if (verbose) printf (_("%s: falling back to default URL\n"), program_name); if (default_url) client.url = default_url; else { if (pies_url_create (&client.url, DEFAULT_PIES_CONTROL_URL)) { grecs_error (NULL, 0, _("%s: cannot create URL: %s"), DEFAULT_PIES_CONTROL_URL, strerror (errno)); exit (EX_SOFTWARE); } } } if (!client.source_addr) client.source_addr = source_addr; if (verbose) printf (_("%s: using URL %s\n"), program_name, client.url->string); } static void config_help (void) { /* TRANSLATORS: do not translate words in quotes */ static char docstring[] = N_("Configuration file structure for piesctl.\n" "For more information, use command \"info piesctl configuration\"."); grecs_print_docstring (docstring, 0, stdout); grecs_print_statement_array (piesctl_keywords, 1, 0, stdout); } static void piesctl_diag (grecs_locus_t const *locus, int err, int errcode, const char *msg) { fflush (stdout); fprintf (stderr, "%s: ", program_name); if (locus) { YY_LOCATION_PRINT (stderr, *locus); fputc(':', stderr); fputc(' ', stderr); } if (!err) fprintf (stderr, _("warning: ")); fprintf (stderr, "%s", msg); if (errcode) fprintf (stderr, ": %s", strerror (errno)); fputc ('\n', stderr); } enum http_method { METH_GET, METH_POST, METH_DELETE, METH_PUT, METH_OPTIONS, METH_INVALID }; char *method_names[] = { [METH_GET] = "GET", [METH_POST] = "POST", [METH_DELETE] = "DELETE", [METH_PUT] = "PUT", [METH_OPTIONS] = "OPTIONS", NULL }; static char http_version[] = "HTTP/1.1"; struct shttp_io { int code; struct grecs_symtab *headers; unsigned long content_length; char *content; }; struct shttp_buf { char *base; size_t size; }; struct shttp_connection { FILE *fp; struct pies_url *url; struct shttp_io req; struct shttp_io resp; struct shttp_buf sendbuf; struct shttp_buf readbuf; char *b64auth; char *status_line[3]; struct json_value *result; }; struct http_header { char *name; char *value; }; static unsigned http_header_hash (void *data, unsigned long n_buckets) { const struct http_header *p = data; return grecs_hash_string_ci (p->name, n_buckets); } static int http_header_compare (void const *data1, void const *data2) { const struct http_header *p1 = data1; const struct http_header *p2 = data2; return strcasecmp (p1->name, p2->name); } static int http_header_copy (void *a, void *b) { struct http_header *dst = a; struct http_header *src = b; dst->name = grecs_strdup (src->name); dst->value = src->value ? grecs_strdup (src->value) : NULL; return 0; } static void http_header_free (void *p) { const struct http_header *h = p; grecs_free (h->name); grecs_free (h->value); free (p); } static void shttp_io_init (struct shttp_io *io) { io->code = 0; io->content_length = 0; grecs_free (io->content); io->content = NULL; grecs_symtab_clear (io->headers); } static int shttp_io_header_put (struct shttp_io *io, char const *name, char const *fmt, ...) { int install = 1; struct http_header key, *ret; va_list ap; size_t len = 0; if (!io->headers) io->headers = grecs_symtab_create (sizeof (struct http_header), http_header_hash, http_header_compare, http_header_copy, NULL, http_header_free); key.name = (char *) name; key.value = NULL; ret = grecs_symtab_lookup_or_install (io->headers, &key, &install); if (!ret) { grecs_error (NULL, 0, _("cannot install header %s: %s"), name, strerror (errno)); return 1; } if (!install) grecs_free (ret->value); va_start (ap, fmt); grecs_vasprintf (&ret->value, &len, fmt, ap); va_end (ap); return 0; } static char * shttp_io_header_get (struct shttp_io *io, char const *name) { struct http_header key, *ret; if (!io->headers) return NULL; key.name = (char*) name; ret = grecs_symtab_lookup_or_install (io->headers, &key, NULL); return ret ? ret->value : NULL; } static void shttp_io_free (struct shttp_io *io) { grecs_symtab_free (io->headers); grecs_free (io->content); } static struct shttp_connection * shttp_connect (struct pies_url *url, struct grecs_sockaddr *source_addr) { int fd; union pies_sockaddr_storage addr; socklen_t socklen; int flags; struct shttp_connection *conn; FILE *fp; if (strcmp (url->scheme, "unix") == 0 || strcmp (url->scheme, "file") == 0 || strcmp (url->scheme, "socket") == 0) { struct stat st; if (url->port) { grecs_error (NULL, 0, _("%s: invalid connection type: " "port is meaningless for UNIX sockets"), url->string); return NULL; } if (strlen (url->path) > sizeof addr.s_un.sun_path) { errno = EINVAL; grecs_error (NULL, 0, _("%s: UNIX socket name too long"), url->path); return NULL; } addr.s.sa_family = PF_UNIX; socklen = sizeof (addr.s_un); strcpy (addr.s_un.sun_path, url->path); if (stat (url->path, &st)) { if (errno != ENOENT) { grecs_error (NULL, errno, _("%s: %s failed"), url->path, "stat"); return NULL; } } else { if (!S_ISSOCK (st.st_mode)) { grecs_error (NULL, 0, _("%s: not a socket"), url->path); return NULL; } } } else if (strcmp (url->scheme, "inet") == 0) { short pnum; struct hostent *hp; addr.s_in.sin_family = PF_INET; socklen = sizeof (addr.s_in); pnum = url->port ? url->port : 8080; hp = gethostbyname (url->host); if (!hp) { grecs_error (NULL, 0, _("%s: unknown host name"), url->string); return NULL; } addr.s_in.sin_family = hp->h_addrtype; switch (hp->h_addrtype) { case AF_INET: memmove (&addr.s_in.sin_addr, hp->h_addr, 4); addr.s_in.sin_port = htons (pnum); break; default: grecs_error (NULL, 0, _("%s: invalid connection type: " "unsupported address family"), url->string); return NULL; } } else { grecs_error (NULL, 0, _("%s: unsupported protocol"), url->string); return NULL; } fd = socket (addr.s.sa_family, SOCK_STREAM, 0); if (fd == -1) { grecs_error (NULL, errno, _("%s: %s failed"), url->string, "socket"); return NULL; } if ((flags = fcntl (fd, F_GETFD, 0)) == -1 || fcntl (fd, F_SETFD, flags | FD_CLOEXEC) == -1) grecs_error (NULL, 0, _("%s: cannot set close-on-exec: %s"), url->string, strerror (errno)); if (source_addr) { if (source_addr->sa->sa_family != addr.s.sa_family) grecs_error (NULL, 0, _("source and destination address family differ")); else if (bind (fd, source_addr->sa, source_addr->len) < 0) { grecs_error (NULL, errno, _("%s: %s failed"), url->string, "bind"); exit (EX_UNAVAILABLE); } } if (connect (fd, &addr.s, socklen)) { grecs_error (NULL, errno, _("%s: %s failed"), url->string, "connect"); close (fd); return NULL; } fp = fdopen (fd, "w+"); if (!fp) { grecs_error (NULL, errno, _("%s: %s failed"), url->string, "fdopen"); close (fd); return NULL; } conn = grecs_zalloc (sizeof (*conn)); conn->fp = fp; if (pies_url_copy (&conn->url, url)) grecs_alloc_die (); netrc_scan (conn->url); return conn; } static void shttp_auth_free (struct shttp_connection *conn) { if (conn->b64auth) { memset (conn->b64auth, 0, strlen (conn->b64auth)); free (conn->b64auth); conn->b64auth = NULL; } } static void shttp_status_line_free (struct shttp_connection *conn) { free (conn->status_line[0]); free (conn->status_line[1]); free (conn->status_line[2]); conn->status_line[0] = NULL; conn->status_line[1] = NULL; conn->status_line[2] = NULL; } static void shttp_close (struct shttp_connection *conn) { fclose (conn->fp); pies_url_destroy (&conn->url); shttp_io_free (&conn->req); shttp_io_free (&conn->resp); grecs_free (conn->sendbuf.base); grecs_free (conn->readbuf.base); shttp_status_line_free (conn); shttp_auth_free (conn); json_value_free (conn->result); grecs_free (conn); } static void shttp_send_line (struct shttp_connection *conn, char const *fmt, ...) { va_list ap; va_start (ap, fmt); grecs_vasprintf (&conn->sendbuf.base, &conn->sendbuf.size, fmt, ap); va_end (ap); if (verbose > 1) printf ("> %s\n", conn->sendbuf.base); fprintf (conn->fp, "%s\r\n", conn->sendbuf.base); } static int shttp_read_line (struct shttp_connection *conn) { ssize_t rc; rc = grecs_getline (&conn->readbuf.base, &conn->readbuf.size, conn->fp); if (rc > 0) { if (conn->readbuf.base[--rc] == '\n') { if (rc > 0 && conn->readbuf.base[rc-1] == '\r') --rc; conn->readbuf.base[rc] = 0; } if (verbose > 1) printf ("< %s\n", conn->readbuf.base); return 0; } else if (rc == 0) { if (verbose > 1) printf ("\n"); return 1; } grecs_error (NULL, errno, _("HTTP read error")); exit (EX_UNAVAILABLE); } static int send_header (void *sym, void *data) { struct http_header *h = sym; struct shttp_connection *conn = data; shttp_send_line (conn, "%s: %s", h->name, h->value); return 0; } static void shttp_request_send (struct shttp_connection *conn, int method, char const *uri) { shttp_send_line (conn, "%s %s %s", method_names[method], uri, http_version); shttp_send_line (conn, "Host: %s", conn->url->host ? conn->url->host : "localhost"); if (conn->url->user) { shttp_auth_free (conn); grecs_asprintf (&conn->readbuf.base, &conn->readbuf.size, "%s:%s", conn->url->user, conn->url->passwd ? conn->url->passwd : 0); base64_encode_alloc (conn->readbuf.base, strlen (conn->readbuf.base), &conn->b64auth); pies_url_free_user (conn->url); pies_url_free_passwd (conn->url); } if (conn->b64auth) shttp_send_line (conn, "Authorization: Basic %s", conn->b64auth); grecs_symtab_enumerate (conn->req.headers, send_header, conn); if (conn->req.content_length) { shttp_send_line (conn, "Content-Length: %lu", conn->req.content_length); shttp_send_line (conn, "Content-Type: application/json"); } shttp_send_line (conn, ""); if (conn->req.content_length) { size_t s = fwrite (conn->req.content, 1, conn->req.content_length, conn->fp); if (s != conn->req.content_length) { //FIXME: ngettext grecs_error (NULL, 0, _("wrote %lu of %lu bytes of content"), (unsigned long) s, conn->req.content_length); exit (EX_UNAVAILABLE); } } } #define ISWS(c) ((c) == ' ' || (c) == '\t') static void shttp_save_header (struct shttp_connection *conn) { char *p; for (p = conn->readbuf.base; *p; p++) if (*p == ':') { *p++ = 0; p += strspn (p, " \t"); shttp_io_header_put (&conn->resp, conn->readbuf.base, "%s", p); return; } grecs_error (NULL, 0, _("malformed header line: %s"), conn->readbuf.base); exit (EX_UNAVAILABLE); } static void shttp_get_reply (struct shttp_connection *conn) { enum input_state { is_initial, is_headers, is_content } state = is_initial; char const *val; shttp_io_init (&conn->resp); shttp_status_line_free (conn); json_value_free (conn->result); conn->result = NULL; while (state != is_content) { if (shttp_read_line (conn)) break; if (state == is_initial) { unsigned long n; char *p; int rc; rc = strsplit3 (conn->readbuf.base, conn->status_line, 0); if (rc == -1) { grecs_error (NULL, errno, "strsplit3"); exit (EX_OSERR); } else if (rc == 1) { grecs_error (NULL, 0, _("unexpected reply: %s"), conn->readbuf.base); exit (EX_UNAVAILABLE); } if (strcmp (conn->status_line[0], http_version)) { grecs_error (NULL, 0, _("unsupported HTTP version: %s"), conn->status_line[0]); exit (EX_UNAVAILABLE); } n = strtoul (conn->status_line[1], &p, 0); if (*p || n > 599) { grecs_error (NULL, 0, _("bad reply status: %s"), conn->readbuf.base); exit (EX_UNAVAILABLE); } conn->resp.code = n; state = is_headers; } else if (state == is_headers) { if (conn->readbuf.base[0]) shttp_save_header (conn); else state = is_content; } } val = shttp_io_header_get (&conn->resp, "content-length"); if (val) { ssize_t rc; char *p; unsigned long len = strtoul (val, &p, 10); if (*p) { grecs_error (NULL, 0, _("bad content length: %s"), val); exit (EX_UNAVAILABLE); } if (len) { conn->resp.content = grecs_malloc (len + 1); conn->resp.content_length = len; rc = fread (conn->resp.content, len, 1, conn->fp); if (rc != 1) { grecs_error (NULL, ferror (conn->fp) ? errno : 0, _("content read error")); exit (EX_UNAVAILABLE); } val = shttp_io_header_get (&conn->resp, "content-type"); if (!val) grecs_error (NULL, 0, _("no content type specified")); else if (strcmp (val, "application/json")) grecs_error (NULL, 0, _("unsupported content type: %s"), val); else { conn->result = json_parse_string (conn->resp.content, conn->resp.content_length); if (!conn->result) { grecs_error (&json_err_locus, 0, "%s", json_err_diag); grecs_error (NULL, 0, _("original json was: %s"), conn->resp.content); grecs_error (NULL, 0, _("original status line: %s %s %s"), conn->status_line[0], conn->status_line[1], conn->status_line[2]); exit (EX_UNAVAILABLE); } } } } } static void fp_writer (void *closure, char const *text, size_t len) { fwrite (text, len, 1, (FILE*) closure); } static void print_json (FILE *fp, struct json_value *v) { struct json_format fmt = { .indent = 2, .precision = 0, .write = fp_writer, .data = fp }; json_format_value (v, &fmt); fputc ('\n', fp); } static void shttp_format_result (struct shttp_connection *conn, FILE *fp) { fprintf (stderr, _("%s: raw JSON reply follows:\n"), program_name); print_json (fp, conn->result); } static void shttp_print_error (struct shttp_connection *conn) { struct json_value *jv; if (conn->result && (jv = json_value_lookup (conn->result, "error_message"))) { if (jv->type == json_string) grecs_error (NULL, 0, "%s", jv->v.s); else shttp_format_result (conn, stderr); } else grecs_error (NULL, 0, "%s", conn->status_line[2]); } static void shttp_fatal (struct shttp_connection *conn) { int status; shttp_print_error (conn); switch (conn->resp.code) { case 400: case 405: case 406: case 501: case 505: grecs_error (NULL, 0, _("please report")); status = EX_SOFTWARE; break; case 401: status = EX_NOUSER; break; case 403: status = EX_NOPERM; break; case 404: status = EX_NOINPUT; break; default: if (conn->resp.code >= 400 && conn->resp.code < 500) status = EX_TEMPFAIL; else status = EX_UNAVAILABLE; } exit (status); } static void echo_off (struct termios *stored_settings) { struct termios new_settings; tcgetattr (0, stored_settings); new_settings = *stored_settings; new_settings.c_lflag &= (~ECHO); tcsetattr (0, TCSANOW, &new_settings); } static void echo_on (struct termios *stored_settings) { tcsetattr (0, TCSANOW, stored_settings); } static char * getans (int echo, const char *fmt, ...) { struct termios stored_settings; char *reply = NULL; size_t reply_len = 0; size_t len; va_list ap; va_start (ap, fmt); vprintf (fmt, ap); va_end (ap); printf (": "); fflush (stdout); if (!echo) echo_off (&stored_settings); grecs_getline (&reply, &reply_len, stdin); if (!echo) { echo_on (&stored_settings); putchar ('\n'); fflush (stdout); } len = strlen (reply); if (len) reply[len - 1] = 0; return reply; } static void shttp_get_credentials (struct shttp_connection *conn) { char const *val = shttp_io_header_get (&conn->resp, "WWW-Authenticate"); char *realm = NULL; if (val) { struct wordsplit ws; if (wordsplit (val, &ws, WRDSF_NOVAR | WRDSF_NOCMD | WRDSF_QUOTE | WRDSF_SQUEEZE_DELIMS)) grecs_error (NULL, 0, "wordsplit: %s", wordsplit_strerror (&ws)); else { if (ws.ws_wordc > 0) { size_t i; if (strcmp (ws.ws_wordv[0], "Basic")) { grecs_error (NULL, 0, _("unsupported authentication type: %s"), ws.ws_wordv[0]); shttp_fatal (conn); } for (i = 1; i < ws.ws_wordc; i++) if (strncmp (ws.ws_wordv[i], "realm=", 6) == 0) { realm = grecs_strdup (ws.ws_wordv[i] + 6); break; } } wordsplit_free (&ws); } } if (!realm) realm = grecs_strdup ("Server"); conn->url->user = getans (1, _("%s user"), realm); conn->url->passwd = getans (0, _("%s password"), realm); grecs_free (realm); } static void shttp_process (struct shttp_connection *conn, int method, char const *uri) { for (;;) { shttp_request_send (conn, method, uri); shttp_get_reply (conn); if (dump && conn->result) shttp_format_result (conn, stdout); if (conn->resp.code / 100 == 2) return; switch (conn->resp.code) { case 401: if (isatty (fileno (stdin))) shttp_get_credentials (conn); else shttp_fatal (conn); break; case 404: /* Not found */ case 409: /* Conflict */ exit_status = EX_NOTFOUND; shttp_print_error (conn); return; default: shttp_fatal (conn); } } } static struct json_value * shttp_getval (struct shttp_connection *conn, char const *name, enum json_value_type type) { struct json_value *p = NULL; switch (json_object_get (conn->result, name, &p)) { case 0: if (p->type != type) { grecs_error (NULL, 0, _("\"%s\" has wrong type"), name); p = NULL; } break; case 1: grecs_error (NULL, 0, _("no \"%s\" member"), name); break; default: grecs_error (NULL, errno, _("can't get value of \"%s\""), name); } return p; } struct kwtrans { char const *name; int c; }; struct kwtrans mode_trans[] = { { "exec", 'C' }, { "accept", 'A' }, { "inetd", 'I' }, { "pass_fd", 'P' }, { "wait", 'W' }, { "once", 'c' }, { "boot", 'B' }, { "bootwait", 'w' }, { "powerfail", 'F' }, { "powerwait", 'f' }, { "powerokwait", 'o' }, { "ctrlaltdel", '3' }, { "ondemand", 'D' }, { "sysinit", 'i' }, { "powerfailnow", 'n' }, { "kbrequest", 'k' }, { NULL } }; struct kwtrans status_trans[] = { { "running", 'R' }, { "stopped", 'T' }, { "listener", 'L' }, { "sleeping", 's' }, { "stopping", 'S' }, { "finished", 'f' }, { NULL } }; static int kwtoc (char const *s, struct kwtrans const *trans) { for (; trans->name; trans++) { if (strcmp (trans->name, s) == 0) return trans->c; } return '-'; } static struct json_value * getval (struct json_value *v, char const *name, enum json_value_type type, int optional) { struct json_value *p = NULL; switch (json_object_get (v, name, &p)) { case 0: if (p->type != type) { grecs_error (NULL, 0, _("\"%s\" has wrong type"), name); p = NULL; } break; case 1: if (!optional) grecs_error (NULL, 0, _("no \"%s\" member"), name); break; default: grecs_error (NULL, errno, _("can't get value of \"%s\""), name); } return p; } static void print_comp (FILE *fp, struct json_value *v, size_t n) { struct json_value *p; char const *type; char fbuf[5]; int fidx = 0; int status = -1; if (v->type != json_object) { grecs_error (NULL, 0, _("%lu: unexpected value type"), (unsigned long) n); print_json (fp, v); return; } p = getval (v, "type", json_string, 0); if (!p) { print_json (fp, v); return; } type = p->v.s; p = getval (v, "tag", json_string, 0); if (!p) { print_json (fp, v); return; } fprintf (fp, "%-16s ", p->v.s); if (strcmp (type, "component") == 0) { p = getval (v, "mode", json_string, 0); if (p) fbuf[fidx++] = kwtoc (p->v.s, mode_trans); else fbuf[fidx++] = '-'; p = getval (v, "active", json_bool, 0); if (p && p->v.b) { p = getval (v, "status", json_string, 0); if (p) fbuf[fidx++] = status = kwtoc (p->v.s, status_trans); else fbuf[fidx++] = '-'; } else fbuf[fidx++] = '-'; } else if (strcmp (type, "redirector") == 0) { fbuf[fidx++] = 'R'; } else if (strcmp (type, "command") == 0) { fbuf[fidx++] = 'E'; } else { fbuf[fidx++] = '-'; } fbuf[fidx++] = 0; fprintf (fp, "%-8.8s ", fbuf); if (strcmp (type, "component") == 0) { p = getval (v, "PID", json_number, 1); if (p) fprintf (fp, "%10.0f ", p->v.n); else if (status == 'L' /* listener */ && (p = getval (v, "URL", json_string, 1))) fprintf (fp, "%-10s ", p->v.s); else fprintf (fp, "%-10s ", "N/A"); if (status == 's') /* sleeping */ { p = getval (v, "wakeup-time", json_number, 0); if (p) { time_t t = (time_t) p->v.n; fprintftime (fp, "%H:%M:%S", localtime (&t), 0, 0); } } p = getval (v, "argv", json_arr, 0); if (p) { size_t i, n = json_array_size (p); for (i = 0; i < n; i++) { struct json_value *elt; if (json_array_get (p, i, &elt) == 0) fprintf (fp, " %s", elt->v.s); } } } else if (strcmp (type, "redirector") == 0) { p = getval (v, "PID", json_number, 0); if (p) fprintf (fp, "%10.0f ", p->v.n); } else if (strcmp (type, "command") == 0) { p = getval (v, "command", json_string, 0); if (p) fprintf (fp, "%s", p->v.s); } fputc ('\n', fp); } struct cmdline_parser_state { char const *command; int argc; char **argv; struct shttp_connection *conn; }; static char const * next_token (struct cmdline_parser_state *state) { if (state->argc == 0) return NULL; --state->argc; return *state->argv++; } static char const * 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) { struct json_value *ret = json_new_object (); json_object_set (ret, "op", json_new_string (op)); if (arg) json_object_set (ret, "arg", arg); return ret; } static void pcond_parse_or (struct cmdline_parser_state *, struct json_value **); static void pcond_parse_unary (struct cmdline_parser_state *state, struct json_value **ret) { static char *arg_kw[] = { "type", "mode", "status", "component", NULL }; char const *term = require_token (state); if (strcasecmp (term, "all") == 0) *ret = json_new_bool (1); else if (strcasecmp (term, "active") == 0) *ret = json_encode_op (term, NULL); else if (is_array_member (arg_kw, term)) { if (!peek_token (state)) 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)) parse_error (_("%s requires argument"), term); pcond_parse_unary (state, &val); *ret = json_encode_op (term, val); } else if (term[0] == '(') { pcond_parse_or (state, ret); term = next_token (state); if (!term || term[0] != ')') parse_error ("%s", _("unbalanced parentesis")); } else parse_error (_("parse error at %s"), term); } static int is_op (struct json_value *val, char const *opcode) { struct json_value *op; if (json_object_get (val, "op", &op) == 0) { if (op->type == json_string && strcmp (op->v.s, opcode) == 0) return 1; } return 0; } static void binop_append_optimized (struct json_value *ar, struct json_value *val, char const *opcode) { if (is_op (val, opcode)) { size_t i, n; struct json_value *arg; json_object_get (val, "arg", &arg); n = json_array_size (arg); for (i = 0; i < n; i++) { struct json_value *elt; json_array_get (arg, i, &elt); json_array_set (arg, i, NULL); json_array_append (ar, elt); } json_value_free (val); } else json_array_append (ar, val); } static void pcond_parse_and (struct cmdline_parser_state *state, struct json_value **ret) { char const *token; struct json_value *left, *right, *ar; pcond_parse_unary (state, &left); token = peek_token (state); if (!token || strcmp (token, "and")) { *ret = left; return; } next_token (state); pcond_parse_and (state, &right); ar = json_new_array (); json_array_append (ar, left); binop_append_optimized (ar, right, token); *ret = json_encode_op (token, ar); } static void pcond_parse_or (struct cmdline_parser_state *state, struct json_value **ret) { char const *token; struct json_value *left, *right, *ar; pcond_parse_and (state, &left); token = peek_token (state); if (!token || strcmp (token, "or")) { *ret = left; return; } next_token (state); pcond_parse_or (state, &right); ar = json_new_array (); json_array_append (ar, left); binop_append_optimized (ar, right, token); *ret = json_encode_op (token, ar); } static struct json_value * parse_condition (struct cmdline_parser_state *state) { struct json_value *val = NULL; if (state->argc > 0) { pcond_parse_or (state, &val); assert_eol (state); #if 0 print_json (stdout, val); exit (0); #endif } return val; } static char * parse_condition_to_uri (char const *base, struct cmdline_parser_state *state, int mandatory) { struct json_value *val; char *ret; if (mandatory && state->argc == 1) parse_error ("%s", _("condition must be specified")); val = parse_condition (state); ret = piesctl_format (val, base); json_value_free (val); return ret; } static void com_list (struct cmdline_parser_state *state) { struct shttp_connection *conn = state->conn; char *uri = parse_condition_to_uri ("/programs", state, 0); shttp_io_init (&conn->req); shttp_process (conn, METH_GET, uri); grecs_free (uri); if (conn->resp.code == 200 && !dump && conn->result && conn->result->type == json_arr) { size_t i, n = json_array_size (conn->result); for (i = 0; i < n; i++) { struct json_value *v; if (json_array_get (conn->result, i, &v) == 0) print_comp (stdout, v, i); } } } static int json_object_get_type (struct json_value *obj, char const *name, enum json_value_type type, struct json_value **ret) { struct json_value *v; int rc = json_object_get (obj, name, &v); if (rc) return rc; if (v->type != type) { errno = EACCES; return -1; } *ret = v; 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) { if (!dump && conn->result && conn->result->type == json_arr) { size_t i, n = json_array_size (conn->result); for (i = 0; i < n; i++) { struct json_value *elt, *v; if (json_array_get (conn->result, i, &elt) == 0) { if (elt->type != json_object) { grecs_error (NULL, 0, _("%lu: unexpected value type"), (unsigned long) i); print_json (stderr, v); continue; } if (json_object_get_type (elt, "tag", json_string, &v) == 0) { printf ("%s: ", v->v.s); if (json_object_get_type (elt, "status", json_string, &v) == 0) { if (strcmp (v->v.s, "OK") == 0) fputs (v->v.s, stdout); else if (json_object_get_type (elt, "error_message", json_string, &v) == 0) fputs (v->v.s, stdout); else printf (_("unknown error")); } else printf (_("unknown status")); fputc ('\n', stdout); } } } } } /* Provide default condition if there are no more arguments */ static struct cmdline_parser_state * default_cond (struct cmdline_parser_state *state) { if (!peek_token (state)) { static char *dfl[] = { "type", "component", NULL }; state->argc = sizeof (dfl) / sizeof (dfl[0]); state->argv = dfl; } return state; } static void com_stop (struct cmdline_parser_state *state) { struct shttp_connection *conn = state->conn; char *uri = parse_condition_to_uri ("/programs", default_cond (state), 1); shttp_io_init (&conn->req); shttp_process (conn, METH_DELETE, uri); grecs_free (uri); shttp_print_response_status (conn); } static void com_start (struct cmdline_parser_state *state) { struct shttp_connection *conn = state->conn; char *uri = parse_condition_to_uri ("/programs", default_cond (state), 1); shttp_io_init (&conn->req); shttp_process (conn, METH_PUT, uri); grecs_free (uri); shttp_print_response_status (conn); } static void com_restart (struct cmdline_parser_state *state) { struct shttp_connection *conn = state->conn; shttp_io_init (&conn->req); conn->req.content = parse_condition_to_uri (NULL, default_cond (state), 1); conn->req.content_length = strlen (conn->req.content); shttp_process (conn, METH_POST, "/programs"); shttp_print_response_status (conn); } static void id_fmt_string (struct json_value *val) { fputs (val->v.s, stdout); } static void id_fmt_pid (struct json_value *val) { printf ("%.0f", val->v.n); } static void id_fmt_argv (struct json_value *val) { size_t i, n; char const *delim = NULL; n = json_array_size (val); for (i = 0; i < n; i++) { struct json_value *elt; if (json_array_get (val, i, &elt) == 0 && elt->type == json_string) { if (delim) fputs (delim, stdout); fputs (elt->v.s, stdout); delim = " "; } } } struct id_fmt { char *name; enum json_value_type type; void (*format) (struct json_value *); }; static struct id_fmt id_fmt[] = { { "package", json_string, id_fmt_string }, { "version", json_string, id_fmt_string }, { "instance", json_string, id_fmt_string }, { "binary", json_string, id_fmt_string }, { "argv", json_arr, id_fmt_argv }, { "PID", json_number, id_fmt_pid }, { NULL } }; static struct id_fmt * find_id_fmt (char const *name) { struct id_fmt *p; for (p = id_fmt; p->name; p++) if (strcmp (p->name, name) == 0) return p; return NULL; } static void com_id (struct cmdline_parser_state *state) { struct shttp_connection *conn = state->conn; struct json_value *v; struct id_fmt *fmt; if (state->argc == 0) { shttp_io_init (&state->conn->req); shttp_process (state->conn, METH_GET, "/instance"); if (state->conn->resp.code == 200 && !dump) for (fmt = id_fmt; fmt->name; fmt++) { v = shttp_getval (state->conn, fmt->name, fmt->type); if (v) { printf ("%s: ", fmt->name); fmt->format (v); putchar ('\n'); } } } else { char *buf = NULL; size_t size = 0; size_t i; for (i = 1; i < state->argc; i++) { grecs_asprintf (&buf, &size, "/instance/%s", state->argv[i]); shttp_io_init (&conn->req); shttp_process (conn, METH_GET, buf); if (conn->resp.code == 200 && !dump) { fmt = find_id_fmt (state->argv[i]); if (fmt) { v = shttp_getval (conn, fmt->name, fmt->type); if (v) { printf ("%s: ", fmt->name); fmt->format (v); putchar ('\n'); } } else if (json_object_get (conn->result, state->argv[i], &v) == 0) { printf ("%s: ", state->argv[i]); print_json (stdout, v); putchar ('\n'); } } } } } static void com_shutdown (struct cmdline_parser_state *state) { assert_eol (state); shttp_io_init (&state->conn->req); shttp_process (state->conn, METH_DELETE, "/instance/PID"); } static void com_reboot (struct cmdline_parser_state *state) { assert_eol (state); shttp_io_init (&state->conn->req); shttp_process (state->conn, METH_PUT, "/instance/PID"); } /* ::= "telinit" ::= | ::= "runlevel" | "runlevel" ::= "environ" "list" [] | "environ" "set" "=" */ struct telinit_parser { struct cmdline_parser_state *state; int method; struct json_value *query; char *uri; void (*format) (struct shttp_connection *); }; static struct id_fmt runlevel_fmt[] = { { "runlevel", json_string, id_fmt_string }, { "prevlevel", json_string, id_fmt_string }, { "bootstate", json_string, id_fmt_string }, { "initdefault", json_string, id_fmt_string }, { NULL } }; static void telinit_format_runlevel (struct shttp_connection *conn) { struct id_fmt *fmt; for (fmt = runlevel_fmt; fmt->name; fmt++) { struct json_value *v = shttp_getval (conn, fmt->name, fmt->type); if (v) { printf ("%s: ", fmt->name); fmt->format (v); putchar ('\n'); } } } static void telinit_parse_runlevel (struct telinit_parser *p) { char const *tok = next_token (p->state); p->uri = "/runlevel"; if (!tok) { p->method = METH_GET; p->format = telinit_format_runlevel; } else { p->method = METH_PUT; p->query = json_new_object (); json_object_set (p->query, "runlevel", json_new_string (tok)); p->format = NULL; } } static void telinit_format_environ (struct shttp_connection *conn) { int err = 0; switch (conn->result->type) { case json_arr: { size_t i, n; n = json_array_size (conn->result); for (i = 0; i < n; i++) { struct json_value *v; json_array_get (conn->result, i, &v); printf ("%s\n", v->v.s); } } break; case json_object: { struct json_value *val; val = json_object_require_type (conn->result, "status", json_string); if (strcmp (val->v.s, "OK") == 0) { if (json_object_get_type (conn->result, "value", json_string, &val) == 0) printf ("%s\n", val->v.s); } else if (strcmp (val->v.s, "ER") == 0) { if (json_object_get_type (conn->result, "error_message", json_string, &val) == 0) fputs (val->v.s, stderr); else { grecs_error (NULL, 0, "%s", _("unknown error")); err = 1; } } else { grecs_error (NULL, 0, "%s", _("unrecognized response")); err = 1; } } break; default: grecs_error (NULL, 0, "unexpected response type"); err = 1; } if (err) print_json (stderr, conn->result); } static void telinit_format_modenv (struct shttp_connection *conn) { /* Nothing */ } static void telinit_list_environ (struct telinit_parser *p) { char const *tok = next_token (p->state); assert_eol (p->state); p->method = METH_GET; p->format = telinit_format_environ; if (tok) { size_t n = 0; grecs_asprintf (&p->uri, &n, "/environ/%s", tok); } else p->uri = "/environ"; } static void telinit_mod_environ (struct telinit_parser *p, int set) { char const *tok = next_token (p->state); assert_eol (p->state); p->method = set ? METH_PUT : METH_DELETE; p->format = telinit_format_modenv; if (tok) { size_t n = 0; grecs_asprintf (&p->uri, &n, "/environ/%s", tok); } else p->uri = "/environ"; } static void telinit_parse_environ (struct telinit_parser *p) { char const *tok = require_token (p->state); if (strcmp (tok, "list") == 0) telinit_list_environ (p); else if (strcmp (tok, "set") == 0) telinit_mod_environ (p, 1); else if (strcmp (tok, "unset") == 0) telinit_mod_environ (p, 0); else parse_error (_("expected \"list\" or \"set\", but found \"%s\""), tok); } static void telinit_parse (struct telinit_parser *parser) { char const *tok = require_token (parser->state); if (strcmp (tok, "runlevel") == 0) telinit_parse_runlevel (parser); else if (strcmp (tok, "environ") == 0) telinit_parse_environ (parser); else parse_error (_("unrecognized subcommand: %s"), tok); } static void com_telinit (struct cmdline_parser_state *state) { struct shttp_connection *conn = state->conn; struct telinit_parser parser; parser.state = state; parser.method = METH_INVALID; parser.query = NULL; parser.uri = NULL; telinit_parse (&parser); shttp_io_init (&conn->req); switch (parser.method) { case METH_GET: case METH_DELETE: break; case METH_PUT: { struct grecs_txtacc *acc = grecs_txtacc_create ();; struct json_format fmt = { .indent = 0, .precision = 0, .write = acc_writer, .data = acc }; json_format_value (parser.query, &fmt); grecs_txtacc_grow_char (acc, 0); conn->req.content = grecs_txtacc_finish (acc, 1); conn->req.content_length = strlen (conn->req.content); grecs_txtacc_free (acc); } break; default: parse_error ("%s", _("bad syntax")); } json_value_free (parser.query); shttp_process (conn, parser.method, parser.uri); if (conn->resp.code / 100 == 2 && !dump && parser.format) 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 = ""; if (json_array_get (tmp, 1, &x) == 0 && x->type == json_string) loc->end.file = x->v.s; else loc->beg.file = ""; } 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 cmdline_parser_state *state) { conf_parse (state); } typedef void (*ctlcom_t) (struct cmdline_parser_state *); struct comtab { char const *name; char const *argdoc; char const *docstr; ctlcom_t command; }; 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 }, { NULL, NULL, /* TRANSLATORS: You can leave the BNF below untranslated. If you chose to translate it, please do not translate words in double quotes. */ N_("CONDITION is defined as follows:\ \n\ ::= \n\ ::= | \"or\" \n\ ::= | \"and\" \n\ ::= | \"not\" | \"(\" \")\"\n\ ::= \"all\" | \"active\" | \n\ ::= \"type\" | \"mode\" | \"status\" | \"component\"\n\ ::= | \n\ ::= | \n\ ::= \"A\" - \"Z\" | \"a\" - \"z\" | \"0\" - \"9\" |\n\ \"_\" | \".\" | \"*\" | \":\" | \"@\" | \"[\" | \"]\" | \"-\" | \"/\"\n\ ::= \"\"\" \"\"\"\n\ ::= | \n\ ::= | \"\\\\\" | \"\\\"\""), NULL }, { "id", N_("[KEYWORDS...]"), N_("show info about the running GNU Pies instance"), com_id }, { NULL, NULL, /* TRANSLATORS: Please don't translate the keywords. */ 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 }, /* TRANSLATORS: Translate only words in upper case */ { NULL, N_("file add SYNTAX FILE"), N_("add FILE of given SYNTAX to the list of configuration files"), NULL }, /* TRANSLATORS: Translate only words in upper case */ { NULL, N_("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"), com_telinit }, /* TRANSLATORS: Translate only words in upper case */ { NULL, N_("environ list [NAME]"), N_("list init execution environment"), NULL }, /* TRANSLATORS: Translate only words in upper case */ { NULL, N_("environ set NAME=VALUE"), N_("update execution environment"), NULL }, /* TRANSLATORS: Translate only words in upper case */ { NULL, N_("environ unset NAME"), N_("update execution environment"), NULL }, { NULL } }; static int comtabend (struct comtab *p) { return !p->name && !p->argdoc && !p->docstr; } static struct comtab * find_command (char const *name) { struct comtab *p, *match = NULL; size_t match_count = 0; size_t len = strlen (name); for (p = comtab; !comtabend (p); p++) { if (!p->name) continue; if (len <= strlen (p->name) && memcmp (p->name, name, len) == 0) { if (!match) { match = p; ++match_count; } else { if (match_count == 1) fprintf (stderr, _("%s: ambiguous command %s:\n"), program_name, name); ++match_count; fprintf (stderr, " %s\n", p->name); } } } if (match_count == 0) parse_error (_("unknown command: %s"), name); else if (match_count > 1) exit (EX_USAGE); return match; } static void command_help (FILE *fp) { struct comtab *cp; char const *command = NULL; fputs (_("Available commands with their arguments are:"), fp); fputc ('\n', fp); for (cp = comtab; !comtabend (cp); cp++) { if (cp->argdoc) { if (cp->name) { fputc ('\n', fp); command = cp->name; } fputs (command, fp); if (cp->argdoc[0]) { fputc (' ', fp); fputs (gettext (cp->argdoc), fp); } } fputc ('\n', fp); if (cp->docstr[0]) fprintf (fp, " %s\n", gettext (cp->docstr)); } fputc ('\n', fp); } int main (int argc, char **argv) { int i; struct comtab *cmd; struct cmdline_parser_state state; set_program_name (argv[0]); #ifdef ENABLE_NLS setlocale (LC_ALL, ""); bindtextdomain (PACKAGE, LOCALEDIR); textdomain (PACKAGE); #endif grecs_print_diag_fun = piesctl_diag; proginfo.print_help_hook = command_help; parse_options (argc, argv, &i); argc -= i; argv += i; if (argc == 0) parse_error ("%s", _("not enough arguments")); parse_config (); cmd = find_command (argv[0]); state.command = cmd->name; state.argc = argc - 1; state.argv = argv + 1; state.conn = shttp_connect (client.url, client.source_addr); if (!state.conn) return EX_UNAVAILABLE; cmd->command (&state); shttp_close (state.conn); return exit_status; }