summaryrefslogtreecommitdiff
path: root/storage/ndb/src/cw/cpcd/Process.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'storage/ndb/src/cw/cpcd/Process.cpp')
-rw-r--r--storage/ndb/src/cw/cpcd/Process.cpp479
1 files changed, 479 insertions, 0 deletions
diff --git a/storage/ndb/src/cw/cpcd/Process.cpp b/storage/ndb/src/cw/cpcd/Process.cpp
new file mode 100644
index 00000000000..2509f34e882
--- /dev/null
+++ b/storage/ndb/src/cw/cpcd/Process.cpp
@@ -0,0 +1,479 @@
+/* Copyright (C) 2003 MySQL AB
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ 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 General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
+
+#include <ndb_global.h>
+
+#include <BaseString.hpp>
+#include <InputStream.hpp>
+
+#include "common.hpp"
+#include "CPCD.hpp"
+
+#include <pwd.h>
+#ifdef HAVE_GETRLIMIT
+#include <sys/resource.h>
+#endif
+
+void
+CPCD::Process::print(FILE * f){
+ fprintf(f, "define process\n");
+ fprintf(f, "id: %d\n", m_id);
+ fprintf(f, "name: %s\n", m_name.c_str() ? m_name.c_str() : "");
+ fprintf(f, "group: %s\n", m_group.c_str() ? m_group.c_str() : "");
+ fprintf(f, "env: %s\n", m_env.c_str() ? m_env.c_str() : "");
+ fprintf(f, "path: %s\n", m_path.c_str() ? m_path.c_str() : "");
+ fprintf(f, "args: %s\n", m_args.c_str() ? m_args.c_str() : "");
+ fprintf(f, "type: %s\n", m_type.c_str() ? m_type.c_str() : "");
+ fprintf(f, "cwd: %s\n", m_cwd.c_str() ? m_cwd.c_str() : "");
+ fprintf(f, "owner: %s\n", m_owner.c_str() ? m_owner.c_str() : "");
+ fprintf(f, "runas: %s\n", m_runas.c_str() ? m_runas.c_str() : "");
+ fprintf(f, "stdin: %s\n", m_stdin.c_str() ? m_stdin.c_str() : "");
+ fprintf(f, "stdout: %s\n", m_stdout.c_str() ? m_stdout.c_str() : "");
+ fprintf(f, "stderr: %s\n", m_stderr.c_str() ? m_stderr.c_str() : "");
+ fprintf(f, "ulimit: %s\n", m_ulimit.c_str() ? m_ulimit.c_str() : "");
+}
+
+CPCD::Process::Process(const Properties & props, class CPCD *cpcd) {
+ m_id = -1;
+ m_pid = -1;
+ props.get("id", (Uint32 *) &m_id);
+ props.get("name", m_name);
+ props.get("group", m_group);
+ props.get("env", m_env);
+ props.get("path", m_path);
+ props.get("args", m_args);
+ props.get("cwd", m_cwd);
+ props.get("owner", m_owner);
+ props.get("type", m_type);
+ props.get("runas", m_runas);
+
+ props.get("stdin", m_stdin);
+ props.get("stdout", m_stdout);
+ props.get("stderr", m_stderr);
+ props.get("ulimit", m_ulimit);
+ m_status = STOPPED;
+
+ if(strcasecmp(m_type.c_str(), "temporary") == 0){
+ m_processType = TEMPORARY;
+ } else {
+ m_processType = PERMANENT;
+ }
+
+ m_cpcd = cpcd;
+}
+
+void
+CPCD::Process::monitor() {
+ switch(m_status) {
+ case STARTING:
+ break;
+ case RUNNING:
+ if(!isRunning()){
+ m_cpcd->report(m_id, CPCEvent::ET_PROC_STATE_STOPPED);
+ if(m_processType == TEMPORARY){
+ m_status = STOPPED;
+ } else {
+ start();
+ }
+ }
+ break;
+ case STOPPED:
+ assert(!isRunning());
+ break;
+ case STOPPING:
+ break;
+ }
+}
+
+bool
+CPCD::Process::isRunning() {
+
+ if(m_pid <= 1){
+ //logger.critical("isRunning(%d) invalid pid: %d", m_id, m_pid);
+ return false;
+ }
+ /* Check if there actually exists a process with such a pid */
+ errno = 0;
+ int s = kill((pid_t)-m_pid, 0); /* Sending "signal" 0 to a process only
+ * checkes if the process actually exists */
+ if(s != 0) {
+ switch(errno) {
+ case EPERM:
+ logger.critical("Not enough privileges to control pid %d\n", m_pid);
+ break;
+ case ESRCH:
+ /* The pid in the file does not exist, which probably means that it
+ has died, or the file contains garbage for some other reason */
+ break;
+ default:
+ logger.critical("Cannot not control pid %d: %s\n", m_pid, strerror(errno));
+ break;
+ }
+ return false;
+ }
+ return true;
+}
+
+int
+CPCD::Process::readPid() {
+ if(m_pid != -1){
+ logger.critical("Reading pid while != -1(%d)", m_pid);
+ return m_pid;
+ }
+
+ char filename[PATH_MAX*2+1];
+ char buf[1024];
+ FILE *f;
+
+ memset(buf, 0, sizeof(buf));
+
+ BaseString::snprintf(filename, sizeof(filename), "%d", m_id);
+
+ f = fopen(filename, "r");
+
+ if(f == NULL){
+ return -1; /* File didn't exist */
+ }
+
+ errno = 0;
+ size_t r = fread(buf, 1, sizeof(buf), f);
+ fclose(f);
+ if(r > 0)
+ m_pid = strtol(buf, (char **)NULL, 0);
+
+ if(errno == 0){
+ return m_pid;
+ }
+
+ return -1;
+}
+
+int
+CPCD::Process::writePid(int pid) {
+ char tmpfilename[PATH_MAX+1+4+8];
+ char filename[PATH_MAX*2+1];
+ FILE *f;
+
+ BaseString::snprintf(tmpfilename, sizeof(tmpfilename), "tmp.XXXXXX");
+ BaseString::snprintf(filename, sizeof(filename), "%d", m_id);
+
+ int fd = mkstemp(tmpfilename);
+ if(fd < 0) {
+ logger.error("Cannot open `%s': %s\n", tmpfilename, strerror(errno));
+ return -1; /* Couldn't open file */
+ }
+
+ f = fdopen(fd, "w");
+
+ if(f == NULL) {
+ logger.error("Cannot open `%s': %s\n", tmpfilename, strerror(errno));
+ return -1; /* Couldn't open file */
+ }
+
+ fprintf(f, "%d", pid);
+ fclose(f);
+
+ if(rename(tmpfilename, filename) == -1){
+ logger.error("Unable to rename from %s to %s", tmpfilename, filename);
+ return -1;
+ }
+ return 0;
+}
+
+static void
+setup_environment(const char *env) {
+ char **p;
+ p = BaseString::argify("", env);
+ for(int i = 0; p[i] != NULL; i++){
+ /*int res = */ putenv(p[i]);
+ }
+}
+
+static
+int
+set_ulimit(const BaseString & pair){
+#ifdef HAVE_GETRLIMIT
+ errno = 0;
+ Vector<BaseString> list;
+ pair.split(list, ":");
+ if(list.size() != 2){
+ logger.error("Unable to process ulimit: split >%s< list.size()=%d",
+ pair.c_str(), list.size());
+ return -1;
+ }
+
+ int res;
+ rlim_t value = RLIM_INFINITY;
+ if(!(list[1].trim() == "unlimited")){
+ value = atoi(list[1].c_str());
+ }
+
+ struct rlimit rlp;
+#define _RLIMIT_FIX(x) { res = getrlimit(x,&rlp); if(!res){ rlp.rlim_cur = value; res = setrlimit(x, &rlp); }}
+
+ if(list[0].trim() == "c"){
+ _RLIMIT_FIX(RLIMIT_CORE);
+ } else if(list[0] == "d"){
+ _RLIMIT_FIX(RLIMIT_DATA);
+ } else if(list[0] == "f"){
+ _RLIMIT_FIX(RLIMIT_FSIZE);
+ } else if(list[0] == "n"){
+ _RLIMIT_FIX(RLIMIT_NOFILE);
+ } else if(list[0] == "s"){
+ _RLIMIT_FIX(RLIMIT_STACK);
+ } else if(list[0] == "t"){
+ _RLIMIT_FIX(RLIMIT_CPU);
+ } else {
+ res= -11;
+ errno = EINVAL;
+ }
+ if(res){
+ logger.error("Unable to process ulimit: %s res=%d error=%d(%s)",
+ pair.c_str(), res, errno, strerror(errno));
+ return -1;
+ }
+#endif
+ return 0;
+}
+
+void
+CPCD::Process::do_exec() {
+ size_t i;
+ setup_environment(m_env.c_str());
+
+ char **argv = BaseString::argify(m_path.c_str(), m_args.c_str());
+
+ if(strlen(m_cwd.c_str()) > 0) {
+ int err = chdir(m_cwd.c_str());
+ if(err == -1) {
+ BaseString err;
+ logger.error("%s: %s\n", m_cwd.c_str(), strerror(errno));
+ _exit(1);
+ }
+ }
+
+ Vector<BaseString> ulimit;
+ m_ulimit.split(ulimit);
+ for(i = 0; i<ulimit.size(); i++){
+ if(ulimit[i].trim().length() > 0 && set_ulimit(ulimit[i]) != 0){
+ _exit(1);
+ }
+ }
+
+ int fd = open("/dev/null", O_RDWR, 0);
+ if(fd == -1) {
+ logger.error("Cannot open `/dev/null': %s\n", strerror(errno));
+ _exit(1);
+ }
+
+ BaseString * redirects[] = { &m_stdin, &m_stdout, &m_stderr };
+ int fds[3];
+ for(i = 0; i<3; i++){
+ if(redirects[i]->empty()){
+#ifndef DEBUG
+ dup2(fd, i);
+#endif
+ continue;
+ }
+
+ if((* redirects[i]) == "2>&1" && i == 2){
+ dup2(fds[1], 2);
+ continue;
+ }
+
+ /**
+ * Make file
+ */
+ int flags = 0;
+ int mode = S_IRUSR | S_IWUSR ;
+ if(i == 0){
+ flags |= O_RDONLY;
+ } else {
+ flags |= O_WRONLY | O_CREAT | O_APPEND;
+ }
+ int f = fds[i]= open(redirects[i]->c_str(), flags, mode);
+ if(f == -1){
+ logger.error("Cannot redirect %d to/from '%s' : %s\n", i,
+ redirects[i]->c_str(), strerror(errno));
+ _exit(1);
+ }
+ dup2(f, i);
+ }
+
+ /* Close all filedescriptors */
+ for(i = STDERR_FILENO+1; (int)i < getdtablesize(); i++)
+ close(i);
+
+ execv(m_path.c_str(), argv);
+ /* XXX If we reach this point, an error has occurred, but it's kind of hard
+ * to report it, because we've closed all files... So we should probably
+ * create a new logger here */
+ logger.error("Exec failed: %s\n", strerror(errno));
+ /* NOTREACHED */
+}
+
+int
+CPCD::Process::start() {
+ /* We need to fork() twice, so that the second child (grandchild?) can
+ * become a daemon. The original child then writes the pid file,
+ * so that the monitor knows the pid of the new process, and then
+ * exit()s. That way, the monitor process can pickup the pid, and
+ * the running process is a daemon.
+ *
+ * This is a bit tricky but has the following advantages:
+ * - the cpcd can die, and "reconnect" to the monitored clients
+ * without restarting them.
+ * - the cpcd does not have to wait() for the childs. init(1) will
+ * take care of that.
+ */
+ logger.info("Starting %d: %s", m_id, m_name.c_str());
+ m_status = STARTING;
+
+ int pid = -1;
+ switch(m_processType){
+ case TEMPORARY:{
+ /**
+ * Simple fork
+ * don't ignore child
+ */
+ switch(pid = fork()) {
+ case 0: /* Child */
+ setsid();
+ writePid(getpgrp());
+ if(runas(m_runas.c_str()) == 0){
+ signal(SIGCHLD, SIG_DFL);
+ do_exec();
+ }
+ _exit(1);
+ break;
+ case -1: /* Error */
+ logger.error("Cannot fork: %s\n", strerror(errno));
+ m_status = STOPPED;
+ return -1;
+ break;
+ default: /* Parent */
+ logger.debug("Started temporary %d : pid=%d", m_id, pid);
+ m_cpcd->report(m_id, CPCEvent::ET_PROC_STATE_RUNNING);
+ break;
+ }
+ break;
+ }
+ case PERMANENT:{
+ /**
+ * PERMANENT
+ */
+ switch(fork()) {
+ case 0: /* Child */
+ signal(SIGCHLD, SIG_IGN);
+ switch(pid = fork()) {
+ case 0: /* Child */
+ setsid();
+ writePid(getpgrp());
+ if(runas(m_runas.c_str()) != 0){
+ _exit(1);
+ }
+ signal(SIGCHLD, SIG_DFL);
+ do_exec();
+ _exit(1);
+ /* NOTREACHED */
+ break;
+ case -1: /* Error */
+ logger.error("Cannot fork: %s\n", strerror(errno));
+ writePid(-1);
+ _exit(1);
+ break;
+ default: /* Parent */
+ logger.debug("Started permanent %d : pid=%d", m_id, pid);
+ _exit(0);
+ break;
+ }
+ break;
+ case -1: /* Error */
+ logger.error("Cannot fork: %s\n", strerror(errno));
+ m_status = STOPPED;
+ return -1;
+ break;
+ default: /* Parent */
+ m_cpcd->report(m_id, CPCEvent::ET_PROC_STATE_RUNNING);
+ break;
+ }
+ break;
+ }
+ default:
+ logger.critical("Unknown process type");
+ return -1;
+ }
+
+ while(readPid() < 0){
+ sched_yield();
+ }
+
+ errno = 0;
+ pid_t pgid = getpgid(pid);
+
+ if(pgid != -1 && pgid != m_pid){
+ logger.error("pgid and m_pid don't match: %d %d (%d)", pgid, m_pid, pid);
+ }
+
+ if(isRunning()){
+ m_status = RUNNING;
+ return 0;
+ }
+ m_status = STOPPED;
+ return -1;
+}
+
+void
+CPCD::Process::stop() {
+
+ char filename[PATH_MAX*2+1];
+ BaseString::snprintf(filename, sizeof(filename), "%d", m_id);
+ unlink(filename);
+
+ if(m_pid <= 1){
+ logger.critical("Stopping process with bogus pid: %d id: %d",
+ m_pid, m_id);
+ return;
+ }
+ m_status = STOPPING;
+
+ errno = 0;
+ int ret = kill(-m_pid, SIGTERM);
+ switch(ret) {
+ case 0:
+ logger.debug("Sent SIGTERM to pid %d", (int)-m_pid);
+ break;
+ default:
+ logger.debug("kill pid: %d : %s", (int)-m_pid, strerror(errno));
+ break;
+ }
+
+ if(isRunning()){
+ errno = 0;
+ ret = kill(-m_pid, SIGKILL);
+ switch(ret) {
+ case 0:
+ logger.debug("Sent SIGKILL to pid %d", (int)-m_pid);
+ break;
+ default:
+ logger.debug("kill pid: %d : %s\n", (int)-m_pid, strerror(errno));
+ break;
+ }
+ }
+
+ m_pid = -1;
+ m_status = STOPPED;
+}