xref: /minix/minix/servers/pm/event.c (revision 00e393ca)
1 /*
2  * This file implements a generic process event publish/subscribe facility.
3  * The facility for use by non-core system services that implement part of the
4  * userland system call interface.  Currently, it supports two events: a
5  * process catching a signal, and a process being terminated.  A subscribing
6  * service would typically use such events to interrupt a blocking system call
7  * and/or clean up process-bound resources.  As of writing, the only service
8  * that uses this facility is the System V IPC server.
9  *
10  * Each of these events will be published to subscribing services right after
11  * VFS has acknowledged that it has processed the same event.  For each
12  * subscriber, in turn, the process will be blocked (with the EVENT_CALL flag
13  * set) until the subscriber acknowledges the event or PM learns that the
14  * subscriber has died.  Thus, each subscriber adds a serialized messaging
15  * roundtrip for each subscribed event.
16  *
17  * The one and only reason for this synchronous, serialized approach is that it
18  * avoids PM queuing up too many asynchronous messages.  In theory, each
19  * running process may have an event pending, and thus, the serial synchronous
20  * approach requires NR_PROCS asynsend slots.  For a parallel synchronous
21  * approach, this would increase to (NR_PROCS*NR_SUBS).  Worse yet, for an
22  * asynchronous event notification approach, the number of messages that PM can
23  * end up queuing is potentially unbounded, so that is certainly not an option.
24  * At this moment, we expect only one subscriber (the IPC server) which makes
25  * the serial vs parallel point less relevant.
26  *
27  * It is not possible to subscribe to events from certain processes only.  If
28  * a service were to subscribe to process events as part of a system call by
29  * a process (e.g., semop(2) in the case of the IPC server), it may subscribe
30  * "too late" and already have missed a signal event for the process calling
31  * semop(2), for example.  Resolving such race conditions would require major
32  * infrastructure changes.
33  *
34  * A server may however change its event subscription mask at runtime, so as to
35  * limit the number of event messages it receives in a crude fashion.  For the
36  * same race-condition reasons, new subscriptions must always be made when
37  * processing a message that is *not* a system call potentially affected by
38  * events.  In the case of the IPC server, it may subscribe to events from
39  * semget(2) but not semop(2).  For signal events, the delay call system
40  * guarantees the safety of this approach; for exit events, the message type
41  * prioritization does (which is not great; see the TODO item in forkexit.c).
42  *
43  * After changing its mask, a subscribing service may still receive messages
44  * for events it is no longer subscribed to.  It should acknowledge these
45  * messages by sending a reply as usual.
46  */
47 
48 #include "pm.h"
49 #include "mproc.h"
50 #include <assert.h>
51 
52 /*
53  * A realistic upper bound for the number of subscribing services.  The process
54  * event notification system adds a round trip to a service for each subscriber
55  * and uses asynchronous messaging to boot, so clearly it does not scale to
56  * numbers larger than this.
57  */
58 #define NR_SUBS		4
59 
60 static struct {
61 	endpoint_t endpt;		/* endpoint of subscriber */
62 	unsigned int mask;		/* interests bit mask (PROC_EVENT_) */
63 	unsigned int waiting;		/* # procs blocked on reply from it */
64 } subs[NR_SUBS];
65 
66 static unsigned int nsubs = 0;
67 static unsigned int nested = 0;
68 
69 /*
70  * For the current event of the given process, as determined by its flags, send
71  * a process event message to the next subscriber, or resume handling the
72  * event itself if there are no more subscribers to notify.
73  */
74 static void
75 resume_event(struct mproc * rmp)
76 {
77 	message m;
78 	unsigned int i, event;
79 	int r;
80 
81 	assert(rmp->mp_flags & IN_USE);
82 	assert(rmp->mp_flags & EVENT_CALL);
83 	assert(rmp->mp_eventsub != NO_EVENTSUB);
84 
85 	/* Which event should we be concerned about? */
86 	if (rmp->mp_flags & EXITING)
87 		event = PROC_EVENT_EXIT;
88 	else if (rmp->mp_flags & UNPAUSED)
89 		event = PROC_EVENT_SIGNAL;
90 	else
91 		panic("unknown event for flags %x", rmp->mp_flags);
92 
93 	/*
94 	 * If there are additional services interested in this event, send a
95 	 * message to the next one.
96 	 */
97 	for (i = rmp->mp_eventsub; i < nsubs; i++, rmp->mp_eventsub++) {
98 		if (subs[i].mask & event) {
99 			memset(&m, 0, sizeof(m));
100 			m.m_type = PROC_EVENT;
101 			m.m_pm_lsys_proc_event.endpt = rmp->mp_endpoint;
102 			m.m_pm_lsys_proc_event.event = event;
103 
104 			r = asynsend3(subs[i].endpt, &m, AMF_NOREPLY);
105 			if (r != OK)
106 				panic("asynsend failed: %d", r);
107 
108 			assert(subs[i].waiting < NR_PROCS);
109 			subs[i].waiting++;
110 
111 			return;
112 		}
113 	}
114 
115 	/* No more subscribers to be notified, resume the actual event. */
116 	rmp->mp_flags &= ~EVENT_CALL;
117 	rmp->mp_eventsub = NO_EVENTSUB;
118 
119 	if (event == PROC_EVENT_EXIT)
120 		exit_restart(rmp);
121 	else if (event == PROC_EVENT_SIGNAL)
122 		restart_sigs(rmp);
123 }
124 
125 /*
126  * Remove a subscriber from the set, forcefully if we have to.  Ensure that
127  * any processes currently subject to process event notification are updated
128  * accordingly, in a way that no services are skipped for process events.
129  */
130 static void
131 remove_sub(unsigned int slot)
132 {
133 	struct mproc *rmp;
134 	unsigned int i;
135 
136 	/* The loop below needs the remaining items to be kept in order. */
137 	for (i = slot; i < nsubs - 1; i++)
138 		subs[i] = subs[i + 1];
139 	nsubs--;
140 
141 	/* Adjust affected processes' event subscriber indexes to match. */
142 	for (rmp = &mproc[0]; rmp < &mproc[NR_PROCS]; rmp++) {
143 		if ((rmp->mp_flags & (IN_USE | EVENT_CALL)) !=
144 		    (IN_USE | EVENT_CALL))
145 			continue;
146 		assert(rmp->mp_eventsub != NO_EVENTSUB);
147 
148 		/*
149 		 * While resuming a process could trigger new events, event
150 		 * calls always take place after the corresponding VFS calls,
151 		 * making this nesting-safe.  Check anyway, because if nesting
152 		 * does occur, we are in serious (un-debuggable) trouble.
153 		 */
154 		if ((unsigned int)rmp->mp_eventsub == slot) {
155 			nested++;
156 			resume_event(rmp);
157 			nested--;
158 		} else if ((unsigned int)rmp->mp_eventsub > slot)
159 			rmp->mp_eventsub--;
160 	}
161 }
162 
163 /*
164  * Subscribe to process events.  The given event mask denotes the events in
165  * which the caller is interested.  Multiple calls will each replace the mask,
166  * and a mask of zero will unsubscribe the service from events altogether.
167  * Return OK on success, EPERM if the caller may not register for events, or
168  * ENOMEM if all subscriber slots are in use already.
169  */
170 int
171 do_proceventmask(void)
172 {
173 	unsigned int i, mask;
174 
175 	/* This call is for system services only. */
176 	if (!(mp->mp_flags & PRIV_PROC))
177 		return EPERM;
178 
179 	mask = m_in.m_lsys_pm_proceventmask.mask;
180 
181 	/*
182 	 * First check if we need to update or remove an existing entry.
183 	 * We cannot actually remove services for which we are still waiting
184 	 * for a reply, so set their mask to zero for later removal instead.
185 	 */
186 	for (i = 0; i < nsubs; i++) {
187 		if (subs[i].endpt == who_e) {
188 			if (mask == 0 && subs[i].waiting == 0)
189 				remove_sub(i);
190 			else
191 				subs[i].mask = mask;
192 			return OK;
193 		}
194 	}
195 
196 	/* Add a new entry, unless the given mask is empty. */
197 	if (mask == 0)
198 		return OK;
199 
200 	/* This case should never trigger. */
201 	if (nsubs == __arraycount(subs)) {
202 		printf("PM: too many process event subscribers!\n");
203 		return ENOMEM;
204 	}
205 
206 	subs[nsubs].endpt = who_e;
207 	subs[nsubs].mask = mask;
208 	nsubs++;
209 
210 	return OK;
211 }
212 
213 /*
214  * A subscribing service has replied to a process event message from us, or at
215  * least that is what should have happened.  First make sure of this, and then
216  * resume event handling for the affected process.
217  */
218 int
219 do_proc_event_reply(void)
220 {
221 	struct mproc *rmp;
222 	endpoint_t endpt;
223 	unsigned int i, event;
224 	int slot;
225 
226 	assert(nested == 0);
227 
228 	/*
229 	 * Is this an accidental call from a misguided user process?
230 	 * Politely tell it to go away.
231 	 */
232 	if (!(mp->mp_flags & PRIV_PROC))
233 		return ENOSYS;
234 
235 	/*
236 	 * Ensure that we got the reply that we want.  Since this code is
237 	 * relatively new, produce lots of warnings for cases that should never
238 	 * or rarely occur.  Later we can just ignore all mismatching replies.
239 	 */
240 	endpt = m_in.m_pm_lsys_proc_event.endpt;
241 	if (pm_isokendpt(endpt, &slot) != OK) {
242 		printf("PM: proc event reply from %d for invalid endpt %d\n",
243 		    who_e, endpt);
244 		return SUSPEND;
245 	}
246 	rmp = &mproc[slot];
247 	if (!(rmp->mp_flags & EVENT_CALL)) {
248 		printf("PM: proc event reply from %d for endpt %d, no event\n",
249 		    who_e, endpt);
250 		return SUSPEND;
251 	}
252 	if (rmp->mp_eventsub == NO_EVENTSUB ||
253 	    (unsigned int)rmp->mp_eventsub >= nsubs) {
254 		printf("PM: proc event reply from %d for endpt %d index %d\n",
255 		    who_e, endpt, rmp->mp_eventsub);
256 		return SUSPEND;
257 	}
258 	i = rmp->mp_eventsub;
259 	if (subs[i].endpt != who_e) {
260 		printf("PM: proc event reply for %d from %d instead of %d\n",
261 		    endpt, who_e, subs[i].endpt);
262 		return SUSPEND;
263 	}
264 
265 	if (rmp->mp_flags & EXITING)
266 		event = PROC_EVENT_EXIT;
267 	else if (rmp->mp_flags & UNPAUSED)
268 		event = PROC_EVENT_SIGNAL;
269 	else {
270 		printf("PM: proc event reply from %d for %d, bad flags %x\n",
271 		    who_e, endpt, rmp->mp_flags);
272 		return SUSPEND;
273 	}
274 	if (m_in.m_pm_lsys_proc_event.event != event) {
275 		printf("PM: proc event reply from %d for %d for event %d "
276 		    "instead of %d\n", who_e, endpt,
277 		    m_in.m_pm_lsys_proc_event.event, event);
278 		return SUSPEND;
279 	}
280 	/*
281 	 * Do NOT check the event against the subscriber's event mask, since a
282 	 * service may have unsubscribed from an event while it has yet to
283 	 * process some leftover notifications for that event.  We could decide
284 	 * not to wait for the replies to those leftover notifications upon
285 	 * unsubscription, but that could result in problems upon quick
286 	 * resubscription, and such cases may in fact happen in practice.
287 	 */
288 
289 	assert(subs[i].waiting > 0);
290 	subs[i].waiting--;
291 
292 	/*
293 	 * If we are now no longer waiting for any replies from an already
294 	 * unsubscribed (but alive) service, remove it from the set now; this
295 	 * will also resume events for the current process.  In the normal case
296 	 * however, let the current process move on to the next subscriber if
297 	 * there are more, and the actual event otherwise.
298 	 */
299 	if (subs[i].mask == 0 && subs[i].waiting == 0) {
300 		remove_sub(i);
301 	} else {
302 		rmp->mp_eventsub++;
303 
304 		resume_event(rmp);
305 	}
306 
307 	/* In any case, do not reply to this reply message. */
308 	return SUSPEND;
309 }
310 
311 /*
312  * Publish a process event to interested subscribers.  The event is determined
313  * from the process flags.  In addition, if the event is a process exit, also
314  * check if it is a subscribing service that died.
315  */
316 void
317 publish_event(struct mproc * rmp)
318 {
319 	unsigned int i;
320 
321 	assert(nested == 0);
322 	assert((rmp->mp_flags & (IN_USE | EVENT_CALL)) == IN_USE);
323 	assert(rmp->mp_eventsub == NO_EVENTSUB);
324 
325 	/*
326 	 * If a system service exited, we have to check if it was subscribed to
327 	 * process events.  If so, we have to remove it from the set and resume
328 	 * any processes blocked on an event call to that service.
329 	 */
330 	if ((rmp->mp_flags & (PRIV_PROC | EXITING)) == (PRIV_PROC | EXITING)) {
331 		for (i = 0; i < nsubs; i++) {
332 			if (subs[i].endpt == rmp->mp_endpoint) {
333 				/*
334 				 * If the wait count is nonzero, we may or may
335 				 * not get additional replies from this service
336 				 * later.  Those will be ignored.
337 				 */
338 				remove_sub(i);
339 
340 				break;
341 			}
342 		}
343 	}
344 
345 	/*
346 	 * Either send an event message to the first subscriber, or if there
347 	 * are no subscribers, resume processing the event right away.
348 	 */
349 	rmp->mp_flags |= EVENT_CALL;
350 	rmp->mp_eventsub = 0;
351 
352 	resume_event(rmp);
353 }
354