diff --git a/CMakeLists.txt b/CMakeLists.txt index 9089c54..ef8c4c0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -78,6 +78,14 @@ add_test( COMMAND hyprutils_math "math") add_dependencies(tests hyprutils_math) +add_executable(hyprutils_os "tests/os.cpp") +target_link_libraries(hyprutils_os PRIVATE hyprutils PkgConfig::deps) +add_test( + NAME "OS" + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests + COMMAND hyprutils_os "os") +add_dependencies(tests hyprutils_os) + # Installation install(TARGETS hyprutils) install(DIRECTORY "include/hyprutils" DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) diff --git a/include/hyprutils/os/Process.hpp b/include/hyprutils/os/Process.hpp new file mode 100644 index 0000000..71cc743 --- /dev/null +++ b/include/hyprutils/os/Process.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include +#include + +namespace Hyprutils { + namespace OS { + class CProcess { + public: + /* Creates a process object, doesn't run yet */ + CProcess(const std::string& binary_, const std::vector& args_); + + /* Run the process, synchronously, get the stdout and stderr. False on fail */ + bool runSync(); + + /* Run the process, asynchronously. This will detach the process from this object (and process) and let it live a happy life. False on fail. */ + bool runAsync(); + + // only populated when ran sync + const std::string& stdOut(); + const std::string& stdErr(); + + private: + std::string binary, out, err; + std::vector args; + }; + } +} \ No newline at end of file diff --git a/src/os/Process.cpp b/src/os/Process.cpp new file mode 100644 index 0000000..4ffd900 --- /dev/null +++ b/src/os/Process.cpp @@ -0,0 +1,149 @@ +#include +using namespace Hyprutils::OS; + +#include +#include +#include +#include + +#include + +Hyprutils::OS::CProcess::CProcess(const std::string& binary_, const std::vector& args_) : binary(binary_), args(args_) { + ; +} + +bool Hyprutils::OS::CProcess::runSync() { + int outPipe[2], errPipe[2]; + if (pipe(outPipe)) + return false; + if (pipe(errPipe)) { + close(outPipe[0]); + close(outPipe[1]); + return false; + } + + int pid = fork(); + if (pid == -1) { + close(outPipe[0]); + close(outPipe[1]); + close(outPipe[0]); + close(outPipe[1]); + return false; + } + + if (!pid) { + // child + close(outPipe[0]); + close(errPipe[0]); + + dup2(outPipe[1], 1 /* stdout */); + dup2(errPipe[1], 2 /* stderr */); + + // build argv + std::vector argsC; + argsC.emplace_back(strdup(binary.c_str())); + for (auto& arg : args) { + // TODO: does this leak? Can we just pipe c_str() as the strings won't be realloc'd? + argsC.emplace_back(strdup(arg.c_str())); + } + + argsC.emplace_back(nullptr); + + execvp(binary.c_str(), (char* const*)argsC.data()); + exit(1); + } else { + // parent + close(outPipe[1]); + close(errPipe[1]); + + waitpid(pid, nullptr, 0); + + std::string readOutData; + std::array buf; + buf.fill(0); + + // wait for read + size_t ret = 0; + while ((ret = read(outPipe[0], buf.data(), 1023)) > 0) { + readOutData += std::string{(char*)buf.data(), ret}; + } + + out = readOutData; + readOutData = ""; + + while ((ret = read(errPipe[0], buf.data(), 1023)) > 0) { + readOutData += std::string{(char*)buf.data(), ret}; + } + + err = readOutData; + + close(outPipe[0]); + close(errPipe[0]); + + return true; + } + + return true; +} + +bool Hyprutils::OS::CProcess::runAsync() { + int socket[2]; + if (pipe(socket) != 0) + return false; + + pid_t child, grandchild; + child = fork(); + if (child < 0) { + close(socket[0]); + close(socket[1]); + return false; + } + + if (child == 0) { + // run in child + sigset_t set; + sigemptyset(&set); + sigprocmask(SIG_SETMASK, &set, NULL); + + grandchild = fork(); + if (grandchild == 0) { + // run in grandchild + close(socket[0]); + close(socket[1]); + // build argv + std::vector argsC; + argsC.emplace_back(strdup(binary.c_str())); + for (auto& arg : args) { + // TODO: does this leak? Can we just pipe c_str() as the strings won't be realloc'd? + argsC.emplace_back(strdup(arg.c_str())); + } + + argsC.emplace_back(nullptr); + + execvp(binary.c_str(), (char* const*)argsC.data()); + // exit grandchild + _exit(0); + } + close(socket[0]); + write(socket[1], &grandchild, sizeof(grandchild)); + close(socket[1]); + // exit child + _exit(0); + } + // run in parent + close(socket[1]); + read(socket[0], &grandchild, sizeof(grandchild)); + close(socket[0]); + // clear child and leave grandchild to init + waitpid(child, NULL, 0); + + return true; +} + +const std::string& Hyprutils::OS::CProcess::stdOut() { + return out; +} + +const std::string& Hyprutils::OS::CProcess::stdErr() { + return err; +} \ No newline at end of file diff --git a/tests/os.cpp b/tests/os.cpp new file mode 100644 index 0000000..7a932ce --- /dev/null +++ b/tests/os.cpp @@ -0,0 +1,18 @@ +#include +#include "shared.hpp" + +using namespace Hyprutils::OS; + +int main(int argc, char** argv, char** envp) { + int ret = 0; + + CProcess process("echo", {"Hello World!"}); + + EXPECT(process.runAsync(), true); + EXPECT(process.runSync(), true); + + EXPECT(process.stdOut(), std::string{"Hello World!\n"}); + EXPECT(process.stdErr(), std::string{""}); + + return ret; +} \ No newline at end of file