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