180 lines
		
	
	
		
			4.6 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			180 lines
		
	
	
		
			4.6 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
 | 
						|
#include "./process.hpp"
 | 
						|
 | 
						|
#include <algorithm>
 | 
						|
#include <sstream>
 | 
						|
#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<std::uint8_t> 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<std::uint8_t>(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<const std::uint8_t> 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<bool>(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) 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<std::string>& args) 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
 |