/* EQEmu: EQEmulator
Copyright (C) 2001-2026 EQEmu Development Team
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 3 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, see .
*/
#include "proc_launcher.h"
#include "common/types.h"
#include
#include
#include
#include
#include
#ifdef _WINDOWS
#include "common/platform/win/include_windows.h"
#else
#include
#include
#include
#include
#include
#include
#include
#endif
ProcLauncher ProcLauncher::s_launcher;
#ifdef _WINDOWS
const ProcLauncher::ProcRef ProcLauncher::ProcError = 0xFFFFFFFF;
#else
const ProcLauncher::ProcRef ProcLauncher::ProcError = -1;
#endif
ProcLauncher::ProcLauncher()
{
#ifndef WIN32
if(signal(SIGCHLD, ProcLauncher::HandleSigChild) == SIG_ERR)
fprintf(stderr, "Unable to register child signal handler. Thats bad.");
m_signalCount = 0;
#endif
}
void ProcLauncher::Process() {
#ifdef _WINDOWS
std::map::iterator cur, end, tmp;
cur = m_running.begin();
end = m_running.end();
while(cur != end) {
DWORD res;
if(GetExitCodeProcess(cur->second->proc_info.hProcess, &res)) {
//got exit code, see if its still running...
if(res == STILL_ACTIVE) {
++cur;
continue;
}
//else, it died, handle properly
} else {
//not sure the right thing to do here... why would this fail?
//GetLastError();
TerminateProcess(cur->second->proc_info.hProcess, 1);
}
//if we get here, the current process died.
tmp = cur;
++tmp;
ProcessTerminated(cur);
cur = tmp;
}
#else //!WIN32
while(m_signalCount > 0) {
m_signalCount--;
int status;
ProcRef died = waitpid(-1, &status, WNOHANG);
if(died == -1) {
//error waiting... shouldent really happen...
} else if(died == 0) {
//nothing pending...
break;
} else {
//one died...
std::map::iterator ref;
ref = m_running.find(died);
if(ref == m_running.end()) {
//unable to find this process in our list...
} else {
//found... hooray
ProcessTerminated(ref);
}
}
}
#endif //!WIN32
}
void ProcLauncher::ProcessTerminated(std::map::iterator &it) {
if(it->second->handler != nullptr)
it->second->handler->OnTerminate(it->first, it->second);
#ifdef _WINDOWS
CloseHandle(it->second->proc_info.hProcess);
#else //!WIN32
#endif //!WIN32
delete it->second;
m_running.erase(it);
}
ProcLauncher::ProcRef ProcLauncher::Launch(Spec *&to_launch) {
//consume the pointer
Spec *it = to_launch;
to_launch = nullptr;
#ifdef _WINDOWS
STARTUPINFO siStartInfo;
BOOL bFuncRetn = FALSE;
// Set up members of the PROCESS_INFORMATION structure.
ZeroMemory( &it->proc_info, sizeof(PROCESS_INFORMATION) );
// Set up members of the STARTUPINFO structure.
ZeroMemory( &siStartInfo, sizeof(STARTUPINFO) );
siStartInfo.cb = sizeof(STARTUPINFO);
siStartInfo.dwFlags = 0;
//handle output redirection.
HANDLE logOut = nullptr;
BOOL inherit_handles = FALSE;
if(it->logFile.length() > 0) {
inherit_handles = TRUE;
// Set up our log file to redirect output into.
SECURITY_ATTRIBUTES saAttr;
saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
saAttr.bInheritHandle = TRUE; //we want this handle to be inherited by the child.
saAttr.lpSecurityDescriptor = nullptr;
logOut = CreateFile(
it->logFile.c_str(), //lpFileName
FILE_WRITE_DATA, //dwDesiredAccess
FILE_SHARE_READ, //dwShareMode
&saAttr, //lpSecurityAttributes
CREATE_ALWAYS, //dwCreationDisposition
FILE_FLAG_NO_BUFFERING, //dwFlagsAndAttributes
nullptr ); //hTemplateFile
//configure the startup info to redirect output appropriately.
siStartInfo.hStdError = logOut;
siStartInfo.hStdOutput = logOut;
siStartInfo.hStdInput = nullptr;
siStartInfo.dwFlags |= STARTF_USESTDHANDLES;
}
siStartInfo.dwFlags |= CREATE_NEW_CONSOLE;
// Create the child process.
//glue together all the nice command line arguments
std::string args(it->program);
std::vector::iterator cur, end;
cur = it->args.begin();
end = it->args.end();
for(; cur != end; ++cur) {
args += " ";
args += *cur;
}
bFuncRetn = CreateProcess(it->program.c_str(),
const_cast(args.c_str()), // command line
nullptr, // process security attributes
nullptr, // primary thread security attributes
inherit_handles, // handles are not inherited
0, // creation flags (CREATE_NEW_PROCESS_GROUP maybe)
nullptr, // use parent's environment
nullptr, // use parent's current directory
&siStartInfo, // STARTUPINFO pointer
&it->proc_info); // receives PROCESS_INFORMATION
if (bFuncRetn == 0) {
safe_delete(it);
//GetLastError()
return(ProcError);
}
//keep process handle open to get exit code
CloseHandle(it->proc_info.hThread); //we dont need their thread handle
if(logOut != nullptr)
CloseHandle(logOut); //we dont want their output handle either.
ProcRef res = it->proc_info.dwProcessId;
//record this entry..
m_running[res] = it;
return(res);
#else //!WIN32
//build argv
auto argv = new char *[it->args.size() + 2];
unsigned int r;
argv[0] = const_cast(it->program.c_str());
for(r = 1; r <= it->args.size(); r++) {
argv[r] = const_cast(it->args[r-1].c_str());
}
argv[r] = nullptr;
ProcRef res = fork(); //cant use vfork since we are opening the log file.
if(res == -1) {
//error forking... errno
safe_delete(it);
safe_delete_array(argv);
return(ProcError);
}
if(res == 0) {
//child... exec this bitch
//handle output redirection if requested.
if(it->logFile.length() > 0) {
//we will put their output directly into a file.
int outfd = creat(it->logFile.c_str(), S_IRUSR | S_IWUSR | S_IRGRP); // S_I + R/W/X + USR/GRP/OTH
if(outfd == -1) {
fprintf(stderr, "Unable to open log file %s: %s.\n", it->logFile.c_str(), strerror(errno));
close(STDOUT_FILENO);
close(STDERR_FILENO);
close(STDIN_FILENO);
} else {
close(STDOUT_FILENO);
if(dup2(outfd, STDOUT_FILENO) == -1) {
fprintf(stderr, "Unable to duplicate FD %d to %d. Log file will be empty: %s\n", outfd, STDOUT_FILENO, strerror(errno));
const char *err = "Unable to redirect stdout into this file. That sucks.";
write(outfd, err, strlen(err));
}
close(STDERR_FILENO);
if(dup2(outfd, STDERR_FILENO) == -1) {
//can no longer print to screen..
const char *err = "Unable to redirect stderr into this file. You might miss some error info in this log.";
write(outfd, err, strlen(err));
}
close(STDIN_FILENO);
close(outfd); //dont need this one, we have two more copies...
}
}
//call it...
execv(argv[0], argv);
_exit(1);
}
safe_delete_array(argv);
//record this entry..
m_running[res] = it;
return(res);
#endif //!WIN32
}
//if graceful is true, we try to be nice about it if possible
bool ProcLauncher::Terminate(const ProcRef &proc, bool graceful) {
//we are only willing to kill things we started...
auto res = m_running.find(proc);
if(res == m_running.end())
return(false);
//we do not remove it from the list until we have been notified
//that they have been terminated.
#ifdef _WINDOWS
if(!TerminateProcess(res->second->proc_info.hProcess, 0)) {
return(false);
}
#else //!WIN32
int sig;
if(graceful)
sig = SIGTERM;
else
sig = SIGKILL;
if(kill(proc, sig) == -1) {
return(false);
}
#endif //!WIN32
return(true);
}
void ProcLauncher::TerminateAll(bool final) {
if(!final) {
//send a nice terminate to each process, with intention of waiting for them
std::map::iterator cur, end;
cur = m_running.begin();
end = m_running.end();
for(; cur != end; ++cur) {
Terminate(cur->first, true);
}
} else {
//kill each process and remove it from the list
std::map running(m_running);
m_running.clear();
std::map::iterator cur, end;
cur = running.begin();
end = running.end();
for(; cur != end; ++cur) {
Terminate(cur->first, true);
safe_delete(cur->second);
}
}
}
#ifndef WIN32
void ProcLauncher::HandleSigChild(int signum) {
if(signum == SIGCHLD) {
ProcLauncher::get()->m_signalCount++;
}
}
#endif
ProcLauncher::Spec::Spec() {
handler = nullptr;
}
ProcLauncher::Spec::Spec(const Spec &other) {
program = other.program;
args = other.args;
handler = other.handler;
logFile = other.logFile;
}
ProcLauncher::Spec &ProcLauncher::Spec::operator=(const Spec &other) {
program = other.program;
args = other.args;
handler = other.handler;
logFile = other.logFile;
return(*this);
}