diff options
author | Sergey Poznyakoff <gray@gnu.org.ua> | 2015-10-29 13:09:26 +0200 |
---|---|---|
committer | Sergey Poznyakoff <gray@gnu.org.ua> | 2015-10-30 10:12:05 +0200 |
commit | 9861a788306d5f742e633ff6a8a26a913f5e3fd1 (patch) | |
tree | 803ba61295468becef3df24f10fade8cacf44efc | |
parent | 4683f7830491d1d50c5097466d9a066cb41286c7 (diff) | |
download | pies-9861a788306d5f742e633ff6a8a26a913f5e3fd1.tar.gz pies-9861a788306d5f742e633ff6a8a26a913f5e3fd1.tar.bz2 |
Introduce control connection.
* src/ctl.c: New file.
* src/Makefile.am (pies_SOURCES): Add ctl.c
* src/pies.c: New configuration statement "control"
* src/pies.h (instance, ctl_url): New externs.
(register_socket): Change signature.
(update_socket,ctl_open): New protos.
* src/socket.c (listenset): Remove.
(fdset): New static.
(sockinst)<handler>: Array of three pointers: handlers for
read, write, and exception, correspondingly.
(find_socket_handler): Remove.
(register_socket): Take three function pointers as arguments.
Update corresponding elements of fdset.
(update_socket): New function.
(pies_pause): Handle all three I/O operations.
(deregister_socket)
(register_program_socket)
(disable_socket,enable_socket): Reflect the changes.
* src/sysvinit.c (create_fifo): Update call to register_socket.
-rw-r--r-- | src/Makefile.am | 1 | ||||
-rw-r--r-- | src/ctl.c | 459 | ||||
-rw-r--r-- | src/pies.c | 30 | ||||
-rw-r--r-- | src/pies.h | 31 | ||||
-rw-r--r-- | src/socket.c | 149 | ||||
-rw-r--r-- | src/sysvinit.c | 2 |
6 files changed, 638 insertions, 34 deletions
diff --git a/src/Makefile.am b/src/Makefile.am index 17c0d10..04634c7 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -19,6 +19,7 @@ sbin_PROGRAMS = pies pies_SOURCES = \ acl.c\ addrfmt.c\ + ctl.c\ depmap.c\ diag.c\ inetd.c\ 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); +} + @@ -1533,7 +1533,28 @@ component_section_parser (enum grecs_callback_command cmd, return 0; } - +struct grecs_keyword control_keywords[] = { + {"socket", + N_("url: string"), + N_("Listen on the given url."), + grecs_type_string, GRECS_DFLT, + &control.url, 0, _cb_url}, + {"acl", + N_("name: string"), + N_("Set ACL."), + grecs_type_section, GRECS_DFLT, + &control.acl, 0, + acl_section_parser, NULL, acl_keywords}, + {"idle-timeout", + "n", + N_("Disconnect after <n> seconds of inaction (not implemented)."), + grecs_type_uint, GRECS_DFLT, + &control.idle_timeout, 0, + NULL, + }, + { NULL } +}; + /* syslog */ static struct grecs_keyword syslog_kw[] = { {"facility", @@ -1587,6 +1608,12 @@ struct grecs_keyword pies_keywords[] = { grecs_type_section, GRECS_DFLT, NULL, 0, component_section_parser, NULL, component_keywords}, + {"control", + NULL, + N_("Define control socket"), + grecs_type_section, GRECS_DFLT, + NULL, 0, + NULL, NULL, control_keywords }, {"syslog", NULL, N_("Configure syslog logging"), @@ -2447,6 +2474,7 @@ main (int argc, char **argv) diag_setup (DIAG_TO_SYSLOG); } + ctl_open (); if (!init_process) create_pidfile (pidfile); @@ -276,6 +276,7 @@ enum pies_action { ACTION_KBREQUEST }; +extern char *instance; extern char *log_tag; extern int log_facility; extern unsigned long shutdown_timeout; @@ -393,9 +394,25 @@ int pies_url_create (struct pies_url **purl, const char *str); void pies_url_destroy (struct pies_url **purl); const char * pies_url_get_arg (struct pies_url *url, const char *argname); + void pies_pause (void); -void *register_socket (int fd, int (*handler) (int, void *), void *data); + +enum + { + PIES_EVT_RD, + PIES_EVT_WR, + PIES_EVT_EX + }; + +typedef int (*socket_handler_t) (int, void *); + +void *register_socket (int fd, + socket_handler_t rd, + socket_handler_t wr, + socket_handler_t ex, + void *data); void deregister_socket (int fd); +void update_socket (int fd, int evt, socket_handler_t f); int register_program_socket (int socktype, int fd, void *data); int pass_fd (const char *socket, int fd, unsigned time_out); @@ -537,3 +554,15 @@ struct sysvinit_request { void sysvinit_acct (int what, const char *user, const char *id, pid_t pid, const char *line); + +/* ctl.c */ +struct control +{ + struct pies_url *url; + pies_acl_t acl; + unsigned int idle_timeout; +}; + +extern struct control control; + +void ctl_open(void); diff --git a/src/socket.c b/src/socket.c index 78f79f5..13ee5e0 100644 --- a/src/socket.c +++ b/src/socket.c @@ -15,6 +15,7 @@ along with GNU Pies. If not, see <http://www.gnu.org/licenses/>. */ #include "pies.h" +#include <assert.h> static void switch_eids (uid_t *puid, gid_t *pgid, mode_t *pumask) @@ -390,20 +391,21 @@ pass_fd (const char *socket_name, int fd, unsigned maxtime) close (sockfd); return res; } - -fd_set listenset; +fd_set fdset[3]; int fd_max; struct sockinst { struct sockinst *prev, *next; int fd; - int (*handler) (int, void *); + int dead; + socket_handler_t handler[3]; void *data; }; struct sockinst *si_head, *si_tail; +static int si_iterating; static struct sockinst * find_socket (int fd) @@ -416,17 +418,6 @@ find_socket (int fd) return sp; } -struct sockinst * -find_socket_handler (int (*handler) (int, void *)) -{ - struct sockinst *sp; - - for (sp = si_head; sp; sp = sp->next) - if (sp->handler == handler) - break; - return sp; -} - static void calc_fd_max () { @@ -439,20 +430,38 @@ calc_fd_max () } void * -register_socket (int fd, int (*handler) (int, void *), void *data) +register_socket (int fd, + socket_handler_t rd, + socket_handler_t wr, + socket_handler_t ex, + void *data) { struct sockinst *sip = xmalloc (sizeof *sip); sip->fd = fd; - sip->handler = handler; + sip->dead = 0; + sip->handler[PIES_EVT_RD] = rd; + sip->handler[PIES_EVT_WR] = wr; + sip->handler[PIES_EVT_EX] = ex; sip->data = data; sip->next = NULL; sip->prev = si_tail; if (si_tail) si_tail->next = sip; else - si_head = sip; + { + FD_ZERO (&fdset[PIES_EVT_RD]); + FD_ZERO (&fdset[PIES_EVT_WR]); + FD_ZERO (&fdset[PIES_EVT_EX]); + si_head = sip; + } + si_tail = sip; - FD_SET (fd, &listenset); + if (rd) + FD_SET (fd, &fdset[PIES_EVT_RD]); + if (wr) + FD_SET (fd, &fdset[PIES_EVT_WR]); + if (ex) + FD_SET (fd, &fdset[PIES_EVT_EX]); if (fd_max == -1) calc_fd_max (); else if (fd > fd_max) @@ -460,13 +469,30 @@ register_socket (int fd, int (*handler) (int, void *), void *data) return sip; } +/* FIXME: Don't use with disable_socket/enable_socket */ void -deregister_socket (int fd) +update_socket (int fd, int evt, socket_handler_t f) { struct sockinst *sp = find_socket (fd); + assert (sp != NULL); + sp->handler[evt] = f; + if (f) + FD_SET (fd, &fdset[evt]); + else + FD_CLR (fd, &fdset[evt]); +} - if (!sp) - return; +static void +delete_sockinst (struct sockinst *sp) +{ + if (sp->handler[PIES_EVT_RD]) + FD_CLR (sp->fd, &fdset[PIES_EVT_RD]); + if (sp->handler[PIES_EVT_WR]) + FD_CLR (sp->fd, &fdset[PIES_EVT_WR]); + if (sp->handler[PIES_EVT_EX]) + FD_CLR (sp->fd, &fdset[PIES_EVT_EX]); + fd_max = -1; + if (sp->prev) sp->prev->next = sp->next; else @@ -475,9 +501,20 @@ deregister_socket (int fd) sp->next->prev = sp->prev; else si_tail = sp->prev; + free (sp); - FD_CLR (fd, &listenset); - fd_max = -1; +} + +void +deregister_socket (int fd) +{ + struct sockinst *sp = find_socket (fd); + if (!sp) + return; + if (si_iterating) + sp->dead = 1; + else + delete_sockinst (sp); } @@ -489,7 +526,7 @@ register_program_socket (int socktype, int fd, void *data) logmsg (LOG_ERR, "listen: %s", strerror (errno)); return 1; } - register_socket (fd, progman_accept, data); + register_socket (fd, progman_accept, NULL, NULL, data); return 0; } @@ -499,16 +536,26 @@ disable_socket (int fd) if (fd < 0) return; debug (2, (_("disabling fd %d"), fd)); - FD_CLR (fd, &listenset); + FD_CLR (fd, &fdset[PIES_EVT_RD]); + FD_CLR (fd, &fdset[PIES_EVT_WR]); + FD_CLR (fd, &fdset[PIES_EVT_EX]); } void enable_socket (int fd) { + struct sockinst *sp; if (fd < 0) return; debug (2, (_("enabling fd %d"), fd)); - FD_SET (fd, &listenset); + sp = find_socket (fd); + assert (sp != NULL); + if (sp->handler[PIES_EVT_RD]) + FD_SET (fd, &fdset[PIES_EVT_RD]); + if (sp->handler[PIES_EVT_WR]) + FD_SET (fd, &fdset[PIES_EVT_WR]); + if (sp->handler[PIES_EVT_EX]) + FD_SET (fd, &fdset[PIES_EVT_EX]); } void @@ -519,15 +566,55 @@ pies_pause () while (1) { - fd_set rdset = listenset; - int rc = select (fd_max + 1, &rdset, NULL, NULL, NULL); + fd_set rdset = fdset[PIES_EVT_RD]; + fd_set wrset = fdset[PIES_EVT_WR]; + fd_set exset = fdset[PIES_EVT_EX]; + + int rc = select (fd_max + 1, &rdset, &wrset, &exset, NULL); if (rc > 0) { struct sockinst *sp; - + int delete = 0; + ++si_iterating; for (sp = si_head; sp; sp = sp->next) - if (FD_ISSET (sp->fd, &rdset)) - sp->handler (sp->fd, sp->data); + { + if (sp->dead) + { + delete = 1; + continue; + } + if (sp->handler[PIES_EVT_RD] && FD_ISSET (sp->fd, &rdset)) + sp->handler[PIES_EVT_RD] (sp->fd, sp->data); + + if (sp->dead) + { + delete = 1; + continue; + } + if (sp->handler[PIES_EVT_WR] && FD_ISSET (sp->fd, &wrset)) + sp->handler[PIES_EVT_WR] (sp->fd, sp->data); + + if (sp->dead) + { + delete = 1; + continue; + } + if (sp->handler[PIES_EVT_EX] && FD_ISSET (sp->fd, &exset)) + sp->handler[PIES_EVT_EX] (sp->fd, sp->data); + } + --si_iterating; + + if (delete) + { + for (sp = si_head; sp; ) + { + struct sockinst *next = sp->next; + if (sp->dead) + delete_sockinst (sp); + sp = next; + } + calc_fd_max (); + } break; } else if (rc < 0) diff --git a/src/sysvinit.c b/src/sysvinit.c index 1bf3a3b..07a29b5 100644 --- a/src/sysvinit.c +++ b/src/sysvinit.c @@ -532,7 +532,7 @@ create_fifo () strerror (errno)); return; } - register_socket (fd, sysvinit_fifo_handler, NULL); + register_socket (fd, sysvinit_fifo_handler, NULL, NULL, NULL); } static char *try_console[] = { NULL, "/dev/console", "/dev/tty0" }; |