xref: /illumos-gate/usr/src/cmd/cmd-inet/usr.lib/inetd/wait.c (revision a5f69788de7ac07553de47f7fec8c05a9a94c105)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 #pragma ident	"%Z%%M%	%I%	%E% SMI"
28 
29 /*
30  * This file contains a set of routines used to perform wait based method
31  * reaping.
32  */
33 
34 #include <wait.h>
35 #include <sys/param.h>
36 #include <fcntl.h>
37 #include <libcontract.h>
38 #include <errno.h>
39 #include <libintl.h>
40 #include <unistd.h>
41 #include <stdlib.h>
42 #include <string.h>
43 #include <sys/resource.h>
44 #include "inetd_impl.h"
45 
46 /* inetd's open file limit, set in method_init() */
47 #define	INETD_NOFILE_LIMIT RLIM_INFINITY
48 
49 /* structure used to represent an active method process */
50 typedef struct {
51 	int			fd;	/* fd of process's /proc psinfo file */
52 	/* associated contract id if known, else -1 */
53 	ctid_t			cid;
54 	pid_t			pid;
55 	instance_t		*inst;	/* pointer to associated instance */
56 	instance_method_t	method;	/* the method type running */
57 	uu_list_node_t		link;
58 } method_el_t;
59 
60 
61 static void unregister_method(method_el_t *);
62 
63 
64 /* list of currently executing method processes */
65 static uu_list_pool_t		*method_pool = NULL;
66 static uu_list_t		*method_list = NULL;
67 
68 /*
69  * File limit saved during initialization before modification, so that it can
70  * be reverted back to for inetd's exec'd methods.
71  */
72 static struct rlimit		saved_file_limit;
73 
74 /*
75  * Setup structures used for method termination monitoring.
76  * Returns -1 if an allocation failure occurred, else 0.
77  */
78 int
79 method_init(void)
80 {
81 	struct rlimit rl;
82 
83 	debug_msg("Entering method_init");
84 
85 	/*
86 	 * Save aside the old file limit and impose one large enough to support
87 	 * all the /proc file handles we could have open.
88 	 */
89 
90 	(void) getrlimit(RLIMIT_NOFILE, &saved_file_limit);
91 
92 	rl.rlim_cur = rl.rlim_max = INETD_NOFILE_LIMIT;
93 	if (setrlimit(RLIMIT_NOFILE, &rl) == -1) {
94 		error_msg("Failed to set file limit: %s", strerror(errno));
95 		return (-1);
96 	}
97 
98 	if ((method_pool = uu_list_pool_create("method_pool",
99 	    sizeof (method_el_t), offsetof(method_el_t, link), NULL,
100 	    UU_LIST_POOL_DEBUG)) == NULL) {
101 		error_msg("%s: %s", gettext("Failed to create method pool"),
102 		    uu_strerror(uu_error()));
103 		return (-1);
104 	}
105 
106 	if ((method_list = uu_list_create(method_pool, NULL, 0)) == NULL) {
107 		error_msg("%s: %s",
108 		    gettext("Failed to create method list"),
109 		    uu_strerror(uu_error()));
110 		/* let method_fini() clean-up */
111 		return (-1);
112 	}
113 
114 	return (0);
115 }
116 
117 /*
118  * Tear-down structures created in method_init().
119  */
120 void
121 method_fini(void)
122 {
123 	debug_msg("Entering method_fini");
124 
125 	if (method_list != NULL) {
126 		method_el_t *me;
127 
128 		while ((me = uu_list_first(method_list)) != NULL)
129 			unregister_method(me);
130 
131 		(void) uu_list_destroy(method_list);
132 		method_list = NULL;
133 	}
134 	if (method_pool != NULL) {
135 		(void) uu_list_pool_destroy(method_pool);
136 		method_pool = NULL;
137 	}
138 
139 	/* revert file limit */
140 	method_preexec();
141 }
142 
143 /*
144  * Revert file limit back to pre-initialization one. This shouldn't fail as
145  * long as its called *after* descriptor cleanup.
146  */
147 void
148 method_preexec(void)
149 {
150 	(void) setrlimit(RLIMIT_NOFILE, &saved_file_limit);
151 }
152 
153 
154 /*
155  * Callback function that handles the timeout of an instance's method.
156  * 'arg' points at the method_el_t representing the method.
157  */
158 /* ARGSUSED0 */
159 static void
160 method_timeout(iu_tq_t *tq, void *arg)
161 {
162 	method_el_t *mp = arg;
163 
164 	error_msg(gettext("The %s method of instance %s timed-out"),
165 	    methods[mp->method].name, mp->inst->fmri);
166 
167 	mp->inst->timer_id = -1;
168 
169 	if (mp->method == IM_START) {
170 		process_start_term(mp->inst);
171 	} else {
172 		process_non_start_term(mp->inst, IMRET_FAILURE);
173 	}
174 
175 	unregister_method(mp);
176 }
177 
178 /*
179  * Registers the attributes of a running method passed as arguments so that
180  * the method's termination is noticed and any further processing of the
181  * associated instance is carried out. The function also sets up any
182  * necessary timers so we can detect hung methods.
183  * Returns -1 if either it failed to open the /proc psinfo file which is used
184  * to monitor the method process, it failed to setup a required timer or
185  * memory allocation failed; else 0.
186  */
187 int
188 register_method(instance_t *ins, pid_t pid, ctid_t cid, instance_method_t mthd)
189 {
190 	char		path[MAXPATHLEN];
191 	int		fd;
192 	method_el_t	*me;
193 
194 	debug_msg("Entering register_method: inst: %s, pid: %d, mthd: %s",
195 	    ins->fmri, pid, methods[mthd].name);
196 
197 	/* open /proc psinfo file of process to listen for POLLHUP events on */
198 	(void) snprintf(path, sizeof (path), "/proc/%u/psinfo", pid);
199 	for (;;) {
200 		if ((fd = open(path, O_RDONLY)) >= 0) {
201 			break;
202 		} else if (errno != EINTR) {
203 			/*
204 			 * Don't output an error for ENOENT; we get this
205 			 * if a method has gone away whilst we were stopped,
206 			 * and we're now trying to re-listen for it.
207 			 */
208 			if (errno != ENOENT) {
209 				error_msg(gettext("Failed to open %s: %s"),
210 				    path, strerror(errno));
211 			}
212 			return (-1);
213 		}
214 	}
215 
216 	/* add method record to in-memory list */
217 	if ((me = calloc(1, sizeof (method_el_t))) == NULL) {
218 		error_msg(strerror(errno));
219 		(void) close(fd);
220 		return (-1);
221 	}
222 	me->fd = fd;
223 	me->inst = (instance_t *)ins;
224 	me->method = mthd;
225 	me->pid = pid;
226 	me->cid = cid;
227 
228 	/* register a timeout for the method, if required */
229 	if (mthd != IM_START) {
230 		method_info_t *mi = ins->config->methods[mthd];
231 
232 		if (mi->timeout > 0) {
233 			assert(ins->timer_id == -1);
234 			ins->timer_id = iu_schedule_timer(timer_queue,
235 			    mi->timeout, method_timeout, me);
236 			if (ins->timer_id == -1) {
237 				error_msg(gettext(
238 				    "Failed to schedule method timeout"));
239 				free(me);
240 				(void) close(fd);
241 				return (-1);
242 			}
243 		}
244 	}
245 
246 	/*
247 	 * Add fd of psinfo file to poll set, but pass 0 for events to
248 	 * poll for, so we should only get a POLLHUP event on the fd.
249 	 */
250 	if (set_pollfd(fd, 0) == -1) {
251 		cancel_inst_timer(ins);
252 		free(me);
253 		(void) close(fd);
254 		return (-1);
255 	}
256 
257 	uu_list_node_init(me, &me->link, method_pool);
258 	(void) uu_list_insert_after(method_list, NULL, me);
259 
260 	return (0);
261 }
262 
263 /*
264  * A counterpart to register_method(), this function stops the monitoring of a
265  * method process for its termination.
266  */
267 static void
268 unregister_method(method_el_t *me)
269 {
270 	debug_msg("Entering unregister_method: inst: %s, pid: %d, mthd: %s",
271 	    me->inst->fmri, me->pid, methods[me->method].name);
272 
273 	/* cancel any timer associated with the method */
274 	if (me->inst->timer_id != -1)
275 		cancel_inst_timer(me->inst);
276 
277 	/* stop polling on the psinfo file fd */
278 	clear_pollfd(me->fd);
279 	(void) close(me->fd);
280 
281 	/* remove method record from list */
282 	uu_list_remove(method_list, me);
283 
284 	free(me);
285 }
286 
287 /*
288  * Unregister all methods associated with instance 'inst'.
289  */
290 void
291 unregister_instance_methods(const instance_t *inst)
292 {
293 	method_el_t *me = uu_list_first(method_list);
294 
295 	while (me != NULL) {
296 		if (me->inst == inst) {
297 			method_el_t *tmp = me;
298 
299 			me = uu_list_next(method_list, me);
300 			unregister_method(tmp);
301 		} else  {
302 			me = uu_list_next(method_list, me);
303 		}
304 	}
305 }
306 
307 /*
308  * Process any terminated methods. For each method determined to have
309  * terminated, the function determines its return value and calls the
310  * appropriate handling function, depending on the type of the method.
311  */
312 void
313 process_terminated_methods(void)
314 {
315 	method_el_t	*me = uu_list_first(method_list);
316 
317 	debug_msg("Entering process_terminated_methods");
318 
319 	while (me != NULL) {
320 		struct pollfd	*pfd;
321 		pid_t		pid;
322 		int		status;
323 		int		ret;
324 		method_el_t	*tmp;
325 
326 		pfd = find_pollfd(me->fd);
327 
328 		/*
329 		 * We expect to get a POLLHUP back on the fd of the process's
330 		 * open psinfo file from /proc when the method terminates.
331 		 * A POLLERR could(?) mask a POLLHUP, so handle this
332 		 * also.
333 		 */
334 		if ((pfd->revents & (POLLHUP|POLLERR)) == 0) {
335 			me = uu_list_next(method_list, me);
336 			continue;
337 		}
338 
339 		/* get the method's exit code (no need to loop for EINTR) */
340 		pid = waitpid(me->pid, &status, WNOHANG);
341 
342 		switch (pid) {
343 		case 0:					/* child still around */
344 			/*
345 			 * Either poll() is sending us invalid POLLHUP events
346 			 * or is flagging a POLLERR on the fd. Neither should
347 			 * happen, but in the event they do, ignore this fd
348 			 * this time around and wait out the termination
349 			 * of its associated method. This may result in
350 			 * inetd swiftly looping in event_loop(), but means
351 			 * we don't miss the termination of a method.
352 			 */
353 			me = uu_list_next(method_list, me);
354 			continue;
355 
356 		case -1:				/* non-existent child */
357 			assert(errno == ECHILD);
358 			/*
359 			 * the method must not be owned by inetd due to it
360 			 * persisting over an inetd restart. Let's assume the
361 			 * best, that it was successful.
362 			 */
363 			ret = IMRET_SUCCESS;
364 			break;
365 
366 		default:				/* child terminated */
367 			if (WIFEXITED(status)) {
368 				ret = WEXITSTATUS(status);
369 				debug_msg("process %d of instance %s returned "
370 				    "%d", pid, me->inst->fmri, ret);
371 			} else if (WIFSIGNALED(status)) {
372 				/*
373 				 * Terminated by signal.  This may be due
374 				 * to a kill that we sent from a disable or
375 				 * offline event. We flag it as a failure, but
376 				 * this flagged failure will only be processed
377 				 * in the case of non-start methods, or when
378 				 * the instance is still enabled.
379 				 */
380 				debug_msg("process %d of instance %s exited "
381 				    "due to signal %d", pid, me->inst->fmri,
382 				    WTERMSIG(status));
383 				ret = IMRET_FAILURE;
384 			} else {
385 				/*
386 				 * Can we actually get here?  Don't think so.
387 				 * Treat it as a failure, anyway.
388 				 */
389 				debug_msg("waitpid() for %s method of "
390 				    "instance %s returned %d",
391 				    methods[me->method].name, me->inst->fmri,
392 				    status);
393 				ret = IMRET_FAILURE;
394 			}
395 		}
396 
397 		remove_method_ids(me->inst, me->pid, me->cid, me->method);
398 
399 		/* continue state transition processing of the instance */
400 		if (me->method != IM_START) {
401 			process_non_start_term(me->inst, ret);
402 		} else {
403 			process_start_term(me->inst);
404 		}
405 
406 		if (me->cid != -1)
407 			(void) abandon_contract(me->cid);
408 
409 		tmp = me;
410 		me = uu_list_next(method_list, me);
411 		unregister_method(tmp);
412 	}
413 }
414