/*++ /* 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 #include /* Utility library. */ #include #include #include #include /* 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; }