/// \file procs.cpp /// Contains generic functions for managing processes. #include "procs.h" #include "defines.h" #include #include #include #if defined(__FreeBSD__) || defined(__APPLE__) || defined(__MACH__) #include #else #include #endif #include #include #include #include #include #include #include #include "timing.h" std::set Util::Procs::plist; std::set Util::Procs::socketList; bool Util::Procs::handler_set = false; bool Util::Procs::thread_handler = false; tthread::mutex Util::Procs::plistMutex; tthread::thread * Util::Procs::reaper_thread = 0; /// Local-only function. Attempts to reap child and returns current running status. bool Util::Procs::childRunning(pid_t p) { int status; pid_t ret = waitpid(p, &status, WNOHANG); if (ret == p) { tthread::lock_guard guard(plistMutex); int exitcode = -1; if (WIFEXITED(status)) { exitcode = WEXITSTATUS(status); } else if (WIFSIGNALED(status)) { exitcode = -WTERMSIG(status); } if (plist.count(ret)) { HIGH_MSG("Process %d fully terminated with code %d", ret, exitcode); plist.erase(ret); } else { HIGH_MSG("Child process %d exited with code %d", ret, exitcode); } return false; } if (ret < 0 && errno == EINTR) { return childRunning(p); } return !kill(p, 0); } /// sends sig 0 to process (pid). returns true if process is running bool Util::Procs::isRunning(pid_t pid){ return !kill(pid, 0); } /// Called at exit of any program that used a Start* function. /// Waits up to 1 second, then sends SIGINT signal to all managed processes. /// After that waits up to 5 seconds for children to exit, then sends SIGKILL to /// all remaining children. Waits one more second for cleanup to finish, then exits. void Util::Procs::exit_handler() { if (!handler_set){return;} int waiting = 0; std::set listcopy; { tthread::lock_guard guard(plistMutex); listcopy = plist; thread_handler = false; } if (reaper_thread){ reaper_thread->join(); delete reaper_thread; reaper_thread = 0; } std::set::iterator it; if (listcopy.empty()) { return; } //wait up to 0.5 second for applications to shut down while (!listcopy.empty() && waiting <= 25) { for (it = listcopy.begin(); it != listcopy.end(); it++) { if (!childRunning(*it)) { listcopy.erase(it); break; } if (!listcopy.empty()) { Util::wait(20); ++waiting; } } } if (listcopy.empty()) { return; } INFO_MSG("Sending SIGINT and waiting up to 10 seconds for %d children to terminate.", (int)listcopy.size()); waiting = 0; //wait up to 10 seconds for applications to shut down while (!listcopy.empty() && waiting <= 500) { bool doWait = true; for (it = listcopy.begin(); it != listcopy.end(); it++) { if (!childRunning(*it)) { listcopy.erase(it); doWait = false; break; } } if (doWait && !listcopy.empty()) { if ((waiting % 50) == 0){ for (it = listcopy.begin(); it != listcopy.end(); it++) { INFO_MSG("SIGINT %d", *it); kill(*it, SIGINT); } } Util::wait(20); ++waiting; } } if (listcopy.empty()) { return; } ERROR_MSG("Sending SIGKILL to remaining %d children", (int)listcopy.size()); //send sigkill to all remaining if (!listcopy.empty()) { for (it = listcopy.begin(); it != listcopy.end(); it++) { INFO_MSG("SIGKILL %d", *it); kill(*it, SIGKILL); } } INFO_MSG("Waiting up to a second for %d children to terminate.", (int)listcopy.size()); waiting = 0; //wait up to 1 second for applications to shut down while (!listcopy.empty() && waiting <= 50) { for (it = listcopy.begin(); it != listcopy.end(); it++) { if (!childRunning(*it)) { listcopy.erase(it); break; } if (!listcopy.empty()) { Util::wait(20); ++waiting; } } } if (listcopy.empty()) { return; } FAIL_MSG("Giving up with %d children left.", (int)listcopy.size()); } // Joins the reaper thread, if any, before a fork void Util::Procs::fork_prepare(){ tthread::lock_guard guard(plistMutex); if (handler_set){ thread_handler = false; if (reaper_thread){ reaper_thread->join(); delete reaper_thread; reaper_thread = 0; } } } /// Restarts reaper thread if it was joined void Util::Procs::fork_complete(){ tthread::lock_guard guard(plistMutex); if (handler_set){ thread_handler = true; reaper_thread = new tthread::thread(grim_reaper, 0); } } /// Sets up exit and childsig handlers. /// Spawns grim_reaper. exit handler despawns grim_reaper /// Called by every Start* function. void Util::Procs::setHandler() { tthread::lock_guard guard(plistMutex); if (!handler_set) { thread_handler = true; reaper_thread = new tthread::thread(grim_reaper, 0); struct sigaction new_action; new_action.sa_handler = childsig_handler; sigemptyset(&new_action.sa_mask); new_action.sa_flags = 0; sigaction(SIGCHLD, &new_action, NULL); atexit(exit_handler); handler_set = true; } } ///Thread that loops until thread_handler is false. ///Reaps available children and then sleeps for a second. ///Not done in signal handler so we can use a mutex to prevent race conditions. void Util::Procs::grim_reaper(void * n){ VERYHIGH_MSG("Grim reaper start"); while (thread_handler){ { tthread::lock_guard guard(plistMutex); int status; pid_t ret = -1; while (ret != 0) { ret = waitpid(-1, &status, WNOHANG); if (ret <= 0) { //ignore, would block otherwise if (ret == 0 || errno != EINTR) { break; } continue; } int exitcode; if (WIFEXITED(status)) { exitcode = WEXITSTATUS(status); } else if (WIFSIGNALED(status)) { exitcode = -WTERMSIG(status); } else { // not possible break; } if (plist.count(ret)) { HIGH_MSG("Process %d fully terminated with code %d", ret, exitcode); plist.erase(ret); } else { HIGH_MSG("Child process %d exited with code %d", ret, exitcode); } } } Util::sleep(500); } VERYHIGH_MSG("Grim reaper stop"); } /// Ignores everything. Separate thread handles waiting for children. void Util::Procs::childsig_handler(int signum) { return; } /// Runs the given command and returns the stdout output as a string. std::string Util::Procs::getOutputOf(char * const * argv) { std::string ret; int fin = 0, fout = -1, ferr = 0; pid_t myProc = StartPiped(argv, &fin, &fout, &ferr); while (childRunning(myProc)) { Util::sleep(100); } FILE * outFile = fdopen(fout, "r"); char * fileBuf = 0; size_t fileBufLen = 0; while (!(feof(outFile) || ferror(outFile)) && (getline(&fileBuf, &fileBufLen, outFile) != -1)) { ret += fileBuf; } fclose(outFile); free(fileBuf); return ret; } ///This function prepares a deque for getOutputOf and automatically inserts a NULL at the end of the char* const* char* const* Util::Procs::dequeToArgv(std::deque & argDeq){ char** ret = (char**)malloc((argDeq.size()+1)*sizeof(char*)); for (int i = 0; i & argDeq){ std::string ret; char* const* argv = dequeToArgv(argDeq);//Note: Do not edit deque before executing command ret = getOutputOf(argv); return ret; } pid_t Util::Procs::StartPiped(std::deque & argDeq, int * fdin, int * fdout, int * fderr) { pid_t ret; char* const* argv = dequeToArgv(argDeq);//Note: Do not edit deque before executing command ret = Util::Procs::StartPiped(argv, fdin, fdout, fderr); return ret; } /// Starts a new process with given fds if the name is not already active. /// \return 0 if process was not started, process PID otherwise. /// \arg argv Command for this process. /// \arg fdin Standard input file descriptor. If null, /dev/null is assumed. Otherwise, if arg contains -1, a new fd is automatically allocated and written into this arg. Then the arg will be used as fd. /// \arg fdout Same as fdin, but for stdout. /// \arg fdout Same as fdin, but for stderr. pid_t Util::Procs::StartPiped(const char * const * argv, int * fdin, int * fdout, int * fderr) { pid_t pid; int pipein[2], pipeout[2], pipeerr[2]; setHandler(); if (fdin && *fdin == -1 && pipe(pipein) < 0) { ERROR_MSG("stdin pipe creation failed for process %s, reason: %s", argv[0], strerror(errno)); return 0; } if (fdout && *fdout == -1 && pipe(pipeout) < 0) { ERROR_MSG("stdout pipe creation failed for process %s, reason: %s", argv[0], strerror(errno)); if (*fdin == -1) { close(pipein[0]); close(pipein[1]); } return 0; } if (fderr && *fderr == -1 && pipe(pipeerr) < 0) { ERROR_MSG("stderr pipe creation failed for process %s, reason: %s", argv[0], strerror(errno)); if (*fdin == -1) { close(pipein[0]); close(pipein[1]); } if (*fdout == -1) { close(pipeout[0]); close(pipeout[1]); } return 0; } int devnull = -1; if (!fdin || !fdout || !fderr) { devnull = open("/dev/null", O_RDWR); if (devnull == -1) { ERROR_MSG("Could not open /dev/null for process %s, reason: %s", argv[0], strerror(errno)); if (*fdin == -1) { close(pipein[0]); close(pipein[1]); } if (*fdout == -1) { close(pipeout[0]); close(pipeout[1]); } if (*fderr == -1) { close(pipeerr[0]); close(pipeerr[1]); } return 0; } } pid = fork(); if (pid == 0) { //child handler_set = false; //Close all sockets in the socketList for (std::set::iterator it = Util::Procs::socketList.begin(); it != Util::Procs::socketList.end(); ++it){ close(*it); } if (!fdin) { dup2(devnull, STDIN_FILENO); } else if (*fdin == -1) { close(pipein[1]); // close unused write end dup2(pipein[0], STDIN_FILENO); close(pipein[0]); } else if (*fdin != STDIN_FILENO) { dup2(*fdin, STDIN_FILENO); } if (!fdout) { dup2(devnull, STDOUT_FILENO); } else if (*fdout == -1) { close(pipeout[0]); // close unused read end dup2(pipeout[1], STDOUT_FILENO); close(pipeout[1]); } else if (*fdout != STDOUT_FILENO) { dup2(*fdout, STDOUT_FILENO); } if (!fderr) { dup2(devnull, STDERR_FILENO); } else if (*fderr == -1) { close(pipeerr[0]); // close unused read end dup2(pipeerr[1], STDERR_FILENO); close(pipeerr[1]); } else if (*fderr != STDERR_FILENO) { dup2(*fderr, STDERR_FILENO); } if (fdin && *fdin != -1 && *fdin != STDIN_FILENO) { close(*fdin); } if (fdout && *fdout != -1 && *fdout != STDOUT_FILENO) { close(*fdout); } if (fderr && *fderr != -1 && *fderr != STDERR_FILENO) { close(*fderr); } if (devnull != -1) { close(devnull); } //Because execvp requires a char* const* and we have a const char* const* execvp(argv[0], (char* const*)argv); /*LTS-START*/ char * trggr = getenv("MIST_TRIGGER"); if (trggr && strlen(trggr)){ ERROR_MSG("%s trigger failed to execute %s: %s", trggr, argv[0], strerror(errno)); JSON::Value j; j["trigger_fail"] = trggr; Socket::UDPConnection uSock; uSock.SetDestination(UDP_API_HOST, UDP_API_PORT); uSock.SendNow(j.toString()); std::cout << getenv("MIST_TRIG_DEF"); exit(42); } /*LTS-END*/ ERROR_MSG("execvp failed for process %s, reason: %s", argv[0], strerror(errno)); exit(42); } else if (pid == -1) { ERROR_MSG("fork failed for process %s, reason: %s", argv[0], strerror(errno)); if (fdin && *fdin == -1) { close(pipein[0]); close(pipein[1]); } if (fdout && *fdout == -1) { close(pipeout[0]); close(pipeout[1]); } if (fderr && *fderr == -1) { close(pipeerr[0]); close(pipeerr[1]); } if (devnull != -1) { close(devnull); } return 0; } else { //parent { tthread::lock_guard guard(plistMutex); plist.insert(pid); } HIGH_MSG("Piped process %s started, PID %d", argv[0], pid); if (devnull != -1) { close(devnull); } if (fdin && *fdin == -1) { close(pipein[0]); // close unused end end *fdin = pipein[1]; } if (fdout && *fdout == -1) { close(pipeout[1]); // close unused write end *fdout = pipeout[0]; } if (fderr && *fderr == -1) { close(pipeerr[1]); // close unused write end *fderr = pipeerr[0]; } } return pid; } /// Stops the process with this pid, if running. /// \arg name The PID of the process to stop. void Util::Procs::Stop(pid_t name) { kill(name, SIGTERM); } /// Stops the process with this pid, if running. /// \arg name The PID of the process to murder. void Util::Procs::Murder(pid_t name) { kill(name, SIGKILL); } /// (Attempts to) stop all running child processes. void Util::Procs::StopAll() { std::set listcopy; { tthread::lock_guard guard(plistMutex); listcopy = plist; } std::set::iterator it; for (it = listcopy.begin(); it != listcopy.end(); it++) { Stop(*it); } } /// Returns the number of active child processes. int Util::Procs::Count() { tthread::lock_guard guard(plistMutex); return plist.size(); } /// Returns true if a process with this PID is currently active. bool Util::Procs::isActive(pid_t name) { tthread::lock_guard guard(plistMutex); return (kill(name, 0) == 0); } /// Forget about the given PID, keeping it running on shutdown. void Util::Procs::forget(pid_t pid) { tthread::lock_guard guard(plistMutex); plist.erase(pid); } /// Remember the given PID, killing it on shutdown. void Util::Procs::remember(pid_t pid) { tthread::lock_guard guard(plistMutex); plist.insert(pid); }