diff --git a/CMakeLists.txt b/CMakeLists.txt index 17ea5f7..32dbf13 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,6 +19,7 @@ src/Mail/Smtp.cpp src/Serialization/Json.cpp src/Serialization/SQLite.cpp src/Platform/Environment.cpp +src/Platform/Process.cpp src/Streams/FileStream.cpp src/Streams/MemoryStream.cpp src/Streams/NetworkStream.cpp @@ -394,6 +395,9 @@ install(TARGETS tjsonunpretty DESTINATION bin) add_executable(ttime apps/ttime.cpp) target_link_libraries(ttime PUBLIC tessesframework) install(TARGETS ttime DESTINATION bin) +add_executable(tshell apps/tshell.cpp) +target_link_libraries(tshell PUBLIC tessesframework) +install(TARGETS tshell DESTINATION bin) endif() include(InstallRequiredSystemLibraries) diff --git a/CMakePresets.json b/CMakePresets.json index 3b7a47c..5869874 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -41,7 +41,8 @@ "cacheVariables": { "CMAKE_C_COMPILER": "cl.exe", "CMAKE_CXX_COMPILER": "cl.exe", - "TESSESFRAMEWORK_FETCHCONTENT": true + "TESSESFRAMEWORK_ENABLE_MBED": false, + "TESSESFRAMEWORK_FETCHCONTENT": false }, "condition": { "type": "equals", diff --git a/apps/tshell.cpp b/apps/tshell.cpp new file mode 100644 index 0000000..032b40e --- /dev/null +++ b/apps/tshell.cpp @@ -0,0 +1,127 @@ +#include "TessesFramework/TessesFramework.hpp" +#include "TessesFramework/Platform/Process.hpp" +using namespace Tesses::Framework; +using namespace Tesses::Framework::Platform; +using namespace Tesses::Framework::TextStreams; +using namespace Tesses::Framework::Filesystem; + +void split_command(std::string cmd, std::vector& args) +{ + bool inStr=false; + std::string cur={}; + auto flush=[&]()->void { + if(cur.empty()) return; + args.push_back(cur); + cur={}; + }; + for(size_t i = 0; i < cmd.size(); i++) + { + if(inStr) + { + if(cmd[i] == '\"') inStr=false; + else if(cmd[i] == '\\') + { + i++; + if(i < cmd.size()) + { + cmd.push_back(cmd[i]); + } + } + else cur.push_back(cmd[i]); + } + else + { + if(cmd[i] == ' ') flush(); + else if(cmd[i] == '\"') { + inStr=true; + } + else { + cur.push_back(cmd[i]); + } + } + + } + flush(); +} + +int main(int argc,char** argv) +{ + TF_Init(); + + + + while(true) + { + std::cout << VFSPath::GetAbsoluteCurrentDirectory().ToString() << "$ "; + + + std::string text; + StdIn().ReadLine(text); + std::vector args; + split_command(text,args); + if(args.empty()) continue; + if(args[0] == "exit") break; + else if(args[0] == "echo") { if(args.size() > 1) std::cout << args[1] << std::endl;} + else if(args[0] == "cd") { + if(args.size() < 2) + { + VFSPath::SetAbsoluteCurrentDirectory(Environment::SpecialFolders::GetHomeFolder()); + } + else { + VFSPath::SetAbsoluteCurrentDirectory(args[1]); + } + } + else if(args[0] == "printargs") + { + for(size_t i = 1; i < args.size(); i++) + { + std::cout << "\"" << Http::HttpUtils::Replace(args[i],"\"","\\\"") << "\"" << std::endl; + } + } + else if (args[0] == "sigtermtest") + { + if (args.size() < 2) continue; + std::vector args2(args.begin() + 1, args.end()); + + auto path = Environment::GetRealExecutablePath(args2[0]); + Platform::Process p(path.ToString(), args2); + if (p.Start()) + { + std::cout << "Press enter to sigterm" << std::endl; + StdIn().ReadLine(); + p.Kill(SIGTERM); + } + } + else if(args[0] == "rstdi") + { + if(args.size()<3) continue; + std::vector args2(args.begin()+2,args.end()); + auto f = LocalFS.OpenFile(args[1],"rb"); + if(f != nullptr) + { + auto path = Environment::GetRealExecutablePath(args2[0]); + Platform::Process p(path.ToString(),args2); + p.redirectStdIn=true; + if(p.Start()) + { + auto strm = p.GetStdinStream(); + f->CopyTo(strm); + delete strm; + p.CloseStdInNow(); + p.WaitForExit(); + } + delete f; + } + } + else { + auto path = Environment::GetRealExecutablePath(args[0]); + Platform::Process p(path.ToString(),args); + if (p.Start()) + p.WaitForExit(); + else + std::cout << "Failed To Run Process: " << path.ToString() << std::endl; + } + } + + TF_Quit(); +} \ No newline at end of file diff --git a/include/TessesFramework/Platform/Process.hpp b/include/TessesFramework/Platform/Process.hpp index 3915656..8080f41 100644 --- a/include/TessesFramework/Platform/Process.hpp +++ b/include/TessesFramework/Platform/Process.hpp @@ -1,12 +1,19 @@ +#pragma once #include #include #include "TessesFramework/Streams/Stream.hpp" #include "TessesFramework/HiddenField.hpp" +#include +#if defined(_WIN32) +#define SIGKILL 9 +#endif namespace Tesses::Framework::Platform { class Process { private: HiddenField hidden; + bool exited = false; + int exitCode=0; public: std::string name; std::vector args; @@ -16,6 +23,8 @@ class Process { bool redirectStdIn=false; bool redirectStdOut=false; bool redirectStdErr=false; + bool HasExited(); + void CloseStdInNow(); //YOU ARE RESPONSABLE FOR FREEING THIS STREAM OBJECT diff --git a/include/TessesFramework/TessesFramework.hpp b/include/TessesFramework/TessesFramework.hpp index c626c5d..31bbf38 100644 --- a/include/TessesFramework/TessesFramework.hpp +++ b/include/TessesFramework/TessesFramework.hpp @@ -35,6 +35,8 @@ #include "Serialization/Json.hpp" #include "Serialization/SQLite.hpp" #include "Platform/Environment.hpp" +#include "Platform/Process.hpp" +#include "Text/StringConverter.hpp" #include "SDL2/FontCache.hpp" #include "SDL2/Stream.hpp" -#include "SDL2/GUI.hpp" \ No newline at end of file +#include "SDL2/GUI.hpp" diff --git a/src/Filesystem/VFS.cpp b/src/Filesystem/VFS.cpp index 4096491..429d569 100644 --- a/src/Filesystem/VFS.cpp +++ b/src/Filesystem/VFS.cpp @@ -208,6 +208,7 @@ namespace Tesses::Framework::Filesystem } VFSPath VFSPath::MakeAbsolute(VFSPath curDir) { + if (!this->relative) return *this; VFSPath p2 = curDir / *this; return p2.CollapseRelativeParents(); } diff --git a/src/Platform/Environment.cpp b/src/Platform/Environment.cpp index ebe4108..a6b6627 100644 --- a/src/Platform/Environment.cpp +++ b/src/Platform/Environment.cpp @@ -7,6 +7,7 @@ #include #include "TessesFramework/Filesystem/VFSFix.hpp" #include "TessesFramework/Text/StringConverter.hpp" + using namespace Tesses::Framework::Text::StringConverter; #endif #if !defined(_WIN32) @@ -120,8 +121,9 @@ namespace Tesses::Framework::Platform::Environment VFSPath GetRealExecutablePath(VFSPath realPath) { using namespace Tesses::Framework::Http; + - if(!realPath.relative) return realPath.MakeAbsolute(); + if(!realPath.relative) return realPath; if(LocalFS.FileExists(realPath)) return realPath.MakeAbsolute(); const char* path = std::getenv("PATH"); #if defined(_WIN32) @@ -216,9 +218,9 @@ namespace Tesses::Framework::Platform::Environment } FreeEnvironmentStringsW(environ0); #else - for(char** envthing = environ; envthing != NULL; envthing++) + for(char** envthing = environ; *envthing != NULL; envthing++) { - + //if(*envthing == NULL) break; auto items = Http::HttpUtils::SplitString(*envthing,"=",2); if(items.size() == 2) { diff --git a/src/Platform/Process.cpp b/src/Platform/Process.cpp index 9021dc9..f81e734 100644 --- a/src/Platform/Process.cpp +++ b/src/Platform/Process.cpp @@ -1,6 +1,7 @@ #include "TessesFramework/Platform/Process.hpp" #include "TessesFramework/Http/HttpUtils.hpp" #include "TessesFramework/Platform/Environment.hpp" +#include #if defined(_WIN32) @@ -8,7 +9,7 @@ extern "C" { #include } #include "TessesFramework/Filesystem/VFSFix.hpp" -#include 'TessesFramework/Text/StringConverter.hpp' +#include "TessesFramework/Text/StringConverter.hpp" using namespace Tesses::Framework::Text::StringConverter; static void escape_windows_args(std::string& str, std::vector args) { @@ -38,7 +39,6 @@ static void escape_windows_args(std::string& str, std::vector args) #else #include -#include #include @@ -48,16 +48,30 @@ static void escape_windows_args(std::string& str, std::vector args) namespace Tesses::Framework::Platform { - class ProcessData { + class ProcessData : public HiddenFieldData { public: //TODO: Implement for WIN32 #if defined(_WIN32) - STARTUPINFO si; + 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__) @@ -67,6 +81,8 @@ namespace Tesses::Framework::Platform { int stderr_strm; pid_t pid; #endif + + ProcessData() { //TODO: Implement for WIN32 #if defined(_WIN32) @@ -90,7 +106,7 @@ namespace Tesses::Framework::Platform { HANDLE strm; bool writing; bool eos; - #elif defined(GEKKO) || defined(__PS2__) defined(__SWITCH__) + #elif defined(GEKKO) || defined(__PS2__) || defined(__SWITCH__) #else int strm; @@ -105,7 +121,7 @@ namespace Tesses::Framework::Platform { this->writing = writing; this->eos = false; } - #elif defined(GEKKO) || defined(__PS2__) defined(__SWITCH__) + #elif defined(GEKKO) || defined(__PS2__) || defined(__SWITCH__) #else ProcessStream(int strm, bool writing) { @@ -119,7 +135,7 @@ namespace Tesses::Framework::Platform { //TODO: Implement for WIN32 #if defined(_WIN32) return this->strm == NULL || eos; - #elif defined(GEKKO) || defined(__PS2__) defined(__SWITCH__) + #elif defined(GEKKO) || defined(__PS2__) || defined(__SWITCH__) return true; #else return this->strm < 0 || eos; @@ -130,7 +146,7 @@ namespace Tesses::Framework::Platform { //TODO: Implement for WIN32 #if defined(_WIN32) return !writing && this->strm != NULL; - #elif defined(GEKKO) || defined(__PS2__) defined(__SWITCH__) + #elif defined(GEKKO) || defined(__PS2__) || defined(__SWITCH__) return false; #else return !writing && this->strm > -1; @@ -142,7 +158,7 @@ namespace Tesses::Framework::Platform { #if defined(_WIN32) return writing && this->strm != NULL; - #elif defined(GEKKO) || defined(__PS2__) defined(__SWITCH__) + #elif defined(GEKKO) || defined(__PS2__) || defined(__SWITCH__) return false; #else return writing && this->strm > -1; @@ -161,8 +177,8 @@ namespace Tesses::Framework::Platform { if (dataR == 0) { this->eos = true; } - return (size_t)dataW; - #elif defined(GEKKO) || defined(__PS2__) defined(__SWITCH__) + return (size_t)dataR; + #elif defined(GEKKO) || defined(__PS2__) || defined(__SWITCH__) return 0; #else if(this->strm < 0 || this->eos && writing) return 0; @@ -184,7 +200,7 @@ namespace Tesses::Framework::Platform { return 0; return (size_t)dataW; - #elif defined(GEKKO) || defined(__PS2__) defined(__SWITCH__) + #elif defined(GEKKO) || defined(__PS2__) || defined(__SWITCH__) return 0; #else if(this->strm < 0 || !writing) return 0; @@ -213,6 +229,31 @@ namespace Tesses::Framework::Platform { 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()); @@ -232,11 +273,35 @@ namespace Tesses::Framework::Platform { } } } + 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); @@ -245,9 +310,25 @@ namespace Tesses::Framework::Platform { 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 } @@ -258,7 +339,7 @@ namespace Tesses::Framework::Platform { ProcessData* p = this->hidden.GetField(); std::vector> envs; - + if(this->includeThisEnv) Environment::GetEnvironmentVariables(envs); @@ -269,6 +350,7 @@ namespace Tesses::Framework::Platform { { if(item.first == itemNew.first) { + item.second = itemNew.second; has=true; break; @@ -278,6 +360,11 @@ namespace Tesses::Framework::Platform { } #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; @@ -306,66 +393,89 @@ namespace Tesses::Framework::Platform { attr.nLength = sizeof(attr); attr.lpSecurityDescriptor = NULL; attr.bInheritHandle = true; - p->si->hStdInput = NULL; - p->si->hStdOutput = NULL; + p->si.hStdInput = NULL; + p->si.hStdOutput = NULL; - p->si->hStdError = 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; + 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 (!CreatePipe(&p->stdout_strm, &p->si.hStdOutput, &attr, 0)) { if (this->redirectStdIn) { CloseHandle(p->stdin_strm); - CloseHandle(p->si->hStdInput); + 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 (!CreatePipe(&p->stderr_strm, &p->si.hStdError, &attr, 0)) { if (this->redirectStdIn) { CloseHandle(p->stdin_strm); - CloseHandle(p->si->hStdInput); + CloseHandle(p->si.hStdInput); } if (this->redirectStdOut) { CloseHandle(p->stdout_strm); - CloseHandle(p->si->hStdOutput); + CloseHandle(p->si.hStdOutput); } return false; } + + SetHandleInformation(p->stderr_strm, HANDLE_FLAG_INHERIT, 0); } - if (!CreateProcessW(u16_name.c_str(), u16_args.data(), NULL, NULL, (this->redirectStdIn || this->redirectStdOut || this->redirectStdErr), CREATE_UNICODE_ENVIRONMENT, (LPCWSTR)env.c_str(), workDir.empty() ? (LPCWSTR)NULL : (LPCWSTR)workDir.c_str(), &(p->si), &(p->pi))) + 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); + CloseHandle(p->si.hStdInput); } if (this->redirectStdOut) { CloseHandle(p->stdout_strm); - CloseHandle(p->si->hStdOutput); + CloseHandle(p->si.hStdOutput); } if (this->redirectStdErr) { CloseHandle(p->stderr_strm); - CloseHandle(p->si->hStdError); + CloseHandle(p->si.hStdError); } return false; } @@ -386,8 +496,8 @@ CreateProcessW( _Out_ LPPROCESS_INFORMATION lpProcessInformation ); */ - return false; - #elif defined(GEKKO) || defined(__PS2__) defined(__SWITCH__) + return true; + #elif defined(GEKKO) || defined(__PS2__) || defined(__SWITCH__) return false; #else @@ -460,6 +570,7 @@ CreateProcessW( std::vector env2; env2.resize(envs.size()); + for(size_t i = 0; i < envs.size(); i++) { env2[i] = envs[i].first + "=" + envs[i].second; @@ -468,13 +579,13 @@ CreateProcessW( char** argv = new char*[args.size()+1]; argv[args.size()]=NULL; char** envp = new char*[env2.size()+1]; - envp[env.size()]=NULL; + 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 < env.size();i++) + for(size_t i = 0; i < env2.size();i++) { envp[i] = (char*)env2[i].c_str(); } @@ -512,10 +623,31 @@ CreateProcessW( #endif } + + void Process::Kill(int signal) { #if defined(_WIN32) - #elif defined(GEKKO) || defined(__PS2__) defined(__SWITCH__) + 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 @@ -523,22 +655,34 @@ CreateProcessW( int Process::WaitForExit() { + if (this->exited) return this->exitCode; #if defined(_WIN32) - #elif defined(GEKKO) || defined(__PS2__) defined(__SWITCH__) - reutnr -1; + 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) + 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 defined(_WIN32) - return nullptr; - #elif defined(GEKKO) || defined(__PS2__) defined(__SWITCH__) + if (this->exited) return nullptr; + #if defined(GEKKO) || defined(__PS2__) || defined(__SWITCH__) return nullptr; #else return new ProcessStream(this->hidden.GetField()->stdin_strm,true); @@ -546,9 +690,9 @@ CreateProcessW( } Tesses::Framework::Streams::Stream* Process::GetStdoutStream() { - #if defined(_WIN32) - return nullptr; - #elif defined(GEKKO) || defined(__PS2__) defined(__SWITCH__) + + if (this->exited) return nullptr; + #if defined(GEKKO) || defined(__PS2__) || defined(__SWITCH__) return nullptr; #else return new ProcessStream(this->hidden.GetField()->stdout_strm,false); @@ -556,9 +700,9 @@ CreateProcessW( } Tesses::Framework::Streams::Stream* Process::GetStderrStream() { - #if defined(_WIN32) - return nullptr; - #elif defined(GEKKO) || defined(__PS2__) defined(__SWITCH__) + + if (this->exited) return nullptr; + #if defined(GEKKO) || defined(__PS2__) || defined(__SWITCH__) return nullptr; #else return new ProcessStream(this->hidden.GetField()->stderr_strm,false); diff --git a/src/TF_Init.cpp b/src/TF_Init.cpp index ea3ea8a..1bab200 100644 --- a/src/TF_Init.cpp +++ b/src/TF_Init.cpp @@ -132,6 +132,14 @@ namespace Tesses::Framework } #endif + + #if defined(_WIN32) + MSG winMSG; + if (PeekMessage(&winMSG, NULL, WM_QUIT, WM_QUIT, 1)) + { + isRunning = false; + } + #endif #if defined(TESSESFRAMEWORK_ENABLE_SDL2) Tesses::Framework::SDL2::gui.Update(); @@ -182,6 +190,8 @@ namespace Tesses::Framework tzset(); #if defined(_WIN32) system(" "); + signal(SIGINT, _sigInt); + signal(SIGTERM, _sigInt); #endif isRunning=true; @@ -214,6 +224,7 @@ if (iResult != 0) { #else signal(SIGPIPE,SIG_IGN); signal(SIGINT,_sigInt); + signal(SIGTERM, _sigInt); #endif }