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.
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.
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]
22 * Copyright 2009 Sun Microsystems, Inc. All rights reserved.
23 * Use is subject to license terms.
27 * This file contains a set of routines used to perform wait based method
32 #include <sys/param.h>
34 #include <libcontract.h>
40 #include <sys/resource.h>
41 #include "inetd_impl.h"
43 /* inetd's open file limit, set in method_init() */
44 #define INETD_NOFILE_LIMIT RLIM_INFINITY
46 /* structure used to represent an active method process */
48 int fd
; /* fd of process's /proc psinfo file */
49 /* associated contract id if known, else -1 */
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 */
60 static void unregister_method(method_el_t
*);
63 /* list of currently executing method processes */
64 static uu_list_pool_t
*method_pool
= NULL
;
65 static uu_list_t
*method_list
= NULL
;
68 * File limit saved during initialization before modification, so that it can
69 * be reverted back to for inetd's exec'd methods.
71 static struct rlimit saved_file_limit
;
74 * Setup structures used for method termination monitoring.
75 * Returns -1 if an allocation failure occurred, else 0.
83 * Save aside the old file limit and impose one large enough to support
84 * all the /proc file handles we could have open.
87 (void) getrlimit(RLIMIT_NOFILE
, &saved_file_limit
);
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
));
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()));
103 if ((method_list
= uu_list_create(method_pool
, NULL
, 0)) == NULL
) {
105 gettext("Failed to create method list"),
106 uu_strerror(uu_error()));
107 /* let method_fini() clean-up */
115 * Tear-down structures created in method_init().
120 if (method_list
!= NULL
) {
123 while ((me
= uu_list_first(method_list
)) != NULL
)
124 unregister_method(me
);
126 (void) uu_list_destroy(method_list
);
129 if (method_pool
!= NULL
) {
130 (void) uu_list_pool_destroy(method_pool
);
134 /* revert file limit */
139 * Revert file limit back to pre-initialization one. This shouldn't fail as
140 * long as its called *after* descriptor cleanup.
145 (void) setrlimit(RLIMIT_NOFILE
, &saved_file_limit
);
150 * Callback function that handles the timeout of an instance's method.
151 * 'arg' points at the method_el_t representing the method.
155 method_timeout(iu_tq_t
*tq
, void *arg
)
157 method_el_t
*mp
= arg
;
159 error_msg(gettext("The %s method of instance %s timed-out"),
160 methods
[mp
->method
].name
, mp
->inst
->fmri
);
162 mp
->inst
->timer_id
= -1;
164 if (mp
->method
== IM_START
) {
165 process_start_term(mp
->inst
, mp
->proto_name
);
167 process_non_start_term(mp
->inst
, IMRET_FAILURE
);
170 unregister_method(mp
);
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.
183 register_method(instance_t
*ins
, pid_t pid
, ctid_t cid
, instance_method_t mthd
,
186 char path
[MAXPATHLEN
];
190 /* open /proc psinfo file of process to listen for POLLHUP events on */
191 (void) snprintf(path
, sizeof (path
), "/proc/%u/psinfo", pid
);
193 if ((fd
= open(path
, O_RDONLY
)) >= 0) {
195 } else if (errno
!= EINTR
) {
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.
201 if (errno
!= ENOENT
) {
202 error_msg(gettext("Failed to open %s: %s"),
203 path
, strerror(errno
));
209 /* add method record to in-memory list */
210 if ((me
= calloc(1, sizeof (method_el_t
))) == NULL
) {
211 error_msg(strerror(errno
));
216 me
->inst
= (instance_t
*)ins
;
220 if (proto_name
!= NULL
) {
221 if ((me
->proto_name
= strdup(proto_name
)) == NULL
) {
222 error_msg(strerror(errno
));
228 me
->proto_name
= NULL
;
230 /* register a timeout for the method, if required */
231 if (mthd
!= IM_START
) {
232 method_info_t
*mi
= ins
->config
->methods
[mthd
];
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) {
240 "Failed to schedule method timeout"));
241 if (me
->proto_name
!= NULL
)
242 free(me
->proto_name
);
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.
254 if (set_pollfd(fd
, 0) == -1) {
255 cancel_inst_timer(ins
);
256 if (me
->proto_name
!= NULL
)
257 free(me
->proto_name
);
263 uu_list_node_init(me
, &me
->link
, method_pool
);
264 (void) uu_list_insert_after(method_list
, NULL
, me
);
270 * A counterpart to register_method(), this function stops the monitoring of a
271 * method process for its termination.
274 unregister_method(method_el_t
*me
)
276 /* cancel any timer associated with the method */
277 if (me
->inst
->timer_id
!= -1)
278 cancel_inst_timer(me
->inst
);
280 /* stop polling on the psinfo file fd */
281 clear_pollfd(me
->fd
);
282 (void) close(me
->fd
);
284 /* remove method record from list */
285 uu_list_remove(method_list
, me
);
287 if (me
->proto_name
!= NULL
)
288 free(me
->proto_name
);
293 * Unregister all methods associated with instance 'inst'.
296 unregister_instance_methods(const instance_t
*inst
)
298 method_el_t
*me
= uu_list_first(method_list
);
301 if (me
->inst
== inst
) {
302 method_el_t
*tmp
= me
;
304 me
= uu_list_next(method_list
, me
);
305 unregister_method(tmp
);
307 me
= uu_list_next(method_list
, me
);
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.
318 process_terminated_methods(void)
320 method_el_t
*me
= uu_list_first(method_list
);
329 pfd
= find_pollfd(me
->fd
);
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
337 if ((pfd
->revents
& (POLLHUP
|POLLERR
)) == 0) {
338 me
= uu_list_next(method_list
, me
);
342 /* get the method's exit code (no need to loop for EINTR) */
343 pid
= waitpid(me
->pid
, &status
, WNOHANG
);
346 case 0: /* child still around */
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.
356 me
= uu_list_next(method_list
, me
);
359 case -1: /* non-existent child */
360 assert(errno
== ECHILD
);
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.
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
)) {
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.
383 debug_msg("process %ld of instance %s exited "
384 "due to signal %d", pid
, me
->inst
->fmri
,
389 * Can we actually get here? Don't think so.
390 * Treat it as a failure, anyway.
392 debug_msg("waitpid() for %s method of "
393 "instance %s returned %d",
394 methods
[me
->method
].name
, me
->inst
->fmri
,
400 remove_method_ids(me
->inst
, me
->pid
, me
->cid
, me
->method
);
402 /* continue state transition processing of the instance */
403 if (me
->method
!= IM_START
) {
404 process_non_start_term(me
->inst
, ret
);
406 process_start_term(me
->inst
, me
->proto_name
);
410 (void) abandon_contract(me
->cid
);
413 me
= uu_list_next(method_list
, me
);
414 unregister_method(tmp
);