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 /*
226435Sgm209912  * Copyright 2008 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 */
540Sstevel@tonic-gate 	uu_list_node_t		link;
550Sstevel@tonic-gate } method_el_t;
560Sstevel@tonic-gate 
570Sstevel@tonic-gate 
580Sstevel@tonic-gate static void unregister_method(method_el_t *);
590Sstevel@tonic-gate 
600Sstevel@tonic-gate 
610Sstevel@tonic-gate /* list of currently executing method processes */
620Sstevel@tonic-gate static uu_list_pool_t		*method_pool = NULL;
630Sstevel@tonic-gate static uu_list_t		*method_list = NULL;
640Sstevel@tonic-gate 
650Sstevel@tonic-gate /*
660Sstevel@tonic-gate  * File limit saved during initialization before modification, so that it can
670Sstevel@tonic-gate  * be reverted back to for inetd's exec'd methods.
680Sstevel@tonic-gate  */
690Sstevel@tonic-gate static struct rlimit		saved_file_limit;
700Sstevel@tonic-gate 
710Sstevel@tonic-gate /*
720Sstevel@tonic-gate  * Setup structures used for method termination monitoring.
730Sstevel@tonic-gate  * Returns -1 if an allocation failure occurred, else 0.
740Sstevel@tonic-gate  */
750Sstevel@tonic-gate int
760Sstevel@tonic-gate method_init(void)
770Sstevel@tonic-gate {
780Sstevel@tonic-gate 	struct rlimit rl;
790Sstevel@tonic-gate 
800Sstevel@tonic-gate 	/*
810Sstevel@tonic-gate 	 * Save aside the old file limit and impose one large enough to support
820Sstevel@tonic-gate 	 * all the /proc file handles we could have open.
830Sstevel@tonic-gate 	 */
840Sstevel@tonic-gate 
850Sstevel@tonic-gate 	(void) getrlimit(RLIMIT_NOFILE, &saved_file_limit);
860Sstevel@tonic-gate 
870Sstevel@tonic-gate 	rl.rlim_cur = rl.rlim_max = INETD_NOFILE_LIMIT;
880Sstevel@tonic-gate 	if (setrlimit(RLIMIT_NOFILE, &rl) == -1) {
890Sstevel@tonic-gate 		error_msg("Failed to set file limit: %s", strerror(errno));
900Sstevel@tonic-gate 		return (-1);
910Sstevel@tonic-gate 	}
920Sstevel@tonic-gate 
930Sstevel@tonic-gate 	if ((method_pool = uu_list_pool_create("method_pool",
940Sstevel@tonic-gate 	    sizeof (method_el_t), offsetof(method_el_t, link), NULL,
950Sstevel@tonic-gate 	    UU_LIST_POOL_DEBUG)) == NULL) {
960Sstevel@tonic-gate 		error_msg("%s: %s", gettext("Failed to create method pool"),
970Sstevel@tonic-gate 		    uu_strerror(uu_error()));
980Sstevel@tonic-gate 		return (-1);
990Sstevel@tonic-gate 	}
1000Sstevel@tonic-gate 
1010Sstevel@tonic-gate 	if ((method_list = uu_list_create(method_pool, NULL, 0)) == NULL) {
1020Sstevel@tonic-gate 		error_msg("%s: %s",
1030Sstevel@tonic-gate 		    gettext("Failed to create method list"),
1040Sstevel@tonic-gate 		    uu_strerror(uu_error()));
1050Sstevel@tonic-gate 		/* let method_fini() clean-up */
1060Sstevel@tonic-gate 		return (-1);
1070Sstevel@tonic-gate 	}
1080Sstevel@tonic-gate 
1090Sstevel@tonic-gate 	return (0);
1100Sstevel@tonic-gate }
1110Sstevel@tonic-gate 
1120Sstevel@tonic-gate /*
1130Sstevel@tonic-gate  * Tear-down structures created in method_init().
1140Sstevel@tonic-gate  */
1150Sstevel@tonic-gate void
1160Sstevel@tonic-gate method_fini(void)
1170Sstevel@tonic-gate {
1180Sstevel@tonic-gate 	if (method_list != NULL) {
1190Sstevel@tonic-gate 		method_el_t *me;
1200Sstevel@tonic-gate 
1210Sstevel@tonic-gate 		while ((me = uu_list_first(method_list)) != NULL)
1220Sstevel@tonic-gate 			unregister_method(me);
1230Sstevel@tonic-gate 
1240Sstevel@tonic-gate 		(void) uu_list_destroy(method_list);
1250Sstevel@tonic-gate 		method_list = NULL;
1260Sstevel@tonic-gate 	}
1270Sstevel@tonic-gate 	if (method_pool != NULL) {
1280Sstevel@tonic-gate 		(void) uu_list_pool_destroy(method_pool);
1290Sstevel@tonic-gate 		method_pool = NULL;
1300Sstevel@tonic-gate 	}
1310Sstevel@tonic-gate 
1320Sstevel@tonic-gate 	/* revert file limit */
1330Sstevel@tonic-gate 	method_preexec();
1340Sstevel@tonic-gate }
1350Sstevel@tonic-gate 
1360Sstevel@tonic-gate /*
1370Sstevel@tonic-gate  * Revert file limit back to pre-initialization one. This shouldn't fail as
1380Sstevel@tonic-gate  * long as its called *after* descriptor cleanup.
1390Sstevel@tonic-gate  */
1400Sstevel@tonic-gate void
1410Sstevel@tonic-gate method_preexec(void)
1420Sstevel@tonic-gate {
1430Sstevel@tonic-gate 	(void) setrlimit(RLIMIT_NOFILE, &saved_file_limit);
1440Sstevel@tonic-gate }
1450Sstevel@tonic-gate 
1460Sstevel@tonic-gate 
1470Sstevel@tonic-gate /*
1480Sstevel@tonic-gate  * Callback function that handles the timeout of an instance's method.
1490Sstevel@tonic-gate  * 'arg' points at the method_el_t representing the method.
1500Sstevel@tonic-gate  */
1510Sstevel@tonic-gate /* ARGSUSED0 */
1520Sstevel@tonic-gate static void
1530Sstevel@tonic-gate method_timeout(iu_tq_t *tq, void *arg)
1540Sstevel@tonic-gate {
1550Sstevel@tonic-gate 	method_el_t *mp = arg;
1560Sstevel@tonic-gate 
1570Sstevel@tonic-gate 	error_msg(gettext("The %s method of instance %s timed-out"),
1580Sstevel@tonic-gate 	    methods[mp->method].name, mp->inst->fmri);
1590Sstevel@tonic-gate 
1600Sstevel@tonic-gate 	mp->inst->timer_id = -1;
1610Sstevel@tonic-gate 
1620Sstevel@tonic-gate 	if (mp->method == IM_START) {
1630Sstevel@tonic-gate 		process_start_term(mp->inst);
1640Sstevel@tonic-gate 	} else {
1650Sstevel@tonic-gate 		process_non_start_term(mp->inst, IMRET_FAILURE);
1660Sstevel@tonic-gate 	}
1670Sstevel@tonic-gate 
1680Sstevel@tonic-gate 	unregister_method(mp);
1690Sstevel@tonic-gate }
1700Sstevel@tonic-gate 
1710Sstevel@tonic-gate /*
1720Sstevel@tonic-gate  * Registers the attributes of a running method passed as arguments so that
1730Sstevel@tonic-gate  * the method's termination is noticed and any further processing of the
1740Sstevel@tonic-gate  * associated instance is carried out. The function also sets up any
1750Sstevel@tonic-gate  * necessary timers so we can detect hung methods.
1760Sstevel@tonic-gate  * Returns -1 if either it failed to open the /proc psinfo file which is used
1770Sstevel@tonic-gate  * to monitor the method process, it failed to setup a required timer or
1780Sstevel@tonic-gate  * memory allocation failed; else 0.
1790Sstevel@tonic-gate  */
1800Sstevel@tonic-gate int
1810Sstevel@tonic-gate register_method(instance_t *ins, pid_t pid, ctid_t cid, instance_method_t mthd)
1820Sstevel@tonic-gate {
1830Sstevel@tonic-gate 	char		path[MAXPATHLEN];
1840Sstevel@tonic-gate 	int		fd;
1850Sstevel@tonic-gate 	method_el_t	*me;
1860Sstevel@tonic-gate 
1870Sstevel@tonic-gate 	/* open /proc psinfo file of process to listen for POLLHUP events on */
1880Sstevel@tonic-gate 	(void) snprintf(path, sizeof (path), "/proc/%u/psinfo", pid);
1890Sstevel@tonic-gate 	for (;;) {
1900Sstevel@tonic-gate 		if ((fd = open(path, O_RDONLY)) >= 0) {
1910Sstevel@tonic-gate 			break;
1920Sstevel@tonic-gate 		} else if (errno != EINTR) {
1930Sstevel@tonic-gate 			/*
1940Sstevel@tonic-gate 			 * Don't output an error for ENOENT; we get this
1950Sstevel@tonic-gate 			 * if a method has gone away whilst we were stopped,
1960Sstevel@tonic-gate 			 * and we're now trying to re-listen for it.
1970Sstevel@tonic-gate 			 */
1980Sstevel@tonic-gate 			if (errno != ENOENT) {
1990Sstevel@tonic-gate 				error_msg(gettext("Failed to open %s: %s"),
2000Sstevel@tonic-gate 				    path, strerror(errno));
2010Sstevel@tonic-gate 			}
2020Sstevel@tonic-gate 			return (-1);
2030Sstevel@tonic-gate 		}
2040Sstevel@tonic-gate 	}
2050Sstevel@tonic-gate 
2060Sstevel@tonic-gate 	/* add method record to in-memory list */
2070Sstevel@tonic-gate 	if ((me = calloc(1, sizeof (method_el_t))) == NULL) {
2080Sstevel@tonic-gate 		error_msg(strerror(errno));
2090Sstevel@tonic-gate 		(void) close(fd);
2100Sstevel@tonic-gate 		return (-1);
2110Sstevel@tonic-gate 	}
2120Sstevel@tonic-gate 	me->fd = fd;
2130Sstevel@tonic-gate 	me->inst = (instance_t *)ins;
2140Sstevel@tonic-gate 	me->method = mthd;
2150Sstevel@tonic-gate 	me->pid = pid;
2160Sstevel@tonic-gate 	me->cid = cid;
2170Sstevel@tonic-gate 
2180Sstevel@tonic-gate 	/* register a timeout for the method, if required */
2190Sstevel@tonic-gate 	if (mthd != IM_START) {
2200Sstevel@tonic-gate 		method_info_t *mi = ins->config->methods[mthd];
2210Sstevel@tonic-gate 
2220Sstevel@tonic-gate 		if (mi->timeout > 0) {
2230Sstevel@tonic-gate 			assert(ins->timer_id == -1);
2240Sstevel@tonic-gate 			ins->timer_id = iu_schedule_timer(timer_queue,
2250Sstevel@tonic-gate 			    mi->timeout, method_timeout, me);
2260Sstevel@tonic-gate 			if (ins->timer_id == -1) {
2270Sstevel@tonic-gate 				error_msg(gettext(
2280Sstevel@tonic-gate 				    "Failed to schedule method timeout"));
2290Sstevel@tonic-gate 				free(me);
2300Sstevel@tonic-gate 				(void) close(fd);
2310Sstevel@tonic-gate 				return (-1);
2320Sstevel@tonic-gate 			}
2330Sstevel@tonic-gate 		}
2340Sstevel@tonic-gate 	}
2350Sstevel@tonic-gate 
2360Sstevel@tonic-gate 	/*
2370Sstevel@tonic-gate 	 * Add fd of psinfo file to poll set, but pass 0 for events to
2380Sstevel@tonic-gate 	 * poll for, so we should only get a POLLHUP event on the fd.
2390Sstevel@tonic-gate 	 */
2400Sstevel@tonic-gate 	if (set_pollfd(fd, 0) == -1) {
2410Sstevel@tonic-gate 		cancel_inst_timer(ins);
2420Sstevel@tonic-gate 		free(me);
2430Sstevel@tonic-gate 		(void) close(fd);
2440Sstevel@tonic-gate 		return (-1);
2450Sstevel@tonic-gate 	}
2460Sstevel@tonic-gate 
2470Sstevel@tonic-gate 	uu_list_node_init(me, &me->link, method_pool);
2480Sstevel@tonic-gate 	(void) uu_list_insert_after(method_list, NULL, me);
2490Sstevel@tonic-gate 
2500Sstevel@tonic-gate 	return (0);
2510Sstevel@tonic-gate }
2520Sstevel@tonic-gate 
2530Sstevel@tonic-gate /*
2540Sstevel@tonic-gate  * A counterpart to register_method(), this function stops the monitoring of a
2550Sstevel@tonic-gate  * method process for its termination.
2560Sstevel@tonic-gate  */
2570Sstevel@tonic-gate static void
2580Sstevel@tonic-gate unregister_method(method_el_t *me)
2590Sstevel@tonic-gate {
2600Sstevel@tonic-gate 	/* cancel any timer associated with the method */
2610Sstevel@tonic-gate 	if (me->inst->timer_id != -1)
2620Sstevel@tonic-gate 		cancel_inst_timer(me->inst);
2630Sstevel@tonic-gate 
2640Sstevel@tonic-gate 	/* stop polling on the psinfo file fd */
2650Sstevel@tonic-gate 	clear_pollfd(me->fd);
2660Sstevel@tonic-gate 	(void) close(me->fd);
2670Sstevel@tonic-gate 
2680Sstevel@tonic-gate 	/* remove method record from list */
2690Sstevel@tonic-gate 	uu_list_remove(method_list, me);
2700Sstevel@tonic-gate 
2710Sstevel@tonic-gate 	free(me);
2720Sstevel@tonic-gate }
2730Sstevel@tonic-gate 
2740Sstevel@tonic-gate /*
2750Sstevel@tonic-gate  * Unregister all methods associated with instance 'inst'.
2760Sstevel@tonic-gate  */
2770Sstevel@tonic-gate void
2780Sstevel@tonic-gate unregister_instance_methods(const instance_t *inst)
2790Sstevel@tonic-gate {
2800Sstevel@tonic-gate 	method_el_t *me = uu_list_first(method_list);
2810Sstevel@tonic-gate 
2820Sstevel@tonic-gate 	while (me != NULL) {
2830Sstevel@tonic-gate 		if (me->inst == inst) {
2840Sstevel@tonic-gate 			method_el_t *tmp = me;
2850Sstevel@tonic-gate 
2860Sstevel@tonic-gate 			me = uu_list_next(method_list, me);
2870Sstevel@tonic-gate 			unregister_method(tmp);
2880Sstevel@tonic-gate 		} else  {
2890Sstevel@tonic-gate 			me = uu_list_next(method_list, me);
2900Sstevel@tonic-gate 		}
2910Sstevel@tonic-gate 	}
2920Sstevel@tonic-gate }
2930Sstevel@tonic-gate 
2940Sstevel@tonic-gate /*
2950Sstevel@tonic-gate  * Process any terminated methods. For each method determined to have
2960Sstevel@tonic-gate  * terminated, the function determines its return value and calls the
2970Sstevel@tonic-gate  * appropriate handling function, depending on the type of the method.
2980Sstevel@tonic-gate  */
2990Sstevel@tonic-gate void
3000Sstevel@tonic-gate process_terminated_methods(void)
3010Sstevel@tonic-gate {
3020Sstevel@tonic-gate 	method_el_t	*me = uu_list_first(method_list);
3030Sstevel@tonic-gate 
3040Sstevel@tonic-gate 	while (me != NULL) {
3050Sstevel@tonic-gate 		struct pollfd	*pfd;
3060Sstevel@tonic-gate 		pid_t		pid;
3070Sstevel@tonic-gate 		int		status;
3080Sstevel@tonic-gate 		int		ret;
3090Sstevel@tonic-gate 		method_el_t	*tmp;
3100Sstevel@tonic-gate 
3110Sstevel@tonic-gate 		pfd = find_pollfd(me->fd);
3120Sstevel@tonic-gate 
3130Sstevel@tonic-gate 		/*
3140Sstevel@tonic-gate 		 * We expect to get a POLLHUP back on the fd of the process's
3150Sstevel@tonic-gate 		 * open psinfo file from /proc when the method terminates.
3160Sstevel@tonic-gate 		 * A POLLERR could(?) mask a POLLHUP, so handle this
3170Sstevel@tonic-gate 		 * also.
3180Sstevel@tonic-gate 		 */
3190Sstevel@tonic-gate 		if ((pfd->revents & (POLLHUP|POLLERR)) == 0) {
3200Sstevel@tonic-gate 			me = uu_list_next(method_list, me);
3210Sstevel@tonic-gate 			continue;
3220Sstevel@tonic-gate 		}
3230Sstevel@tonic-gate 
3240Sstevel@tonic-gate 		/* get the method's exit code (no need to loop for EINTR) */
3250Sstevel@tonic-gate 		pid = waitpid(me->pid, &status, WNOHANG);
3260Sstevel@tonic-gate 
3270Sstevel@tonic-gate 		switch (pid) {
3280Sstevel@tonic-gate 		case 0:					/* child still around */
3290Sstevel@tonic-gate 			/*
3300Sstevel@tonic-gate 			 * Either poll() is sending us invalid POLLHUP events
3310Sstevel@tonic-gate 			 * or is flagging a POLLERR on the fd. Neither should
3320Sstevel@tonic-gate 			 * happen, but in the event they do, ignore this fd
3330Sstevel@tonic-gate 			 * this time around and wait out the termination
3340Sstevel@tonic-gate 			 * of its associated method. This may result in
3350Sstevel@tonic-gate 			 * inetd swiftly looping in event_loop(), but means
3360Sstevel@tonic-gate 			 * we don't miss the termination of a method.
3370Sstevel@tonic-gate 			 */
3380Sstevel@tonic-gate 			me = uu_list_next(method_list, me);
3390Sstevel@tonic-gate 			continue;
3400Sstevel@tonic-gate 
3410Sstevel@tonic-gate 		case -1:				/* non-existent child */
3420Sstevel@tonic-gate 			assert(errno == ECHILD);
3430Sstevel@tonic-gate 			/*
3440Sstevel@tonic-gate 			 * the method must not be owned by inetd due to it
3450Sstevel@tonic-gate 			 * persisting over an inetd restart. Let's assume the
3460Sstevel@tonic-gate 			 * best, that it was successful.
3470Sstevel@tonic-gate 			 */
3480Sstevel@tonic-gate 			ret = IMRET_SUCCESS;
3490Sstevel@tonic-gate 			break;
3500Sstevel@tonic-gate 
3510Sstevel@tonic-gate 		default:				/* child terminated */
3520Sstevel@tonic-gate 			if (WIFEXITED(status)) {
3530Sstevel@tonic-gate 				ret = WEXITSTATUS(status);
354*7632SNick.Todd@Sun.COM 				debug_msg("process %ld of instance %s returned "
3550Sstevel@tonic-gate 				    "%d", pid, me->inst->fmri, ret);
3560Sstevel@tonic-gate 			} else if (WIFSIGNALED(status)) {
3570Sstevel@tonic-gate 				/*
3580Sstevel@tonic-gate 				 * Terminated by signal.  This may be due
3590Sstevel@tonic-gate 				 * to a kill that we sent from a disable or
3600Sstevel@tonic-gate 				 * offline event. We flag it as a failure, but
3610Sstevel@tonic-gate 				 * this flagged failure will only be processed
3620Sstevel@tonic-gate 				 * in the case of non-start methods, or when
3630Sstevel@tonic-gate 				 * the instance is still enabled.
3640Sstevel@tonic-gate 				 */
365*7632SNick.Todd@Sun.COM 				debug_msg("process %ld of instance %s exited "
3660Sstevel@tonic-gate 				    "due to signal %d", pid, me->inst->fmri,
3670Sstevel@tonic-gate 				    WTERMSIG(status));
3680Sstevel@tonic-gate 				ret = IMRET_FAILURE;
3690Sstevel@tonic-gate 			} else {
3700Sstevel@tonic-gate 				/*
3710Sstevel@tonic-gate 				 * Can we actually get here?  Don't think so.
3720Sstevel@tonic-gate 				 * Treat it as a failure, anyway.
3730Sstevel@tonic-gate 				 */
3740Sstevel@tonic-gate 				debug_msg("waitpid() for %s method of "
3750Sstevel@tonic-gate 				    "instance %s returned %d",
3760Sstevel@tonic-gate 				    methods[me->method].name, me->inst->fmri,
3770Sstevel@tonic-gate 				    status);
3780Sstevel@tonic-gate 				ret = IMRET_FAILURE;
3790Sstevel@tonic-gate 			}
3800Sstevel@tonic-gate 		}
3810Sstevel@tonic-gate 
3820Sstevel@tonic-gate 		remove_method_ids(me->inst, me->pid, me->cid, me->method);
3830Sstevel@tonic-gate 
3840Sstevel@tonic-gate 		/* continue state transition processing of the instance */
3850Sstevel@tonic-gate 		if (me->method != IM_START) {
3860Sstevel@tonic-gate 			process_non_start_term(me->inst, ret);
3870Sstevel@tonic-gate 		} else {
3880Sstevel@tonic-gate 			process_start_term(me->inst);
3890Sstevel@tonic-gate 		}
3900Sstevel@tonic-gate 
3910Sstevel@tonic-gate 		if (me->cid != -1)
3920Sstevel@tonic-gate 			(void) abandon_contract(me->cid);
3930Sstevel@tonic-gate 
3940Sstevel@tonic-gate 		tmp = me;
3950Sstevel@tonic-gate 		me = uu_list_next(method_list, me);
3960Sstevel@tonic-gate 		unregister_method(tmp);
3970Sstevel@tonic-gate 	}
3980Sstevel@tonic-gate }
399