#include "./process.hpp" #include #include #include "../detect.hpp" #include "../debug/assert.hpp" #include "../util/string.hpp" #if MIJIN_TARGET_OS == MIJIN_OS_LINUX || MIJIN_TARGET_OS == MIJIN_OS_OSX 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: MIJIN_ERROR("Unsupported mode for ProcessStream::open()"); return StreamError::NOT_SUPPORTED; } handle = popen(command, modeStr); // NOLINT(cppcoreguidelines-owning-memory,cert-env33-c) if (!handle) { return StreamError::IO_ERROR; } return StreamError::SUCCESS; } int ProcessStream::close() { MIJIN_ASSERT(handle != nullptr, "Process stream has not been opened."); const int result = pclose(handle); handle = nullptr; return result; } StreamError ProcessStream::readRaw(std::span buffer, const ReadOptions& options, std::size_t* outBytesRead) { MIJIN_ASSERT(handle != nullptr, "Process stream has not been openend."); MIJIN_ASSERT(mode == FileOpenMode::READ || mode == FileOpenMode::READ_WRITE, "Cannot read from this process stream."); if (bufferedChar >= 0 && !buffer.empty()) { 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 (!options.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) { MIJIN_ASSERT(handle != nullptr, "Process stream has not been opened."); MIJIN_ASSERT(mode == FileOpenMode::WRITE || mode == FileOpenMode::READ_WRITE, "Cannot write to this process stream."); 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() { MIJIN_ASSERT(handle != nullptr, "Process stream has not been opened."); 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); MIJIN_ASSERT(result == 0, "Error flushing process stream."); } bool ProcessStream::isAtEnd() { MIJIN_ASSERT(handle != nullptr, "Process stream has not been opened."); if (bufferedChar >= 0) { return false; } bufferedChar = std::fgetc(handle); return static_cast(std::feof(handle)); } StreamFeatures ProcessStream::getFeatures() { if (handle) { return { .read = (mode == FileOpenMode::READ), .write = (mode == FileOpenMode::WRITE || mode == FileOpenMode::READ_WRITE), .tell = true, .seek = false, .readOptions = { .partial = true } }; } return {}; } std::string shellEscape(const std::string& arg) MIJIN_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; default: break; } oss << chr; } if (requiresQuotes) { oss << '"'; } return oss.str(); } std::string makeShellCommand(const std::vector& args) MIJIN_NOEXCEPT { using namespace mijin::pipe; return args | Map(&shellEscape) | Join(" "); } } // namespace mijin #endif // MIJIN_TARGET_OS == MIJIN_OS_LINUX || MIJIN_TARGET_OS == MIJIN_OS_OSX