| /* | |
| * Copyright (C) Igor Sysoev | |
| * Copyright (C) Nginx, Inc. | |
| */ | |
| #include <ngx_config.h> | |
| #include <ngx_core.h> | |
| #include <ngx_event.h> | |
| #include <ngx_channel.h> | |
| typedef struct { | |
| int signo; | |
| char *signame; | |
| char *name; | |
| void (*handler)(int signo, siginfo_t *siginfo, void *ucontext); | |
| } ngx_signal_t; | |
| static void ngx_execute_proc(ngx_cycle_t *cycle, void *data); | |
| static void ngx_signal_handler(int signo, siginfo_t *siginfo, void *ucontext); | |
| static void ngx_process_get_status(void); | |
| static void ngx_unlock_mutexes(ngx_pid_t pid); | |
| int ngx_argc; | |
| char **ngx_argv; | |
| char **ngx_os_argv; | |
| ngx_int_t ngx_process_slot; | |
| ngx_socket_t ngx_channel; | |
| ngx_int_t ngx_last_process; | |
| ngx_process_t ngx_processes[NGX_MAX_PROCESSES]; | |
| ngx_signal_t signals[] = { | |
| { ngx_signal_value(NGX_RECONFIGURE_SIGNAL), | |
| "SIG" ngx_value(NGX_RECONFIGURE_SIGNAL), | |
| "reload", | |
| ngx_signal_handler }, | |
| { ngx_signal_value(NGX_REOPEN_SIGNAL), | |
| "SIG" ngx_value(NGX_REOPEN_SIGNAL), | |
| "reopen", | |
| ngx_signal_handler }, | |
| { ngx_signal_value(NGX_NOACCEPT_SIGNAL), | |
| "SIG" ngx_value(NGX_NOACCEPT_SIGNAL), | |
| "", | |
| ngx_signal_handler }, | |
| { ngx_signal_value(NGX_TERMINATE_SIGNAL), | |
| "SIG" ngx_value(NGX_TERMINATE_SIGNAL), | |
| "stop", | |
| ngx_signal_handler }, | |
| { ngx_signal_value(NGX_SHUTDOWN_SIGNAL), | |
| "SIG" ngx_value(NGX_SHUTDOWN_SIGNAL), | |
| "quit", | |
| ngx_signal_handler }, | |
| { ngx_signal_value(NGX_CHANGEBIN_SIGNAL), | |
| "SIG" ngx_value(NGX_CHANGEBIN_SIGNAL), | |
| "", | |
| ngx_signal_handler }, | |
| { SIGALRM, "SIGALRM", "", ngx_signal_handler }, | |
| { SIGINT, "SIGINT", "", ngx_signal_handler }, | |
| { SIGIO, "SIGIO", "", ngx_signal_handler }, | |
| { SIGCHLD, "SIGCHLD", "", ngx_signal_handler }, | |
| { SIGSYS, "SIGSYS, SIG_IGN", "", NULL }, | |
| { SIGPIPE, "SIGPIPE, SIG_IGN", "", NULL }, | |
| { 0, NULL, "", NULL } | |
| }; | |
| ngx_pid_t | |
| ngx_spawn_process(ngx_cycle_t *cycle, ngx_spawn_proc_pt proc, void *data, | |
| char *name, ngx_int_t respawn) | |
| { | |
| u_long on; | |
| ngx_pid_t pid; | |
| ngx_int_t s; | |
| if (respawn >= 0) { | |
| s = respawn; | |
| } else { | |
| for (s = 0; s < ngx_last_process; s++) { | |
| if (ngx_processes[s].pid == -1) { | |
| break; | |
| } | |
| } | |
| if (s == NGX_MAX_PROCESSES) { | |
| ngx_log_error(NGX_LOG_ALERT, cycle->log, 0, | |
| "no more than %d processes can be spawned", | |
| NGX_MAX_PROCESSES); | |
| return NGX_INVALID_PID; | |
| } | |
| } | |
| if (respawn != NGX_PROCESS_DETACHED) { | |
| /* Solaris 9 still has no AF_LOCAL */ | |
| if (socketpair(AF_UNIX, SOCK_STREAM, 0, ngx_processes[s].channel) == -1) | |
| { | |
| ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno, | |
| "socketpair() failed while spawning \"%s\"", name); | |
| return NGX_INVALID_PID; | |
| } | |
| ngx_log_debug2(NGX_LOG_DEBUG_CORE, cycle->log, 0, | |
| "channel %d:%d", | |
| ngx_processes[s].channel[0], | |
| ngx_processes[s].channel[1]); | |
| if (ngx_nonblocking(ngx_processes[s].channel[0]) == -1) { | |
| ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno, | |
| ngx_nonblocking_n " failed while spawning \"%s\"", | |
| name); | |
| ngx_close_channel(ngx_processes[s].channel, cycle->log); | |
| return NGX_INVALID_PID; | |
| } | |
| if (ngx_nonblocking(ngx_processes[s].channel[1]) == -1) { | |
| ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno, | |
| ngx_nonblocking_n " failed while spawning \"%s\"", | |
| name); | |
| ngx_close_channel(ngx_processes[s].channel, cycle->log); | |
| return NGX_INVALID_PID; | |
| } | |
| on = 1; | |
| if (ioctl(ngx_processes[s].channel[0], FIOASYNC, &on) == -1) { | |
| ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno, | |
| "ioctl(FIOASYNC) failed while spawning \"%s\"", name); | |
| ngx_close_channel(ngx_processes[s].channel, cycle->log); | |
| return NGX_INVALID_PID; | |
| } | |
| if (fcntl(ngx_processes[s].channel[0], F_SETOWN, ngx_pid) == -1) { | |
| ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno, | |
| "fcntl(F_SETOWN) failed while spawning \"%s\"", name); | |
| ngx_close_channel(ngx_processes[s].channel, cycle->log); | |
| return NGX_INVALID_PID; | |
| } | |
| if (fcntl(ngx_processes[s].channel[0], F_SETFD, FD_CLOEXEC) == -1) { | |
| ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno, | |
| "fcntl(FD_CLOEXEC) failed while spawning \"%s\"", | |
| name); | |
| ngx_close_channel(ngx_processes[s].channel, cycle->log); | |
| return NGX_INVALID_PID; | |
| } | |
| if (fcntl(ngx_processes[s].channel[1], F_SETFD, FD_CLOEXEC) == -1) { | |
| ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno, | |
| "fcntl(FD_CLOEXEC) failed while spawning \"%s\"", | |
| name); | |
| ngx_close_channel(ngx_processes[s].channel, cycle->log); | |
| return NGX_INVALID_PID; | |
| } | |
| ngx_channel = ngx_processes[s].channel[1]; | |
| } else { | |
| ngx_processes[s].channel[0] = -1; | |
| ngx_processes[s].channel[1] = -1; | |
| } | |
| ngx_process_slot = s; | |
| pid = fork(); | |
| switch (pid) { | |
| case -1: | |
| ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno, | |
| "fork() failed while spawning \"%s\"", name); | |
| ngx_close_channel(ngx_processes[s].channel, cycle->log); | |
| return NGX_INVALID_PID; | |
| case 0: | |
| ngx_pid = ngx_getpid(); | |
| proc(cycle, data); | |
| break; | |
| default: | |
| break; | |
| } | |
| ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "start %s %P", name, pid); | |
| ngx_processes[s].pid = pid; | |
| ngx_processes[s].exited = 0; | |
| if (respawn >= 0) { | |
| return pid; | |
| } | |
| ngx_processes[s].proc = proc; | |
| ngx_processes[s].data = data; | |
| ngx_processes[s].name = name; | |
| ngx_processes[s].exiting = 0; | |
| switch (respawn) { | |
| case NGX_PROCESS_NORESPAWN: | |
| ngx_processes[s].respawn = 0; | |
| ngx_processes[s].just_spawn = 0; | |
| ngx_processes[s].detached = 0; | |
| break; | |
| case NGX_PROCESS_JUST_SPAWN: | |
| ngx_processes[s].respawn = 0; | |
| ngx_processes[s].just_spawn = 1; | |
| ngx_processes[s].detached = 0; | |
| break; | |
| case NGX_PROCESS_RESPAWN: | |
| ngx_processes[s].respawn = 1; | |
| ngx_processes[s].just_spawn = 0; | |
| ngx_processes[s].detached = 0; | |
| break; | |
| case NGX_PROCESS_JUST_RESPAWN: | |
| ngx_processes[s].respawn = 1; | |
| ngx_processes[s].just_spawn = 1; | |
| ngx_processes[s].detached = 0; | |
| break; | |
| case NGX_PROCESS_DETACHED: | |
| ngx_processes[s].respawn = 0; | |
| ngx_processes[s].just_spawn = 0; | |
| ngx_processes[s].detached = 1; | |
| break; | |
| } | |
| if (s == ngx_last_process) { | |
| ngx_last_process++; | |
| } | |
| return pid; | |
| } | |
| ngx_pid_t | |
| ngx_execute(ngx_cycle_t *cycle, ngx_exec_ctx_t *ctx) | |
| { | |
| return ngx_spawn_process(cycle, ngx_execute_proc, ctx, ctx->name, | |
| NGX_PROCESS_DETACHED); | |
| } | |
| static void | |
| ngx_execute_proc(ngx_cycle_t *cycle, void *data) | |
| { | |
| ngx_exec_ctx_t *ctx = data; | |
| if (execve(ctx->path, ctx->argv, ctx->envp) == -1) { | |
| ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno, | |
| "execve() failed while executing %s \"%s\"", | |
| ctx->name, ctx->path); | |
| } | |
| exit(1); | |
| } | |
| ngx_int_t | |
| ngx_init_signals(ngx_log_t *log) | |
| { | |
| ngx_signal_t *sig; | |
| struct sigaction sa; | |
| for (sig = signals; sig->signo != 0; sig++) { | |
| ngx_memzero(&sa, sizeof(struct sigaction)); | |
| if (sig->handler) { | |
| sa.sa_sigaction = sig->handler; | |
| sa.sa_flags = SA_SIGINFO; | |
| } else { | |
| sa.sa_handler = SIG_IGN; | |
| } | |
| sigemptyset(&sa.sa_mask); | |
| if (sigaction(sig->signo, &sa, NULL) == -1) { | |
| #if (NGX_VALGRIND) | |
| ngx_log_error(NGX_LOG_ALERT, log, ngx_errno, | |
| "sigaction(%s) failed, ignored", sig->signame); | |
| #else | |
| ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, | |
| "sigaction(%s) failed", sig->signame); | |
| return NGX_ERROR; | |
| #endif | |
| } | |
| } | |
| return NGX_OK; | |
| } | |
| static void | |
| ngx_signal_handler(int signo, siginfo_t *siginfo, void *ucontext) | |
| { | |
| char *action; | |
| ngx_int_t ignore; | |
| ngx_err_t err; | |
| ngx_signal_t *sig; | |
| ignore = 0; | |
| err = ngx_errno; | |
| for (sig = signals; sig->signo != 0; sig++) { | |
| if (sig->signo == signo) { | |
| break; | |
| } | |
| } | |
| ngx_time_sigsafe_update(); | |
| action = ""; | |
| switch (ngx_process) { | |
| case NGX_PROCESS_MASTER: | |
| case NGX_PROCESS_SINGLE: | |
| switch (signo) { | |
| case ngx_signal_value(NGX_SHUTDOWN_SIGNAL): | |
| ngx_quit = 1; | |
| action = ", shutting down"; | |
| break; | |
| case ngx_signal_value(NGX_TERMINATE_SIGNAL): | |
| case SIGINT: | |
| ngx_terminate = 1; | |
| action = ", exiting"; | |
| break; | |
| case ngx_signal_value(NGX_NOACCEPT_SIGNAL): | |
| if (ngx_daemonized) { | |
| ngx_noaccept = 1; | |
| action = ", stop accepting connections"; | |
| } | |
| break; | |
| case ngx_signal_value(NGX_RECONFIGURE_SIGNAL): | |
| ngx_reconfigure = 1; | |
| action = ", reconfiguring"; | |
| break; | |
| case ngx_signal_value(NGX_REOPEN_SIGNAL): | |
| ngx_reopen = 1; | |
| action = ", reopening logs"; | |
| break; | |
| case ngx_signal_value(NGX_CHANGEBIN_SIGNAL): | |
| if (getppid() > 1 || ngx_new_binary > 0) { | |
| /* | |
| * Ignore the signal in the new binary if its parent is | |
| * not the init process, i.e. the old binary's process | |
| * is still running. Or ignore the signal in the old binary's | |
| * process if the new binary's process is already running. | |
| */ | |
| action = ", ignoring"; | |
| ignore = 1; | |
| break; | |
| } | |
| ngx_change_binary = 1; | |
| action = ", changing binary"; | |
| break; | |
| case SIGALRM: | |
| ngx_sigalrm = 1; | |
| break; | |
| case SIGIO: | |
| ngx_sigio = 1; | |
| break; | |
| case SIGCHLD: | |
| ngx_reap = 1; | |
| break; | |
| } | |
| break; | |
| case NGX_PROCESS_WORKER: | |
| case NGX_PROCESS_HELPER: | |
| switch (signo) { | |
| case ngx_signal_value(NGX_NOACCEPT_SIGNAL): | |
| if (!ngx_daemonized) { | |
| break; | |
| } | |
| ngx_debug_quit = 1; | |
| /* fall through */ | |
| case ngx_signal_value(NGX_SHUTDOWN_SIGNAL): | |
| ngx_quit = 1; | |
| action = ", shutting down"; | |
| break; | |
| case ngx_signal_value(NGX_TERMINATE_SIGNAL): | |
| case SIGINT: | |
| ngx_terminate = 1; | |
| action = ", exiting"; | |
| break; | |
| case ngx_signal_value(NGX_REOPEN_SIGNAL): | |
| ngx_reopen = 1; | |
| action = ", reopening logs"; | |
| break; | |
| case ngx_signal_value(NGX_RECONFIGURE_SIGNAL): | |
| case ngx_signal_value(NGX_CHANGEBIN_SIGNAL): | |
| case SIGIO: | |
| action = ", ignoring"; | |
| break; | |
| } | |
| break; | |
| } | |
| if (siginfo && siginfo->si_pid) { | |
| ngx_log_error(NGX_LOG_NOTICE, ngx_cycle->log, 0, | |
| "signal %d (%s) received from %P%s", | |
| signo, sig->signame, siginfo->si_pid, action); | |
| } else { | |
| ngx_log_error(NGX_LOG_NOTICE, ngx_cycle->log, 0, | |
| "signal %d (%s) received%s", | |
| signo, sig->signame, action); | |
| } | |
| if (ignore) { | |
| ngx_log_error(NGX_LOG_CRIT, ngx_cycle->log, 0, | |
| "the changing binary signal is ignored: " | |
| "you should shutdown or terminate " | |
| "before either old or new binary's process"); | |
| } | |
| if (signo == SIGCHLD) { | |
| ngx_process_get_status(); | |
| } | |
| ngx_set_errno(err); | |
| } | |
| static void | |
| ngx_process_get_status(void) | |
| { | |
| int status; | |
| char *process; | |
| ngx_pid_t pid; | |
| ngx_err_t err; | |
| ngx_int_t i; | |
| ngx_uint_t one; | |
| one = 0; | |
| for ( ;; ) { | |
| pid = waitpid(-1, &status, WNOHANG); | |
| if (pid == 0) { | |
| return; | |
| } | |
| if (pid == -1) { | |
| err = ngx_errno; | |
| if (err == NGX_EINTR) { | |
| continue; | |
| } | |
| if (err == NGX_ECHILD && one) { | |
| return; | |
| } | |
| /* | |
| * Solaris always calls the signal handler for each exited process | |
| * despite waitpid() may be already called for this process. | |
| * | |
| * When several processes exit at the same time FreeBSD may | |
| * erroneously call the signal handler for exited process | |
| * despite waitpid() may be already called for this process. | |
| */ | |
| if (err == NGX_ECHILD) { | |
| ngx_log_error(NGX_LOG_INFO, ngx_cycle->log, err, | |
| "waitpid() failed"); | |
| return; | |
| } | |
| ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, err, | |
| "waitpid() failed"); | |
| return; | |
| } | |
| one = 1; | |
| process = "unknown process"; | |
| for (i = 0; i < ngx_last_process; i++) { | |
| if (ngx_processes[i].pid == pid) { | |
| ngx_processes[i].status = status; | |
| ngx_processes[i].exited = 1; | |
| process = ngx_processes[i].name; | |
| break; | |
| } | |
| } | |
| if (WTERMSIG(status)) { | |
| #ifdef WCOREDUMP | |
| ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, 0, | |
| "%s %P exited on signal %d%s", | |
| process, pid, WTERMSIG(status), | |
| WCOREDUMP(status) ? " (core dumped)" : ""); | |
| #else | |
| ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, 0, | |
| "%s %P exited on signal %d", | |
| process, pid, WTERMSIG(status)); | |
| #endif | |
| } else { | |
| ngx_log_error(NGX_LOG_NOTICE, ngx_cycle->log, 0, | |
| "%s %P exited with code %d", | |
| process, pid, WEXITSTATUS(status)); | |
| } | |
| if (WEXITSTATUS(status) == 2 && ngx_processes[i].respawn) { | |
| ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, 0, | |
| "%s %P exited with fatal code %d " | |
| "and cannot be respawned", | |
| process, pid, WEXITSTATUS(status)); | |
| ngx_processes[i].respawn = 0; | |
| } | |
| ngx_unlock_mutexes(pid); | |
| } | |
| } | |
| static void | |
| ngx_unlock_mutexes(ngx_pid_t pid) | |
| { | |
| ngx_uint_t i; | |
| ngx_shm_zone_t *shm_zone; | |
| ngx_list_part_t *part; | |
| ngx_slab_pool_t *sp; | |
| /* | |
| * unlock the accept mutex if the abnormally exited process | |
| * held it | |
| */ | |
| if (ngx_accept_mutex_ptr) { | |
| (void) ngx_shmtx_force_unlock(&ngx_accept_mutex, pid); | |
| } | |
| /* | |
| * unlock shared memory mutexes if held by the abnormally exited | |
| * process | |
| */ | |
| part = (ngx_list_part_t *) &ngx_cycle->shared_memory.part; | |
| shm_zone = part->elts; | |
| for (i = 0; /* void */ ; i++) { | |
| if (i >= part->nelts) { | |
| if (part->next == NULL) { | |
| break; | |
| } | |
| part = part->next; | |
| shm_zone = part->elts; | |
| i = 0; | |
| } | |
| sp = (ngx_slab_pool_t *) shm_zone[i].shm.addr; | |
| if (ngx_shmtx_force_unlock(&sp->mutex, pid)) { | |
| ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, 0, | |
| "shared memory zone \"%V\" was locked by %P", | |
| &shm_zone[i].shm.name, pid); | |
| } | |
| } | |
| } | |
| void | |
| ngx_debug_point(void) | |
| { | |
| ngx_core_conf_t *ccf; | |
| ccf = (ngx_core_conf_t *) ngx_get_conf(ngx_cycle->conf_ctx, | |
| ngx_core_module); | |
| switch (ccf->debug_points) { | |
| case NGX_DEBUG_POINTS_STOP: | |
| raise(SIGSTOP); | |
| break; | |
| case NGX_DEBUG_POINTS_ABORT: | |
| ngx_abort(); | |
| } | |
| } | |
| ngx_int_t | |
| ngx_os_signal_process(ngx_cycle_t *cycle, char *name, ngx_pid_t pid) | |
| { | |
| ngx_signal_t *sig; | |
| for (sig = signals; sig->signo != 0; sig++) { | |
| if (ngx_strcmp(name, sig->name) == 0) { | |
| if (kill(pid, sig->signo) != -1) { | |
| return 0; | |
| } | |
| ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno, | |
| "kill(%P, %d) failed", pid, sig->signo); | |
| } | |
| } | |
| return 1; | |
| } |