1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License, Version 1.0 only 6 * (the "License"). You may not use this file except in compliance 7 * with the License. 8 * 9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 10 * or http://www.opensolaris.org/os/licensing. 11 * See the License for the specific language governing permissions 12 * and limitations under the License. 13 * 14 * When distributing Covered Code, include this CDDL HEADER in each 15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 16 * If applicable, add the following below this CDDL HEADER, with the 17 * fields enclosed by brackets "[]" replaced with your own identifying 18 * information: Portions Copyright [yyyy] [name of copyright owner] 19 * 20 * CDDL HEADER END 21 */ 22 /* 23 * Copyright 2005 Sun Microsystems, Inc. All rights reserved. 24 * Use is subject to license terms. 25 */ 26 27 #pragma ident "%Z%%M% %I% %E% SMI" 28 29 /* 30 * This file contains a set of routines used to perform wait based method 31 * reaping. 32 */ 33 34 #include <wait.h> 35 #include <sys/param.h> 36 #include <fcntl.h> 37 #include <libcontract.h> 38 #include <errno.h> 39 #include <libintl.h> 40 #include <unistd.h> 41 #include <stdlib.h> 42 #include <string.h> 43 #include <sys/resource.h> 44 #include "inetd_impl.h" 45 46 /* inetd's open file limit, set in method_init() */ 47 #define INETD_NOFILE_LIMIT RLIM_INFINITY 48 49 /* structure used to represent an active method process */ 50 typedef struct { 51 int fd; /* fd of process's /proc psinfo file */ 52 /* associated contract id if known, else -1 */ 53 ctid_t cid; 54 pid_t pid; 55 instance_t *inst; /* pointer to associated instance */ 56 instance_method_t method; /* the method type running */ 57 uu_list_node_t link; 58 } method_el_t; 59 60 61 static void unregister_method(method_el_t *); 62 63 64 /* list of currently executing method processes */ 65 static uu_list_pool_t *method_pool = NULL; 66 static uu_list_t *method_list = NULL; 67 68 /* 69 * File limit saved during initialization before modification, so that it can 70 * be reverted back to for inetd's exec'd methods. 71 */ 72 static struct rlimit saved_file_limit; 73 74 /* 75 * Setup structures used for method termination monitoring. 76 * Returns -1 if an allocation failure occurred, else 0. 77 */ 78 int 79 method_init(void) 80 { 81 struct rlimit rl; 82 83 debug_msg("Entering method_init"); 84 85 /* 86 * Save aside the old file limit and impose one large enough to support 87 * all the /proc file handles we could have open. 88 */ 89 90 (void) getrlimit(RLIMIT_NOFILE, &saved_file_limit); 91 92 rl.rlim_cur = rl.rlim_max = INETD_NOFILE_LIMIT; 93 if (setrlimit(RLIMIT_NOFILE, &rl) == -1) { 94 error_msg("Failed to set file limit: %s", strerror(errno)); 95 return (-1); 96 } 97 98 if ((method_pool = uu_list_pool_create("method_pool", 99 sizeof (method_el_t), offsetof(method_el_t, link), NULL, 100 UU_LIST_POOL_DEBUG)) == NULL) { 101 error_msg("%s: %s", gettext("Failed to create method pool"), 102 uu_strerror(uu_error())); 103 return (-1); 104 } 105 106 if ((method_list = uu_list_create(method_pool, NULL, 0)) == NULL) { 107 error_msg("%s: %s", 108 gettext("Failed to create method list"), 109 uu_strerror(uu_error())); 110 /* let method_fini() clean-up */ 111 return (-1); 112 } 113 114 return (0); 115 } 116 117 /* 118 * Tear-down structures created in method_init(). 119 */ 120 void 121 method_fini(void) 122 { 123 debug_msg("Entering method_fini"); 124 125 if (method_list != NULL) { 126 method_el_t *me; 127 128 while ((me = uu_list_first(method_list)) != NULL) 129 unregister_method(me); 130 131 (void) uu_list_destroy(method_list); 132 method_list = NULL; 133 } 134 if (method_pool != NULL) { 135 (void) uu_list_pool_destroy(method_pool); 136 method_pool = NULL; 137 } 138 139 /* revert file limit */ 140 method_preexec(); 141 } 142 143 /* 144 * Revert file limit back to pre-initialization one. This shouldn't fail as 145 * long as its called *after* descriptor cleanup. 146 */ 147 void 148 method_preexec(void) 149 { 150 (void) setrlimit(RLIMIT_NOFILE, &saved_file_limit); 151 } 152 153 154 /* 155 * Callback function that handles the timeout of an instance's method. 156 * 'arg' points at the method_el_t representing the method. 157 */ 158 /* ARGSUSED0 */ 159 static void 160 method_timeout(iu_tq_t *tq, void *arg) 161 { 162 method_el_t *mp = arg; 163 164 error_msg(gettext("The %s method of instance %s timed-out"), 165 methods[mp->method].name, mp->inst->fmri); 166 167 mp->inst->timer_id = -1; 168 169 if (mp->method == IM_START) { 170 process_start_term(mp->inst); 171 } else { 172 process_non_start_term(mp->inst, IMRET_FAILURE); 173 } 174 175 unregister_method(mp); 176 } 177 178 /* 179 * Registers the attributes of a running method passed as arguments so that 180 * the method's termination is noticed and any further processing of the 181 * associated instance is carried out. The function also sets up any 182 * necessary timers so we can detect hung methods. 183 * Returns -1 if either it failed to open the /proc psinfo file which is used 184 * to monitor the method process, it failed to setup a required timer or 185 * memory allocation failed; else 0. 186 */ 187 int 188 register_method(instance_t *ins, pid_t pid, ctid_t cid, instance_method_t mthd) 189 { 190 char path[MAXPATHLEN]; 191 int fd; 192 method_el_t *me; 193 194 debug_msg("Entering register_method: inst: %s, pid: %d, mthd: %s", 195 ins->fmri, pid, methods[mthd].name); 196 197 /* open /proc psinfo file of process to listen for POLLHUP events on */ 198 (void) snprintf(path, sizeof (path), "/proc/%u/psinfo", pid); 199 for (;;) { 200 if ((fd = open(path, O_RDONLY)) >= 0) { 201 break; 202 } else if (errno != EINTR) { 203 /* 204 * Don't output an error for ENOENT; we get this 205 * if a method has gone away whilst we were stopped, 206 * and we're now trying to re-listen for it. 207 */ 208 if (errno != ENOENT) { 209 error_msg(gettext("Failed to open %s: %s"), 210 path, strerror(errno)); 211 } 212 return (-1); 213 } 214 } 215 216 /* add method record to in-memory list */ 217 if ((me = calloc(1, sizeof (method_el_t))) == NULL) { 218 error_msg(strerror(errno)); 219 (void) close(fd); 220 return (-1); 221 } 222 me->fd = fd; 223 me->inst = (instance_t *)ins; 224 me->method = mthd; 225 me->pid = pid; 226 me->cid = cid; 227 228 /* register a timeout for the method, if required */ 229 if (mthd != IM_START) { 230 method_info_t *mi = ins->config->methods[mthd]; 231 232 if (mi->timeout > 0) { 233 assert(ins->timer_id == -1); 234 ins->timer_id = iu_schedule_timer(timer_queue, 235 mi->timeout, method_timeout, me); 236 if (ins->timer_id == -1) { 237 error_msg(gettext( 238 "Failed to schedule method timeout")); 239 free(me); 240 (void) close(fd); 241 return (-1); 242 } 243 } 244 } 245 246 /* 247 * Add fd of psinfo file to poll set, but pass 0 for events to 248 * poll for, so we should only get a POLLHUP event on the fd. 249 */ 250 if (set_pollfd(fd, 0) == -1) { 251 cancel_inst_timer(ins); 252 free(me); 253 (void) close(fd); 254 return (-1); 255 } 256 257 uu_list_node_init(me, &me->link, method_pool); 258 (void) uu_list_insert_after(method_list, NULL, me); 259 260 return (0); 261 } 262 263 /* 264 * A counterpart to register_method(), this function stops the monitoring of a 265 * method process for its termination. 266 */ 267 static void 268 unregister_method(method_el_t *me) 269 { 270 debug_msg("Entering unregister_method: inst: %s, pid: %d, mthd: %s", 271 me->inst->fmri, me->pid, methods[me->method].name); 272 273 /* cancel any timer associated with the method */ 274 if (me->inst->timer_id != -1) 275 cancel_inst_timer(me->inst); 276 277 /* stop polling on the psinfo file fd */ 278 clear_pollfd(me->fd); 279 (void) close(me->fd); 280 281 /* remove method record from list */ 282 uu_list_remove(method_list, me); 283 284 free(me); 285 } 286 287 /* 288 * Unregister all methods associated with instance 'inst'. 289 */ 290 void 291 unregister_instance_methods(const instance_t *inst) 292 { 293 method_el_t *me = uu_list_first(method_list); 294 295 while (me != NULL) { 296 if (me->inst == inst) { 297 method_el_t *tmp = me; 298 299 me = uu_list_next(method_list, me); 300 unregister_method(tmp); 301 } else { 302 me = uu_list_next(method_list, me); 303 } 304 } 305 } 306 307 /* 308 * Process any terminated methods. For each method determined to have 309 * terminated, the function determines its return value and calls the 310 * appropriate handling function, depending on the type of the method. 311 */ 312 void 313 process_terminated_methods(void) 314 { 315 method_el_t *me = uu_list_first(method_list); 316 317 debug_msg("Entering process_terminated_methods"); 318 319 while (me != NULL) { 320 struct pollfd *pfd; 321 pid_t pid; 322 int status; 323 int ret; 324 method_el_t *tmp; 325 326 pfd = find_pollfd(me->fd); 327 328 /* 329 * We expect to get a POLLHUP back on the fd of the process's 330 * open psinfo file from /proc when the method terminates. 331 * A POLLERR could(?) mask a POLLHUP, so handle this 332 * also. 333 */ 334 if ((pfd->revents & (POLLHUP|POLLERR)) == 0) { 335 me = uu_list_next(method_list, me); 336 continue; 337 } 338 339 /* get the method's exit code (no need to loop for EINTR) */ 340 pid = waitpid(me->pid, &status, WNOHANG); 341 342 switch (pid) { 343 case 0: /* child still around */ 344 /* 345 * Either poll() is sending us invalid POLLHUP events 346 * or is flagging a POLLERR on the fd. Neither should 347 * happen, but in the event they do, ignore this fd 348 * this time around and wait out the termination 349 * of its associated method. This may result in 350 * inetd swiftly looping in event_loop(), but means 351 * we don't miss the termination of a method. 352 */ 353 me = uu_list_next(method_list, me); 354 continue; 355 356 case -1: /* non-existent child */ 357 assert(errno == ECHILD); 358 /* 359 * the method must not be owned by inetd due to it 360 * persisting over an inetd restart. Let's assume the 361 * best, that it was successful. 362 */ 363 ret = IMRET_SUCCESS; 364 break; 365 366 default: /* child terminated */ 367 if (WIFEXITED(status)) { 368 ret = WEXITSTATUS(status); 369 debug_msg("process %d of instance %s returned " 370 "%d", pid, me->inst->fmri, ret); 371 } else if (WIFSIGNALED(status)) { 372 /* 373 * Terminated by signal. This may be due 374 * to a kill that we sent from a disable or 375 * offline event. We flag it as a failure, but 376 * this flagged failure will only be processed 377 * in the case of non-start methods, or when 378 * the instance is still enabled. 379 */ 380 debug_msg("process %d of instance %s exited " 381 "due to signal %d", pid, me->inst->fmri, 382 WTERMSIG(status)); 383 ret = IMRET_FAILURE; 384 } else { 385 /* 386 * Can we actually get here? Don't think so. 387 * Treat it as a failure, anyway. 388 */ 389 debug_msg("waitpid() for %s method of " 390 "instance %s returned %d", 391 methods[me->method].name, me->inst->fmri, 392 status); 393 ret = IMRET_FAILURE; 394 } 395 } 396 397 remove_method_ids(me->inst, me->pid, me->cid, me->method); 398 399 /* continue state transition processing of the instance */ 400 if (me->method != IM_START) { 401 process_non_start_term(me->inst, ret); 402 } else { 403 process_start_term(me->inst); 404 } 405 406 if (me->cid != -1) 407 (void) abandon_contract(me->cid); 408 409 tmp = me; 410 me = uu_list_next(method_list, me); 411 unregister_method(tmp); 412 } 413 } 414