/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * This file contains a set of routines used to perform wait based method * reaping. */ #include #include #include #include #include #include #include #include #include #include #include "inetd_impl.h" /* inetd's open file limit, set in method_init() */ #define INETD_NOFILE_LIMIT RLIM_INFINITY /* structure used to represent an active method process */ typedef struct { int fd; /* fd of process's /proc psinfo file */ /* associated contract id if known, else -1 */ ctid_t cid; pid_t pid; instance_t *inst; /* pointer to associated instance */ instance_method_t method; /* the method type running */ /* associated endpoint protocol name if known, else NULL */ char *proto_name; uu_list_node_t link; } method_el_t; static void unregister_method(method_el_t *); /* list of currently executing method processes */ static uu_list_pool_t *method_pool = NULL; static uu_list_t *method_list = NULL; /* * File limit saved during initialization before modification, so that it can * be reverted back to for inetd's exec'd methods. */ static struct rlimit saved_file_limit; /* * Setup structures used for method termination monitoring. * Returns -1 if an allocation failure occurred, else 0. */ int method_init(void) { struct rlimit rl; /* * Save aside the old file limit and impose one large enough to support * all the /proc file handles we could have open. */ (void) getrlimit(RLIMIT_NOFILE, &saved_file_limit); rl.rlim_cur = rl.rlim_max = INETD_NOFILE_LIMIT; if (setrlimit(RLIMIT_NOFILE, &rl) == -1) { error_msg("Failed to set file limit: %s", strerror(errno)); return (-1); } if ((method_pool = uu_list_pool_create("method_pool", sizeof (method_el_t), offsetof(method_el_t, link), NULL, UU_LIST_POOL_DEBUG)) == NULL) { error_msg("%s: %s", gettext("Failed to create method pool"), uu_strerror(uu_error())); return (-1); } if ((method_list = uu_list_create(method_pool, NULL, 0)) == NULL) { error_msg("%s: %s", gettext("Failed to create method list"), uu_strerror(uu_error())); /* let method_fini() clean-up */ return (-1); } return (0); } /* * Tear-down structures created in method_init(). */ void method_fini(void) { if (method_list != NULL) { method_el_t *me; while ((me = uu_list_first(method_list)) != NULL) unregister_method(me); (void) uu_list_destroy(method_list); method_list = NULL; } if (method_pool != NULL) { (void) uu_list_pool_destroy(method_pool); method_pool = NULL; } /* revert file limit */ method_preexec(); } /* * Revert file limit back to pre-initialization one. This shouldn't fail as * long as its called *after* descriptor cleanup. */ void method_preexec(void) { (void) setrlimit(RLIMIT_NOFILE, &saved_file_limit); } /* * Callback function that handles the timeout of an instance's method. * 'arg' points at the method_el_t representing the method. */ /* ARGSUSED0 */ static void method_timeout(iu_tq_t *tq, void *arg) { method_el_t *mp = arg; error_msg(gettext("The %s method of instance %s timed-out"), methods[mp->method].name, mp->inst->fmri); mp->inst->timer_id = -1; if (mp->method == IM_START) { process_start_term(mp->inst, mp->proto_name); } else { process_non_start_term(mp->inst, IMRET_FAILURE); } unregister_method(mp); } /* * Registers the attributes of a running method passed as arguments so that * the method's termination is noticed and any further processing of the * associated instance is carried out. The function also sets up any * necessary timers so we can detect hung methods. * Returns -1 if either it failed to open the /proc psinfo file which is used * to monitor the method process, it failed to setup a required timer or * memory allocation failed; else 0. */ int register_method(instance_t *ins, pid_t pid, ctid_t cid, instance_method_t mthd, char *proto_name) { char path[MAXPATHLEN]; int fd; method_el_t *me; /* open /proc psinfo file of process to listen for POLLHUP events on */ (void) snprintf(path, sizeof (path), "/proc/%u/psinfo", pid); for (;;) { if ((fd = open(path, O_RDONLY)) >= 0) { break; } else if (errno != EINTR) { /* * Don't output an error for ENOENT; we get this * if a method has gone away whilst we were stopped, * and we're now trying to re-listen for it. */ if (errno != ENOENT) { error_msg(gettext("Failed to open %s: %s"), path, strerror(errno)); } return (-1); } } /* add method record to in-memory list */ if ((me = calloc(1, sizeof (method_el_t))) == NULL) { error_msg(strerror(errno)); (void) close(fd); return (-1); } me->fd = fd; me->inst = (instance_t *)ins; me->method = mthd; me->pid = pid; me->cid = cid; if (proto_name != NULL) { if ((me->proto_name = strdup(proto_name)) == NULL) { error_msg(strerror(errno)); free(me); (void) close(fd); return (-1); } } else me->proto_name = NULL; /* register a timeout for the method, if required */ if (mthd != IM_START) { method_info_t *mi = ins->config->methods[mthd]; if (mi->timeout > 0) { assert(ins->timer_id == -1); ins->timer_id = iu_schedule_timer(timer_queue, mi->timeout, method_timeout, me); if (ins->timer_id == -1) { error_msg(gettext( "Failed to schedule method timeout")); if (me->proto_name != NULL) free(me->proto_name); free(me); (void) close(fd); return (-1); } } } /* * Add fd of psinfo file to poll set, but pass 0 for events to * poll for, so we should only get a POLLHUP event on the fd. */ if (set_pollfd(fd, 0) == -1) { cancel_inst_timer(ins); if (me->proto_name != NULL) free(me->proto_name); free(me); (void) close(fd); return (-1); } uu_list_node_init(me, &me->link, method_pool); (void) uu_list_insert_after(method_list, NULL, me); return (0); } /* * A counterpart to register_method(), this function stops the monitoring of a * method process for its termination. */ static void unregister_method(method_el_t *me) { /* cancel any timer associated with the method */ if (me->inst->timer_id != -1) cancel_inst_timer(me->inst); /* stop polling on the psinfo file fd */ clear_pollfd(me->fd); (void) close(me->fd); /* remove method record from list */ uu_list_remove(method_list, me); if (me->proto_name != NULL) free(me->proto_name); free(me); } /* * Unregister all methods associated with instance 'inst'. */ void unregister_instance_methods(const instance_t *inst) { method_el_t *me = uu_list_first(method_list); while (me != NULL) { if (me->inst == inst) { method_el_t *tmp = me; me = uu_list_next(method_list, me); unregister_method(tmp); } else { me = uu_list_next(method_list, me); } } } /* * Process any terminated methods. For each method determined to have * terminated, the function determines its return value and calls the * appropriate handling function, depending on the type of the method. */ void process_terminated_methods(void) { method_el_t *me = uu_list_first(method_list); while (me != NULL) { struct pollfd *pfd; pid_t pid; int status; int ret; method_el_t *tmp; pfd = find_pollfd(me->fd); /* * We expect to get a POLLHUP back on the fd of the process's * open psinfo file from /proc when the method terminates. * A POLLERR could(?) mask a POLLHUP, so handle this * also. */ if ((pfd->revents & (POLLHUP|POLLERR)) == 0) { me = uu_list_next(method_list, me); continue; } /* get the method's exit code (no need to loop for EINTR) */ pid = waitpid(me->pid, &status, WNOHANG); switch (pid) { case 0: /* child still around */ /* * Either poll() is sending us invalid POLLHUP events * or is flagging a POLLERR on the fd. Neither should * happen, but in the event they do, ignore this fd * this time around and wait out the termination * of its associated method. This may result in * inetd swiftly looping in event_loop(), but means * we don't miss the termination of a method. */ me = uu_list_next(method_list, me); continue; case -1: /* non-existent child */ assert(errno == ECHILD); /* * the method must not be owned by inetd due to it * persisting over an inetd restart. Let's assume the * best, that it was successful. */ ret = IMRET_SUCCESS; break; default: /* child terminated */ if (WIFEXITED(status)) { ret = WEXITSTATUS(status); debug_msg("process %ld of instance %s returned " "%d", pid, me->inst->fmri, ret); } else if (WIFSIGNALED(status)) { /* * Terminated by signal. This may be due * to a kill that we sent from a disable or * offline event. We flag it as a failure, but * this flagged failure will only be processed * in the case of non-start methods, or when * the instance is still enabled. */ debug_msg("process %ld of instance %s exited " "due to signal %d", pid, me->inst->fmri, WTERMSIG(status)); ret = IMRET_FAILURE; } else { /* * Can we actually get here? Don't think so. * Treat it as a failure, anyway. */ debug_msg("waitpid() for %s method of " "instance %s returned %d", methods[me->method].name, me->inst->fmri, status); ret = IMRET_FAILURE; } } remove_method_ids(me->inst, me->pid, me->cid, me->method); /* continue state transition processing of the instance */ if (me->method != IM_START) { process_non_start_term(me->inst, ret); } else { process_start_term(me->inst, me->proto_name); } if (me->cid != -1) (void) abandon_contract(me->cid); tmp = me; me = uu_list_next(method_list, me); unregister_method(tmp); } }