1/*++
2/* NAME
3/*	master_status 3
4/* SUMMARY
5/*	Postfix master - process child status reports
6/* SYNOPSIS
7/*	#include "master.h"
8/*
9/*	void	master_status_init(serv)
10/*	MASTER_SERV *serv;
11/*
12/*	void	master_status_cleanup(serv)
13/*	MASTER_SERV *serv;
14/* DESCRIPTION
15/*	This module reads and processes status reports from child processes.
16/*
17/*	master_status_init() enables the processing of child status updates
18/*	for the specified service. Child process status updates (process
19/*	available, process taken) are passed on to the master_avail_XXX()
20/*	routines.
21/*
22/*	master_status_cleanup() disables child status update processing
23/*	for the specified service.
24/* DIAGNOSTICS
25/*	Panic: internal inconsistency. Warnings: a child process sends
26/*	incomplete or incorrect information.
27/* BUGS
28/* SEE ALSO
29/*	master_avail(3)
30/* LICENSE
31/* .ad
32/* .fi
33/*	The Secure Mailer license must be distributed with this software.
34/* AUTHOR(S)
35/*	Wietse Venema
36/*	IBM T.J. Watson Research
37/*	P.O. Box 704
38/*	Yorktown Heights, NY 10598, USA
39/*--*/
40
41/* System libraries. */
42
43#include <sys_defs.h>
44#include <unistd.h>
45
46/* Utility library. */
47
48#include <msg.h>
49#include <events.h>
50#include <binhash.h>
51#include <iostuff.h>
52
53/* Application-specific. */
54
55#include "master_proto.h"
56#include "master.h"
57
58/* master_status_event - status read event handler */
59
60static void master_status_event(int event, char *context)
61{
62    const char *myname = "master_status_event";
63    MASTER_SERV *serv = (MASTER_SERV *) context;
64    MASTER_STATUS stat;
65    MASTER_PROC *proc;
66    MASTER_PID pid;
67    int     n;
68
69    if (event == 0)				/* XXX Can this happen?  */
70	return;
71
72    /*
73     * We always keep the child end of the status pipe open, so an EOF read
74     * condition means that we're seriously confused. We use non-blocking
75     * reads so that we don't get stuck when someone sends a partial message.
76     * Messages are short, so a partial read means someone wrote less than a
77     * whole status message. Hopefully the next read will be in sync again...
78     * We use a global child process status table because when a child dies
79     * only its pid is known - we do not know what service it came from.
80     */
81    switch (n = read(serv->status_fd[0], (char *) &stat, sizeof(stat))) {
82
83    case -1:
84	msg_warn("%s: read: %m", myname);
85	return;
86
87    case 0:
88	msg_panic("%s: read EOF status", myname);
89	/* NOTREACHED */
90
91    default:
92	msg_warn("service %s(%s): child (pid %d) sent partial status update (%d bytes)",
93		 serv->ext_name, serv->name, stat.pid, n);
94	return;
95
96    case sizeof(stat):
97	pid = stat.pid;
98	if (msg_verbose)
99	    msg_info("%s: pid %d gen %u avail %d",
100		     myname, stat.pid, stat.gen, stat.avail);
101    }
102
103    /*
104     * Sanity checks. Do not freak out when the child sends garbage because
105     * it is confused or for other reasons. However, be sure to freak out
106     * when our own data structures are inconsistent. A process not found
107     * condition can happen when we reap a process before receiving its
108     * status update, so this is not an error.
109     */
110    if ((proc = (MASTER_PROC *) binhash_find(master_child_table,
111					(char *) &pid, sizeof(pid))) == 0) {
112	if (msg_verbose)
113	    msg_info("%s: process id not found: %d", myname, stat.pid);
114	return;
115    }
116    if (proc->gen != stat.gen) {
117	msg_info("ignoring status update from child pid %d generation %u",
118		 pid, stat.gen);
119	return;
120    }
121    if (proc->serv != serv)
122	msg_panic("%s: pointer corruption: %p != %p",
123		  myname, (void *) proc->serv, (void *) serv);
124
125    /*
126     * Update our idea of the child process status. Allow redundant status
127     * updates, because different types of events may be processed out of
128     * order. Otherwise, warn about weird status updates but do not take
129     * action. It's all gossip after all.
130     */
131    if (proc->avail == stat.avail)
132	return;
133    switch (stat.avail) {
134    case MASTER_STAT_AVAIL:
135	proc->use_count++;
136	master_avail_more(serv, proc);
137	break;
138    case MASTER_STAT_TAKEN:
139	master_avail_less(serv, proc);
140	break;
141    default:
142	msg_warn("%s: ignoring unknown status: %d allegedly from pid: %d",
143		 myname, stat.pid, stat.avail);
144	break;
145    }
146}
147
148/* master_status_init - start status event processing for this service */
149
150void    master_status_init(MASTER_SERV *serv)
151{
152    const char *myname = "master_status_init";
153
154    /*
155     * Sanity checks.
156     */
157    if (serv->status_fd[0] >= 0 || serv->status_fd[1] >= 0)
158	msg_panic("%s: status events already enabled", myname);
159    if (msg_verbose)
160	msg_info("%s: %s", myname, serv->name);
161
162    /*
163     * Make the read end of this service's status pipe non-blocking so that
164     * we can detect partial writes on the child side. We use a duplex pipe
165     * so that the child side becomes readable when the master goes away.
166     */
167    if (duplex_pipe(serv->status_fd) < 0)
168	msg_fatal("pipe: %m");
169    non_blocking(serv->status_fd[0], BLOCKING);
170    close_on_exec(serv->status_fd[0], CLOSE_ON_EXEC);
171    close_on_exec(serv->status_fd[1], CLOSE_ON_EXEC);
172    event_enable_read(serv->status_fd[0], master_status_event, (char *) serv);
173}
174
175/* master_status_cleanup - stop status event processing for this service */
176
177void    master_status_cleanup(MASTER_SERV *serv)
178{
179    const char *myname = "master_status_cleanup";
180
181    /*
182     * Sanity checks.
183     */
184    if (serv->status_fd[0] < 0 || serv->status_fd[1] < 0)
185	msg_panic("%s: status events not enabled", myname);
186    if (msg_verbose)
187	msg_info("%s: %s", myname, serv->name);
188
189    /*
190     * Dispose of this service's status pipe after disabling read events.
191     */
192    event_disable_readwrite(serv->status_fd[0]);
193    if (close(serv->status_fd[0]) != 0)
194	msg_warn("%s: close status descriptor (read side): %m", myname);
195    if (close(serv->status_fd[1]) != 0)
196	msg_warn("%s: close status descriptor (write side): %m", myname);
197    serv->status_fd[0] = serv->status_fd[1] = -1;
198}
199