blob: acc078dd666553785033f330069f39cba8f6b13e [file] [log] [blame] [edit]
/*
* Copyright (C) Tildeslash Ltd. All rights reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* In addition, as a special exception, the copyright holders give
* permission to link the code of portions of this program with the
* OpenSSL library under certain conditions as described in each
* individual source file, and distribute linked combinations
* including the two.
*
* You must obey the GNU Affero General Public License in all respects
* for all of the code used other than OpenSSL.
*/
#include "config.h"
#ifdef HAVE_STDIO_H
#include <stdio.h>
#endif
#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif
#ifdef HAVE_ERRNO_H
#include <errno.h>
#endif
#ifdef HAVE_SIGNAL_H
#include <signal.h>
#endif
#ifdef HAVE_STRING_H
#include <string.h>
#endif
#ifdef HAVE_STRINGS_H
#include <strings.h>
#endif
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef HAVE_SYS_WAIT_H
#include <sys/wait.h>
#endif
#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif
#ifdef HAVE_SYS_STAT_H
#include <sys/stat.h>
#endif
#ifdef HAVE_FCNTL_H
#include <fcntl.h>
#endif
#include <sys/reboot.h>
#include "event.h"
#include "alert.h"
#include "monit.h"
#include "engine.h"
// libmonit
#include "util/Str.h"
#include "system/Time.h"
/**
* Function for spawning of a process. This function fork's twice to
* avoid creating any zombie processes. Inspired by code from
* W. Richard Stevens book, APUE.
*
* @file
*/
/* ------------------------------------------------------------- Definitions */
/* Do not exceed 8 bits here */
enum ExitStatus_E {
setgid_ERROR = 0x1,
setuid_ERROR = 0x2,
redirect_ERROR = 0x4,
fork_ERROR = 0x8
};
/* ----------------------------------------------------------------- Private */
/*
* Setup the environment with special MONIT_xxx variables. The program
* executed may use such variable for various purposes.
*/
static void set_monit_environment(Service_T S, command_t C, Event_T E) {
char date[42];
Time_string(Time_now(), date);
setenv("MONIT_DATE", Str_dup(date), 1);
setenv("MONIT_SERVICE", S->name, 1);
setenv("MONIT_HOST", Run.localhostname, 1);
setenv("MONIT_EVENT", E ? Event_get_description(E) : C == S->start ? "Started" : C == S->stop ? "Stopped" : "No Event", 1);
setenv("MONIT_DESCRIPTION", E ? Event_get_message(E) : C == S->start ? "Started" : C == S->stop ? "Stopped" : "No Event", 1);
if (S->type == TYPE_PROCESS) {
putenv(Str_cat("MONIT_PROCESS_PID=%d", Util_isProcessRunning(S, FALSE)));
putenv(Str_cat("MONIT_PROCESS_MEMORY=%ld", S->inf->priv.process.mem_kbyte));
putenv(Str_cat("MONIT_PROCESS_CHILDREN=%d", S->inf->priv.process.children));
putenv(Str_cat("MONIT_PROCESS_CPU_PERCENT=%d", S->inf->priv.process.cpu_percent));
}
}
/* ------------------------------------------------------------------ Public */
/**
* Execute the given command. If the execution fails, the wait_start()
* thread in control.c should notice this and send an alert message.
* @param P A Service object
* @param C A Command object
* @param E An optional event object. May be NULL.
*/
void spawn(Service_T S, command_t C, Event_T E) {
pid_t pid;
sigset_t mask;
sigset_t save;
int stat_loc= 0;
int exit_status;
ASSERT(S);
ASSERT(C);
if(access(C->arg[0], X_OK) != 0) {
LogError("Error: Could not execute %s\n", C->arg[0]);
return;
}
/*
* Block SIGCHLD
*/
sigemptyset(&mask);
sigaddset(&mask, SIGCHLD);
pthread_sigmask(SIG_BLOCK, &mask, &save);
pid= fork();
if(pid < 0) {
LogError("Cannot fork a new process, rebooting: %s\n", strerror(errno));
sync();
reboot(RB_AUTOBOOT);
}
if(pid == 0) {
/*
* Reset to the original umask so programs will inherit the
* same file creation mask monit was started with
*/
umask(Run.umask);
/*
* Switch uid/gid if requested
*/
if(C->has_gid) {
if(0 != setgid(C->gid)) {
stat_loc |= setgid_ERROR;
}
}
if(C->has_uid) {
if(0 != setuid(C->uid)) {
stat_loc |= setuid_ERROR;
}
}
set_monit_environment(S, C, E);
if(! Run.isdaemon) {
for(int i = 0; i < 3; i++)
if(close(i) == -1 || open("/dev/null", O_RDWR) != i)
stat_loc |= redirect_ERROR;
}
Util_closeFds();
setsid();
pid = fork();
if(pid < 0) {
stat_loc |= fork_ERROR;
_exit(stat_loc);
}
if(pid == 0) {
/*
* Reset all signals, so the spawned process is *not* created
* with any inherited SIG_BLOCKs
*/
sigemptyset(&mask);
pthread_sigmask(SIG_SETMASK, &mask, NULL);
signal(SIGINT, SIG_DFL);
signal(SIGHUP, SIG_DFL);
signal(SIGTERM, SIG_DFL);
signal(SIGUSR1, SIG_DFL);
signal(SIGPIPE, SIG_DFL);
(void) execv(C->arg[0], C->arg);
_exit(errno);
}
/* Exit first child and return errors to parent */
_exit(stat_loc);
}
/* Wait for first child - aka second parent, to exit */
if(waitpid(pid, &stat_loc, 0) != pid) {
LogError("Waitpid error\n");
}
exit_status= WEXITSTATUS(stat_loc);
if (exit_status & setgid_ERROR)
LogError("Failed to change gid to '%d' for '%s'\n", C->gid, C->arg[0]);
if (exit_status & setuid_ERROR)
LogError("Failed to change uid to '%d' for '%s'\n", C->uid, C->arg[0]);
if (exit_status & fork_ERROR)
LogError("Cannot fork a new process for '%s'\n", C->arg[0]);
if (exit_status & redirect_ERROR)
LogError("Cannot redirect IO to /dev/null for '%s'\n", C->arg[0]);
/*
* Restore the signal mask
*/
pthread_sigmask(SIG_SETMASK, &save, NULL);
/*
* We do not need to wait for the second child since we forked twice,
* the init system-process will wait for it. So we just return
*/
}