/* 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 pies_url *url;
char const *instance = "pies";
char *config_file;
char default_config_file[] = SYSCONFDIR "/piesctl.conf";
int preprocess_only;
int verbose;
int dump;
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, 0, conf_callback_url},
{ 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 (!url && strcmp (value->v.string, instance) == 0)
*(struct pies_url ***) cb_data = &url;
else
*(struct pies_url ***) 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},
{"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 ();
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 : 0);
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 (!url && verbose)
printf ("%s: URL not found in %s\n",
program_name, file_name);
}
if (!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 : 0);
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 (&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 (!url)
{
if (verbose)
printf ("%s: falling back to default URL\n", program_name);
if (default_url)
url = default_url;
else
{
int rc;
size_t len = 0;
file_name = NULL;
grecs_asprintf (&file_name, &len, DEFAULT_CONTROL_URL, instance);
rc = pies_url_create (&url, file_name);
free (file_name);
if (rc)
{
grecs_error (NULL, 0, _("%s: cannot create URL: %s"),
DEFAULT_CONTROL_URL, strerror (errno));
exit (EX_SOFTWARE);
}
}
}
if (verbose)
printf ("%s: using URL %s\n", program_name, 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)
{
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));
// FIXME: Bind to local address
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_fatal (struct shttp_connection *conn)
{
struct json_value *jv;
int status;
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]);
switch (conn->resp.code)
{
case 400:
case 405:
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 */
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_new_array2 (struct json_value *a, struct json_value *b)
{
struct json_value *ret = json_new_array ();
json_array_append (ret, a);
json_array_append (ret, b);
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));
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 void
pcond_parse_and (struct pcond_parser_state *state, struct json_value **ret)
{
char const *token;
struct json_value *left, *right;
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);
*ret = json_encode_op (token, json_new_array2 (left, right));
}
static void
pcond_parse_or (struct pcond_parser_state *state, struct json_value **ret)
{
char const *token;
struct json_value *left, *right;
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);
*ret = json_encode_op (token, json_new_array2 (left, right));
}
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 *
json_to_string (struct json_value *val)
{
struct grecs_txtacc *acc = grecs_txtacc_create ();
struct json_format fmt = {
.indent = 0,
.precision = 0,
.write = acc_writer,
.data = acc
};
char *ret;
json_format_value (val, &fmt);
grecs_txtacc_grow_char (acc, 0);
ret = grecs_txtacc_finish (acc, 1);
grecs_txtacc_free (acc);
return ret;
}
static char *
parse_condition_to_uri (char const *base, int argc, char **argv)
{
char *ret = NULL;
struct grecs_txtacc *acc;
struct json_value *val;
acc = grecs_txtacc_create ();
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
};
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 int
com_list (struct shttp_connection *conn, int argc, char **argv)
{
char *uri = parse_condition_to_uri ("/programs", argc, argv);
shttp_io_init (&conn->req);
shttp_process (conn, METH_GET, uri);
grecs_free (uri);
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 *v;
if (json_array_get (conn->result, i, &v) == 0)
print_comp (stdout, v, i);
}
}
return 0;
}
static int
com_stop (struct shttp_connection *conn, int argc, char **argv)
{
char *buf = NULL;
size_t len = 0;
size_t i;
for (i = 1; i < argc; i++)
{
struct json_value *v;
grecs_asprintf (&buf, &len, "/programs/%s", argv[i]);
shttp_io_init (&conn->req);
shttp_process (conn, METH_DELETE, buf);
v = shttp_getval (conn, "error_message", json_string);
if (v)
printf ("%s: %s\n", argv[i], v->v.s);
}
free (buf);
return 0;
}
static int
com_start (struct shttp_connection *conn, int argc, char **argv)
{
char *buf = NULL;
size_t len = 0;
size_t i;
for (i = 1; i < argc; i++)
{
struct json_value *v;
grecs_asprintf (&buf, &len, "/programs/%s", argv[i]);
shttp_io_init (&conn->req);
shttp_process (conn, METH_PUT, buf);
v = shttp_getval (conn, "error_message", json_string);
if (v)
printf ("%s: %s\n", argv[i], v->v.s);
}
free (buf);
return 0;
}
static int
com_restart (struct shttp_connection *conn, int argc, char **argv)
{
struct json_value *val = json_new_array ();
size_t i;
if (argc == 1)
{
grecs_error (NULL, 0, _("missing component names"));
exit (EX_USAGE);
}
for (i = 1; i < argc; i++)
json_array_append (val, json_new_string (argv[i]));
shttp_io_init (&conn->req);
conn->req.content = json_to_string (val);
conn->req.content_length = strlen (conn->req.content);
json_value_free (val);
shttp_process (conn, METH_POST, "/programs");
return 0;
}
static int
com_id (struct shttp_connection *conn, int argc, char **argv)
{
struct json_value *v;
if (argc == 1)
{
size_t i;
static char *keywords[] = {
"package",
"version",
"instance",
"binary",
};
shttp_io_init (&conn->req);
shttp_process (conn, METH_GET, "/instance");
for (i = 0; i < sizeof (keywords)/ sizeof (keywords[0]); i++)
{
v = shttp_getval (conn, keywords[i], json_string);
if (v)
printf ("%s: %s\n", keywords[i], v->v.s);
}
v = shttp_getval (conn, "PID", json_number);
if (v)
printf ("PID: %.0f\n", v->v.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 (json_object_get (conn->result, argv[i], &v) == 0)
{
printf ("%s: ", argv[i]);
print_json (stdout, v);
}
}
}
return 0;
}
static int
com_shutdown (struct shttp_connection *conn, int argc, char **argv)
{
shttp_io_init (&conn->req);
shttp_process (conn, METH_DELETE, "/instance/PID");
return 0;
}
static int
com_reboot (struct shttp_connection *conn, int argc, char **argv)
{
shttp_io_init (&conn->req);
shttp_process (conn, METH_PUT, "/instance/PID");
return 0;
}
typedef int (*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_("[EXPR]"), N_("list configured components"), com_list },
{ "stop", N_("TAG [TAG...]"), N_("stop components"), com_stop },
{ "start", N_("TAG [TAG...]"), N_("start components"), com_start },
{ "restart", N_("TAG [TAG...]"), 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 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);
}
int
main (int argc, char **argv)
{
int i, rc;
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 (url);
if (!conn)
return EX_UNAVAILABLE;
cmd = find_command (argv[0]);
argv[0] = (char*) cmd->name;
rc = cmd->command (conn, argc, argv);
shttp_close (conn);
return rc;
}