From 1bfa33ac7c167cd863b88a5cac7690d511851e6e Mon Sep 17 00:00:00 2001 From: Sergey Poznyakoff Date: Sun, 6 Jan 2013 17:04:28 +0200 Subject: Fix runlevel transition algorithm, implement SysV-style fifo interface. * src/prog.h: New file. * src/Makefile.am: Add new file. * src/cmdline.opt: New option --telinit (-T). * src/diag.c (vlogmsg): In sysvin it mode, write directly to the console. Close it when finished. * src/pies.c (_cb_initdefault, _cb_runlevels): Use is_valid_runlevel to check if the specified runlevels are ok. (main): In sysvinit mode, reset action to ACTION_CONT. * src/pies.h (progman_filter): New proto. (progman_accept,register_socket): Change signature. (deregister_socket): New proto. (register_program_socket): New proto. * src/progman.c: Move constant and adatatype definitions to prog.h (prog_stop): Remove static qualifier. (console_open): Likewise. (progman_accept): Use new socket API. (progman_stop): Correctly handle timeouts. (progman_foreach): New function. * src/socket.c: Register all sockets along with their handlers in a doubly-linked list. (sockinst): New struct. (register_socket,deregister_socket): New functions. (register_program_socket): New function. (pies_pause): Traverse the list to find which fd has changed. Use its registered handler to handle the event. * src/sysvinit.c: Include prog.h (is_valid_runlevel): New function. (sysvinit_fifo_handler,check_fifo): New static functions. (inittrans): Fix transition algorithm. (telinit): New function. --- src/Makefile.am | 3 +- src/cmdline.opt | 9 ++- src/diag.c | 31 ++++++++- src/pies.c | 16 +++-- src/pies.h | 36 ++++++++++- src/prog.h | 85 +++++++++++++++++++++++++ src/progman.c | 125 +++++++++++------------------------- src/socket.c | 106 ++++++++++++++++++++++++++++--- src/sysvinit.c | 192 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 9 files changed, 488 insertions(+), 115 deletions(-) create mode 100644 src/prog.h diff --git a/src/Makefile.am b/src/Makefile.am index 3752eae..9e9bf80 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -40,7 +40,8 @@ noinst_HEADERS = \ cmdline.h\ meta1gram.h\ meta1lex.h\ - pies.h + pies.h\ + prog.h meta1lex.c: meta1gram.h diff --git a/src/cmdline.opt b/src/cmdline.opt index 31fcf20..76fc4da 100644 --- a/src/cmdline.opt +++ b/src/cmdline.opt @@ -110,7 +110,14 @@ BEGIN log_to_stderr_only = 1; lint_mode = 1; END - + +OPTION(telinit,T,RUNLEVEL, + []) +BEGIN + log_to_stderr_only = 1; + exit (telinit (optarg)); +END + GROUP(Preprocessor) OPTION(define,D,[], diff --git a/src/diag.c b/src/diag.c index fe057d0..bb64ab9 100644 --- a/src/diag.c +++ b/src/diag.c @@ -42,17 +42,42 @@ syslog_printer (int prio, const char *fmt, va_list ap) #endif } +static FILE * +stderr_open () +{ + if (!init_process) + return stderr; + else + { + int fd = console_open (O_WRONLY|O_NOCTTY|O_NDELAY); + if (fd == -1) + return NULL; + return fdopen (fd, "w"); + } +} + +static void +stderr_close (FILE *fp) +{ + if (init_process) + fclose (fp); +} + void vlogmsg (int prio, const char *fmt, va_list ap) { if (DIAG_OUTPUT (DIAG_TO_STDERR)) { va_list aq; - fprintf (stderr, "%s: ", program_name); + FILE *fp = stderr_open (); + if (!fp) + return; + fprintf (fp, "%s: ", program_name); va_copy (aq, ap); - vfprintf (stderr, fmt, aq); + vfprintf (fp, fmt, aq); va_end (aq); - fprintf (stderr, "\n"); + fprintf (fp, "\n"); + stderr_close (fp); } if (DIAG_OUTPUT (DIAG_TO_SYSLOG)) diff --git a/src/pies.c b/src/pies.c index a2dc5e0..41ca486 100644 --- a/src/pies.c +++ b/src/pies.c @@ -959,8 +959,6 @@ _cb_flags (enum grecs_callback_command cmd, return 0; } -static const char valid_runlevels[] = "0123456789Ss"; - static int _cb_initdefault (enum grecs_callback_command cmd, grecs_locus_t *locus, @@ -975,7 +973,7 @@ _cb_initdefault (enum grecs_callback_command cmd, grecs_error (locus, 0, _("argument must be a single character")); return 1; } - if (!strchr (valid_runlevels, value->v.string[0])) + if (!is_valid_runlevel (value->v.string[0])) { grecs_error (locus, 0, _("not a valid runlevel")); return 1; @@ -995,7 +993,7 @@ _cb_runlevels (enum grecs_callback_command cmd, return 1; for (p = value->v.string; *p; p++) { - if (!strchr (valid_runlevels, *p)) + if (!is_valid_runlevel (*p)) { grecs_error (locus, 0, _("not a valid runlevel: %c")); return 1; @@ -2332,7 +2330,7 @@ main (int argc, char **argv) (unsigned long) pid); exit (EX_USAGE); } - + logmsg (LOG_INFO, _("%s %s starting"), proginfo.package, proginfo.version); if (!foreground) @@ -2381,6 +2379,14 @@ main (int argc, char **argv) progman_dump_stats (statfile); action = ACTION_CONT; break; + + case ACTION_STOP: + case ACTION_RESTART: + if (init_process) + { + debug (1, ("ignoring stop/restart")); + action = ACTION_CONT; + } } if (action == ACTION_CONT) { diff --git a/src/pies.h b/src/pies.h index e7ad1c6..2203d87 100644 --- a/src/pies.h +++ b/src/pies.h @@ -285,11 +285,13 @@ void progman_start (void); void progman_wake_sleeping (int); void progman_stop (void); void progman_cleanup (int expect_term); +void progman_filter (int (*filter) (struct component *, void *data), + void *data); void progman_stop_component (const char *name); void progman_dump_stats (const char *filename); void progman_dump_prereq (void); void progman_dump_depmap (void); -int progman_accept (int socket); +int progman_accept (int socket, void *data); int progman_build_depmap (void); void progman_create_sockets (void); struct component *progman_lookup_component (const char *tag); @@ -362,7 +364,10 @@ 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); -int register_socket (int socktype, int fd); +void *register_socket (int fd, int (*handler) (int, void *), void *data); +void deregister_socket (int fd); + +int register_program_socket (int socktype, int fd, void *data); int pass_fd (const char *socket, int fd, unsigned time_out); int create_socket (struct pies_url *url, int socket_type, const char *user, mode_t umask); @@ -456,6 +461,33 @@ struct inetd_builtin *inetd_builtin_lookup (const char *service, int socktype); void sysvinit_begin (void); int inittrans (void); int is_comp_wait (struct component *comp); +int is_valid_runlevel (int c); + +#ifndef INIT_FIFO +# define INIT_FIFO "/dev/initctl" +#endif + +#define INIT_MAGIC 0x03091969 +#define INIT_CMD_START 0 +#define INIT_CMD_RUNLVL 1 +#define INIT_CMD_POWERFAIL 2 +#define INIT_CMD_POWERFAILNOW 3 +#define INIT_CMD_POWEROK 4 +#define INIT_CMD_BSD 5 +#define INIT_CMD_SETENV 6 +#define INIT_CMD_UNSETENV 7 + +#define INIT_CMD_CHANGECONS 12345 + +struct sysvinit_request { + int magic; /* Magic number */ + int cmd; /* What kind of request */ + int runlevel; /* Runlevel to change to */ + int sleeptime; /* Time between TERM and KILL */ + char pad[368]; +}; + + /* utmp.c */ #define SYSV_ACCT_BOOT 0 diff --git a/src/prog.h b/src/prog.h new file mode 100644 index 0000000..2fd4c47 --- /dev/null +++ b/src/prog.h @@ -0,0 +1,85 @@ +/* This file is part of GNU Pies. + Copyright (C) 2008, 2009, 2010, 2011 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 . */ + +enum prog_type + { + TYPE_COMPONENT, + TYPE_REDIRECTOR, + TYPE_COMMAND + }; + +enum prog_status + { + status_enabled, /* Component enabled. prog->pid!=0 shows if it is + actually running */ + status_disabled, /* Component is disabled. */ + status_listener, /* Component is an inetd listener */ + status_sleeping, /* Component is sleeping. An attempt to start it will + be made at prog->v.p.timestamp + SLEEPTIME */ + status_stopping, /* Component is being stopped */ + status_finished, /* A "once" component has finished */ + }; + +struct conn_class +{ + const char *tag; + union pies_sockaddr_storage sa_storage; + size_t sa_len; + size_t count; +}; + +struct prog +{ + struct prog *next, *prev; + enum prog_type type; + pid_t pid; /* PID */ + char *tag; /* Entry tag (for diagnostics purposes) */ + char **prereq; + int facility; + union + { + struct + { + struct component *comp; + size_t idx; /* Numeric identifier */ + int socket; + struct prog *redir[2]; /* Pointers to redirectors */ + time_t timestamp; /* Time of last startup */ + size_t failcount; /* Number of failed starts since timestamp */ + enum prog_status status; /* Current component status */ + char *runlevels; + /* If status == status_listener: */ + size_t num_instances; /* Number of running instances */ + /* If comp->type == pies_comp_inetd && status == status_enabled */ + struct prog *listener; + union pies_sockaddr_storage sa_storage; + size_t sa_len; + struct conn_class *cclass; + } p; + + struct + { + struct prog *master; + } r; + + struct + { + char *command; + } c; + } v; +}; + +void progman_foreach (int (*filter) (struct prog *, void *data), void *data); diff --git a/src/progman.c b/src/progman.c index 97216b8..2b375fe 100644 --- a/src/progman.c +++ b/src/progman.c @@ -16,74 +16,7 @@ #include "pies.h" #include - -enum prog_type - { - TYPE_COMPONENT, - TYPE_REDIRECTOR, - TYPE_COMMAND - }; - -enum prog_status - { - status_enabled, /* Component enabled. prog->pid!=0 shows if it is - actually running */ - status_disabled, /* Component is disabled. */ - status_listener, /* Component is an inetd listener */ - status_sleeping, /* Component is sleeping. An attempt to start it will - be made at prog->v.p.timestamp + SLEEPTIME */ - status_stopping, /* Component is being stopped */ - status_finished, /* A "once" component has finished */ - }; - -struct conn_class -{ - const char *tag; - union pies_sockaddr_storage sa_storage; - size_t sa_len; - size_t count; -}; - -struct prog -{ - struct prog *next, *prev; - enum prog_type type; - pid_t pid; /* PID */ - char *tag; /* Entry tag (for diagnostics purposes) */ - char **prereq; - int facility; - union - { - struct - { - struct component *comp; - size_t idx; /* Numeric identifier */ - int socket; - struct prog *redir[2]; /* Pointers to redirectors */ - time_t timestamp; /* Time of last startup */ - size_t failcount; /* Number of failed starts since timestamp */ - enum prog_status status; /* Current component status */ - char *runlevels; - /* If status == status_listener: */ - size_t num_instances; /* Number of running instances */ - /* If comp->type == pies_comp_inetd && status == status_enabled */ - struct prog *listener; - union pies_sockaddr_storage sa_storage; - size_t sa_len; - struct conn_class *cclass; - } p; - - struct - { - struct prog *master; - } r; - - struct - { - char *command; - } c; - } v; -}; +#include "prog.h" #define IS_COMPONENT(p) ((p)->type == TYPE_COMPONENT) @@ -209,7 +142,7 @@ prog_lookup_by_idx (unsigned idx) return prog; } -static void prog_stop (struct prog *prog, int sig); +void prog_stop (struct prog *prog, int sig); static int prog_start_prerequisites (struct prog *prog); void @@ -1139,7 +1072,7 @@ progman_run_comp (struct component *comp, int fd, prog_execute (prog); } -static int +int console_open (int mode) { int i, fd; @@ -1565,15 +1498,9 @@ _prog_wait (struct prog *p) } int -progman_accept (int socket) +progman_accept (int socket, void *data) { - struct prog *p = prog_lookup_by_socket (socket); - if (!p) - { - logmsg (LOG_EMERG, - _("INTERNAL ERROR: no matching prog for fd %d"), socket); - return 1; - } + struct prog *p = data; if (p->v.p.comp->socket_type == SOCK_STREAM && !(p->v.p.comp->flags & CF_WAIT)) @@ -1756,7 +1683,7 @@ progman_create_sockets () comp->privs.user, comp->umask); if (fd == -1) prog->v.p.status = status_disabled; - else if (register_socket (comp->socket_type, fd)) + else if (register_program_socket (comp->socket_type, fd, prog)) { close (fd); prog->v.p.status = status_disabled; @@ -1989,7 +1916,7 @@ prog_stop_dependents (struct prog *prog) free (pos); } -static void +void prog_stop (struct prog *prog, int sig) { if (prog->pid == 0) @@ -2023,20 +1950,31 @@ prog_stop_all (int sig) prog_stop (prog, sig); } -void -progman_stop () +static int +progman_wait () { - unsigned long i; + time_t start = time (NULL); - prog_stop_all (SIGTERM); - for (i = 0; i < shutdown_timeout; i++) + do { progman_cleanup (1); if (progman_running_count () == 0) - return; + return 0; sleep (1); } - prog_stop_all (SIGKILL); + while (time (NULL) - start < shutdown_timeout); + return 1; +} + +void +progman_stop () +{ + prog_stop_all (SIGTERM); + if (progman_wait ()) + { + prog_stop_all (SIGKILL); + progman_wait (); + } } static void @@ -2533,15 +2471,24 @@ progman_cleanup (int expect_term) progman_wake_sleeping (0); } +void +progman_foreach (int (*filter) (struct prog *, void *data), void *data) +{ + struct prog *prog; + for (prog = proghead; prog; prog = prog->next) + if (IS_COMPONENT (prog) && filter (prog, data)) + break; +} + void progman_stop_component (const char *name) { struct prog *prog; - logmsg (LOG_INFO, _("stopping component `%s'"), name); for (prog = proghead; prog; prog = prog->next) if (IS_COMPONENT (prog) && strcmp (prog->tag, name) == 0) { + logmsg (LOG_INFO, _("stopping component `%s'"), prog->tag); switch (prog->v.p.status) { case status_enabled: @@ -2561,7 +2508,7 @@ progman_stop_component (const char *name) default: logmsg (LOG_INFO, _("stopping component `%s': component not started"), - name); + prog->tag); } } } diff --git a/src/socket.c b/src/socket.c index e32701f..fdce7d1 100644 --- a/src/socket.c +++ b/src/socket.c @@ -395,17 +395,101 @@ pass_fd (const char *socket_name, int fd, unsigned maxtime) fd_set listenset; int fd_max; +struct sockinst +{ + struct sockinst *prev, *next; + int fd; + int (*handler) (int, void *); + void *data; +}; + +struct sockinst *si_head, *si_tail; + +static struct sockinst * +find_socket (int fd) +{ + struct sockinst *sp; + + for (sp = si_head; sp; sp = sp->next) + if (sp->fd == fd) + break; + 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 int +calc_fd_max () +{ + struct sockinst *sp; + + fd_max = -1; + for (sp = si_head; sp; sp = sp->next) + if (sp->fd > fd_max) + fd_max = sp->fd; +} + +void * +register_socket (int fd, int (*handler) (int, void *), void *data) +{ + struct sockinst *sip = xmalloc (sizeof *sip); + sip->fd = fd; + sip->handler = handler; + sip->data = data; + sip->next = NULL; + sip->prev = si_tail; + if (si_tail) + si_tail->next = sip; + else + si_head = sip; + si_tail = sip; + FD_SET (fd, &listenset); + if (fd_max == -1) + calc_fd_max (); + else if (fd > fd_max) + fd_max = fd; + return sip; +} + +void +deregister_socket (int fd) +{ + struct sockinst *sp = find_socket (fd); + + if (!sp) + return; + if (sp->prev) + sp->prev->next = sp->next; + else + si_head = sp->next; + if (sp->next) + sp->next->prev = sp->prev; + else + si_tail = sp->prev; + free (sp); + FD_CLR (fd, &listenset); + fd_max = -1; +} + + int -register_socket (int socktype, int fd) +register_program_socket (int socktype, int fd, void *data) { if (socktype == SOCK_STREAM && listen (fd, 8) == -1) { logmsg (LOG_ERR, "listen: %s", strerror (errno)); return 1; } - FD_SET (fd, &listenset); - if (fd > fd_max) - fd_max = fd; + register_socket (fd, progman_accept, data); return 0; } @@ -430,18 +514,20 @@ enable_socket (int fd) void pies_pause () { + if (fd_max == -1) + calc_fd_max (); + while (1) { fd_set rdset = listenset; int rc = select (fd_max + 1, &rdset, NULL, NULL, NULL); if (rc > 0) { - int i; - for (i = 0; i <= fd_max; i++) - { - if (FD_ISSET (i, &rdset)) - progman_accept (i); - } + struct sockinst *sp; + + for (sp = si_head; sp; sp = sp->next) + if (FD_ISSET (sp->fd, &rdset)) + sp->handler (sp->fd, sp->data); break; } else if (rc < 0) diff --git a/src/sysvinit.c b/src/sysvinit.c index ef765a1..940125f 100644 --- a/src/sysvinit.c +++ b/src/sysvinit.c @@ -15,6 +15,7 @@ along with GNU Pies. If not, see . */ #include "pies.h" +#include "prog.h" enum boot_state { @@ -124,6 +125,146 @@ enablecomp (struct component *comp, int finished, void *data) return rc; } +static const char valid_runlevel_arg[] = "0123456789SsQqAaBbCcUu"; + +int +is_valid_runlevel (int c) +{ + return !!strchr (valid_runlevel_arg, c); +} + +static void create_fifo (void); + +static int +sysvinit_stop_filter (struct prog *prog, void *data) +{ + switch (prog->v.p.status) + { + case status_enabled: + case status_listener: + prog_stop (prog, SIGTERM); + prog->v.p.status = status_disabled; /* See FIXME, progman.c:2364 */ + break; + } + return 0; +} + +static int +sysvinit_fifo_handler (int fd, void *data) +{ + static size_t size; + union + { + struct sysvinit_request req; + char data[sizeof (struct sysvinit_request)]; + } buf; + int rc; + + rc = read (fd, buf.data + size, sizeof (struct sysvinit_request) - size); + if (rc == -1) + { + logmsg (LOG_ERR, _("error reading from %s: %s"), INIT_FIFO, + strerror (errno)); + size = 0; + return 0; + } + if (rc == 0) + { + logmsg (LOG_ERR, _("end of file on %s: reopening"), INIT_FIFO); + size = 0; + close (fd); + deregister_socket (fd); + create_fifo (); + return 0; + } + + size += rc; + + if (size == sizeof (struct sysvinit_request)) + { + if (buf.req.magic != INIT_MAGIC) + logmsg (LOG_ERR, _("got invalid initreq")); + else + { + debug (1, ("INITREQ: cmd=%d, runlevel=%d, sleeptime=%d", + buf.req.cmd, buf.req.runlevel, buf.req.sleeptime)); + switch (buf.req.cmd) + { + case INIT_CMD_RUNLVL: + buf.req.runlevel = toupper (buf.req.runlevel); + if (buf.req.runlevel != runlevel) + { + progman_stop (); + dfl_level = buf.req.runlevel; + inittrans (); + } + break; + + /* FIXME: react on other commands */ + } + } + size = 0; + } + return 0; +} + +static void +create_fifo () +{ + static int fd = -1; + struct stat st, fst; + + if (stat (INIT_FIFO, &st) < 0) + { + if (errno != ENOENT) + { + logmsg (LOG_ERR, "cannot stat fifo %s: %s", INIT_FIFO, + strerror (errno)); + return; + } + else if (mkfifo (INIT_FIFO, 0600)) + { + logmsg (LOG_ERR, "cannot create fifo %s: %s", INIT_FIFO, + strerror (errno)); + return; + } + } + else + { + if (!S_ISFIFO (st.st_mode)) + { + logmsg (LOG_ERR, "not a fifo: %s", INIT_FIFO); + return; + } + + chmod (INIT_FIFO, 0600); + } + + if (fd != -1) + { + fstat (fd, &fst); + if (fst.st_dev != st.st_dev || fst.st_ino != st.st_ino) + { + deregister_socket (fd); + close (fd); + } + debug (1, ("reopening %s", INIT_FIFO)); + } + + /* Opening the socket in read-write mode ensures we won't get EOF + on it when the caller party closes connection (at least on Linux). + Nevertheless, the svinit_fifo_handler is prepared for that eventuality, + too. */ + fd = open (INIT_FIFO, O_RDWR|O_NONBLOCK); + if (fd == -1) + { + logmsg (LOG_ERR, "cannot open %s: %s", INIT_FIFO, + strerror (errno)); + return; + } + register_socket (fd, sysvinit_fifo_handler, NULL); +} + void sysvinit_begin () { @@ -140,9 +281,12 @@ inittrans () static int wait = 0; if (progman_running_p ()) - /* Noting to do if there are processes left in the previous runlevel */ - return 0; - + { + debug (1, ("%s exiting: some components still running",__FUNCTION__)); + /* Noting to do if there are processes left in the previous runlevel */ + return 0; + } + if (runlevel == 0) n = runlevel_index (dfl_level ? dfl_level : initdefault); else @@ -160,6 +304,7 @@ inittrans () boot_state_name[newstate])); boot_state = newstate; trans = 1; + wait = 0; } switch (boot_state) @@ -170,12 +315,13 @@ inittrans () sysvinit_acct (SYSV_ACCT_BOOT, "reboot", "~~", 0, "~"); break; case single0: - case single1: newlevel = 'S'; break; + case single1: case normal: /* boot -> normal */ newlevel = dfl_level ? dfl_level : initdefault; + create_fifo (); } if (newlevel && newlevel != runlevel) { @@ -198,6 +344,7 @@ inittrans () return 1; } progman_sysvinit_enable (enablecomp, NULL); + wait = 0; } return trans; } @@ -219,3 +366,40 @@ is_comp_wait (struct component *comp) return 1; } +int +telinit (const char *arg) +{ + int fd; + struct sysvinit_request req; + + if (arg[1] || !is_valid_runlevel (*arg)) + { + logmsg (LOG_CRIT, "invalid argument"); + exit (EX_USAGE); + } + memset (&req, 0, sizeof (req)); + req.magic = INIT_MAGIC; + req.cmd = INIT_CMD_RUNLVL; + req.runlevel = *arg; +#if 0 + req.sleeptime = sltime; +#endif + + signal (SIGALRM, SIG_DFL); + alarm (5); + fd = open (INIT_FIFO, O_WRONLY); + if (fd < 0) + { + logmsg (LOG_ERR, _("can't open %s: %s"), INIT_FIFO, strerror (errno)); + exit (EX_UNAVAILABLE); + } + if (write (fd, &req, sizeof (req)) != sizeof (req)) + { + logmsg (LOG_ERR, _("error writing to %s: %s"), + INIT_FIFO, strerror (errno)); + exit (EX_UNAVAILABLE); + } + alarm (0); + close (fd); + exit (0); +} -- cgit v1.2.1