10Sstevel@tonic-gate /* 20Sstevel@tonic-gate * CDDL HEADER START 30Sstevel@tonic-gate * 40Sstevel@tonic-gate * The contents of this file are subject to the terms of the 56435Sgm209912 * Common Development and Distribution License (the "License"). 66435Sgm209912 * You may not use this file except in compliance with the License. 70Sstevel@tonic-gate * 80Sstevel@tonic-gate * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 90Sstevel@tonic-gate * or http://www.opensolaris.org/os/licensing. 100Sstevel@tonic-gate * See the License for the specific language governing permissions 110Sstevel@tonic-gate * and limitations under the License. 120Sstevel@tonic-gate * 130Sstevel@tonic-gate * When distributing Covered Code, include this CDDL HEADER in each 140Sstevel@tonic-gate * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 150Sstevel@tonic-gate * If applicable, add the following below this CDDL HEADER, with the 160Sstevel@tonic-gate * fields enclosed by brackets "[]" replaced with your own identifying 170Sstevel@tonic-gate * information: Portions Copyright [yyyy] [name of copyright owner] 180Sstevel@tonic-gate * 190Sstevel@tonic-gate * CDDL HEADER END 200Sstevel@tonic-gate */ 210Sstevel@tonic-gate /* 22*9272SRenaud.Manus@Sun.COM * Copyright 2009 Sun Microsystems, Inc. All rights reserved. 230Sstevel@tonic-gate * Use is subject to license terms. 240Sstevel@tonic-gate */ 250Sstevel@tonic-gate 260Sstevel@tonic-gate /* 270Sstevel@tonic-gate * This file contains a set of routines used to perform wait based method 280Sstevel@tonic-gate * reaping. 290Sstevel@tonic-gate */ 300Sstevel@tonic-gate 310Sstevel@tonic-gate #include <wait.h> 320Sstevel@tonic-gate #include <sys/param.h> 330Sstevel@tonic-gate #include <fcntl.h> 340Sstevel@tonic-gate #include <libcontract.h> 350Sstevel@tonic-gate #include <errno.h> 360Sstevel@tonic-gate #include <libintl.h> 370Sstevel@tonic-gate #include <unistd.h> 380Sstevel@tonic-gate #include <stdlib.h> 390Sstevel@tonic-gate #include <string.h> 400Sstevel@tonic-gate #include <sys/resource.h> 410Sstevel@tonic-gate #include "inetd_impl.h" 420Sstevel@tonic-gate 430Sstevel@tonic-gate /* inetd's open file limit, set in method_init() */ 440Sstevel@tonic-gate #define INETD_NOFILE_LIMIT RLIM_INFINITY 450Sstevel@tonic-gate 460Sstevel@tonic-gate /* structure used to represent an active method process */ 470Sstevel@tonic-gate typedef struct { 480Sstevel@tonic-gate int fd; /* fd of process's /proc psinfo file */ 490Sstevel@tonic-gate /* associated contract id if known, else -1 */ 500Sstevel@tonic-gate ctid_t cid; 510Sstevel@tonic-gate pid_t pid; 520Sstevel@tonic-gate instance_t *inst; /* pointer to associated instance */ 530Sstevel@tonic-gate instance_method_t method; /* the method type running */ 54*9272SRenaud.Manus@Sun.COM /* associated endpoint protocol name if known, else NULL */ 55*9272SRenaud.Manus@Sun.COM char *proto_name; 560Sstevel@tonic-gate uu_list_node_t link; 570Sstevel@tonic-gate } method_el_t; 580Sstevel@tonic-gate 590Sstevel@tonic-gate 600Sstevel@tonic-gate static void unregister_method(method_el_t *); 610Sstevel@tonic-gate 620Sstevel@tonic-gate 630Sstevel@tonic-gate /* list of currently executing method processes */ 640Sstevel@tonic-gate static uu_list_pool_t *method_pool = NULL; 650Sstevel@tonic-gate static uu_list_t *method_list = NULL; 660Sstevel@tonic-gate 670Sstevel@tonic-gate /* 680Sstevel@tonic-gate * File limit saved during initialization before modification, so that it can 690Sstevel@tonic-gate * be reverted back to for inetd's exec'd methods. 700Sstevel@tonic-gate */ 710Sstevel@tonic-gate static struct rlimit saved_file_limit; 720Sstevel@tonic-gate 730Sstevel@tonic-gate /* 740Sstevel@tonic-gate * Setup structures used for method termination monitoring. 750Sstevel@tonic-gate * Returns -1 if an allocation failure occurred, else 0. 760Sstevel@tonic-gate */ 770Sstevel@tonic-gate int 780Sstevel@tonic-gate method_init(void) 790Sstevel@tonic-gate { 800Sstevel@tonic-gate struct rlimit rl; 810Sstevel@tonic-gate 820Sstevel@tonic-gate /* 830Sstevel@tonic-gate * Save aside the old file limit and impose one large enough to support 840Sstevel@tonic-gate * all the /proc file handles we could have open. 850Sstevel@tonic-gate */ 860Sstevel@tonic-gate 870Sstevel@tonic-gate (void) getrlimit(RLIMIT_NOFILE, &saved_file_limit); 880Sstevel@tonic-gate 890Sstevel@tonic-gate rl.rlim_cur = rl.rlim_max = INETD_NOFILE_LIMIT; 900Sstevel@tonic-gate if (setrlimit(RLIMIT_NOFILE, &rl) == -1) { 910Sstevel@tonic-gate error_msg("Failed to set file limit: %s", strerror(errno)); 920Sstevel@tonic-gate return (-1); 930Sstevel@tonic-gate } 940Sstevel@tonic-gate 950Sstevel@tonic-gate if ((method_pool = uu_list_pool_create("method_pool", 960Sstevel@tonic-gate sizeof (method_el_t), offsetof(method_el_t, link), NULL, 970Sstevel@tonic-gate UU_LIST_POOL_DEBUG)) == NULL) { 980Sstevel@tonic-gate error_msg("%s: %s", gettext("Failed to create method pool"), 990Sstevel@tonic-gate uu_strerror(uu_error())); 1000Sstevel@tonic-gate return (-1); 1010Sstevel@tonic-gate } 1020Sstevel@tonic-gate 1030Sstevel@tonic-gate if ((method_list = uu_list_create(method_pool, NULL, 0)) == NULL) { 1040Sstevel@tonic-gate error_msg("%s: %s", 1050Sstevel@tonic-gate gettext("Failed to create method list"), 1060Sstevel@tonic-gate uu_strerror(uu_error())); 1070Sstevel@tonic-gate /* let method_fini() clean-up */ 1080Sstevel@tonic-gate return (-1); 1090Sstevel@tonic-gate } 1100Sstevel@tonic-gate 1110Sstevel@tonic-gate return (0); 1120Sstevel@tonic-gate } 1130Sstevel@tonic-gate 1140Sstevel@tonic-gate /* 1150Sstevel@tonic-gate * Tear-down structures created in method_init(). 1160Sstevel@tonic-gate */ 1170Sstevel@tonic-gate void 1180Sstevel@tonic-gate method_fini(void) 1190Sstevel@tonic-gate { 1200Sstevel@tonic-gate if (method_list != NULL) { 1210Sstevel@tonic-gate method_el_t *me; 1220Sstevel@tonic-gate 1230Sstevel@tonic-gate while ((me = uu_list_first(method_list)) != NULL) 1240Sstevel@tonic-gate unregister_method(me); 1250Sstevel@tonic-gate 1260Sstevel@tonic-gate (void) uu_list_destroy(method_list); 1270Sstevel@tonic-gate method_list = NULL; 1280Sstevel@tonic-gate } 1290Sstevel@tonic-gate if (method_pool != NULL) { 1300Sstevel@tonic-gate (void) uu_list_pool_destroy(method_pool); 1310Sstevel@tonic-gate method_pool = NULL; 1320Sstevel@tonic-gate } 1330Sstevel@tonic-gate 1340Sstevel@tonic-gate /* revert file limit */ 1350Sstevel@tonic-gate method_preexec(); 1360Sstevel@tonic-gate } 1370Sstevel@tonic-gate 1380Sstevel@tonic-gate /* 1390Sstevel@tonic-gate * Revert file limit back to pre-initialization one. This shouldn't fail as 1400Sstevel@tonic-gate * long as its called *after* descriptor cleanup. 1410Sstevel@tonic-gate */ 1420Sstevel@tonic-gate void 1430Sstevel@tonic-gate method_preexec(void) 1440Sstevel@tonic-gate { 1450Sstevel@tonic-gate (void) setrlimit(RLIMIT_NOFILE, &saved_file_limit); 1460Sstevel@tonic-gate } 1470Sstevel@tonic-gate 1480Sstevel@tonic-gate 1490Sstevel@tonic-gate /* 1500Sstevel@tonic-gate * Callback function that handles the timeout of an instance's method. 1510Sstevel@tonic-gate * 'arg' points at the method_el_t representing the method. 1520Sstevel@tonic-gate */ 1530Sstevel@tonic-gate /* ARGSUSED0 */ 1540Sstevel@tonic-gate static void 1550Sstevel@tonic-gate method_timeout(iu_tq_t *tq, void *arg) 1560Sstevel@tonic-gate { 1570Sstevel@tonic-gate method_el_t *mp = arg; 1580Sstevel@tonic-gate 1590Sstevel@tonic-gate error_msg(gettext("The %s method of instance %s timed-out"), 1600Sstevel@tonic-gate methods[mp->method].name, mp->inst->fmri); 1610Sstevel@tonic-gate 1620Sstevel@tonic-gate mp->inst->timer_id = -1; 1630Sstevel@tonic-gate 1640Sstevel@tonic-gate if (mp->method == IM_START) { 165*9272SRenaud.Manus@Sun.COM process_start_term(mp->inst, mp->proto_name); 1660Sstevel@tonic-gate } else { 1670Sstevel@tonic-gate process_non_start_term(mp->inst, IMRET_FAILURE); 1680Sstevel@tonic-gate } 1690Sstevel@tonic-gate 1700Sstevel@tonic-gate unregister_method(mp); 1710Sstevel@tonic-gate } 1720Sstevel@tonic-gate 1730Sstevel@tonic-gate /* 1740Sstevel@tonic-gate * Registers the attributes of a running method passed as arguments so that 1750Sstevel@tonic-gate * the method's termination is noticed and any further processing of the 1760Sstevel@tonic-gate * associated instance is carried out. The function also sets up any 1770Sstevel@tonic-gate * necessary timers so we can detect hung methods. 1780Sstevel@tonic-gate * Returns -1 if either it failed to open the /proc psinfo file which is used 1790Sstevel@tonic-gate * to monitor the method process, it failed to setup a required timer or 1800Sstevel@tonic-gate * memory allocation failed; else 0. 1810Sstevel@tonic-gate */ 1820Sstevel@tonic-gate int 183*9272SRenaud.Manus@Sun.COM register_method(instance_t *ins, pid_t pid, ctid_t cid, instance_method_t mthd, 184*9272SRenaud.Manus@Sun.COM char *proto_name) 1850Sstevel@tonic-gate { 1860Sstevel@tonic-gate char path[MAXPATHLEN]; 1870Sstevel@tonic-gate int fd; 1880Sstevel@tonic-gate method_el_t *me; 1890Sstevel@tonic-gate 1900Sstevel@tonic-gate /* open /proc psinfo file of process to listen for POLLHUP events on */ 1910Sstevel@tonic-gate (void) snprintf(path, sizeof (path), "/proc/%u/psinfo", pid); 1920Sstevel@tonic-gate for (;;) { 1930Sstevel@tonic-gate if ((fd = open(path, O_RDONLY)) >= 0) { 1940Sstevel@tonic-gate break; 1950Sstevel@tonic-gate } else if (errno != EINTR) { 1960Sstevel@tonic-gate /* 1970Sstevel@tonic-gate * Don't output an error for ENOENT; we get this 1980Sstevel@tonic-gate * if a method has gone away whilst we were stopped, 1990Sstevel@tonic-gate * and we're now trying to re-listen for it. 2000Sstevel@tonic-gate */ 2010Sstevel@tonic-gate if (errno != ENOENT) { 2020Sstevel@tonic-gate error_msg(gettext("Failed to open %s: %s"), 2030Sstevel@tonic-gate path, strerror(errno)); 2040Sstevel@tonic-gate } 2050Sstevel@tonic-gate return (-1); 2060Sstevel@tonic-gate } 2070Sstevel@tonic-gate } 2080Sstevel@tonic-gate 2090Sstevel@tonic-gate /* add method record to in-memory list */ 2100Sstevel@tonic-gate if ((me = calloc(1, sizeof (method_el_t))) == NULL) { 2110Sstevel@tonic-gate error_msg(strerror(errno)); 2120Sstevel@tonic-gate (void) close(fd); 2130Sstevel@tonic-gate return (-1); 2140Sstevel@tonic-gate } 2150Sstevel@tonic-gate me->fd = fd; 2160Sstevel@tonic-gate me->inst = (instance_t *)ins; 2170Sstevel@tonic-gate me->method = mthd; 2180Sstevel@tonic-gate me->pid = pid; 2190Sstevel@tonic-gate me->cid = cid; 220*9272SRenaud.Manus@Sun.COM if (proto_name != NULL) { 221*9272SRenaud.Manus@Sun.COM if ((me->proto_name = strdup(proto_name)) == NULL) { 222*9272SRenaud.Manus@Sun.COM error_msg(strerror(errno)); 223*9272SRenaud.Manus@Sun.COM free(me); 224*9272SRenaud.Manus@Sun.COM (void) close(fd); 225*9272SRenaud.Manus@Sun.COM return (-1); 226*9272SRenaud.Manus@Sun.COM } 227*9272SRenaud.Manus@Sun.COM } else 228*9272SRenaud.Manus@Sun.COM me->proto_name = NULL; 2290Sstevel@tonic-gate 2300Sstevel@tonic-gate /* register a timeout for the method, if required */ 2310Sstevel@tonic-gate if (mthd != IM_START) { 2320Sstevel@tonic-gate method_info_t *mi = ins->config->methods[mthd]; 2330Sstevel@tonic-gate 2340Sstevel@tonic-gate if (mi->timeout > 0) { 2350Sstevel@tonic-gate assert(ins->timer_id == -1); 2360Sstevel@tonic-gate ins->timer_id = iu_schedule_timer(timer_queue, 2370Sstevel@tonic-gate mi->timeout, method_timeout, me); 2380Sstevel@tonic-gate if (ins->timer_id == -1) { 2390Sstevel@tonic-gate error_msg(gettext( 2400Sstevel@tonic-gate "Failed to schedule method timeout")); 241*9272SRenaud.Manus@Sun.COM if (me->proto_name != NULL) 242*9272SRenaud.Manus@Sun.COM free(me->proto_name); 2430Sstevel@tonic-gate free(me); 2440Sstevel@tonic-gate (void) close(fd); 2450Sstevel@tonic-gate return (-1); 2460Sstevel@tonic-gate } 2470Sstevel@tonic-gate } 2480Sstevel@tonic-gate } 2490Sstevel@tonic-gate 2500Sstevel@tonic-gate /* 2510Sstevel@tonic-gate * Add fd of psinfo file to poll set, but pass 0 for events to 2520Sstevel@tonic-gate * poll for, so we should only get a POLLHUP event on the fd. 2530Sstevel@tonic-gate */ 2540Sstevel@tonic-gate if (set_pollfd(fd, 0) == -1) { 2550Sstevel@tonic-gate cancel_inst_timer(ins); 256*9272SRenaud.Manus@Sun.COM if (me->proto_name != NULL) 257*9272SRenaud.Manus@Sun.COM free(me->proto_name); 2580Sstevel@tonic-gate free(me); 2590Sstevel@tonic-gate (void) close(fd); 2600Sstevel@tonic-gate return (-1); 2610Sstevel@tonic-gate } 2620Sstevel@tonic-gate 2630Sstevel@tonic-gate uu_list_node_init(me, &me->link, method_pool); 2640Sstevel@tonic-gate (void) uu_list_insert_after(method_list, NULL, me); 2650Sstevel@tonic-gate 2660Sstevel@tonic-gate return (0); 2670Sstevel@tonic-gate } 2680Sstevel@tonic-gate 2690Sstevel@tonic-gate /* 2700Sstevel@tonic-gate * A counterpart to register_method(), this function stops the monitoring of a 2710Sstevel@tonic-gate * method process for its termination. 2720Sstevel@tonic-gate */ 2730Sstevel@tonic-gate static void 2740Sstevel@tonic-gate unregister_method(method_el_t *me) 2750Sstevel@tonic-gate { 2760Sstevel@tonic-gate /* cancel any timer associated with the method */ 2770Sstevel@tonic-gate if (me->inst->timer_id != -1) 2780Sstevel@tonic-gate cancel_inst_timer(me->inst); 2790Sstevel@tonic-gate 2800Sstevel@tonic-gate /* stop polling on the psinfo file fd */ 2810Sstevel@tonic-gate clear_pollfd(me->fd); 2820Sstevel@tonic-gate (void) close(me->fd); 2830Sstevel@tonic-gate 2840Sstevel@tonic-gate /* remove method record from list */ 2850Sstevel@tonic-gate uu_list_remove(method_list, me); 2860Sstevel@tonic-gate 287*9272SRenaud.Manus@Sun.COM if (me->proto_name != NULL) 288*9272SRenaud.Manus@Sun.COM free(me->proto_name); 2890Sstevel@tonic-gate free(me); 2900Sstevel@tonic-gate } 2910Sstevel@tonic-gate 2920Sstevel@tonic-gate /* 2930Sstevel@tonic-gate * Unregister all methods associated with instance 'inst'. 2940Sstevel@tonic-gate */ 2950Sstevel@tonic-gate void 2960Sstevel@tonic-gate unregister_instance_methods(const instance_t *inst) 2970Sstevel@tonic-gate { 2980Sstevel@tonic-gate method_el_t *me = uu_list_first(method_list); 2990Sstevel@tonic-gate 3000Sstevel@tonic-gate while (me != NULL) { 3010Sstevel@tonic-gate if (me->inst == inst) { 3020Sstevel@tonic-gate method_el_t *tmp = me; 3030Sstevel@tonic-gate 3040Sstevel@tonic-gate me = uu_list_next(method_list, me); 3050Sstevel@tonic-gate unregister_method(tmp); 3060Sstevel@tonic-gate } else { 3070Sstevel@tonic-gate me = uu_list_next(method_list, me); 3080Sstevel@tonic-gate } 3090Sstevel@tonic-gate } 3100Sstevel@tonic-gate } 3110Sstevel@tonic-gate 3120Sstevel@tonic-gate /* 3130Sstevel@tonic-gate * Process any terminated methods. For each method determined to have 3140Sstevel@tonic-gate * terminated, the function determines its return value and calls the 3150Sstevel@tonic-gate * appropriate handling function, depending on the type of the method. 3160Sstevel@tonic-gate */ 3170Sstevel@tonic-gate void 3180Sstevel@tonic-gate process_terminated_methods(void) 3190Sstevel@tonic-gate { 3200Sstevel@tonic-gate method_el_t *me = uu_list_first(method_list); 3210Sstevel@tonic-gate 3220Sstevel@tonic-gate while (me != NULL) { 3230Sstevel@tonic-gate struct pollfd *pfd; 3240Sstevel@tonic-gate pid_t pid; 3250Sstevel@tonic-gate int status; 3260Sstevel@tonic-gate int ret; 3270Sstevel@tonic-gate method_el_t *tmp; 3280Sstevel@tonic-gate 3290Sstevel@tonic-gate pfd = find_pollfd(me->fd); 3300Sstevel@tonic-gate 3310Sstevel@tonic-gate /* 3320Sstevel@tonic-gate * We expect to get a POLLHUP back on the fd of the process's 3330Sstevel@tonic-gate * open psinfo file from /proc when the method terminates. 3340Sstevel@tonic-gate * A POLLERR could(?) mask a POLLHUP, so handle this 3350Sstevel@tonic-gate * also. 3360Sstevel@tonic-gate */ 3370Sstevel@tonic-gate if ((pfd->revents & (POLLHUP|POLLERR)) == 0) { 3380Sstevel@tonic-gate me = uu_list_next(method_list, me); 3390Sstevel@tonic-gate continue; 3400Sstevel@tonic-gate } 3410Sstevel@tonic-gate 3420Sstevel@tonic-gate /* get the method's exit code (no need to loop for EINTR) */ 3430Sstevel@tonic-gate pid = waitpid(me->pid, &status, WNOHANG); 3440Sstevel@tonic-gate 3450Sstevel@tonic-gate switch (pid) { 3460Sstevel@tonic-gate case 0: /* child still around */ 3470Sstevel@tonic-gate /* 3480Sstevel@tonic-gate * Either poll() is sending us invalid POLLHUP events 3490Sstevel@tonic-gate * or is flagging a POLLERR on the fd. Neither should 3500Sstevel@tonic-gate * happen, but in the event they do, ignore this fd 3510Sstevel@tonic-gate * this time around and wait out the termination 3520Sstevel@tonic-gate * of its associated method. This may result in 3530Sstevel@tonic-gate * inetd swiftly looping in event_loop(), but means 3540Sstevel@tonic-gate * we don't miss the termination of a method. 3550Sstevel@tonic-gate */ 3560Sstevel@tonic-gate me = uu_list_next(method_list, me); 3570Sstevel@tonic-gate continue; 3580Sstevel@tonic-gate 3590Sstevel@tonic-gate case -1: /* non-existent child */ 3600Sstevel@tonic-gate assert(errno == ECHILD); 3610Sstevel@tonic-gate /* 3620Sstevel@tonic-gate * the method must not be owned by inetd due to it 3630Sstevel@tonic-gate * persisting over an inetd restart. Let's assume the 3640Sstevel@tonic-gate * best, that it was successful. 3650Sstevel@tonic-gate */ 3660Sstevel@tonic-gate ret = IMRET_SUCCESS; 3670Sstevel@tonic-gate break; 3680Sstevel@tonic-gate 3690Sstevel@tonic-gate default: /* child terminated */ 3700Sstevel@tonic-gate if (WIFEXITED(status)) { 3710Sstevel@tonic-gate ret = WEXITSTATUS(status); 3727632SNick.Todd@Sun.COM debug_msg("process %ld of instance %s returned " 3730Sstevel@tonic-gate "%d", pid, me->inst->fmri, ret); 3740Sstevel@tonic-gate } else if (WIFSIGNALED(status)) { 3750Sstevel@tonic-gate /* 3760Sstevel@tonic-gate * Terminated by signal. This may be due 3770Sstevel@tonic-gate * to a kill that we sent from a disable or 3780Sstevel@tonic-gate * offline event. We flag it as a failure, but 3790Sstevel@tonic-gate * this flagged failure will only be processed 3800Sstevel@tonic-gate * in the case of non-start methods, or when 3810Sstevel@tonic-gate * the instance is still enabled. 3820Sstevel@tonic-gate */ 3837632SNick.Todd@Sun.COM debug_msg("process %ld of instance %s exited " 3840Sstevel@tonic-gate "due to signal %d", pid, me->inst->fmri, 3850Sstevel@tonic-gate WTERMSIG(status)); 3860Sstevel@tonic-gate ret = IMRET_FAILURE; 3870Sstevel@tonic-gate } else { 3880Sstevel@tonic-gate /* 3890Sstevel@tonic-gate * Can we actually get here? Don't think so. 3900Sstevel@tonic-gate * Treat it as a failure, anyway. 3910Sstevel@tonic-gate */ 3920Sstevel@tonic-gate debug_msg("waitpid() for %s method of " 3930Sstevel@tonic-gate "instance %s returned %d", 3940Sstevel@tonic-gate methods[me->method].name, me->inst->fmri, 3950Sstevel@tonic-gate status); 3960Sstevel@tonic-gate ret = IMRET_FAILURE; 3970Sstevel@tonic-gate } 3980Sstevel@tonic-gate } 3990Sstevel@tonic-gate 4000Sstevel@tonic-gate remove_method_ids(me->inst, me->pid, me->cid, me->method); 4010Sstevel@tonic-gate 4020Sstevel@tonic-gate /* continue state transition processing of the instance */ 4030Sstevel@tonic-gate if (me->method != IM_START) { 4040Sstevel@tonic-gate process_non_start_term(me->inst, ret); 4050Sstevel@tonic-gate } else { 406*9272SRenaud.Manus@Sun.COM process_start_term(me->inst, me->proto_name); 4070Sstevel@tonic-gate } 4080Sstevel@tonic-gate 4090Sstevel@tonic-gate if (me->cid != -1) 4100Sstevel@tonic-gate (void) abandon_contract(me->cid); 4110Sstevel@tonic-gate 4120Sstevel@tonic-gate tmp = me; 4130Sstevel@tonic-gate me = uu_list_next(method_list, me); 4140Sstevel@tonic-gate unregister_method(tmp); 4150Sstevel@tonic-gate } 4160Sstevel@tonic-gate } 417