1/* Licensed to the Apache Software Foundation (ASF) under one or more 2 * contributor license agreements. See the NOTICE file distributed with 3 * this work for additional information regarding copyright ownership. 4 * The ASF licenses this file to You under the Apache License, Version 2.0 5 * (the "License"); you may not use this file except in compliance with 6 * the License. You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17/* The purpose of this file is to store the code that MOST mpm's will need 18 * this does not mean a function only goes into this file if every MPM needs 19 * it. It means that if a function is needed by more than one MPM, and 20 * future maintenance would be served by making the code common, then the 21 * function belongs here. 22 * 23 * This is going in src/main because it is not platform specific, it is 24 * specific to multi-process servers, but NOT to Unix. Which is why it 25 * does not belong in src/os/unix 26 */ 27 28#include "apr.h" 29#include "apr_thread_proc.h" 30#include "apr_signal.h" 31#include "apr_strings.h" 32#define APR_WANT_STRFUNC 33#include "apr_want.h" 34#include "apr_getopt.h" 35#include "apr_optional.h" 36#include "apr_allocator.h" 37 38#include "httpd.h" 39#include "http_config.h" 40#include "http_core.h" 41#include "http_log.h" 42#include "http_main.h" 43#include "mpm_common.h" 44#include "mod_core.h" 45#include "ap_mpm.h" 46#include "ap_listen.h" 47#include "util_mutex.h" 48 49#include "scoreboard.h" 50 51#ifdef HAVE_PWD_H 52#include <pwd.h> 53#endif 54#ifdef HAVE_GRP_H 55#include <grp.h> 56#endif 57#if APR_HAVE_UNISTD_H 58#include <unistd.h> 59#endif 60 61/* we know core's module_index is 0 */ 62#undef APLOG_MODULE_INDEX 63#define APLOG_MODULE_INDEX AP_CORE_MODULE_INDEX 64 65#if AP_ENABLE_EXCEPTION_HOOK 66APR_HOOK_STRUCT( 67 APR_HOOK_LINK(fatal_exception) 68 APR_HOOK_LINK(monitor) 69 APR_HOOK_LINK(drop_privileges) 70 APR_HOOK_LINK(mpm) 71 APR_HOOK_LINK(mpm_query) 72 APR_HOOK_LINK(mpm_register_timed_callback) 73 APR_HOOK_LINK(mpm_get_name) 74 APR_HOOK_LINK(end_generation) 75 APR_HOOK_LINK(child_status) 76) 77AP_IMPLEMENT_HOOK_RUN_ALL(int, fatal_exception, 78 (ap_exception_info_t *ei), (ei), OK, DECLINED) 79#else 80APR_HOOK_STRUCT( 81 APR_HOOK_LINK(monitor) 82 APR_HOOK_LINK(drop_privileges) 83 APR_HOOK_LINK(mpm) 84 APR_HOOK_LINK(mpm_query) 85 APR_HOOK_LINK(mpm_register_timed_callback) 86 APR_HOOK_LINK(mpm_get_name) 87 APR_HOOK_LINK(end_generation) 88 APR_HOOK_LINK(child_status) 89) 90#endif 91AP_IMPLEMENT_HOOK_RUN_ALL(int, monitor, 92 (apr_pool_t *p, server_rec *s), (p, s), OK, DECLINED) 93AP_IMPLEMENT_HOOK_RUN_ALL(int, drop_privileges, 94 (apr_pool_t * pchild, server_rec * s), 95 (pchild, s), OK, DECLINED) 96AP_IMPLEMENT_HOOK_RUN_FIRST(int, mpm, 97 (apr_pool_t *pconf, apr_pool_t *plog, server_rec *s), 98 (pconf, plog, s), DECLINED) 99AP_IMPLEMENT_HOOK_RUN_FIRST(int, mpm_query, 100 (int query_code, int *result, apr_status_t *_rv), 101 (query_code, result, _rv), DECLINED) 102AP_IMPLEMENT_HOOK_RUN_FIRST(apr_status_t, mpm_register_timed_callback, 103 (apr_time_t t, ap_mpm_callback_fn_t *cbfn, void *baton), 104 (t, cbfn, baton), APR_ENOTIMPL) 105AP_IMPLEMENT_HOOK_VOID(end_generation, 106 (server_rec *s, ap_generation_t gen), 107 (s, gen)) 108AP_IMPLEMENT_HOOK_VOID(child_status, 109 (server_rec *s, pid_t pid, ap_generation_t gen, int slot, mpm_child_status status), 110 (s,pid,gen,slot,status)) 111 112/* hooks with no args are implemented last, after disabling APR hook probes */ 113#if defined(APR_HOOK_PROBES_ENABLED) 114#undef APR_HOOK_PROBES_ENABLED 115#undef APR_HOOK_PROBE_ENTRY 116#define APR_HOOK_PROBE_ENTRY(ud,ns,name,args) 117#undef APR_HOOK_PROBE_RETURN 118#define APR_HOOK_PROBE_RETURN(ud,ns,name,rv,args) 119#undef APR_HOOK_PROBE_INVOKE 120#define APR_HOOK_PROBE_INVOKE(ud,ns,name,src,args) 121#undef APR_HOOK_PROBE_COMPLETE 122#define APR_HOOK_PROBE_COMPLETE(ud,ns,name,src,rv,args) 123#undef APR_HOOK_INT_DCL_UD 124#define APR_HOOK_INT_DCL_UD 125#endif 126AP_IMPLEMENT_HOOK_RUN_FIRST(const char *, mpm_get_name, 127 (void), 128 (), NULL) 129 130typedef struct mpm_gen_info_t { 131 APR_RING_ENTRY(mpm_gen_info_t) link; 132 int gen; /* which gen? */ 133 int active; /* number of active processes */ 134 int done; /* gen finished? (whether or not active processes) */ 135} mpm_gen_info_t; 136 137APR_RING_HEAD(mpm_gen_info_head_t, mpm_gen_info_t); 138static struct mpm_gen_info_head_t *geninfo, *unused_geninfo; 139static int gen_head_init; /* yuck */ 140 141/* variables representing config directives implemented here */ 142AP_DECLARE_DATA const char *ap_pid_fname; 143AP_DECLARE_DATA int ap_max_requests_per_child; 144AP_DECLARE_DATA char ap_coredump_dir[MAX_STRING_LEN]; 145AP_DECLARE_DATA int ap_coredumpdir_configured; 146AP_DECLARE_DATA int ap_graceful_shutdown_timeout; 147AP_DECLARE_DATA apr_uint32_t ap_max_mem_free; 148AP_DECLARE_DATA apr_size_t ap_thread_stacksize; 149 150#define ALLOCATOR_MAX_FREE_DEFAULT (2048*1024) 151 152/* Set defaults for config directives implemented here. This is 153 * called from core's pre-config hook, so MPMs which need to override 154 * one of these should run their pre-config hook after that of core. 155 */ 156void mpm_common_pre_config(apr_pool_t *pconf) 157{ 158 ap_pid_fname = DEFAULT_PIDLOG; 159 ap_max_requests_per_child = 0; /* unlimited */ 160 apr_cpystrn(ap_coredump_dir, ap_server_root, sizeof(ap_coredump_dir)); 161 ap_coredumpdir_configured = 0; 162 ap_graceful_shutdown_timeout = 0; /* unlimited */ 163 ap_max_mem_free = ALLOCATOR_MAX_FREE_DEFAULT; 164 ap_thread_stacksize = 0; /* use system default */ 165} 166 167/* number of calls to wait_or_timeout between writable probes */ 168#ifndef INTERVAL_OF_WRITABLE_PROBES 169#define INTERVAL_OF_WRITABLE_PROBES 10 170#endif 171static int wait_or_timeout_counter; 172 173AP_DECLARE(void) ap_wait_or_timeout(apr_exit_why_e *status, int *exitcode, 174 apr_proc_t *ret, apr_pool_t *p, 175 server_rec *s) 176{ 177 apr_status_t rv; 178 179 ++wait_or_timeout_counter; 180 if (wait_or_timeout_counter == INTERVAL_OF_WRITABLE_PROBES) { 181 wait_or_timeout_counter = 0; 182 ap_run_monitor(p, s); 183 } 184 185 rv = apr_proc_wait_all_procs(ret, exitcode, status, APR_NOWAIT, p); 186 if (APR_STATUS_IS_EINTR(rv)) { 187 ret->pid = -1; 188 return; 189 } 190 191 if (APR_STATUS_IS_CHILD_DONE(rv)) { 192 return; 193 } 194 195 apr_sleep(apr_time_from_sec(1)); 196 ret->pid = -1; 197 return; 198} 199 200#if defined(TCP_NODELAY) 201void ap_sock_disable_nagle(apr_socket_t *s) 202{ 203 /* The Nagle algorithm says that we should delay sending partial 204 * packets in hopes of getting more data. We don't want to do 205 * this; we are not telnet. There are bad interactions between 206 * persistent connections and Nagle's algorithm that have very severe 207 * performance penalties. (Failing to disable Nagle is not much of a 208 * problem with simple HTTP.) 209 * 210 * In spite of these problems, failure here is not a shooting offense. 211 */ 212 apr_status_t status = apr_socket_opt_set(s, APR_TCP_NODELAY, 1); 213 214 if (status != APR_SUCCESS) { 215 ap_log_error(APLOG_MARK, APLOG_WARNING, status, ap_server_conf, APLOGNO(00542) 216 "apr_socket_opt_set: (TCP_NODELAY)"); 217 } 218} 219#endif 220 221#ifdef HAVE_GETPWNAM 222AP_DECLARE(uid_t) ap_uname2id(const char *name) 223{ 224 struct passwd *ent; 225 226 if (name[0] == '#') 227 return (atoi(&name[1])); 228 229 if (!(ent = getpwnam(name))) { 230 ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, APLOGNO(00543) 231 "%s: bad user name %s", ap_server_argv0, name); 232 exit(1); 233 } 234 235 return (ent->pw_uid); 236} 237#endif 238 239#ifdef HAVE_GETGRNAM 240AP_DECLARE(gid_t) ap_gname2id(const char *name) 241{ 242 struct group *ent; 243 244 if (name[0] == '#') 245 return (atoi(&name[1])); 246 247 if (!(ent = getgrnam(name))) { 248 ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, APLOGNO(00544) 249 "%s: bad group name %s", ap_server_argv0, name); 250 exit(1); 251 } 252 253 return (ent->gr_gid); 254} 255#endif 256 257#ifndef HAVE_INITGROUPS 258int initgroups(const char *name, gid_t basegid) 259{ 260#if defined(_OSD_POSIX) || defined(OS2) || defined(WIN32) || defined(NETWARE) 261 return 0; 262#else 263 gid_t groups[NGROUPS_MAX]; 264 struct group *g; 265 int index = 0; 266 267 setgrent(); 268 269 groups[index++] = basegid; 270 271 while (index < NGROUPS_MAX && ((g = getgrent()) != NULL)) { 272 if (g->gr_gid != basegid) { 273 char **names; 274 275 for (names = g->gr_mem; *names != NULL; ++names) { 276 if (!strcmp(*names, name)) 277 groups[index++] = g->gr_gid; 278 } 279 } 280 } 281 282 endgrent(); 283 284 return setgroups(index, groups); 285#endif 286} 287#endif /* def HAVE_INITGROUPS */ 288 289/* standard mpm configuration handling */ 290 291const char *ap_mpm_set_pidfile(cmd_parms *cmd, void *dummy, 292 const char *arg) 293{ 294 const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); 295 if (err != NULL) { 296 return err; 297 } 298 299 if (cmd->server->is_virtual) { 300 return "PidFile directive not allowed in <VirtualHost>"; 301 } 302 303 ap_pid_fname = arg; 304 return NULL; 305} 306 307void ap_mpm_dump_pidfile(apr_pool_t *p, apr_file_t *out) 308{ 309 apr_file_printf(out, "PidFile: \"%s\"\n", 310 ap_server_root_relative(p, ap_pid_fname)); 311} 312 313const char *ap_mpm_set_max_requests(cmd_parms *cmd, void *dummy, 314 const char *arg) 315{ 316 const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); 317 if (err != NULL) { 318 return err; 319 } 320 321 if (!strcasecmp(cmd->cmd->name, "MaxRequestsPerChild")) { 322 ap_log_error(APLOG_MARK, APLOG_INFO, 0, NULL, APLOGNO(00545) 323 "MaxRequestsPerChild is deprecated, use " 324 "MaxConnectionsPerChild instead."); 325 } 326 327 ap_max_requests_per_child = atoi(arg); 328 329 return NULL; 330} 331 332const char *ap_mpm_set_coredumpdir(cmd_parms *cmd, void *dummy, 333 const char *arg) 334{ 335 apr_finfo_t finfo; 336 const char *fname; 337 const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); 338 if (err != NULL) { 339 return err; 340 } 341 342 fname = ap_server_root_relative(cmd->pool, arg); 343 if (!fname) { 344 return apr_pstrcat(cmd->pool, "Invalid CoreDumpDirectory path ", 345 arg, NULL); 346 } 347 if (apr_stat(&finfo, fname, APR_FINFO_TYPE, cmd->pool) != APR_SUCCESS) { 348 return apr_pstrcat(cmd->pool, "CoreDumpDirectory ", fname, 349 " does not exist", NULL); 350 } 351 if (finfo.filetype != APR_DIR) { 352 return apr_pstrcat(cmd->pool, "CoreDumpDirectory ", fname, 353 " is not a directory", NULL); 354 } 355 apr_cpystrn(ap_coredump_dir, fname, sizeof(ap_coredump_dir)); 356 ap_coredumpdir_configured = 1; 357 return NULL; 358} 359 360AP_DECLARE(const char *)ap_mpm_set_graceful_shutdown(cmd_parms *cmd, 361 void *dummy, 362 const char *arg) 363{ 364 const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); 365 if (err != NULL) { 366 return err; 367 } 368 ap_graceful_shutdown_timeout = atoi(arg); 369 return NULL; 370} 371 372const char *ap_mpm_set_max_mem_free(cmd_parms *cmd, void *dummy, 373 const char *arg) 374{ 375 long value; 376 const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); 377 if (err != NULL) { 378 return err; 379 } 380 381 errno = 0; 382 value = strtol(arg, NULL, 10); 383 if (value < 0 || errno == ERANGE) 384 return apr_pstrcat(cmd->pool, "Invalid MaxMemFree value: ", 385 arg, NULL); 386 387 ap_max_mem_free = (apr_uint32_t)value * 1024; 388 389 return NULL; 390} 391 392const char *ap_mpm_set_thread_stacksize(cmd_parms *cmd, void *dummy, 393 const char *arg) 394{ 395 long value; 396 const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); 397 if (err != NULL) { 398 return err; 399 } 400 401 errno = 0; 402 value = strtol(arg, NULL, 10); 403 if (value < 0 || errno == ERANGE) 404 return apr_pstrcat(cmd->pool, "Invalid ThreadStackSize value: ", 405 arg, NULL); 406 407 ap_thread_stacksize = (apr_size_t)value; 408 409 return NULL; 410} 411 412AP_DECLARE(apr_status_t) ap_mpm_query(int query_code, int *result) 413{ 414 apr_status_t rv; 415 416 if (ap_run_mpm_query(query_code, result, &rv) == DECLINED) { 417 rv = APR_EGENERAL; 418 } 419 420 return rv; 421} 422 423static void end_gen(mpm_gen_info_t *gi) 424{ 425 ap_log_error(APLOG_MARK, APLOG_TRACE4, 0, ap_server_conf, 426 "end of generation %d", gi->gen); 427 ap_run_end_generation(ap_server_conf, gi->gen); 428 APR_RING_REMOVE(gi, link); 429 APR_RING_INSERT_HEAD(unused_geninfo, gi, mpm_gen_info_t, link); 430} 431 432apr_status_t ap_mpm_end_gen_helper(void *unused) /* cleanup on pconf */ 433{ 434 int gen = ap_config_generation - 1; /* differs from MPM generation */ 435 mpm_gen_info_t *cur; 436 437 if (geninfo == NULL) { 438 /* initial pconf teardown, MPM hasn't run */ 439 return APR_SUCCESS; 440 } 441 442 cur = APR_RING_FIRST(geninfo); 443 while (cur != APR_RING_SENTINEL(geninfo, mpm_gen_info_t, link) && 444 cur->gen != gen) { 445 cur = APR_RING_NEXT(cur, link); 446 } 447 448 if (cur == APR_RING_SENTINEL(geninfo, mpm_gen_info_t, link)) { 449 /* last child of generation already exited */ 450 ap_log_error(APLOG_MARK, APLOG_TRACE4, 0, ap_server_conf, 451 "no record of generation %d", gen); 452 } 453 else { 454 cur->done = 1; 455 if (cur->active == 0) { 456 end_gen(cur); 457 } 458 } 459 460 return APR_SUCCESS; 461} 462 463/* core's child-status hook 464 * tracks number of remaining children per generation and 465 * runs the end-generation hook when the last child of 466 * a generation exits 467 */ 468void ap_core_child_status(server_rec *s, pid_t pid, 469 ap_generation_t gen, int slot, 470 mpm_child_status status) 471{ 472 mpm_gen_info_t *cur; 473 const char *status_msg = "unknown status"; 474 475 if (!gen_head_init) { /* where to run this? */ 476 gen_head_init = 1; 477 geninfo = apr_pcalloc(s->process->pool, sizeof *geninfo); 478 unused_geninfo = apr_pcalloc(s->process->pool, sizeof *unused_geninfo); 479 APR_RING_INIT(geninfo, mpm_gen_info_t, link); 480 APR_RING_INIT(unused_geninfo, mpm_gen_info_t, link); 481 } 482 483 cur = APR_RING_FIRST(geninfo); 484 while (cur != APR_RING_SENTINEL(geninfo, mpm_gen_info_t, link) && 485 cur->gen != gen) { 486 cur = APR_RING_NEXT(cur, link); 487 } 488 489 switch(status) { 490 case MPM_CHILD_STARTED: 491 status_msg = "started"; 492 if (cur == APR_RING_SENTINEL(geninfo, mpm_gen_info_t, link)) { 493 /* first child for this generation */ 494 if (!APR_RING_EMPTY(unused_geninfo, mpm_gen_info_t, link)) { 495 cur = APR_RING_FIRST(unused_geninfo); 496 APR_RING_REMOVE(cur, link); 497 cur->active = cur->done = 0; 498 } 499 else { 500 cur = apr_pcalloc(s->process->pool, sizeof *cur); 501 } 502 cur->gen = gen; 503 APR_RING_ELEM_INIT(cur, link); 504 APR_RING_INSERT_HEAD(geninfo, cur, mpm_gen_info_t, link); 505 } 506 ap_random_parent_after_fork(); 507 ++cur->active; 508 break; 509 case MPM_CHILD_EXITED: 510 status_msg = "exited"; 511 if (cur == APR_RING_SENTINEL(geninfo, mpm_gen_info_t, link)) { 512 ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(00546) 513 "no record of generation %d of exiting child %" APR_PID_T_FMT, 514 gen, pid); 515 } 516 else { 517 --cur->active; 518 if (!cur->active && cur->done) { /* no children, server has stopped/restarted */ 519 end_gen(cur); 520 } 521 } 522 break; 523 case MPM_CHILD_LOST_SLOT: 524 status_msg = "lost slot"; 525 /* we don't track by slot, so it doesn't matter */ 526 break; 527 } 528 ap_log_error(APLOG_MARK, APLOG_TRACE4, 0, s, 529 "mpm child %" APR_PID_T_FMT " (gen %d/slot %d) %s", 530 pid, gen, slot, status_msg); 531} 532 533AP_DECLARE(apr_status_t) ap_mpm_register_timed_callback(apr_time_t t, ap_mpm_callback_fn_t *cbfn, void *baton) 534{ 535 return ap_run_mpm_register_timed_callback(t, cbfn, baton); 536} 537 538AP_DECLARE(const char *)ap_show_mpm(void) 539{ 540 const char *name = ap_run_mpm_get_name(); 541 542 if (!name) { 543 name = ""; 544 } 545 546 return name; 547} 548 549AP_DECLARE(const char *)ap_check_mpm(void) 550{ 551 static const char *last_mpm_name = NULL; 552 553 if (!_hooks.link_mpm || _hooks.link_mpm->nelts == 0) 554 return "No MPM loaded."; 555 else if (_hooks.link_mpm->nelts > 1) 556 return "More than one MPM loaded."; 557 558 if (last_mpm_name) { 559 if (strcmp(last_mpm_name, ap_show_mpm())) { 560 return "The MPM cannot be changed during restart."; 561 } 562 } 563 else { 564 last_mpm_name = apr_pstrdup(ap_pglobal, ap_show_mpm()); 565 } 566 567 return NULL; 568} 569