#include "./process.hpp" #include #include #include "../util/iterators.hpp" #include "../util/string.hpp" namespace mijin { ProcessStream::~ProcessStream() { if (handle) { close(); } } StreamError ProcessStream::open(const char* command, FileOpenMode mode_) { mode = mode_; const char* modeStr; // NOLINT(cppcoreguidelines-init-variables) switch (mode_) { case FileOpenMode::READ: modeStr = "r"; break; case FileOpenMode::WRITE: modeStr = "w"; break; case FileOpenMode::READ_WRITE: modeStr = "rw"; break; default: assert(!"Unsupported mode for ProcessStream::open()"); return StreamError::NOT_SUPPORTED; } handle = popen(command, modeStr); // NOLINT(cppcoreguidelines-owning-memory) if (!handle) { return StreamError::IO_ERROR; } return StreamError::SUCCESS; } int ProcessStream::close() { assert(handle); const int result = pclose(handle); handle = nullptr; return result; } StreamError ProcessStream::readRaw(std::span buffer, bool partial, std::size_t* outBytesRead) { assert(handle); assert(mode == FileOpenMode::READ || mode == FileOpenMode::READ_WRITE); if (bufferedChar >= 0 && buffer.size() > 0) { buffer[0] = static_cast(bufferedChar); } const std::size_t readBytes = std::fread(buffer.data(), 1, buffer.size(), handle); if (std::ferror(handle)) { return StreamError::IO_ERROR; } if (!partial && readBytes < buffer.size()) { return StreamError::IO_ERROR; } if (outBytesRead != nullptr) { *outBytesRead = readBytes + (bufferedChar >= 0 ? 1 : 0); } bufferedChar = -1; return StreamError::SUCCESS; } StreamError ProcessStream::writeRaw(std::span buffer) { assert(handle); assert(mode == FileOpenMode::WRITE || mode == FileOpenMode::READ_WRITE); const std::size_t written = std::fwrite(buffer.data(), 1, buffer.size(), handle); if (written != buffer.size() || std::ferror(handle)) { return StreamError::IO_ERROR; } return StreamError::SUCCESS; } std::size_t ProcessStream::tell() { assert(handle); return std::ftell(handle); // TODO: does this work? } StreamError ProcessStream::seek(std::intptr_t pos, SeekMode seekMode) { (void) pos; (void) seekMode; return StreamError::NOT_SUPPORTED; } void ProcessStream::flush() { const int result = std::fflush(handle); assert(result == 0); } bool ProcessStream::isAtEnd() { assert(handle); if (bufferedChar >= 0) { return false; } bufferedChar = std::fgetc(handle); if (std::feof(handle)) { return true; } return false; } StreamFeatures ProcessStream::getFeatures() { if (handle) { return { .read = (mode == FileOpenMode::READ), .write = (mode == FileOpenMode::WRITE || mode == FileOpenMode::READ_WRITE), .tell = true, .seek = false }; } return {}; } std::string shellEscape(const std::string& arg) noexcept { std::ostringstream oss; const bool requiresQuotes = std::any_of(arg.begin(), arg.end(), [&](const char chr) { return std::isspace(chr); }); if (requiresQuotes) { oss << '"'; } for (const char chr : arg) { // TODO: oh Windows this will probably be different switch (chr) { case '"': case '\'': case '\\': case '$': oss << '\\'; break; } oss << chr; } if (requiresQuotes) { oss << '"'; } return oss.str(); } std::string makeShellCommand(const std::vector& args) noexcept { using namespace mijin::pipe; return args | Map(&shellEscape) | Join(" "); } } // namespace mijin