#include "TessesFramework/Platform/Process.hpp" #include "TessesFramework/Http/HttpUtils.hpp" #include "TessesFramework/Platform/Environment.hpp" #include #if defined(_WIN32) extern "C" { #include } #include "TessesFramework/Filesystem/VFSFix.hpp" #include "TessesFramework/Text/StringConverter.hpp" using namespace Tesses::Framework::Text::StringConverter; static void escape_windows_args(std::string& str, std::vector args) { bool first = true; for (auto item : args) { if (first) { str.push_back('\"'); first = false; } else { str.append(" \""); } for (auto c : item) { if (c == '"') str.append("\\\""); else str.push_back(c); } str.push_back('\"'); } } #else #include #include #endif namespace Tesses::Framework::Platform { class ProcessData : public HiddenFieldData { public: //TODO: Implement for WIN32 #if defined(_WIN32) STARTUPINFOW si; PROCESS_INFORMATION pi; HANDLE stdin_strm; HANDLE stdout_strm; HANDLE stderr_strm; static int __stdcall KillGraceFully(HWND hndle, LPARAM arg) { auto pd=static_cast((void*)arg); DWORD curProc; GetWindowThreadProcessId(hndle, &curProc); if (curProc == pd->pi.dwProcessId) { PostMessage(hndle, WM_CLOSE, 0, 0); return true; } return false; } #elif defined(GEKKO) || defined(__PS2__) || defined(__SWITCH__) #else int stdin_strm; int stdout_strm; int stderr_strm; pid_t pid; #endif ProcessData() { //TODO: Implement for WIN32 #if defined(_WIN32) stdin_strm = NULL; stdout_strm = NULL; stderr_strm = NULL; #elif defined(GEKKO) || defined(__PS2__) || defined(__SWITCH__) #else this->stdin_strm=-1; this->stdout_strm = -1; this->stderr_strm=-1; #endif } }; class ProcessStream : public Tesses::Framework::Streams::Stream { #if defined(_WIN32) HANDLE strm; bool writing; bool eos; #elif defined(GEKKO) || defined(__PS2__) || defined(__SWITCH__) #else int strm; bool writing; bool eos; #endif public: #if defined(_WIN32) ProcessStream(HANDLE strm, bool writing) { this->strm = strm; this->writing = writing; this->eos = false; } #elif defined(GEKKO) || defined(__PS2__) || defined(__SWITCH__) #else ProcessStream(int strm, bool writing) { this->strm = strm; this->writing = writing; this->eos=false; } #endif bool EndOfStream() { //TODO: Implement for WIN32 #if defined(_WIN32) return this->strm == NULL || eos; #elif defined(GEKKO) || defined(__PS2__) || defined(__SWITCH__) return true; #else return this->strm < 0 || eos; #endif } bool CanRead() { //TODO: Implement for WIN32 #if defined(_WIN32) return !writing && this->strm != NULL; #elif defined(GEKKO) || defined(__PS2__) || defined(__SWITCH__) return false; #else return !writing && this->strm > -1; #endif } bool CanWrite() { //TODO: Implement for WIN32 #if defined(_WIN32) return writing && this->strm != NULL; #elif defined(GEKKO) || defined(__PS2__) || defined(__SWITCH__) return false; #else return writing && this->strm > -1; #endif } size_t Read(uint8_t* buff, size_t sz) { //TODO: Implement for WIN32 #if defined(_WIN32) if (this->strm == NULL || this->eos && writing) return 0; DWORD dataR = (DWORD)sz; if (!ReadFile(this->strm, buff, dataR, &dataR, NULL)) return 0; if (dataR == 0) { this->eos = true; } return (size_t)dataR; #elif defined(GEKKO) || defined(__PS2__) || defined(__SWITCH__) return 0; #else if(this->strm < 0 || this->eos && writing) return 0; auto r = read(this->strm,buff,sz); if(r == -1) return 0; if(r == 0 && sz != 0) { this->eos=true; return 0;} return (size_t)r; #endif } size_t Write(const uint8_t* buff, size_t sz) { //TODO: Implement for WIN32 #if defined(_WIN32) if (this->strm == NULL || !writing) return 0; DWORD dataW=(DWORD)sz; if (!WriteFile(this->strm, buff, dataW, &dataW, NULL)) return 0; return (size_t)dataW; #elif defined(GEKKO) || defined(__PS2__) || defined(__SWITCH__) return 0; #else if(this->strm < 0 || !writing) return 0; auto r = write(this->strm,buff,sz); if(r == -1) return 0; return (size_t)r; #endif } }; Process::Process() : Process("",{},true) { } Process::Process(std::string name, std::vector args,bool includeThisEnv) : Process(name,args,std::vector>(),includeThisEnv) { } Process::Process(std::string name, std::vector args, std::vector> env,bool includeThisEnv) { this->name = name; this->args = args; this->env = env; this->includeThisEnv = includeThisEnv; this->hidden.AllocField(); } bool Process::HasExited() { if (this->exited) return true; ProcessData* p = this->hidden.GetField(); #if defined(_WIN32) if (WaitForSingleObject(p->pi.hProcess, 0) == WAIT_OBJECT_0) { DWORD ec = 0; GetExitCodeProcess(p->pi.hProcess,&ec); this->exitCode = (int)ec; this->exited = true; return true; } #else int r; if (waitpid(this->hidden.GetField()->pid, &r, WNOHANG) != -1) { this->exited = true; this->exitCode = r; return r; } #endif return false; } Process::Process(std::string name, std::vector args, std::vector env,bool includeThisEnv) : Process(name,args,std::vector>(),includeThisEnv) { this->env.resize(env.size()); for(size_t i =0; i < env.size(); i++) { auto res=Http::HttpUtils::SplitString(env[i],"=",2); if(res.size() == 2) { this->env[i].first = res[0]; this->env[i].second = res[1]; } else if(res.size() == 1) { this->env[i].first = res[0]; this->env[i].second = ""; } } } void Process::CloseStdInNow() { ProcessData* p = this->hidden.GetField(); #if defined(_WIN32) if (p->stdin_strm != NULL) { CloseHandle(p->stdin_strm); p->stdin_strm = NULL; } #elif defined(GEKKO) || defined(__PS2__) || defined(__SWITCH__) #else if (p->stdin_strm > -1) { close(p->stdin_strm); p->stdin_strm = -1; } #endif } Process::~Process() { ProcessData* p = this->hidden.GetField(); #if defined(_WIN32) if(!this->exited) Kill(SIGTERM); if (p->stdin_strm != NULL) CloseHandle(p->stdin_strm); if (p->stdout_strm != NULL) CloseHandle(p->stdout_strm); if (p->stderr_strm != NULL) CloseHandle(p->stderr_strm); CloseHandle(p->pi.hProcess); CloseHandle(p->pi.hThread); #elif defined(GEKKO) || defined(__PS2__) || defined(__SWITCH__) #else if (!this->exited) { Kill(SIGTERM); WaitForExit(); } if (p->stdin_strm != -1) close(p->stdin_strm); if (p->stdout_strm != -1) close(p->stdout_strm); if (p->stderr_strm != -1) close(p->stderr_strm); #endif } bool Process::Start() { ProcessData* p = this->hidden.GetField(); std::vector> envs; if(this->includeThisEnv) Environment::GetEnvironmentVariables(envs); for(auto itemNew : this->env) { bool has=false; for(auto& item : envs) { if(item.first == itemNew.first) { item.second = itemNew.second; has=true; break; } } if(!has) envs.push_back(itemNew); } #if defined(_WIN32) ZeroMemory(&p->si, sizeof(p->si)); p->si.cb = sizeof(p->si); ZeroMemory(&p->pi, sizeof(p->pi)); std::u16string u16_name; std::u16string u16_args; std::string args; escape_windows_args(args,this->args); UTF16::FromUTF8(u16_name,this->name); UTF16::FromUTF8(u16_args, args); std::u16string env = {}; for (auto envItem : envs) { auto partOld = envItem.first + "=" + envItem.second; std::u16string part = {}; UTF16::FromUTF8(part,partOld); env.append(part); env.push_back(0); } env.push_back(0); std::u16string workDir = {}; if (!this->workingDirectory.empty()) UTF16::FromUTF8(workDir, this->workingDirectory); SECURITY_ATTRIBUTES attr; attr.nLength = sizeof(attr); attr.lpSecurityDescriptor = NULL; attr.bInheritHandle = true; p->si.hStdInput = NULL; p->si.hStdOutput = NULL; p->si.hStdError = NULL; p->stdin_strm = NULL; p->stdout_strm = NULL; p->stderr_strm = NULL; if (this->redirectStdIn || this->redirectStdOut || this->redirectStdErr) { p->si.dwFlags |= STARTF_USESTDHANDLES; if (!this->redirectStdIn) { p->si.hStdInput = GetStdHandle(STD_INPUT_HANDLE); } if (!this->redirectStdOut) { p->si.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE); } if (!this->redirectStdOut) { p->si.hStdError = GetStdHandle(STD_ERROR_HANDLE); } } if (this->redirectStdIn) { if (!CreatePipe(&p->si.hStdInput, &p->stdin_strm, &attr,0)) return false; SetHandleInformation(p->stdin_strm, HANDLE_FLAG_INHERIT, 0); } if (this->redirectStdOut) { if (!CreatePipe(&p->stdout_strm, &p->si.hStdOutput, &attr, 0)) { if (this->redirectStdIn) { CloseHandle(p->stdin_strm); CloseHandle(p->si.hStdInput); } return false; } SetHandleInformation(p->stdout_strm, HANDLE_FLAG_INHERIT, 0); } if (this->redirectStdErr) { if (!CreatePipe(&p->stderr_strm, &p->si.hStdError, &attr, 0)) { if (this->redirectStdIn) { CloseHandle(p->stdin_strm); CloseHandle(p->si.hStdInput); } if (this->redirectStdOut) { CloseHandle(p->stdout_strm); CloseHandle(p->si.hStdOutput); } return false; } SetHandleInformation(p->stderr_strm, HANDLE_FLAG_INHERIT, 0); } if (!CreateProcessW((LPCWSTR)u16_name.c_str(), (LPWSTR)u16_args.data(), NULL, NULL, (this->redirectStdIn || this->redirectStdOut || this->redirectStdErr), CREATE_UNICODE_ENVIRONMENT, (LPVOID)env.c_str(), workDir.empty() ? (LPCWSTR)NULL : (LPCWSTR)workDir.c_str(), &(p->si), &(p->pi))) { if (this->redirectStdIn) { CloseHandle(p->stdin_strm); CloseHandle(p->si.hStdInput); } if (this->redirectStdOut) { CloseHandle(p->stdout_strm); CloseHandle(p->si.hStdOutput); } if (this->redirectStdErr) { CloseHandle(p->stderr_strm); CloseHandle(p->si.hStdError); } return false; } /* BOOL WINAPI CreateProcessW( _In_opt_ LPCWSTR lpApplicationName, _Inout_opt_ LPWSTR lpCommandLine, _In_opt_ LPSECURITY_ATTRIBUTES lpProcessAttributes, _In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes, _In_ BOOL bInheritHandles, _In_ DWORD dwCreationFlags, _In_opt_ LPVOID lpEnvironment, _In_opt_ LPCWSTR lpCurrentDirectory, _In_ LPSTARTUPINFOW lpStartupInfo, _Out_ LPPROCESS_INFORMATION lpProcessInformation ); */ return true; #elif defined(GEKKO) || defined(__PS2__) || defined(__SWITCH__) return false; #else int strm_stdin[2]; int strm_stdout[2]; int strm_stderr[2]; if(this->redirectStdIn) { if(pipe(strm_stdin) == -1) return false; p->stdin_strm = strm_stdin[1]; } if(this->redirectStdOut) { if(pipe(strm_stdout) == -1) { if(this->redirectStdIn) { close(strm_stdin[0]); close(strm_stdin[1]); } return false; } p->stdout_strm = strm_stdout[0]; } if(this->redirectStdErr) { if(pipe(strm_stderr) == -1) { if(this->redirectStdIn) { close(strm_stdin[0]); close(strm_stdin[1]); } if(this->redirectStdOut) { close(strm_stdout[0]); close(strm_stdout[1]); } return false; } p->stderr_strm = strm_stderr[0]; } auto pid=fork(); if(pid == -1) { if(this->redirectStdIn) { close(strm_stdin[0]); close(strm_stdin[1]); } if(this->redirectStdOut) { close(strm_stdout[0]); close(strm_stdout[1]); } if(this->redirectStdErr) { close(strm_stderr[0]); close(strm_stderr[1]); } return false; } if(pid == 0) { std::vector env2; env2.resize(envs.size()); for(size_t i = 0; i < envs.size(); i++) { env2[i] = envs[i].first + "=" + envs[i].second; } char** argv = new char*[args.size()+1]; argv[args.size()]=NULL; char** envp = new char*[env2.size()+1]; envp[env2.size()]=NULL; for(size_t i = 0; i < args.size();i++) { argv[i] = (char*)args[i].c_str(); } for(size_t i = 0; i < env2.size();i++) { envp[i] = (char*)env2[i].c_str(); } if(this->redirectStdIn) { dup2(strm_stdin[0],0); close(strm_stdin[0]); close(strm_stdin[1]); } if(this->redirectStdOut) { dup2(strm_stdout[1],1); close(strm_stdout[0]); close(strm_stdout[1]); } if(this->redirectStdErr) { dup2(strm_stderr[1],2); close(strm_stderr[0]); close(strm_stderr[1]); } execve(this->name.c_str(),argv,envp); exit(1); } p->pid = pid; if(this->redirectStdIn) close(strm_stdin[0]); if(this->redirectStdOut) close(strm_stdout[1]); if(this->redirectStdErr) close(strm_stderr[1]); return true; #endif } void Process::Kill(int signal) { #if defined(_WIN32) if (signal != SIGKILL && signal != SIGTERM) std::cout << "WARN: We terminated the process" << std::endl; if (signal == SIGTERM) { auto win = this->hidden.GetField(); if (EnumWindows(ProcessData::KillGraceFully, (LPARAM)(void*)win)) { if (WaitForSingleObject(win->pi.hProcess, 60000) != WAIT_OBJECT_0) { PostThreadMessage(win->pi.dwThreadId, WM_QUIT, 0, 0); } } else { PostThreadMessage(win->pi.dwThreadId, WM_QUIT, 0, 0); } } else TerminateProcess(this->hidden.GetField()->pi.hProcess,-1); #elif defined(GEKKO) || defined(__PS2__) || defined(__SWITCH__) #else kill(this->hidden.GetField()->pid,signal); #endif } int Process::WaitForExit() { if (this->exited) return this->exitCode; #if defined(_WIN32) auto p = this->hidden.GetField(); WaitForSingleObject(p->pi.hProcess, INFINITE); DWORD ret=0; GetExitCodeProcess(p->pi.hThread, &ret); this->exitCode = (int)ret; this->exited = true; return (int)ret; #elif defined(GEKKO) || defined(__PS2__) || defined(__SWITCH__) return -1; #else int r; if (waitpid(this->hidden.GetField()->pid, &r, 0) != -1) { this->exited = true; this->exitCode = r; return r; } return -1; #endif } Tesses::Framework::Streams::Stream* Process::GetStdinStream() { if (this->exited) return nullptr; #if defined(GEKKO) || defined(__PS2__) || defined(__SWITCH__) return nullptr; #else return new ProcessStream(this->hidden.GetField()->stdin_strm,true); #endif } Tesses::Framework::Streams::Stream* Process::GetStdoutStream() { if (this->exited) return nullptr; #if defined(GEKKO) || defined(__PS2__) || defined(__SWITCH__) return nullptr; #else return new ProcessStream(this->hidden.GetField()->stdout_strm,false); #endif } Tesses::Framework::Streams::Stream* Process::GetStderrStream() { if (this->exited) return nullptr; #if defined(GEKKO) || defined(__PS2__) || defined(__SWITCH__) return nullptr; #else return new ProcessStream(this->hidden.GetField()->stderr_strm,false); #endif } }