diff options
Diffstat (limited to 'src/master/master_status.c')
-rw-r--r-- | src/master/master_status.c | 198 |
1 files changed, 198 insertions, 0 deletions
diff --git a/src/master/master_status.c b/src/master/master_status.c new file mode 100644 index 0000000..fb3bb73 --- /dev/null +++ b/src/master/master_status.c @@ -0,0 +1,198 @@ +/*++ +/* NAME +/* master_status 3 +/* SUMMARY +/* Postfix master - process child status reports +/* SYNOPSIS +/* #include "master.h" +/* +/* void master_status_init(serv) +/* MASTER_SERV *serv; +/* +/* void master_status_cleanup(serv) +/* MASTER_SERV *serv; +/* DESCRIPTION +/* This module reads and processes status reports from child processes. +/* +/* master_status_init() enables the processing of child status updates +/* for the specified service. Child process status updates (process +/* available, process taken) are passed on to the master_avail_XXX() +/* routines. +/* +/* master_status_cleanup() disables child status update processing +/* for the specified service. +/* DIAGNOSTICS +/* Panic: internal inconsistency. Warnings: a child process sends +/* incomplete or incorrect information. +/* BUGS +/* SEE ALSO +/* master_avail(3) +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System libraries. */ + +#include <sys_defs.h> +#include <unistd.h> + +/* Utility library. */ + +#include <msg.h> +#include <events.h> +#include <binhash.h> +#include <iostuff.h> + +/* Application-specific. */ + +#include "master_proto.h" +#include "master.h" + +/* master_status_event - status read event handler */ + +static void master_status_event(int event, void *context) +{ + const char *myname = "master_status_event"; + MASTER_SERV *serv = (MASTER_SERV *) context; + MASTER_STATUS stat; + MASTER_PROC *proc; + MASTER_PID pid; + int n; + + if (event == 0) /* XXX Can this happen? */ + return; + + /* + * We always keep the child end of the status pipe open, so an EOF read + * condition means that we're seriously confused. We use non-blocking + * reads so that we don't get stuck when someone sends a partial message. + * Messages are short, so a partial read means someone wrote less than a + * whole status message. Hopefully the next read will be in sync again... + * We use a global child process status table because when a child dies + * only its pid is known - we do not know what service it came from. + */ + switch (n = read(serv->status_fd[0], (void *) &stat, sizeof(stat))) { + + case -1: + msg_warn("%s: read: %m", myname); + return; + + case 0: + msg_panic("%s: read EOF status", myname); + /* NOTREACHED */ + + default: + msg_warn("service %s(%s): child (pid %d) sent partial status update (%d bytes)", + serv->ext_name, serv->name, stat.pid, n); + return; + + case sizeof(stat): + pid = stat.pid; + if (msg_verbose) + msg_info("%s: pid %d gen %u avail %d", + myname, stat.pid, stat.gen, stat.avail); + } + + /* + * Sanity checks. Do not freak out when the child sends garbage because + * it is confused or for other reasons. However, be sure to freak out + * when our own data structures are inconsistent. A process not found + * condition can happen when we reap a process before receiving its + * status update, so this is not an error. + */ + if ((proc = (MASTER_PROC *) binhash_find(master_child_table, + (void *) &pid, sizeof(pid))) == 0) { + if (msg_verbose) + msg_info("%s: process id not found: %d", myname, stat.pid); + return; + } + if (proc->gen != stat.gen) { + msg_info("ignoring status update from child pid %d generation %u", + pid, stat.gen); + return; + } + if (proc->serv != serv) + msg_panic("%s: pointer corruption: %p != %p", + myname, (void *) proc->serv, (void *) serv); + + /* + * Update our idea of the child process status. Allow redundant status + * updates, because different types of events may be processed out of + * order. Otherwise, warn about weird status updates but do not take + * action. It's all gossip after all. + */ + if (proc->avail == stat.avail) + return; + switch (stat.avail) { + case MASTER_STAT_AVAIL: + proc->use_count++; + master_avail_more(serv, proc); + break; + case MASTER_STAT_TAKEN: + master_avail_less(serv, proc); + break; + default: + msg_warn("%s: ignoring unknown status: %d allegedly from pid: %d", + myname, stat.pid, stat.avail); + break; + } +} + +/* master_status_init - start status event processing for this service */ + +void master_status_init(MASTER_SERV *serv) +{ + const char *myname = "master_status_init"; + + /* + * Sanity checks. + */ + if (serv->status_fd[0] >= 0 || serv->status_fd[1] >= 0) + msg_panic("%s: status events already enabled", myname); + if (msg_verbose) + msg_info("%s: %s", myname, serv->name); + + /* + * Make the read end of this service's status pipe non-blocking so that + * we can detect partial writes on the child side. We use a duplex pipe + * so that the child side becomes readable when the master goes away. + */ + if (duplex_pipe(serv->status_fd) < 0) + msg_fatal("pipe: %m"); + non_blocking(serv->status_fd[0], BLOCKING); + close_on_exec(serv->status_fd[0], CLOSE_ON_EXEC); + close_on_exec(serv->status_fd[1], CLOSE_ON_EXEC); + event_enable_read(serv->status_fd[0], master_status_event, (void *) serv); +} + +/* master_status_cleanup - stop status event processing for this service */ + +void master_status_cleanup(MASTER_SERV *serv) +{ + const char *myname = "master_status_cleanup"; + + /* + * Sanity checks. + */ + if (serv->status_fd[0] < 0 || serv->status_fd[1] < 0) + msg_panic("%s: status events not enabled", myname); + if (msg_verbose) + msg_info("%s: %s", myname, serv->name); + + /* + * Dispose of this service's status pipe after disabling read events. + */ + event_disable_readwrite(serv->status_fd[0]); + if (close(serv->status_fd[0]) != 0) + msg_warn("%s: close status descriptor (read side): %m", myname); + if (close(serv->status_fd[1]) != 0) + msg_warn("%s: close status descriptor (write side): %m", myname); + serv->status_fd[0] = serv->status_fd[1] = -1; +} |