summaryrefslogtreecommitdiffstats
path: root/src/master/master_status.c
blob: fb3bb732eba99b1b92ccb798d0a4e76635456f30 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
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;
}