/* 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 "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 () { 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) { static char docstring[] = /* TRANSLATORS: do not translate words between ` and ' */ N_("Configuration file structure for piesctl.\n" "For more information, use `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, 0, _("%s: cannot stat socket: %s"), url->path, strerror (errno)); 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, 0, _("%s: unable to create new socket: %s"), url->string, strerror(errno)); 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, _("failed to bind")); exit (EX_UNAVAILABLE); } } if (connect (fd, &addr.s, socklen)) { grecs_error (NULL, 0, _("%s: cannot connect: %s"), url->string, strerror (errno)); close (fd); return NULL; } fp = fdopen (fd, "w+"); if (!fp) { grecs_error (NULL, 0, "fdopen: %s", strerror (errno)); 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) { 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[] = { { "enabled", 'R' }, { "disabled", 'D' }, { "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 (v) fbuf[fidx++] = kwtoc (p->v.s, mode_trans); else fbuf[fidx++] = '-'; p = getval (v, "status", json_string, 0); if (p) fbuf[fidx++] = status = kwtoc (p->v.s, status_trans); 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 pcond_parser_state { int argc; char **argv; }; static char * next_token (struct pcond_parser_state *state) { if (state->argc == 0) return NULL; --state->argc; return *state->argv++; } static char const * peek_token (struct pcond_parser_state *state) { if (state->argc == 0) return NULL; return *state->argv; } 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)); json_object_set (ret, "arg", arg); return ret; } static void pcond_parse_or (struct pcond_parser_state *, struct json_value **); static void pcond_parse_unary (struct pcond_parser_state *state, struct json_value **ret) { char const *term = next_token (state); if (!term) *ret = json_new_bool (1); else { static char *arg_terms[] = { "type", "mode", "status", "component", NULL }; if (strcasecmp (term, "all") == 0) *ret = json_new_bool (1); else if (is_array_member (arg_terms, term)) { if (!peek_token (state)) { grecs_error (NULL, 0, _("%s requires argument"), term); exit (EX_USAGE); } *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); } 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] != ')') { grecs_error (NULL, 0, _("unbalanced parentesis")); exit (EX_USAGE); } } else { grecs_error (NULL, 0, _("parse error at %s"), term); exit (EX_USAGE); } } } 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 pcond_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 pcond_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 (int argc, char **argv) { struct json_value *val = NULL; if (argc > 1) { struct pcond_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); } #if 0 print_json (stdout, val); exit (0); #endif } 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; 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); 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); json_value_free (val); } grecs_txtacc_grow_char (acc, 0); ret = grecs_txtacc_finish (acc, 1); grecs_txtacc_free (acc); return ret; } static void com_list (struct shttp_connection *conn, int argc, char **argv) { char *uri = parse_condition_to_uri ("/programs", argc, argv, 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 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); } } } } } static void com_stop (struct shttp_connection *conn, int argc, char **argv) { char *uri = parse_condition_to_uri ("/programs", argc, argv, 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 shttp_connection *conn, int argc, char **argv) { char *uri = parse_condition_to_uri ("/programs", argc, argv, 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 shttp_connection *conn, int argc, char **argv) { shttp_io_init (&conn->req); conn->req.content = parse_condition_to_uri (NULL, argc, argv, 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 shttp_connection *conn, int argc, char **argv) { struct json_value *v; struct id_fmt *fmt; if (argc == 1) { shttp_io_init (&conn->req); shttp_process (conn, METH_GET, "/instance"); if (conn->resp.code == 200 && !dump) for (fmt = id_fmt; fmt->name; fmt++) { v = shttp_getval (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 < argc; i++) { grecs_asprintf (&buf, &size, "/instance/%s", argv[i]); shttp_io_init (&conn->req); shttp_process (conn, METH_GET, buf); if (conn->resp.code == 200 && !dump) { fmt = find_id_fmt (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, argv[i], &v) == 0) { printf ("%s: ", argv[i]); print_json (stdout, v); putchar ('\n'); } } } } } static void com_shutdown (struct shttp_connection *conn, int argc, char **argv) { shttp_io_init (&conn->req); shttp_process (conn, METH_DELETE, "/instance/PID"); } static void com_reboot (struct shttp_connection *conn, int argc, char **argv) { shttp_io_init (&conn->req); shttp_process (conn, METH_PUT, "/instance/PID"); } typedef void (*ctlcom_t) (struct shttp_connection *, int, char **); 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 }, { "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 }, { NULL } }; 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; p->name; p++) 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) { grecs_error (NULL, 0, _("unknown command: %s"), name); exit (EX_USAGE); } else if (match_count > 1) exit (EX_USAGE); return match; } static void command_help (FILE *fp) { struct comtab *cp; fputs (_("Available commands with their arguments are:"), fp); fputc ('\n', fp); fputc ('\n', fp); for (cp = comtab; cp->name; cp++) { fputs (cp->name, fp); if (cp->argdoc) { fputc (' ', fp); fputs (gettext (cp->argdoc), fp); } fputc ('\n', fp); fprintf (fp, " %s\n", gettext (cp->docstr)); } fputc ('\n', fp); fputs (_("Condition is defined as:"), fp); fputc ('\n', fp); fputc ('\n', fp); fputs ("\ ::= \n\ ::= | \"or\" \n\ ::= | \"and\" \n\ ::= | \"not\" | \"(\" \")\"\n\ ::= \"all\" | \n\ ::= \"type\" | \"mode\" | \"status\" | \"component\"\n\ ::= | \n\ ::= | \n\ ::= \"A\" - \"Z\" | \"a\" - \"z\" | \"0\" - \"9\" |\n\ \"_\" | \".\" | \"*\" | \":\" | \"@\" | \"[\" | \"]\" | \"-\" | \"/\"\n\ ::= \"\"\" \"\"\"\n\ ::= | \n\ ::= | \"\\\\\" | \"\\\"\"\n\n", fp); } int main (int argc, char **argv) { int i; struct shttp_connection *conn; struct comtab *cmd; 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) { grecs_error (NULL, 0, _("not enough arguments")); exit (EX_USAGE); } parse_config (); conn = shttp_connect (client.url, client.source_addr); if (!conn) return EX_UNAVAILABLE; cmd = find_command (argv[0]); argv[0] = (char*) cmd->name; cmd->command (conn, argc, argv); shttp_close (conn); return exit_status; }