aboutsummaryrefslogtreecommitdiff
path: root/src/ctl.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/ctl.c')
-rw-r--r--src/ctl.c459
1 files changed, 459 insertions, 0 deletions
diff --git a/src/ctl.c b/src/ctl.c
new file mode 100644
index 0000000..cfa0e13
--- /dev/null
+++ b/src/ctl.c
@@ -0,0 +1,459 @@
+/* This file is part of GNU Pies.
+ Copyright (C) 2007-2013 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 <http://www.gnu.org/licenses/>. */
+
+#include "pies.h"
+#include "prog.h"
+#include "xvasprintf.h"
+
+#define DEFAULT_CONTROL_URL "unix:///tmp/%s.ctl"
+
+struct control control;
+
+
+struct ctlbuf
+{
+ char *base;
+ size_t size;
+ size_t level;
+ size_t pos;
+};
+
+#define CTLBUFSIZE 16
+#define EOT '\04'
+
+static void
+ctlbuf_init (struct ctlbuf *buf)
+{
+ buf->base = NULL;
+ buf->size = 0;
+ buf->level = 0;
+ buf->pos = 0;
+}
+
+static void
+ctlbuf_free (struct ctlbuf *buf)
+{
+ free (buf->base);
+}
+
+static void
+ctlbuf_alloc (struct ctlbuf *buf, size_t s)
+{
+ while (buf->level + s >= buf->size)
+ {
+ if (buf->size == 0)
+ buf->size = CTLBUFSIZE;
+ buf->base = x2realloc (buf->base, &buf->size);
+ }
+}
+
+static void
+ctlbuf_write (struct ctlbuf *buf, char const *str, size_t n)
+{
+ ctlbuf_alloc (buf, n);
+ memcpy (buf->base + buf->level, str, n);
+ buf->level += n;
+}
+
+#define ctlbuf_rdsize(b) ((b)->level - (b)->pos)
+
+static ssize_t
+ctlbuf_read (struct ctlbuf *buf, char *str, size_t n)
+{
+ size_t size = ctlbuf_rdsize (buf);
+ if (size < n)
+ n = size;
+ memcpy (str, buf->base + buf->pos, n);
+ buf->pos += n;
+ return n;
+}
+
+static void
+ctlbuf_flush (struct ctlbuf *buf)
+{
+ buf->pos = buf->level = 0;
+}
+
+static void
+ctlbuf_chomp (struct ctlbuf *buf)
+{
+ if (buf->base)
+ {
+ while (buf->level > 0
+ && strchr(" \t\r\n", buf->base[buf->level-1]))
+ --buf->level;
+ }
+ ctlbuf_write (buf, "", 1);
+}
+
+#define CTL_END_STATE 0
+#define CTL_INITIAL_STATE 0x01
+#define CTL_AUTHENTICATED_STATE 0x02
+#define CTL_ALL_STATES (CTL_INITIAL_STATE|CTL_AUTHENTICATED_STATE)
+
+struct ctlio;
+
+static void cmd_quit (struct ctlio *, size_t, char **);
+static void cmd_noop (struct ctlio *, size_t, char **);
+static void cmd_help (struct ctlio *, size_t, char **);
+static void cmd_id (struct ctlio *, size_t, char **);
+
+struct ctlio_command
+{
+ char *verb;
+ char *descr;
+ int states;
+ int minargs;
+ size_t maxargs;
+ void (*handler) (struct ctlio *, size_t, char **);
+};
+
+static struct ctlio_command cmdtab[] = {
+ { "noop", "no operation",
+ CTL_ALL_STATES, 1, 1, cmd_noop },
+ { "id", "identify the instance",
+ CTL_AUTHENTICATED_STATE, 1, 1, cmd_id },
+ { "quit", "quit the session",
+ CTL_ALL_STATES, 1, 1, cmd_quit },
+ { "help", "display help",
+ CTL_ALL_STATES, 1, 1, cmd_help },
+ { NULL }
+};
+
+static struct ctlio_command *
+ctlio_command_find (char const *verb)
+{
+ struct ctlio_command *cp;
+
+ for (cp = cmdtab; cp->verb; cp++)
+ if (strcasecmp (cp->verb, verb) == 0)
+ return cp;
+ return NULL;
+}
+
+#define CRLF "\r\n"
+
+struct ctlio
+{
+ int state;
+ struct ctlbuf ibuf;
+ struct ctlbuf obuf;
+ struct wordsplit ws;
+ int wsflags;
+};
+
+static struct ctlio *
+ctlio_create (void)
+{
+ struct ctlio *io;
+
+ io = xmalloc (sizeof (*io));
+ io->state = CTL_INITIAL_STATE;
+ ctlbuf_init (&io->ibuf);
+ ctlbuf_init (&io->obuf);
+ io->wsflags = WRDSF_DEFFLAGS;
+ return io;
+}
+
+void
+ctlio_destroy (struct ctlio *io)
+{
+ ctlbuf_free (&io->ibuf);
+ ctlbuf_free (&io->obuf);
+ if (io->wsflags & WRDSF_REUSE)
+ wordsplit_free (&io->ws);
+ free (io);
+}
+
+static int
+ctlio_end (int fd, struct ctlio *io)
+{
+ deregister_socket (fd);
+ close (fd);
+ ctlio_destroy (io);
+ return 1;
+}
+
+static void
+ctlio_print (struct ctlio *io, const char *text)
+{
+ ctlbuf_write (&io->obuf, text, strlen (text));
+}
+
+static void
+ctlio_printf (struct ctlio *io, const char *fmt, ...)
+{
+ va_list ap;
+ char *str;
+
+ va_start (ap, fmt);
+ str = xvasprintf (fmt, ap);
+ va_end (ap);
+ ctlio_print (io, str);
+ free (str);
+}
+
+static void
+ctlio_eol (struct ctlio *io)
+{
+ ctlio_print (io, CRLF);
+}
+
+static void
+ctlio_eot (struct ctlio *io)
+{
+ ctlio_print (io, "." CRLF);
+}
+
+static void
+ctlio_reply (struct ctlio *io, const char *code, const char *fmt, ...)
+{
+ ctlio_print (io, code);
+ if (fmt)
+ {
+ va_list ap;
+ char *str;
+
+ va_start (ap, fmt);
+ str = xvasprintf (fmt, ap);
+ va_end (ap);
+ ctlio_print (io, " ");
+ ctlio_print (io, str);
+ free (str);
+ }
+ ctlio_eol (io);
+}
+
+static void
+ctlio_do_command (struct ctlio *io)
+{
+ int rc;
+ struct ctlio_command *cmd;
+
+ ctlbuf_chomp (&io->ibuf);
+ ctlbuf_flush (&io->obuf);
+ rc = wordsplit (io->ibuf.base, &io->ws, io->wsflags);
+ io->wsflags |= WRDSF_REUSE;
+ if (rc)
+ {
+ logmsg (LOG_ERR, _("can't parse input line: %s"),
+ wordsplit_strerror (&io->ws));
+ ctlio_reply (io, "550", "parse error");
+ return;
+ }
+
+ cmd = ctlio_command_find (io->ws.ws_wordv[0]);
+ if (!cmd)
+ {
+ ctlio_reply (io, "500", "unknown command");
+ return;
+ }
+ if (!(cmd->states & io->state))
+ {
+ ctlio_reply (io, "510", "command not valid in state");
+ return;
+ }
+ if (cmd->minargs && io->ws.ws_wordc < cmd->minargs)
+ {
+ ctlio_reply (io, "500", "too few arguments");
+ return;
+ }
+ if (cmd->maxargs && io->ws.ws_wordc > cmd->maxargs)
+ {
+ ctlio_reply (io, "500", "too many arguments");
+ return;
+ }
+ cmd->handler (io, io->ws.ws_wordc, io->ws.ws_wordv);
+}
+
+static void
+ctlio_initial_reply (struct ctlio *io)
+{
+ ctlio_printf (io, "220 %s", instance);
+ ctlio_printf (io, " <%s>", "foobarbaz");
+ //FIXME: auth mechanisms
+ ctlio_eol (io);
+}
+
+static void
+cmd_noop (struct ctlio *io, size_t argc, char **argv)
+{
+ ctlio_reply (io, "220", "%s attending", instance);
+}
+
+static void
+cmd_quit (struct ctlio *io, size_t argc, char **argv)
+{
+ ctlio_reply (io, "221", "bye");
+ io->state = CTL_END_STATE;
+}
+
+static void
+cmd_id (struct ctlio *io, size_t argc, char **argv)
+{
+ ctlio_reply (io, "110", "instance identification follows");
+ ctlio_printf (io, "Package:%s%s", PACKAGE_NAME, CRLF);
+ ctlio_printf (io, "Version:%s%s", PACKAGE_VERSION, CRLF);
+#if HAVE_DECL_PROGRAM_INVOCATION_NAME
+ ctlio_printf (io, "Binary:%s%s", program_invocation_name, CRLF);
+#endif
+ ctlio_printf (io, "Instance:%s%s", instance, CRLF);
+ ctlio_eot (io);
+}
+
+static void
+cmd_help (struct ctlio *io, size_t argc, char **argv)
+{
+ struct ctlio_command *cp;
+
+ ctlio_reply (io, "113", "help text follows");
+ for (cp = cmdtab; cp->verb; cp++)
+ {
+ ctlio_printf (io, "%-9s%s", cp->verb, cp->descr);
+ ctlio_eol (io);
+ }
+ ctlio_eot (io);
+}
+
+
+static int ctlrd (int fd, void *data);
+static int ctlwr (int fd, void *data);
+
+static int
+ctlrd (int fd, void *data)
+{
+ ssize_t n;
+ char c;
+ struct ctlio *io = data;
+
+ n = read (fd, &c, 1);
+ // logmsg (LOG_DEBUG, "%s called: %d,%d", __FUNCTION__, n,c);
+ if (n == 1)
+ {
+ switch (c) {
+ case '\n':
+ ctlio_do_command (io);
+ ctlbuf_flush (&io->ibuf);
+ update_socket (fd, PIES_EVT_RD, NULL);
+ update_socket (fd, PIES_EVT_WR, ctlwr);
+ break;
+
+ case EOT:
+ return ctlio_end (fd, io);
+
+ default:
+ ctlbuf_write (&io->ibuf, &c, 1);
+ }
+ }
+ else
+ {
+ if (n == -1)
+ logmsg (LOG_ERR, "error reading from control socket: %s",
+ strerror (errno));
+ return ctlio_end (fd, io);
+ }
+
+ return 0;
+}
+
+static int
+ctlwr (int fd, void *data)
+{
+ char c;
+ struct ctlio *io = data;
+ // logmsg (LOG_DEBUG, "%s called", __FUNCTION__);
+ if (ctlbuf_read (&io->obuf, &c, 1))
+ write (fd, &c, 1);
+ else if (io->state == CTL_END_STATE)
+ return ctlio_end (fd, io);
+ else
+ {
+ update_socket (fd, PIES_EVT_WR, NULL);
+ update_socket (fd, PIES_EVT_RD, ctlrd);
+ ctlbuf_flush (&io->obuf);
+ }
+ return 0;
+}
+
+static int
+ctl_accept (int socket, void *data)
+{
+ int fd;
+ union pies_sockaddr_storage addr;
+ socklen_t addrlen = sizeof addr;
+ struct ctlio *io;
+
+ fd = accept (socket, (struct sockaddr*) &addr, &addrlen);
+ if (fd == -1)
+ {
+ logmsg (LOG_ERR, _("accept failed: %s"), strerror (errno));
+ return 1;
+ }
+
+ if (debug_level >= 1)
+ {
+ char *s = sockaddr_to_astr ((struct sockaddr *)&addr, addrlen);
+ logmsg (LOG_DEBUG, _("%s wants %s"), s, "control socket");
+ free (s);
+ }
+
+ if (check_acl (control.acl, (struct sockaddr *)&addr, addrlen))
+ {
+ close (fd);
+ return 1;
+ }
+ /* FIXME: Check number of connections? */
+
+ io = ctlio_create ();
+ ctlio_initial_reply (io);
+ register_socket (fd, NULL, ctlwr, NULL, io);
+
+ return 0;
+}
+
+void
+ctl_open ()
+{
+ int fd;
+
+ if (!control.url)
+ {
+ char *str = xasprintf (DEFAULT_CONTROL_URL, instance);
+ if (pies_url_create (&control.url, str))
+ {
+ logmsg (LOG_CRIT, _("%s: cannot create URL: %s"),
+ str, strerror (errno));
+ }
+ free (str);
+ }
+
+ fd = create_socket (control.url, SOCK_STREAM, NULL, 0600);
+ if (fd == -1)
+ {
+ logmsg (LOG_CRIT, _("can't create control socket %s"), control.url->string);
+ exit (EX_UNAVAILABLE);
+ }
+
+ if (listen (fd, 8))
+ {
+ logmsg (LOG_CRIT, "can't listen on control socket %s: %s",
+ strerror (errno));
+ exit (EX_UNAVAILABLE);
+ }
+
+ register_socket (fd, ctl_accept, NULL, NULL, NULL);
+}
+

Return to:

Send suggestions and report system problems to the System administrator.