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