179 lines
		
	
	
		
			4.1 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			179 lines
		
	
	
		
			4.1 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| 
 | |
| #include "./process.hpp"
 | |
| 
 | |
| #include <algorithm>
 | |
| #include <sstream>
 | |
| #include "../detect.hpp"
 | |
| #include "../util/iterators.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:
 | |
|             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<std::uint8_t> 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<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 (!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)
 | |
| {
 | |
|     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<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
 |