/* This file is part of GNU Pies.
Copyright (C) 2015-2019 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_PIES_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.conf\".");
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_foreach (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_PIES_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' },
{ "startup", 'S' },
{ "shutdown", 'E' },
{ "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 == 0)
parse_error ("%s", _("condition must be specified"));
val = parse_condition (state);
ret = piesctl_format (val, base);
json_value_free (val);
return ret;
}
/* 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_list (struct cmdline_parser_state *state)
{
struct shttp_connection *conn = state->conn;
char *uri = parse_condition_to_uri ("/programs", default_cond (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);
}
}
}
}
}
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 = 0; 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;
}