1*0Sstevel@tonic-gate /* 2*0Sstevel@tonic-gate * CDDL HEADER START 3*0Sstevel@tonic-gate * 4*0Sstevel@tonic-gate * The contents of this file are subject to the terms of the 5*0Sstevel@tonic-gate * Common Development and Distribution License, Version 1.0 only 6*0Sstevel@tonic-gate * (the "License"). You may not use this file except in compliance 7*0Sstevel@tonic-gate * with the License. 8*0Sstevel@tonic-gate * 9*0Sstevel@tonic-gate * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 10*0Sstevel@tonic-gate * or http://www.opensolaris.org/os/licensing. 11*0Sstevel@tonic-gate * See the License for the specific language governing permissions 12*0Sstevel@tonic-gate * and limitations under the License. 13*0Sstevel@tonic-gate * 14*0Sstevel@tonic-gate * When distributing Covered Code, include this CDDL HEADER in each 15*0Sstevel@tonic-gate * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 16*0Sstevel@tonic-gate * If applicable, add the following below this CDDL HEADER, with the 17*0Sstevel@tonic-gate * fields enclosed by brackets "[]" replaced with your own identifying 18*0Sstevel@tonic-gate * information: Portions Copyright [yyyy] [name of copyright owner] 19*0Sstevel@tonic-gate * 20*0Sstevel@tonic-gate * CDDL HEADER END 21*0Sstevel@tonic-gate */ 22*0Sstevel@tonic-gate /* 23*0Sstevel@tonic-gate * Copyright 2004 Sun Microsystems, Inc. All rights reserved. 24*0Sstevel@tonic-gate * Use is subject to license terms. 25*0Sstevel@tonic-gate */ 26*0Sstevel@tonic-gate 27*0Sstevel@tonic-gate #pragma ident "%Z%%M% %I% %E% SMI" 28*0Sstevel@tonic-gate 29*0Sstevel@tonic-gate /* 30*0Sstevel@tonic-gate * method.c - method execution functions 31*0Sstevel@tonic-gate * 32*0Sstevel@tonic-gate * This file contains the routines needed to run a method: a fork(2)-exec(2) 33*0Sstevel@tonic-gate * invocation monitored using either the contract filesystem or waitpid(2). 34*0Sstevel@tonic-gate * (Plain fork1(2) support is provided in fork.c.) 35*0Sstevel@tonic-gate * 36*0Sstevel@tonic-gate * Contract Transfer 37*0Sstevel@tonic-gate * When we restart a service, we want to transfer any contracts that the old 38*0Sstevel@tonic-gate * service's contract inherited. This means that (a) we must not abandon the 39*0Sstevel@tonic-gate * old contract when the service dies and (b) we must write the id of the old 40*0Sstevel@tonic-gate * contract into the terms of the new contract. There should be limits to 41*0Sstevel@tonic-gate * (a), though, since we don't want to keep the contract around forever. To 42*0Sstevel@tonic-gate * this end we'll say that services in the offline state may have a contract 43*0Sstevel@tonic-gate * to be transfered and services in the disabled or maintenance states cannot. 44*0Sstevel@tonic-gate * This means that when a service transitions from online (or degraded) to 45*0Sstevel@tonic-gate * offline, the contract should be preserved, and when the service transitions 46*0Sstevel@tonic-gate * from offline to online (i.e., the start method), we'll transfer inherited 47*0Sstevel@tonic-gate * contracts. 48*0Sstevel@tonic-gate */ 49*0Sstevel@tonic-gate 50*0Sstevel@tonic-gate #include <sys/contract/process.h> 51*0Sstevel@tonic-gate #include <sys/ctfs.h> 52*0Sstevel@tonic-gate #include <sys/stat.h> 53*0Sstevel@tonic-gate #include <sys/time.h> 54*0Sstevel@tonic-gate #include <sys/types.h> 55*0Sstevel@tonic-gate #include <sys/uio.h> 56*0Sstevel@tonic-gate #include <sys/wait.h> 57*0Sstevel@tonic-gate #include <alloca.h> 58*0Sstevel@tonic-gate #include <assert.h> 59*0Sstevel@tonic-gate #include <errno.h> 60*0Sstevel@tonic-gate #include <fcntl.h> 61*0Sstevel@tonic-gate #include <libcontract.h> 62*0Sstevel@tonic-gate #include <libcontract_priv.h> 63*0Sstevel@tonic-gate #include <libgen.h> 64*0Sstevel@tonic-gate #include <librestart.h> 65*0Sstevel@tonic-gate #include <libscf.h> 66*0Sstevel@tonic-gate #include <limits.h> 67*0Sstevel@tonic-gate #include <port.h> 68*0Sstevel@tonic-gate #include <sac.h> 69*0Sstevel@tonic-gate #include <signal.h> 70*0Sstevel@tonic-gate #include <stdlib.h> 71*0Sstevel@tonic-gate #include <string.h> 72*0Sstevel@tonic-gate #include <strings.h> 73*0Sstevel@tonic-gate #include <unistd.h> 74*0Sstevel@tonic-gate 75*0Sstevel@tonic-gate #include "startd.h" 76*0Sstevel@tonic-gate 77*0Sstevel@tonic-gate #define SBIN_SH "/sbin/sh" 78*0Sstevel@tonic-gate 79*0Sstevel@tonic-gate /* 80*0Sstevel@tonic-gate * Mapping from restart_on method-type to contract events. Must correspond to 81*0Sstevel@tonic-gate * enum method_restart_t. 82*0Sstevel@tonic-gate */ 83*0Sstevel@tonic-gate static uint_t method_events[] = { 84*0Sstevel@tonic-gate /* METHOD_RESTART_ALL */ 85*0Sstevel@tonic-gate CT_PR_EV_HWERR | CT_PR_EV_SIGNAL | CT_PR_EV_CORE | CT_PR_EV_EMPTY, 86*0Sstevel@tonic-gate /* METHOD_RESTART_EXTERNAL_FAULT */ 87*0Sstevel@tonic-gate CT_PR_EV_HWERR | CT_PR_EV_SIGNAL, 88*0Sstevel@tonic-gate /* METHOD_RESTART_ANY_FAULT */ 89*0Sstevel@tonic-gate CT_PR_EV_HWERR | CT_PR_EV_SIGNAL | CT_PR_EV_CORE 90*0Sstevel@tonic-gate }; 91*0Sstevel@tonic-gate 92*0Sstevel@tonic-gate /* 93*0Sstevel@tonic-gate * method_record_start(restarter_inst_t *) 94*0Sstevel@tonic-gate * Record a service start for rate limiting. Place the current time 95*0Sstevel@tonic-gate * in the circular array of instance starts. 96*0Sstevel@tonic-gate */ 97*0Sstevel@tonic-gate static void 98*0Sstevel@tonic-gate method_record_start(restarter_inst_t *inst) 99*0Sstevel@tonic-gate { 100*0Sstevel@tonic-gate int index = inst->ri_start_index++ % RINST_START_TIMES; 101*0Sstevel@tonic-gate 102*0Sstevel@tonic-gate inst->ri_start_time[index] = gethrtime(); 103*0Sstevel@tonic-gate } 104*0Sstevel@tonic-gate 105*0Sstevel@tonic-gate /* 106*0Sstevel@tonic-gate * method_rate_critical(restarter_inst_t *) 107*0Sstevel@tonic-gate * Return true if the average start interval is less than the permitted 108*0Sstevel@tonic-gate * interval. Implicit success if insufficient measurements for an 109*0Sstevel@tonic-gate * average exist. 110*0Sstevel@tonic-gate */ 111*0Sstevel@tonic-gate static int 112*0Sstevel@tonic-gate method_rate_critical(restarter_inst_t *inst) 113*0Sstevel@tonic-gate { 114*0Sstevel@tonic-gate uint_t n = inst->ri_start_index; 115*0Sstevel@tonic-gate hrtime_t avg_ns = 0; 116*0Sstevel@tonic-gate 117*0Sstevel@tonic-gate if (inst->ri_start_index < RINST_START_TIMES) 118*0Sstevel@tonic-gate return (0); 119*0Sstevel@tonic-gate 120*0Sstevel@tonic-gate avg_ns = 121*0Sstevel@tonic-gate (inst->ri_start_time[(n - 1) % RINST_START_TIMES] - 122*0Sstevel@tonic-gate inst->ri_start_time[n % RINST_START_TIMES]) / 123*0Sstevel@tonic-gate (RINST_START_TIMES - 1); 124*0Sstevel@tonic-gate 125*0Sstevel@tonic-gate return (avg_ns < RINST_FAILURE_RATE_NS); 126*0Sstevel@tonic-gate } 127*0Sstevel@tonic-gate 128*0Sstevel@tonic-gate /* 129*0Sstevel@tonic-gate * int method_is_transient() 130*0Sstevel@tonic-gate * Determine if the method for the given instance is transient, 131*0Sstevel@tonic-gate * from a contract perspective. Return 1 if it is, and 0 if it isn't. 132*0Sstevel@tonic-gate */ 133*0Sstevel@tonic-gate static int 134*0Sstevel@tonic-gate method_is_transient(restarter_inst_t *inst, int type) 135*0Sstevel@tonic-gate { 136*0Sstevel@tonic-gate if (instance_is_transient_style(inst) || type != METHOD_START) 137*0Sstevel@tonic-gate return (1); 138*0Sstevel@tonic-gate else 139*0Sstevel@tonic-gate return (0); 140*0Sstevel@tonic-gate } 141*0Sstevel@tonic-gate 142*0Sstevel@tonic-gate /* 143*0Sstevel@tonic-gate * void method_store_contract() 144*0Sstevel@tonic-gate * Store the newly created contract id into local structures and 145*0Sstevel@tonic-gate * the repository. If the repository connection is broken it is rebound. 146*0Sstevel@tonic-gate */ 147*0Sstevel@tonic-gate static void 148*0Sstevel@tonic-gate method_store_contract(restarter_inst_t *inst, int type, ctid_t *cid) 149*0Sstevel@tonic-gate { 150*0Sstevel@tonic-gate int r; 151*0Sstevel@tonic-gate boolean_t primary; 152*0Sstevel@tonic-gate 153*0Sstevel@tonic-gate if (errno = contract_latest(cid)) 154*0Sstevel@tonic-gate uu_die("%s: Couldn't get new contract's id", inst->ri_i.i_fmri); 155*0Sstevel@tonic-gate 156*0Sstevel@tonic-gate primary = !method_is_transient(inst, type); 157*0Sstevel@tonic-gate 158*0Sstevel@tonic-gate if (!primary) { 159*0Sstevel@tonic-gate if (inst->ri_i.i_transient_ctid != 0) { 160*0Sstevel@tonic-gate log_framework(LOG_INFO, 161*0Sstevel@tonic-gate "%s: transient ctid expected to be 0 but " 162*0Sstevel@tonic-gate "was set to %ld\n", inst->ri_i.i_fmri, 163*0Sstevel@tonic-gate inst->ri_i.i_transient_ctid); 164*0Sstevel@tonic-gate } 165*0Sstevel@tonic-gate 166*0Sstevel@tonic-gate inst->ri_i.i_transient_ctid = *cid; 167*0Sstevel@tonic-gate } else { 168*0Sstevel@tonic-gate if (inst->ri_i.i_primary_ctid != 0) { 169*0Sstevel@tonic-gate /* 170*0Sstevel@tonic-gate * There was an old contract that we transferred. 171*0Sstevel@tonic-gate * Remove it. 172*0Sstevel@tonic-gate */ 173*0Sstevel@tonic-gate method_remove_contract(inst, B_TRUE, B_FALSE); 174*0Sstevel@tonic-gate } 175*0Sstevel@tonic-gate 176*0Sstevel@tonic-gate if (inst->ri_i.i_primary_ctid != 0) { 177*0Sstevel@tonic-gate log_framework(LOG_INFO, 178*0Sstevel@tonic-gate "%s: primary ctid expected to be 0 but " 179*0Sstevel@tonic-gate "was set to %ld\n", inst->ri_i.i_fmri, 180*0Sstevel@tonic-gate inst->ri_i.i_primary_ctid); 181*0Sstevel@tonic-gate } 182*0Sstevel@tonic-gate 183*0Sstevel@tonic-gate inst->ri_i.i_primary_ctid = *cid; 184*0Sstevel@tonic-gate inst->ri_i.i_primary_ctid_stopped = 0; 185*0Sstevel@tonic-gate 186*0Sstevel@tonic-gate contract_hash_store(*cid, inst->ri_id); 187*0Sstevel@tonic-gate } 188*0Sstevel@tonic-gate 189*0Sstevel@tonic-gate again: 190*0Sstevel@tonic-gate if (inst->ri_mi_deleted) 191*0Sstevel@tonic-gate return; 192*0Sstevel@tonic-gate 193*0Sstevel@tonic-gate r = restarter_store_contract(inst->ri_m_inst, *cid, primary ? 194*0Sstevel@tonic-gate RESTARTER_CONTRACT_PRIMARY : RESTARTER_CONTRACT_TRANSIENT); 195*0Sstevel@tonic-gate switch (r) { 196*0Sstevel@tonic-gate case 0: 197*0Sstevel@tonic-gate break; 198*0Sstevel@tonic-gate 199*0Sstevel@tonic-gate case ECANCELED: 200*0Sstevel@tonic-gate inst->ri_mi_deleted = B_TRUE; 201*0Sstevel@tonic-gate break; 202*0Sstevel@tonic-gate 203*0Sstevel@tonic-gate case ECONNABORTED: 204*0Sstevel@tonic-gate libscf_handle_rebind(scf_instance_handle(inst->ri_m_inst)); 205*0Sstevel@tonic-gate /* FALLTHROUGH */ 206*0Sstevel@tonic-gate 207*0Sstevel@tonic-gate case EBADF: 208*0Sstevel@tonic-gate libscf_reget_instance(inst); 209*0Sstevel@tonic-gate goto again; 210*0Sstevel@tonic-gate 211*0Sstevel@tonic-gate case ENOMEM: 212*0Sstevel@tonic-gate case EPERM: 213*0Sstevel@tonic-gate case EACCES: 214*0Sstevel@tonic-gate case EROFS: 215*0Sstevel@tonic-gate uu_die("%s: Couldn't store contract id %ld", 216*0Sstevel@tonic-gate inst->ri_i.i_fmri, *cid); 217*0Sstevel@tonic-gate /* NOTREACHED */ 218*0Sstevel@tonic-gate 219*0Sstevel@tonic-gate case EINVAL: 220*0Sstevel@tonic-gate default: 221*0Sstevel@tonic-gate bad_error("restarter_store_contract", r); 222*0Sstevel@tonic-gate } 223*0Sstevel@tonic-gate } 224*0Sstevel@tonic-gate 225*0Sstevel@tonic-gate /* 226*0Sstevel@tonic-gate * void method_remove_contract() 227*0Sstevel@tonic-gate * Remove any non-permanent contracts from internal structures and 228*0Sstevel@tonic-gate * the repository, then abandon them. 229*0Sstevel@tonic-gate * Returns 230*0Sstevel@tonic-gate * 0 - success 231*0Sstevel@tonic-gate * ECANCELED - inst was deleted from the repository 232*0Sstevel@tonic-gate * 233*0Sstevel@tonic-gate * If the repository connection was broken, it is rebound. 234*0Sstevel@tonic-gate */ 235*0Sstevel@tonic-gate void 236*0Sstevel@tonic-gate method_remove_contract(restarter_inst_t *inst, boolean_t primary, 237*0Sstevel@tonic-gate boolean_t abandon) 238*0Sstevel@tonic-gate { 239*0Sstevel@tonic-gate ctid_t * const ctidp = primary ? &inst->ri_i.i_primary_ctid : 240*0Sstevel@tonic-gate &inst->ri_i.i_transient_ctid; 241*0Sstevel@tonic-gate 242*0Sstevel@tonic-gate int r; 243*0Sstevel@tonic-gate 244*0Sstevel@tonic-gate assert(*ctidp != 0); 245*0Sstevel@tonic-gate 246*0Sstevel@tonic-gate log_framework(LOG_DEBUG, "Removing %s contract %lu for %s.\n", 247*0Sstevel@tonic-gate primary ? "primary" : "transient", *ctidp, inst->ri_i.i_fmri); 248*0Sstevel@tonic-gate 249*0Sstevel@tonic-gate if (abandon) 250*0Sstevel@tonic-gate contract_abandon(*ctidp); 251*0Sstevel@tonic-gate 252*0Sstevel@tonic-gate again: 253*0Sstevel@tonic-gate if (inst->ri_mi_deleted) { 254*0Sstevel@tonic-gate r = ECANCELED; 255*0Sstevel@tonic-gate goto out; 256*0Sstevel@tonic-gate } 257*0Sstevel@tonic-gate 258*0Sstevel@tonic-gate r = restarter_remove_contract(inst->ri_m_inst, *ctidp, primary ? 259*0Sstevel@tonic-gate RESTARTER_CONTRACT_PRIMARY : RESTARTER_CONTRACT_TRANSIENT); 260*0Sstevel@tonic-gate switch (r) { 261*0Sstevel@tonic-gate case 0: 262*0Sstevel@tonic-gate break; 263*0Sstevel@tonic-gate 264*0Sstevel@tonic-gate case ECANCELED: 265*0Sstevel@tonic-gate inst->ri_mi_deleted = B_TRUE; 266*0Sstevel@tonic-gate break; 267*0Sstevel@tonic-gate 268*0Sstevel@tonic-gate case ECONNABORTED: 269*0Sstevel@tonic-gate libscf_handle_rebind(scf_instance_handle(inst->ri_m_inst)); 270*0Sstevel@tonic-gate /* FALLTHROUGH */ 271*0Sstevel@tonic-gate 272*0Sstevel@tonic-gate case EBADF: 273*0Sstevel@tonic-gate libscf_reget_instance(inst); 274*0Sstevel@tonic-gate goto again; 275*0Sstevel@tonic-gate 276*0Sstevel@tonic-gate case ENOMEM: 277*0Sstevel@tonic-gate case EPERM: 278*0Sstevel@tonic-gate case EACCES: 279*0Sstevel@tonic-gate case EROFS: 280*0Sstevel@tonic-gate log_error(LOG_INFO, "%s: Couldn't remove contract id %ld: " 281*0Sstevel@tonic-gate "%s.\n", inst->ri_i.i_fmri, *ctidp, strerror(r)); 282*0Sstevel@tonic-gate break; 283*0Sstevel@tonic-gate 284*0Sstevel@tonic-gate case EINVAL: 285*0Sstevel@tonic-gate default: 286*0Sstevel@tonic-gate bad_error("restarter_remove_contract", r); 287*0Sstevel@tonic-gate } 288*0Sstevel@tonic-gate 289*0Sstevel@tonic-gate out: 290*0Sstevel@tonic-gate if (primary) 291*0Sstevel@tonic-gate contract_hash_remove(*ctidp); 292*0Sstevel@tonic-gate 293*0Sstevel@tonic-gate *ctidp = 0; 294*0Sstevel@tonic-gate } 295*0Sstevel@tonic-gate 296*0Sstevel@tonic-gate /* 297*0Sstevel@tonic-gate * int method_ready_contract(restarter_inst_t *, int, method_restart_t, int) 298*0Sstevel@tonic-gate * 299*0Sstevel@tonic-gate * Activate a contract template for the type method of inst. type, 300*0Sstevel@tonic-gate * restart_on, and cte_mask dictate the critical events term of the contract. 301*0Sstevel@tonic-gate * Returns 302*0Sstevel@tonic-gate * 0 - success 303*0Sstevel@tonic-gate * ECANCELED - inst has been deleted from the repository 304*0Sstevel@tonic-gate */ 305*0Sstevel@tonic-gate static int 306*0Sstevel@tonic-gate method_ready_contract(restarter_inst_t *inst, int type, 307*0Sstevel@tonic-gate method_restart_t restart_on, uint_t cte_mask) 308*0Sstevel@tonic-gate { 309*0Sstevel@tonic-gate int tmpl, err, istrans, iswait, ret; 310*0Sstevel@tonic-gate uint_t cevents, fevents; 311*0Sstevel@tonic-gate 312*0Sstevel@tonic-gate /* 313*0Sstevel@tonic-gate * Correctly supporting wait-style services is tricky without 314*0Sstevel@tonic-gate * rearchitecting startd to cope with multiple event sources 315*0Sstevel@tonic-gate * simultaneously trying to stop an instance. Until a better 316*0Sstevel@tonic-gate * solution is implemented, we avoid this problem for 317*0Sstevel@tonic-gate * wait-style services by making contract events fatal and 318*0Sstevel@tonic-gate * letting the wait code alone handle stopping the service. 319*0Sstevel@tonic-gate */ 320*0Sstevel@tonic-gate iswait = instance_is_wait_style(inst); 321*0Sstevel@tonic-gate istrans = method_is_transient(inst, type); 322*0Sstevel@tonic-gate 323*0Sstevel@tonic-gate tmpl = open64(CTFS_ROOT "/process/template", O_RDWR); 324*0Sstevel@tonic-gate if (tmpl == -1) 325*0Sstevel@tonic-gate uu_die("Could not create contract template"); 326*0Sstevel@tonic-gate 327*0Sstevel@tonic-gate /* 328*0Sstevel@tonic-gate * We assume non-login processes are unlikely to create 329*0Sstevel@tonic-gate * multiple process groups, and set CT_PR_PGRPONLY for all 330*0Sstevel@tonic-gate * wait-style services' contracts. 331*0Sstevel@tonic-gate */ 332*0Sstevel@tonic-gate err = ct_pr_tmpl_set_param(tmpl, CT_PR_INHERIT | CT_PR_REGENT | 333*0Sstevel@tonic-gate (iswait ? CT_PR_PGRPONLY : 0)); 334*0Sstevel@tonic-gate assert(err == 0); 335*0Sstevel@tonic-gate 336*0Sstevel@tonic-gate if (istrans) { 337*0Sstevel@tonic-gate cevents = 0; 338*0Sstevel@tonic-gate fevents = 0; 339*0Sstevel@tonic-gate } else { 340*0Sstevel@tonic-gate assert(restart_on >= 0); 341*0Sstevel@tonic-gate assert(restart_on <= METHOD_RESTART_ANY_FAULT); 342*0Sstevel@tonic-gate cevents = method_events[restart_on] & ~cte_mask; 343*0Sstevel@tonic-gate fevents = iswait ? 344*0Sstevel@tonic-gate (method_events[restart_on] & ~cte_mask & CT_PR_ALLFATAL) : 345*0Sstevel@tonic-gate 0; 346*0Sstevel@tonic-gate } 347*0Sstevel@tonic-gate 348*0Sstevel@tonic-gate err = ct_tmpl_set_critical(tmpl, cevents); 349*0Sstevel@tonic-gate assert(err == 0); 350*0Sstevel@tonic-gate 351*0Sstevel@tonic-gate err = ct_tmpl_set_informative(tmpl, 0); 352*0Sstevel@tonic-gate assert(err == 0); 353*0Sstevel@tonic-gate err = ct_pr_tmpl_set_fatal(tmpl, fevents); 354*0Sstevel@tonic-gate assert(err == 0); 355*0Sstevel@tonic-gate 356*0Sstevel@tonic-gate err = ct_tmpl_set_cookie(tmpl, istrans ? METHOD_OTHER_COOKIE : 357*0Sstevel@tonic-gate METHOD_START_COOKIE); 358*0Sstevel@tonic-gate assert(err == 0); 359*0Sstevel@tonic-gate 360*0Sstevel@tonic-gate if (type == METHOD_START && inst->ri_i.i_primary_ctid != 0) { 361*0Sstevel@tonic-gate ret = ct_pr_tmpl_set_transfer(tmpl, inst->ri_i.i_primary_ctid); 362*0Sstevel@tonic-gate switch (ret) { 363*0Sstevel@tonic-gate case 0: 364*0Sstevel@tonic-gate break; 365*0Sstevel@tonic-gate 366*0Sstevel@tonic-gate case ENOTEMPTY: 367*0Sstevel@tonic-gate /* No contracts for you! */ 368*0Sstevel@tonic-gate method_remove_contract(inst, B_TRUE, B_TRUE); 369*0Sstevel@tonic-gate if (inst->ri_mi_deleted) { 370*0Sstevel@tonic-gate ret = ECANCELED; 371*0Sstevel@tonic-gate goto out; 372*0Sstevel@tonic-gate } 373*0Sstevel@tonic-gate break; 374*0Sstevel@tonic-gate 375*0Sstevel@tonic-gate case EINVAL: 376*0Sstevel@tonic-gate case ESRCH: 377*0Sstevel@tonic-gate case EACCES: 378*0Sstevel@tonic-gate default: 379*0Sstevel@tonic-gate bad_error("ct_pr_tmpl_set_transfer", ret); 380*0Sstevel@tonic-gate } 381*0Sstevel@tonic-gate } 382*0Sstevel@tonic-gate 383*0Sstevel@tonic-gate err = ct_tmpl_activate(tmpl); 384*0Sstevel@tonic-gate assert(err == 0); 385*0Sstevel@tonic-gate 386*0Sstevel@tonic-gate ret = 0; 387*0Sstevel@tonic-gate 388*0Sstevel@tonic-gate out: 389*0Sstevel@tonic-gate err = close(tmpl); 390*0Sstevel@tonic-gate assert(err == 0); 391*0Sstevel@tonic-gate 392*0Sstevel@tonic-gate return (ret); 393*0Sstevel@tonic-gate } 394*0Sstevel@tonic-gate 395*0Sstevel@tonic-gate static const char *method_names[] = { "start", "stop", "refresh" }; 396*0Sstevel@tonic-gate 397*0Sstevel@tonic-gate static void 398*0Sstevel@tonic-gate exec_method(const restarter_inst_t *inst, int type, const char *method, 399*0Sstevel@tonic-gate struct method_context *mcp, uint8_t need_session) 400*0Sstevel@tonic-gate { 401*0Sstevel@tonic-gate char *cmd; 402*0Sstevel@tonic-gate const char *errf; 403*0Sstevel@tonic-gate char **nenv; 404*0Sstevel@tonic-gate 405*0Sstevel@tonic-gate cmd = uu_msprintf("exec %s", method); 406*0Sstevel@tonic-gate 407*0Sstevel@tonic-gate if (inst->ri_utmpx_prefix[0] != '\0' && inst->ri_utmpx_prefix != NULL) 408*0Sstevel@tonic-gate (void) utmpx_mark_init(getpid(), inst->ri_utmpx_prefix); 409*0Sstevel@tonic-gate 410*0Sstevel@tonic-gate setlog(inst->ri_logstem); 411*0Sstevel@tonic-gate log_instance(inst, B_FALSE, "Executing %s method (\"%s\")", 412*0Sstevel@tonic-gate method_names[type], method); 413*0Sstevel@tonic-gate 414*0Sstevel@tonic-gate if (need_session) 415*0Sstevel@tonic-gate (void) setpgrp(); 416*0Sstevel@tonic-gate 417*0Sstevel@tonic-gate /* Set credentials. */ 418*0Sstevel@tonic-gate errno = restarter_set_method_context(mcp, &errf); 419*0Sstevel@tonic-gate if (errno != 0) { 420*0Sstevel@tonic-gate (void) fputs("svc.startd could not set context for method: ", 421*0Sstevel@tonic-gate stderr); 422*0Sstevel@tonic-gate 423*0Sstevel@tonic-gate if (errno == -1) { 424*0Sstevel@tonic-gate if (strcmp(errf, "core_set_process_path") == 0) { 425*0Sstevel@tonic-gate (void) fputs("Could not set corefile path.\n", 426*0Sstevel@tonic-gate stderr); 427*0Sstevel@tonic-gate } else if (strcmp(errf, "setproject") == 0) { 428*0Sstevel@tonic-gate (void) fprintf(stderr, "%s: a resource control " 429*0Sstevel@tonic-gate "assignment failed\n", errf); 430*0Sstevel@tonic-gate } else if (strcmp(errf, "pool_set_binding") == 0) { 431*0Sstevel@tonic-gate (void) fprintf(stderr, "%s: a system error " 432*0Sstevel@tonic-gate "occurred\n", errf); 433*0Sstevel@tonic-gate } else { 434*0Sstevel@tonic-gate #ifndef NDEBUG 435*0Sstevel@tonic-gate uu_warn("%s:%d: Bad function name \"%s\" for " 436*0Sstevel@tonic-gate "error %d from " 437*0Sstevel@tonic-gate "restarter_set_method_context().\n", 438*0Sstevel@tonic-gate __FILE__, __LINE__, errf, errno); 439*0Sstevel@tonic-gate #endif 440*0Sstevel@tonic-gate abort(); 441*0Sstevel@tonic-gate } 442*0Sstevel@tonic-gate 443*0Sstevel@tonic-gate exit(1); 444*0Sstevel@tonic-gate } 445*0Sstevel@tonic-gate 446*0Sstevel@tonic-gate if (errf != NULL && strcmp(errf, "pool_set_binding") == 0) { 447*0Sstevel@tonic-gate switch (errno) { 448*0Sstevel@tonic-gate case ENOENT: 449*0Sstevel@tonic-gate (void) fprintf(stderr, "%s: the pool could not " 450*0Sstevel@tonic-gate "be found\n", errf); 451*0Sstevel@tonic-gate break; 452*0Sstevel@tonic-gate 453*0Sstevel@tonic-gate case EBADF: 454*0Sstevel@tonic-gate (void) fprintf(stderr, "%s: the configuration " 455*0Sstevel@tonic-gate "is invalid\n", errf); 456*0Sstevel@tonic-gate break; 457*0Sstevel@tonic-gate 458*0Sstevel@tonic-gate default: 459*0Sstevel@tonic-gate #ifndef NDEBUG 460*0Sstevel@tonic-gate uu_warn("%s:%d: Bad error %d for function %s " 461*0Sstevel@tonic-gate "in restarter_set_method_context().\n", 462*0Sstevel@tonic-gate __FILE__, __LINE__, errno, errf); 463*0Sstevel@tonic-gate #endif 464*0Sstevel@tonic-gate abort(); 465*0Sstevel@tonic-gate } 466*0Sstevel@tonic-gate 467*0Sstevel@tonic-gate exit(SMF_EXIT_ERR_CONFIG); 468*0Sstevel@tonic-gate } 469*0Sstevel@tonic-gate 470*0Sstevel@tonic-gate if (errf != NULL) { 471*0Sstevel@tonic-gate perror(errf); 472*0Sstevel@tonic-gate 473*0Sstevel@tonic-gate switch (errno) { 474*0Sstevel@tonic-gate case EINVAL: 475*0Sstevel@tonic-gate case EPERM: 476*0Sstevel@tonic-gate case ENOENT: 477*0Sstevel@tonic-gate case ENAMETOOLONG: 478*0Sstevel@tonic-gate case ERANGE: 479*0Sstevel@tonic-gate case ESRCH: 480*0Sstevel@tonic-gate exit(SMF_EXIT_ERR_CONFIG); 481*0Sstevel@tonic-gate /* NOTREACHED */ 482*0Sstevel@tonic-gate 483*0Sstevel@tonic-gate default: 484*0Sstevel@tonic-gate exit(1); 485*0Sstevel@tonic-gate } 486*0Sstevel@tonic-gate } 487*0Sstevel@tonic-gate 488*0Sstevel@tonic-gate switch (errno) { 489*0Sstevel@tonic-gate case ENOMEM: 490*0Sstevel@tonic-gate (void) fputs("Out of memory.\n", stderr); 491*0Sstevel@tonic-gate exit(1); 492*0Sstevel@tonic-gate /* NOTREACHED */ 493*0Sstevel@tonic-gate 494*0Sstevel@tonic-gate case ENOENT: 495*0Sstevel@tonic-gate (void) fputs("Missing passwd entry for user.\n", 496*0Sstevel@tonic-gate stderr); 497*0Sstevel@tonic-gate exit(SMF_EXIT_ERR_CONFIG); 498*0Sstevel@tonic-gate /* NOTREACHED */ 499*0Sstevel@tonic-gate 500*0Sstevel@tonic-gate default: 501*0Sstevel@tonic-gate #ifndef NDEBUG 502*0Sstevel@tonic-gate uu_warn("%s:%d: Bad miscellaneous error %d from " 503*0Sstevel@tonic-gate "restarter_set_method_context().\n", __FILE__, 504*0Sstevel@tonic-gate __LINE__, errno); 505*0Sstevel@tonic-gate #endif 506*0Sstevel@tonic-gate abort(); 507*0Sstevel@tonic-gate } 508*0Sstevel@tonic-gate } 509*0Sstevel@tonic-gate 510*0Sstevel@tonic-gate nenv = set_smf_env(mcp->env, mcp->env_sz, NULL, inst, method); 511*0Sstevel@tonic-gate 512*0Sstevel@tonic-gate log_preexec(); 513*0Sstevel@tonic-gate 514*0Sstevel@tonic-gate (void) execle(SBIN_SH, SBIN_SH, "-c", cmd, NULL, nenv); 515*0Sstevel@tonic-gate 516*0Sstevel@tonic-gate exit(10); 517*0Sstevel@tonic-gate } 518*0Sstevel@tonic-gate 519*0Sstevel@tonic-gate static void 520*0Sstevel@tonic-gate write_status(restarter_inst_t *inst, const char *mname, int stat) 521*0Sstevel@tonic-gate { 522*0Sstevel@tonic-gate int r; 523*0Sstevel@tonic-gate 524*0Sstevel@tonic-gate again: 525*0Sstevel@tonic-gate if (inst->ri_mi_deleted) 526*0Sstevel@tonic-gate return; 527*0Sstevel@tonic-gate 528*0Sstevel@tonic-gate r = libscf_write_method_status(inst->ri_m_inst, mname, stat); 529*0Sstevel@tonic-gate switch (r) { 530*0Sstevel@tonic-gate case 0: 531*0Sstevel@tonic-gate break; 532*0Sstevel@tonic-gate 533*0Sstevel@tonic-gate case ECONNABORTED: 534*0Sstevel@tonic-gate libscf_reget_instance(inst); 535*0Sstevel@tonic-gate goto again; 536*0Sstevel@tonic-gate 537*0Sstevel@tonic-gate case ECANCELED: 538*0Sstevel@tonic-gate inst->ri_mi_deleted = 1; 539*0Sstevel@tonic-gate break; 540*0Sstevel@tonic-gate 541*0Sstevel@tonic-gate case EPERM: 542*0Sstevel@tonic-gate case EACCES: 543*0Sstevel@tonic-gate case EROFS: 544*0Sstevel@tonic-gate log_framework(LOG_INFO, "Could not write exit status " 545*0Sstevel@tonic-gate "for %s method of %s: %s.\n", mname, 546*0Sstevel@tonic-gate inst->ri_i.i_fmri, strerror(r)); 547*0Sstevel@tonic-gate break; 548*0Sstevel@tonic-gate 549*0Sstevel@tonic-gate case ENAMETOOLONG: 550*0Sstevel@tonic-gate default: 551*0Sstevel@tonic-gate bad_error("libscf_write_method_status", r); 552*0Sstevel@tonic-gate } 553*0Sstevel@tonic-gate } 554*0Sstevel@tonic-gate 555*0Sstevel@tonic-gate /* 556*0Sstevel@tonic-gate * int method_run() 557*0Sstevel@tonic-gate * Execute the type method of instp. If it requires a fork(), wait for it 558*0Sstevel@tonic-gate * to return and return its exit code in *exit_code. Otherwise set 559*0Sstevel@tonic-gate * *exit_code to 0 if the method succeeds & -1 if it fails. If the 560*0Sstevel@tonic-gate * repository connection is broken, it is rebound, but inst may not be 561*0Sstevel@tonic-gate * reset. 562*0Sstevel@tonic-gate * Returns 563*0Sstevel@tonic-gate * 0 - success 564*0Sstevel@tonic-gate * EINVAL - A correct method or method context couldn't be retrieved. 565*0Sstevel@tonic-gate * EIO - Contract kill failed. 566*0Sstevel@tonic-gate * EFAULT - Method couldn't be executed successfully. 567*0Sstevel@tonic-gate * ELOOP - Retry threshold exceeded. 568*0Sstevel@tonic-gate * ECANCELED - inst was deleted from the repository before method was run 569*0Sstevel@tonic-gate * ERANGE - Timeout retry threshold exceeded. 570*0Sstevel@tonic-gate * EAGAIN - Failed due to external cause, retry. 571*0Sstevel@tonic-gate */ 572*0Sstevel@tonic-gate int 573*0Sstevel@tonic-gate method_run(restarter_inst_t **instp, int type, int *exit_code) 574*0Sstevel@tonic-gate { 575*0Sstevel@tonic-gate char *method; 576*0Sstevel@tonic-gate int ret_status; 577*0Sstevel@tonic-gate pid_t pid; 578*0Sstevel@tonic-gate method_restart_t restart_on; 579*0Sstevel@tonic-gate uint_t cte_mask; 580*0Sstevel@tonic-gate uint8_t need_session; 581*0Sstevel@tonic-gate scf_handle_t *h; 582*0Sstevel@tonic-gate scf_snapshot_t *snap; 583*0Sstevel@tonic-gate const char *mname; 584*0Sstevel@tonic-gate const char *errstr; 585*0Sstevel@tonic-gate struct method_context *mcp; 586*0Sstevel@tonic-gate int result = 0, timeout_fired = 0; 587*0Sstevel@tonic-gate int sig, r; 588*0Sstevel@tonic-gate boolean_t transient; 589*0Sstevel@tonic-gate uint64_t timeout; 590*0Sstevel@tonic-gate uint8_t timeout_retry; 591*0Sstevel@tonic-gate ctid_t ctid; 592*0Sstevel@tonic-gate int ctfd = -1; 593*0Sstevel@tonic-gate ct_evthdl_t ctev; 594*0Sstevel@tonic-gate uint_t evtype; 595*0Sstevel@tonic-gate restarter_inst_t *inst = *instp; 596*0Sstevel@tonic-gate int id = inst->ri_id; 597*0Sstevel@tonic-gate 598*0Sstevel@tonic-gate assert(PTHREAD_MUTEX_HELD(&inst->ri_lock)); 599*0Sstevel@tonic-gate assert(instance_in_transition(inst)); 600*0Sstevel@tonic-gate 601*0Sstevel@tonic-gate if (inst->ri_mi_deleted) 602*0Sstevel@tonic-gate return (ECANCELED); 603*0Sstevel@tonic-gate 604*0Sstevel@tonic-gate *exit_code = 0; 605*0Sstevel@tonic-gate 606*0Sstevel@tonic-gate assert(0 <= type && type <= 2); 607*0Sstevel@tonic-gate mname = method_names[type]; 608*0Sstevel@tonic-gate 609*0Sstevel@tonic-gate if (type == METHOD_START) 610*0Sstevel@tonic-gate inst->ri_pre_online_hook(); 611*0Sstevel@tonic-gate 612*0Sstevel@tonic-gate h = scf_instance_handle(inst->ri_m_inst); 613*0Sstevel@tonic-gate 614*0Sstevel@tonic-gate snap = scf_snapshot_create(h); 615*0Sstevel@tonic-gate if (snap == NULL || 616*0Sstevel@tonic-gate scf_instance_get_snapshot(inst->ri_m_inst, "running", snap) != 0) { 617*0Sstevel@tonic-gate log_framework(LOG_DEBUG, 618*0Sstevel@tonic-gate "Could not get running snapshot for %s. " 619*0Sstevel@tonic-gate "Using editing version to run method %s.\n", 620*0Sstevel@tonic-gate inst->ri_i.i_fmri, mname); 621*0Sstevel@tonic-gate scf_snapshot_destroy(snap); 622*0Sstevel@tonic-gate snap = NULL; 623*0Sstevel@tonic-gate } 624*0Sstevel@tonic-gate 625*0Sstevel@tonic-gate /* 626*0Sstevel@tonic-gate * After this point, we may be logging to the instance log. 627*0Sstevel@tonic-gate * Make sure we've noted where that log is as a property of 628*0Sstevel@tonic-gate * the instance. 629*0Sstevel@tonic-gate */ 630*0Sstevel@tonic-gate r = libscf_note_method_log(inst->ri_m_inst, st->st_log_prefix, 631*0Sstevel@tonic-gate inst->ri_logstem); 632*0Sstevel@tonic-gate if (r != 0) { 633*0Sstevel@tonic-gate log_framework(LOG_WARNING, 634*0Sstevel@tonic-gate "%s: couldn't note log location: %s\n", 635*0Sstevel@tonic-gate inst->ri_i.i_fmri, strerror(r)); 636*0Sstevel@tonic-gate } 637*0Sstevel@tonic-gate 638*0Sstevel@tonic-gate if ((method = libscf_get_method(h, type, inst, snap, &restart_on, 639*0Sstevel@tonic-gate &cte_mask, &need_session, &timeout, &timeout_retry)) == NULL) { 640*0Sstevel@tonic-gate if (errno == LIBSCF_PGROUP_ABSENT) { 641*0Sstevel@tonic-gate log_framework(LOG_DEBUG, 642*0Sstevel@tonic-gate "%s: instance has no method property group '%s'.\n", 643*0Sstevel@tonic-gate inst->ri_i.i_fmri, mname); 644*0Sstevel@tonic-gate if (type == METHOD_REFRESH) 645*0Sstevel@tonic-gate log_instance(inst, B_TRUE, "No '%s' method " 646*0Sstevel@tonic-gate "defined. Treating as :true.", mname); 647*0Sstevel@tonic-gate else 648*0Sstevel@tonic-gate log_instance(inst, B_TRUE, "Method property " 649*0Sstevel@tonic-gate "group '%s' is not present.", mname); 650*0Sstevel@tonic-gate scf_snapshot_destroy(snap); 651*0Sstevel@tonic-gate return (0); 652*0Sstevel@tonic-gate } else if (errno == LIBSCF_PROPERTY_ABSENT) { 653*0Sstevel@tonic-gate log_framework(LOG_DEBUG, 654*0Sstevel@tonic-gate "%s: instance has no '%s/exec' method property.\n", 655*0Sstevel@tonic-gate inst->ri_i.i_fmri, mname); 656*0Sstevel@tonic-gate log_instance(inst, B_TRUE, "Method property '%s/exec " 657*0Sstevel@tonic-gate "is not present.", mname); 658*0Sstevel@tonic-gate scf_snapshot_destroy(snap); 659*0Sstevel@tonic-gate return (0); 660*0Sstevel@tonic-gate } else { 661*0Sstevel@tonic-gate log_error(LOG_WARNING, 662*0Sstevel@tonic-gate "%s: instance libscf_get_method failed\n", 663*0Sstevel@tonic-gate inst->ri_i.i_fmri); 664*0Sstevel@tonic-gate scf_snapshot_destroy(snap); 665*0Sstevel@tonic-gate return (EINVAL); 666*0Sstevel@tonic-gate } 667*0Sstevel@tonic-gate } 668*0Sstevel@tonic-gate 669*0Sstevel@tonic-gate /* open service contract if stopping a non-transient service */ 670*0Sstevel@tonic-gate if (type == METHOD_STOP && (!instance_is_transient_style(inst))) { 671*0Sstevel@tonic-gate if (inst->ri_i.i_primary_ctid == 0) { 672*0Sstevel@tonic-gate /* service is not running, nothing to stop */ 673*0Sstevel@tonic-gate log_framework(LOG_DEBUG, "%s: instance has no primary " 674*0Sstevel@tonic-gate "contract, no service to stop.\n", 675*0Sstevel@tonic-gate inst->ri_i.i_fmri); 676*0Sstevel@tonic-gate scf_snapshot_destroy(snap); 677*0Sstevel@tonic-gate return (0); 678*0Sstevel@tonic-gate } 679*0Sstevel@tonic-gate if ((ctfd = contract_open(inst->ri_i.i_primary_ctid, "process", 680*0Sstevel@tonic-gate "events", O_RDONLY)) < 0) { 681*0Sstevel@tonic-gate result = EFAULT; 682*0Sstevel@tonic-gate log_instance(inst, B_TRUE, "Could not open service " 683*0Sstevel@tonic-gate "contract %ld. Stop method not run.\n", 684*0Sstevel@tonic-gate inst->ri_i.i_primary_ctid); 685*0Sstevel@tonic-gate goto out; 686*0Sstevel@tonic-gate } 687*0Sstevel@tonic-gate } 688*0Sstevel@tonic-gate 689*0Sstevel@tonic-gate if (restarter_is_null_method(method)) { 690*0Sstevel@tonic-gate log_framework(LOG_DEBUG, "%s: null method succeeds\n", 691*0Sstevel@tonic-gate inst->ri_i.i_fmri); 692*0Sstevel@tonic-gate 693*0Sstevel@tonic-gate log_instance(inst, B_TRUE, "Executing %s method (null)", mname); 694*0Sstevel@tonic-gate 695*0Sstevel@tonic-gate if (type == METHOD_START) 696*0Sstevel@tonic-gate write_status(inst, mname, 0); 697*0Sstevel@tonic-gate goto out; 698*0Sstevel@tonic-gate } 699*0Sstevel@tonic-gate 700*0Sstevel@tonic-gate sig = restarter_is_kill_method(method); 701*0Sstevel@tonic-gate if (sig >= 0) { 702*0Sstevel@tonic-gate 703*0Sstevel@tonic-gate if (inst->ri_i.i_primary_ctid == 0) { 704*0Sstevel@tonic-gate log_error(LOG_ERR, "%s: :kill with no contract\n", 705*0Sstevel@tonic-gate inst->ri_i.i_fmri); 706*0Sstevel@tonic-gate result = EINVAL; 707*0Sstevel@tonic-gate goto out; 708*0Sstevel@tonic-gate } 709*0Sstevel@tonic-gate 710*0Sstevel@tonic-gate log_framework(LOG_DEBUG, 711*0Sstevel@tonic-gate "%s: :killing contract with signal %d\n", 712*0Sstevel@tonic-gate inst->ri_i.i_fmri, sig); 713*0Sstevel@tonic-gate 714*0Sstevel@tonic-gate log_instance(inst, B_TRUE, "Executing %s method (:kill)", 715*0Sstevel@tonic-gate mname); 716*0Sstevel@tonic-gate 717*0Sstevel@tonic-gate if (contract_kill(inst->ri_i.i_primary_ctid, sig, 718*0Sstevel@tonic-gate inst->ri_i.i_fmri) != 0) { 719*0Sstevel@tonic-gate result = EIO; 720*0Sstevel@tonic-gate goto out; 721*0Sstevel@tonic-gate } else 722*0Sstevel@tonic-gate goto assured_kill; 723*0Sstevel@tonic-gate } 724*0Sstevel@tonic-gate 725*0Sstevel@tonic-gate log_framework(LOG_DEBUG, "%s: forking to run method %s\n", 726*0Sstevel@tonic-gate inst->ri_i.i_fmri, method); 727*0Sstevel@tonic-gate 728*0Sstevel@tonic-gate errstr = restarter_get_method_context(RESTARTER_METHOD_CONTEXT_VERSION, 729*0Sstevel@tonic-gate inst->ri_m_inst, snap, mname, method, &mcp); 730*0Sstevel@tonic-gate 731*0Sstevel@tonic-gate if (errstr != NULL) { 732*0Sstevel@tonic-gate log_error(LOG_WARNING, "%s: %s\n", inst->ri_i.i_fmri, errstr); 733*0Sstevel@tonic-gate result = EINVAL; 734*0Sstevel@tonic-gate goto out; 735*0Sstevel@tonic-gate } 736*0Sstevel@tonic-gate 737*0Sstevel@tonic-gate r = method_ready_contract(inst, type, restart_on, cte_mask); 738*0Sstevel@tonic-gate if (r != 0) { 739*0Sstevel@tonic-gate assert(r == ECANCELED); 740*0Sstevel@tonic-gate assert(inst->ri_mi_deleted); 741*0Sstevel@tonic-gate restarter_free_method_context(mcp); 742*0Sstevel@tonic-gate result = ECANCELED; 743*0Sstevel@tonic-gate goto out; 744*0Sstevel@tonic-gate } 745*0Sstevel@tonic-gate 746*0Sstevel@tonic-gate /* 747*0Sstevel@tonic-gate * Validate safety of method contexts, to save children work. 748*0Sstevel@tonic-gate */ 749*0Sstevel@tonic-gate if (!restarter_rm_libs_loadable()) 750*0Sstevel@tonic-gate log_framework(LOG_DEBUG, "%s: method contexts limited " 751*0Sstevel@tonic-gate "to root-accessible libraries\n", inst->ri_i.i_fmri); 752*0Sstevel@tonic-gate 753*0Sstevel@tonic-gate /* 754*0Sstevel@tonic-gate * If the service is restarting too quickly, send it to 755*0Sstevel@tonic-gate * maintenance. 756*0Sstevel@tonic-gate */ 757*0Sstevel@tonic-gate if (type == METHOD_START) { 758*0Sstevel@tonic-gate method_record_start(inst); 759*0Sstevel@tonic-gate if (method_rate_critical(inst)) { 760*0Sstevel@tonic-gate log_instance(inst, B_TRUE, "Restarting too quickly, " 761*0Sstevel@tonic-gate "changing state to maintenance"); 762*0Sstevel@tonic-gate result = ELOOP; 763*0Sstevel@tonic-gate goto out; 764*0Sstevel@tonic-gate } 765*0Sstevel@tonic-gate } 766*0Sstevel@tonic-gate 767*0Sstevel@tonic-gate pid = startd_fork1(NULL); 768*0Sstevel@tonic-gate if (pid == 0) 769*0Sstevel@tonic-gate exec_method(inst, type, method, mcp, need_session); 770*0Sstevel@tonic-gate 771*0Sstevel@tonic-gate if (pid == -1) { 772*0Sstevel@tonic-gate log_error(LOG_WARNING, 773*0Sstevel@tonic-gate "%s: Couldn't fork to execute method %s\n", 774*0Sstevel@tonic-gate inst->ri_i.i_fmri, method); 775*0Sstevel@tonic-gate result = EFAULT; 776*0Sstevel@tonic-gate goto out; 777*0Sstevel@tonic-gate } 778*0Sstevel@tonic-gate 779*0Sstevel@tonic-gate restarter_free_method_context(mcp); 780*0Sstevel@tonic-gate 781*0Sstevel@tonic-gate /* 782*0Sstevel@tonic-gate * Get the contract id, decide whether it is primary or transient, and 783*0Sstevel@tonic-gate * stash it in inst & the repository. 784*0Sstevel@tonic-gate */ 785*0Sstevel@tonic-gate method_store_contract(inst, type, &ctid); 786*0Sstevel@tonic-gate 787*0Sstevel@tonic-gate /* 788*0Sstevel@tonic-gate * Similarly for the start method PID. 789*0Sstevel@tonic-gate */ 790*0Sstevel@tonic-gate if (type == METHOD_START && !inst->ri_mi_deleted) 791*0Sstevel@tonic-gate (void) libscf_write_start_pid(inst->ri_m_inst, pid); 792*0Sstevel@tonic-gate 793*0Sstevel@tonic-gate if (instance_is_wait_style(inst) && type == METHOD_START) { 794*0Sstevel@tonic-gate /* Wait style instances don't get timeouts on start methods. */ 795*0Sstevel@tonic-gate if (wait_register(pid, inst->ri_i.i_fmri, 1, 0)) { 796*0Sstevel@tonic-gate log_error(LOG_WARNING, 797*0Sstevel@tonic-gate "%s: couldn't register %ld for wait\n", 798*0Sstevel@tonic-gate inst->ri_i.i_fmri, pid); 799*0Sstevel@tonic-gate result = EFAULT; 800*0Sstevel@tonic-gate goto contract_out; 801*0Sstevel@tonic-gate } 802*0Sstevel@tonic-gate write_status(inst, mname, 0); 803*0Sstevel@tonic-gate 804*0Sstevel@tonic-gate } else { 805*0Sstevel@tonic-gate int r, err; 806*0Sstevel@tonic-gate time_t start_time; 807*0Sstevel@tonic-gate time_t end_time; 808*0Sstevel@tonic-gate 809*0Sstevel@tonic-gate /* 810*0Sstevel@tonic-gate * Because on upgrade/live-upgrade we may have no chance 811*0Sstevel@tonic-gate * to override faulty timeout values on the way to 812*0Sstevel@tonic-gate * manifest import, all services on the path to manifest 813*0Sstevel@tonic-gate * import are treated the same as INFINITE timeout services. 814*0Sstevel@tonic-gate */ 815*0Sstevel@tonic-gate 816*0Sstevel@tonic-gate start_time = time(NULL); 817*0Sstevel@tonic-gate if (timeout != METHOD_TIMEOUT_INFINITE && !is_timeout_ovr(inst)) 818*0Sstevel@tonic-gate timeout_insert(inst, ctid, timeout); 819*0Sstevel@tonic-gate else 820*0Sstevel@tonic-gate timeout = METHOD_TIMEOUT_INFINITE; 821*0Sstevel@tonic-gate 822*0Sstevel@tonic-gate /* Unlock the instance while waiting for the method. */ 823*0Sstevel@tonic-gate MUTEX_UNLOCK(&inst->ri_lock); 824*0Sstevel@tonic-gate 825*0Sstevel@tonic-gate do 826*0Sstevel@tonic-gate r = waitpid(pid, &ret_status, NULL); 827*0Sstevel@tonic-gate while (r == -1 && errno == EINTR); 828*0Sstevel@tonic-gate if (r == -1) 829*0Sstevel@tonic-gate err = errno; 830*0Sstevel@tonic-gate 831*0Sstevel@tonic-gate /* Re-grab the lock. */ 832*0Sstevel@tonic-gate inst = inst_lookup_by_id(id); 833*0Sstevel@tonic-gate 834*0Sstevel@tonic-gate /* 835*0Sstevel@tonic-gate * inst can't be removed, as the removal thread waits 836*0Sstevel@tonic-gate * for completion of this one. 837*0Sstevel@tonic-gate */ 838*0Sstevel@tonic-gate assert(inst != NULL); 839*0Sstevel@tonic-gate *instp = inst; 840*0Sstevel@tonic-gate 841*0Sstevel@tonic-gate if (inst->ri_timeout != NULL && inst->ri_timeout->te_fired) 842*0Sstevel@tonic-gate timeout_fired = 1; 843*0Sstevel@tonic-gate 844*0Sstevel@tonic-gate timeout_remove(inst, ctid); 845*0Sstevel@tonic-gate 846*0Sstevel@tonic-gate log_framework(LOG_DEBUG, 847*0Sstevel@tonic-gate "%s method for %s exited with status %d.\n", mname, 848*0Sstevel@tonic-gate inst->ri_i.i_fmri, WEXITSTATUS(ret_status)); 849*0Sstevel@tonic-gate 850*0Sstevel@tonic-gate if (r == -1) { 851*0Sstevel@tonic-gate log_error(LOG_WARNING, 852*0Sstevel@tonic-gate "Couldn't waitpid() for %s method of %s (%s).\n", 853*0Sstevel@tonic-gate mname, inst->ri_i.i_fmri, strerror(err)); 854*0Sstevel@tonic-gate result = EFAULT; 855*0Sstevel@tonic-gate goto contract_out; 856*0Sstevel@tonic-gate } 857*0Sstevel@tonic-gate 858*0Sstevel@tonic-gate if (type == METHOD_START) 859*0Sstevel@tonic-gate write_status(inst, mname, ret_status); 860*0Sstevel@tonic-gate 861*0Sstevel@tonic-gate /* return ERANGE if this service doesn't retry on timeout */ 862*0Sstevel@tonic-gate if (timeout_fired == 1 && timeout_retry == 0) { 863*0Sstevel@tonic-gate result = ERANGE; 864*0Sstevel@tonic-gate goto contract_out; 865*0Sstevel@tonic-gate } 866*0Sstevel@tonic-gate 867*0Sstevel@tonic-gate if (!WIFEXITED(ret_status)) { 868*0Sstevel@tonic-gate /* 869*0Sstevel@tonic-gate * If method didn't exit itself (it was killed by an 870*0Sstevel@tonic-gate * external entity, etc.), consider the entire 871*0Sstevel@tonic-gate * method_run as failed. 872*0Sstevel@tonic-gate */ 873*0Sstevel@tonic-gate if (WIFSIGNALED(ret_status)) { 874*0Sstevel@tonic-gate char buf[SIG2STR_MAX]; 875*0Sstevel@tonic-gate (void) sig2str(WTERMSIG(ret_status), buf); 876*0Sstevel@tonic-gate 877*0Sstevel@tonic-gate log_error(LOG_WARNING, "%s: Method \"%s\" " 878*0Sstevel@tonic-gate "failed due to signal %s.\n", 879*0Sstevel@tonic-gate inst->ri_i.i_fmri, method, buf); 880*0Sstevel@tonic-gate log_instance(inst, B_TRUE, "Method \"%s\" " 881*0Sstevel@tonic-gate "failed due to signal %s", mname, buf); 882*0Sstevel@tonic-gate } else { 883*0Sstevel@tonic-gate log_error(LOG_WARNING, "%s: Method \"%s\" " 884*0Sstevel@tonic-gate "failed with exit status %d.\n", 885*0Sstevel@tonic-gate inst->ri_i.i_fmri, method, 886*0Sstevel@tonic-gate WEXITSTATUS(ret_status)); 887*0Sstevel@tonic-gate log_instance(inst, B_TRUE, "Method \"%s\" " 888*0Sstevel@tonic-gate "failed with exit status %d", mname, 889*0Sstevel@tonic-gate WEXITSTATUS(ret_status)); 890*0Sstevel@tonic-gate } 891*0Sstevel@tonic-gate result = EAGAIN; 892*0Sstevel@tonic-gate goto contract_out; 893*0Sstevel@tonic-gate } 894*0Sstevel@tonic-gate 895*0Sstevel@tonic-gate *exit_code = WEXITSTATUS(ret_status); 896*0Sstevel@tonic-gate if (*exit_code != 0) { 897*0Sstevel@tonic-gate log_error(LOG_WARNING, 898*0Sstevel@tonic-gate "%s: Method \"%s\" failed with exit status %d.\n", 899*0Sstevel@tonic-gate inst->ri_i.i_fmri, method, WEXITSTATUS(ret_status)); 900*0Sstevel@tonic-gate } 901*0Sstevel@tonic-gate 902*0Sstevel@tonic-gate log_instance(inst, B_TRUE, "Method \"%s\" exited with status " 903*0Sstevel@tonic-gate "%d", mname, *exit_code); 904*0Sstevel@tonic-gate 905*0Sstevel@tonic-gate if (*exit_code != 0) 906*0Sstevel@tonic-gate goto contract_out; 907*0Sstevel@tonic-gate 908*0Sstevel@tonic-gate end_time = time(NULL); 909*0Sstevel@tonic-gate 910*0Sstevel@tonic-gate /* Give service contract remaining seconds to empty */ 911*0Sstevel@tonic-gate if (timeout != METHOD_TIMEOUT_INFINITE) 912*0Sstevel@tonic-gate timeout -= (end_time - start_time); 913*0Sstevel@tonic-gate } 914*0Sstevel@tonic-gate 915*0Sstevel@tonic-gate assured_kill: 916*0Sstevel@tonic-gate /* 917*0Sstevel@tonic-gate * For stop methods, assure that the service contract has emptied 918*0Sstevel@tonic-gate * before returning. 919*0Sstevel@tonic-gate */ 920*0Sstevel@tonic-gate if (type == METHOD_STOP && (!instance_is_transient_style(inst)) && 921*0Sstevel@tonic-gate !(contract_is_empty(inst->ri_i.i_primary_ctid))) { 922*0Sstevel@tonic-gate 923*0Sstevel@tonic-gate if (timeout != METHOD_TIMEOUT_INFINITE) 924*0Sstevel@tonic-gate timeout_insert(inst, inst->ri_i.i_primary_ctid, 925*0Sstevel@tonic-gate timeout); 926*0Sstevel@tonic-gate 927*0Sstevel@tonic-gate for (;;) { 928*0Sstevel@tonic-gate do { 929*0Sstevel@tonic-gate r = ct_event_read_critical(ctfd, &ctev); 930*0Sstevel@tonic-gate } while (r == EINTR); 931*0Sstevel@tonic-gate if (r != 0) 932*0Sstevel@tonic-gate break; 933*0Sstevel@tonic-gate 934*0Sstevel@tonic-gate evtype = ct_event_get_type(ctev); 935*0Sstevel@tonic-gate ct_event_free(ctev); 936*0Sstevel@tonic-gate if (evtype == CT_PR_EV_EMPTY) 937*0Sstevel@tonic-gate break; 938*0Sstevel@tonic-gate } 939*0Sstevel@tonic-gate if (r) { 940*0Sstevel@tonic-gate result = EFAULT; 941*0Sstevel@tonic-gate log_instance(inst, B_TRUE, "Error reading service " 942*0Sstevel@tonic-gate "contract %ld.\n", inst->ri_i.i_primary_ctid); 943*0Sstevel@tonic-gate } 944*0Sstevel@tonic-gate 945*0Sstevel@tonic-gate if (timeout != METHOD_TIMEOUT_INFINITE) 946*0Sstevel@tonic-gate if (inst->ri_timeout->te_fired) 947*0Sstevel@tonic-gate result = EFAULT; 948*0Sstevel@tonic-gate 949*0Sstevel@tonic-gate timeout_remove(inst, inst->ri_i.i_primary_ctid); 950*0Sstevel@tonic-gate } 951*0Sstevel@tonic-gate 952*0Sstevel@tonic-gate contract_out: 953*0Sstevel@tonic-gate /* Abandon contracts for transient methods & methods that fail. */ 954*0Sstevel@tonic-gate transient = method_is_transient(inst, type); 955*0Sstevel@tonic-gate if ((transient || *exit_code != 0 || result != 0) && 956*0Sstevel@tonic-gate (restarter_is_kill_method(method) < 0)) 957*0Sstevel@tonic-gate method_remove_contract(inst, !transient, B_TRUE); 958*0Sstevel@tonic-gate 959*0Sstevel@tonic-gate out: 960*0Sstevel@tonic-gate if (ctfd >= 0) 961*0Sstevel@tonic-gate (void) close(ctfd); 962*0Sstevel@tonic-gate scf_snapshot_destroy(snap); 963*0Sstevel@tonic-gate free(method); 964*0Sstevel@tonic-gate return (result); 965*0Sstevel@tonic-gate } 966*0Sstevel@tonic-gate 967*0Sstevel@tonic-gate /* 968*0Sstevel@tonic-gate * The method thread executes a service method to effect a state transition. 969*0Sstevel@tonic-gate * The next_state of info->sf_id should be non-_NONE on entrance, and it will 970*0Sstevel@tonic-gate * be _NONE on exit (state will either be what next_state was (on success), or 971*0Sstevel@tonic-gate * it will be _MAINT (on error)). 972*0Sstevel@tonic-gate * 973*0Sstevel@tonic-gate * There are six classes of methods to consider: start & other (stop, refresh) 974*0Sstevel@tonic-gate * for each of "normal" services, wait services, and transient services. For 975*0Sstevel@tonic-gate * each, the method must be fetched from the repository & executed. fork()ed 976*0Sstevel@tonic-gate * methods must be waited on, except for the start method of wait services 977*0Sstevel@tonic-gate * (which must be registered with the wait subsystem via wait_register()). If 978*0Sstevel@tonic-gate * the method succeeded (returned 0), then for start methods its contract 979*0Sstevel@tonic-gate * should be recorded as the primary contract for the service. For other 980*0Sstevel@tonic-gate * methods, it should be abandoned. If the method fails, then depending on 981*0Sstevel@tonic-gate * the failure, either the method should be reexecuted or the service should 982*0Sstevel@tonic-gate * be put into maintenance. Either way the contract should be abandoned. 983*0Sstevel@tonic-gate */ 984*0Sstevel@tonic-gate void * 985*0Sstevel@tonic-gate method_thread(void *arg) 986*0Sstevel@tonic-gate { 987*0Sstevel@tonic-gate fork_info_t *info = arg; 988*0Sstevel@tonic-gate restarter_inst_t *inst; 989*0Sstevel@tonic-gate scf_handle_t *local_handle; 990*0Sstevel@tonic-gate scf_instance_t *s_inst = NULL; 991*0Sstevel@tonic-gate int r, exit_code; 992*0Sstevel@tonic-gate boolean_t retryable; 993*0Sstevel@tonic-gate const char *aux; 994*0Sstevel@tonic-gate 995*0Sstevel@tonic-gate assert(0 <= info->sf_method_type && info->sf_method_type <= 2); 996*0Sstevel@tonic-gate 997*0Sstevel@tonic-gate /* Get (and lock) the restarter_inst_t. */ 998*0Sstevel@tonic-gate inst = inst_lookup_by_id(info->sf_id); 999*0Sstevel@tonic-gate 1000*0Sstevel@tonic-gate assert(inst->ri_method_thread != 0); 1001*0Sstevel@tonic-gate assert(instance_in_transition(inst) == 1); 1002*0Sstevel@tonic-gate 1003*0Sstevel@tonic-gate /* 1004*0Sstevel@tonic-gate * We cannot leave this function with inst in transition, because 1005*0Sstevel@tonic-gate * protocol.c withholds messages for inst otherwise. 1006*0Sstevel@tonic-gate */ 1007*0Sstevel@tonic-gate 1008*0Sstevel@tonic-gate log_framework(LOG_DEBUG, "method_thread() running %s method for %s.\n", 1009*0Sstevel@tonic-gate method_names[info->sf_method_type], inst->ri_i.i_fmri); 1010*0Sstevel@tonic-gate 1011*0Sstevel@tonic-gate local_handle = libscf_handle_create_bound_loop(); 1012*0Sstevel@tonic-gate 1013*0Sstevel@tonic-gate rebind_retry: 1014*0Sstevel@tonic-gate /* get scf_instance_t */ 1015*0Sstevel@tonic-gate switch (r = libscf_fmri_get_instance(local_handle, inst->ri_i.i_fmri, 1016*0Sstevel@tonic-gate &s_inst)) { 1017*0Sstevel@tonic-gate case 0: 1018*0Sstevel@tonic-gate break; 1019*0Sstevel@tonic-gate 1020*0Sstevel@tonic-gate case ECONNABORTED: 1021*0Sstevel@tonic-gate libscf_handle_rebind(local_handle); 1022*0Sstevel@tonic-gate goto rebind_retry; 1023*0Sstevel@tonic-gate 1024*0Sstevel@tonic-gate case ENOENT: 1025*0Sstevel@tonic-gate /* 1026*0Sstevel@tonic-gate * It's not there, but we need to call this so protocol.c 1027*0Sstevel@tonic-gate * doesn't think it's in transition anymore. 1028*0Sstevel@tonic-gate */ 1029*0Sstevel@tonic-gate (void) restarter_instance_update_states(local_handle, inst, 1030*0Sstevel@tonic-gate inst->ri_i.i_state, RESTARTER_STATE_NONE, RERR_NONE, 1031*0Sstevel@tonic-gate NULL); 1032*0Sstevel@tonic-gate goto out; 1033*0Sstevel@tonic-gate 1034*0Sstevel@tonic-gate case EINVAL: 1035*0Sstevel@tonic-gate case ENOTSUP: 1036*0Sstevel@tonic-gate default: 1037*0Sstevel@tonic-gate bad_error("libscf_fmri_get_instance", r); 1038*0Sstevel@tonic-gate } 1039*0Sstevel@tonic-gate 1040*0Sstevel@tonic-gate inst->ri_m_inst = s_inst; 1041*0Sstevel@tonic-gate inst->ri_mi_deleted = B_FALSE; 1042*0Sstevel@tonic-gate 1043*0Sstevel@tonic-gate retry: 1044*0Sstevel@tonic-gate if (info->sf_method_type == METHOD_START) 1045*0Sstevel@tonic-gate log_transition(inst, START_REQUESTED); 1046*0Sstevel@tonic-gate 1047*0Sstevel@tonic-gate r = method_run(&inst, info->sf_method_type, &exit_code); 1048*0Sstevel@tonic-gate 1049*0Sstevel@tonic-gate if (r == 0 && exit_code == 0) { 1050*0Sstevel@tonic-gate /* Success! */ 1051*0Sstevel@tonic-gate assert(inst->ri_i.i_next_state != RESTARTER_STATE_NONE); 1052*0Sstevel@tonic-gate 1053*0Sstevel@tonic-gate /* 1054*0Sstevel@tonic-gate * When a stop method succeeds, remove the primary contract of 1055*0Sstevel@tonic-gate * the service, unless we're going to offline, in which case 1056*0Sstevel@tonic-gate * retain the contract so we can transfer inherited contracts to 1057*0Sstevel@tonic-gate * the replacement service. 1058*0Sstevel@tonic-gate */ 1059*0Sstevel@tonic-gate 1060*0Sstevel@tonic-gate if (info->sf_method_type == METHOD_STOP && 1061*0Sstevel@tonic-gate inst->ri_i.i_primary_ctid != 0) { 1062*0Sstevel@tonic-gate if (inst->ri_i.i_next_state == RESTARTER_STATE_OFFLINE) 1063*0Sstevel@tonic-gate inst->ri_i.i_primary_ctid_stopped = 1; 1064*0Sstevel@tonic-gate else 1065*0Sstevel@tonic-gate method_remove_contract(inst, B_TRUE, B_TRUE); 1066*0Sstevel@tonic-gate } 1067*0Sstevel@tonic-gate /* 1068*0Sstevel@tonic-gate * We don't care whether the handle was rebound because this is 1069*0Sstevel@tonic-gate * the last thing we do with it. 1070*0Sstevel@tonic-gate */ 1071*0Sstevel@tonic-gate (void) restarter_instance_update_states(local_handle, inst, 1072*0Sstevel@tonic-gate inst->ri_i.i_next_state, RESTARTER_STATE_NONE, 1073*0Sstevel@tonic-gate info->sf_event_type, NULL); 1074*0Sstevel@tonic-gate 1075*0Sstevel@tonic-gate (void) update_fault_count(inst, FAULT_COUNT_RESET); 1076*0Sstevel@tonic-gate 1077*0Sstevel@tonic-gate goto out; 1078*0Sstevel@tonic-gate } 1079*0Sstevel@tonic-gate 1080*0Sstevel@tonic-gate /* Failure. Retry or go to maintenance. */ 1081*0Sstevel@tonic-gate 1082*0Sstevel@tonic-gate if (r != 0 && r != EAGAIN) { 1083*0Sstevel@tonic-gate retryable = B_FALSE; 1084*0Sstevel@tonic-gate } else { 1085*0Sstevel@tonic-gate switch (exit_code) { 1086*0Sstevel@tonic-gate case SMF_EXIT_ERR_CONFIG: 1087*0Sstevel@tonic-gate case SMF_EXIT_ERR_NOSMF: 1088*0Sstevel@tonic-gate case SMF_EXIT_ERR_PERM: 1089*0Sstevel@tonic-gate case SMF_EXIT_ERR_FATAL: 1090*0Sstevel@tonic-gate retryable = B_FALSE; 1091*0Sstevel@tonic-gate break; 1092*0Sstevel@tonic-gate 1093*0Sstevel@tonic-gate default: 1094*0Sstevel@tonic-gate retryable = B_TRUE; 1095*0Sstevel@tonic-gate } 1096*0Sstevel@tonic-gate } 1097*0Sstevel@tonic-gate 1098*0Sstevel@tonic-gate if (retryable && update_fault_count(inst, FAULT_COUNT_INCR) != 1) 1099*0Sstevel@tonic-gate goto retry; 1100*0Sstevel@tonic-gate 1101*0Sstevel@tonic-gate /* maintenance */ 1102*0Sstevel@tonic-gate if (r == ELOOP) 1103*0Sstevel@tonic-gate log_transition(inst, START_FAILED_REPEATEDLY); 1104*0Sstevel@tonic-gate else if (r == ERANGE) 1105*0Sstevel@tonic-gate log_transition(inst, START_FAILED_TIMEOUT_FATAL); 1106*0Sstevel@tonic-gate else if (exit_code == SMF_EXIT_ERR_CONFIG) 1107*0Sstevel@tonic-gate log_transition(inst, START_FAILED_CONFIGURATION); 1108*0Sstevel@tonic-gate else if (exit_code == SMF_EXIT_ERR_FATAL) 1109*0Sstevel@tonic-gate log_transition(inst, START_FAILED_FATAL); 1110*0Sstevel@tonic-gate else 1111*0Sstevel@tonic-gate log_transition(inst, START_FAILED_OTHER); 1112*0Sstevel@tonic-gate 1113*0Sstevel@tonic-gate if (r == ELOOP) 1114*0Sstevel@tonic-gate aux = "restarting_too_quickly"; 1115*0Sstevel@tonic-gate else if (retryable) 1116*0Sstevel@tonic-gate aux = "fault_threshold_reached"; 1117*0Sstevel@tonic-gate else 1118*0Sstevel@tonic-gate aux = "method_failed"; 1119*0Sstevel@tonic-gate 1120*0Sstevel@tonic-gate (void) restarter_instance_update_states(local_handle, inst, 1121*0Sstevel@tonic-gate RESTARTER_STATE_MAINT, RESTARTER_STATE_NONE, RERR_FAULT, 1122*0Sstevel@tonic-gate (char *)aux); 1123*0Sstevel@tonic-gate 1124*0Sstevel@tonic-gate if (!method_is_transient(inst, info->sf_method_type) && 1125*0Sstevel@tonic-gate inst->ri_i.i_primary_ctid != 0) 1126*0Sstevel@tonic-gate method_remove_contract(inst, B_TRUE, B_TRUE); 1127*0Sstevel@tonic-gate 1128*0Sstevel@tonic-gate out: 1129*0Sstevel@tonic-gate inst->ri_method_thread = 0; 1130*0Sstevel@tonic-gate MUTEX_UNLOCK(&inst->ri_lock); 1131*0Sstevel@tonic-gate (void) pthread_cond_broadcast(&inst->ri_method_cv); 1132*0Sstevel@tonic-gate 1133*0Sstevel@tonic-gate scf_instance_destroy(s_inst); 1134*0Sstevel@tonic-gate scf_handle_destroy(local_handle); 1135*0Sstevel@tonic-gate startd_free(info, sizeof (fork_info_t)); 1136*0Sstevel@tonic-gate return (NULL); 1137*0Sstevel@tonic-gate } 1138