1
0
mirror of synced 2026-01-13 15:17:07 +00:00
2025-02-13 22:27:42 +00:00

480 lines
16 KiB
C++

#include "console.h"
#if defined(__CYGWIN__) || defined(_WIN32)
#define WIN32_LEAN_AND_MEAN
#ifndef NOMINMAX
#define NOMINMAX
#endif //NOMINMAX
#include <windows.h>
#endif
#if defined(PARTIAL_DEBUG) && defined(_MSC_VER)
#pragma optimize ("", off)
#endif
// Console_c
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
Console_c::Console_c(const Configuration_c &aConfig, CLogger_c &aLogger, uint16_t aPort, bool aAutoPort, bool aDisableAutoTerminal) :
mLogger(aLogger),
mAccepted(false),
mConnectionSocket(nullptr),
mPort(aPort),
mAutoPort(aAutoPort),
#if defined(_WIN32)
mConsoleCommand(aConfig.get_optional<std::string>("ConsoleCommandWindows")),
#else
mConsoleCommand(aConfig.get_optional<std::string>("ConsoleCommandLinux")),
#endif
mNewTerminalCommand(aConfig.get_optional<std::string>("NewTerminalCommand")),
mReceiveFull(false),
mPollCnt(0),
mTelnetState(TelnetStates_e::WaitIAC),
mTelnetAction(TelnetCodes_e::IAC),
mEatLF(aConfig.get<bool>("EatLF", true)),
mEatNull(aConfig.get<bool>("EatNull", true)),
mMapBackspace(aConfig.get<bool>("MapBackspace", true)),
mBackspaceChar(aConfig.get<int>("BackspaceChar", 127) & 0xff),
mDisableAutoTerminal(aDisableAutoTerminal),
mLogInitialized(false),
mInSendFromQueue(0)
{
if (!mConsoleCommand.is_initialized()) {
mConsoleCommand = aConfig.get_optional<std::string>("ConsoleCommand");
}
for (auto &TelnetOption : mTelnetOptions) {
TelnetOption.Do = false;
TelnetOption.Will = false;
TelnetOption.OutstandingDoDont = false;
TelnetOption.OutstandingWillWont = false;
}
do {
try {
mServerSocket = std::make_shared<boost::asio::ip::tcp::acceptor>(mIoService, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), mPort), false);
break;
}
catch (boost::system::system_error &Ex) {
switch (Ex.code().value()) {
case EADDRINUSE:
case 10048: //case WSAEADDRINUSE:
if (mAutoPort) {
++mPort;
break;
}
// Deliberate fall-through in case of no mAutoPort
default:
throw Generic_x() << "Can't open console port " << DecPrinter(mPort) << " with error: " << Ex.code().message();
}
}
} while (true);
mLogFileName = aConfig.get<std::string>("LogFile", "");
}
Console_c::~Console_c() {
Flush();
}
bool Console_c::HasInput() const { return mReceiveFull; }
char Console_c::GetChar() {
CRAY_ASSERT(mReceiveFull);
volatile char RetVal = mReceiveBuf[0];
mReceiveFull = false;
PrimeForReceive();
return RetVal;
}
void Console_c::SendString(std::string &aData) {
if (!mLogInitialized) {
if (!mLogFileName.empty()) {
if (mLogFileName == "auto") {
mLogFileName = GetName();
}
std::ofstream LogFile(mLogFileName.c_str(), std::ios_base::out);
}
mLogInitialized = true;
}
if (!mLogFileName.empty()) {
std::ofstream LogFile(mLogFileName.c_str(), std::ios_base::out | std::ios_base::app);
LogFile << aData;
}
SendStringInternal(aData, true);
}
void Console_c::SendFromQueue() {
if (mTransmitQueue.empty()) return;
AcceptIfNeeded(false); // This might recursively call SendFromQueue, so re-test the empty condition afterwards
if (mTransmitQueue.empty()) return;
if (mAccepted) {
CRAY_ASSERT(mConnectionSocket != nullptr);
try {
// Start an (async) write
TransmitQueueEntry_s &Head = mTransmitQueue.front();
boost::asio::async_write(
*mConnectionSocket,
boost::asio::buffer(Head.mData),
boost::bind(&Console_c::WriteHandler, this, boost::asio::placeholders::error)
);
for (int i = 0; i < 100; ++i) {
if (mInSendFromQueue < 100) {
++mInSendFromQueue;
mIoService.reset();
mIoService.poll();
--mInSendFromQueue;
}
}
}
catch (boost::system::system_error &) {
CloseSocket();
}
}
}
void Console_c::SendOptionString(TelnetCodes_e aAction, TelnetOptions_e aOption, bool aRequest) {
char Str[4];
Str[0] = char(TelnetCodes_e::IAC);
Str[1] = char(aAction);
Str[2] = char(aOption);
Str[3] = 0;
// std::cout << (aRequest ? " Request<" : " Reply<" ) << DecPrinter(int(aAction) & 0xff, 2) << ", " << DecPrinter(int(aOption) & 0xff, 2) << ">" << std::flush;
std::string OptStr(Str);
SendStringInternal(OptStr, false);
switch (aAction) {
case TelnetCodes_e::DO:
case TelnetCodes_e::DONT:
mTelnetOptions[size_t(aOption)].OutstandingDoDont = aRequest;
mTelnetOptions[size_t(aOption)].OutstandingWillWont = false;
break;
case TelnetCodes_e::WILL:
case TelnetCodes_e::WONT:
mTelnetOptions[size_t(aOption)].OutstandingDoDont = false;
mTelnetOptions[size_t(aOption)].OutstandingWillWont = aRequest;
break;
default:
CRAY_ASSERT(false);
break;
}
// std::cout << " STAT:<" << DecPrinter(int(aOption) & 0xff, 2) << ", " << (mTelnetOptions[size_t(aOption)].OutstandingWillWont ? "OW " : "-- ") << (mTelnetOptions[size_t(aOption)].OutstandingDoDont ? "OD " : "-- ") << ">" << std::flush;
}
void Console_c::SendStringInternal(std::string &aData, bool aDoNotify) {
bool SendNow = mTransmitQueue.empty();
//if (aDoNotify) std::cout << aData << std::flush;
mTransmitQueue.push(TransmitQueueEntry_s(aData, aDoNotify));
if (SendNow) SendFromQueue();
}
void Console_c::Flush() {
while (!mTransmitQueue.empty()) Poll();
}
void Console_c::SendString(const char *aData, size_t aLen) {
std::string Data(aData, aLen);
SendString(Data);
}
void Console_c::SendString(const char *aData) {
std::string Data(aData);
SendString(Data);
}
void Console_c::SendChar(char aData) {
char Data[2];
Data[0] = aData;
Data[1] = 0;
SendString(Data);
}
void Console_c::Reset() {
ResetDetail();
}
void Console_c::Tick() {
if (++mPollCnt > 1000) {
while (HasInput()) {
if (!ReceiveHandlerDetail()) break;
for (size_t Idx = 0; Idx < 10; ++Idx) {
Poll();
if (HasInput()) break;
}
}
Poll();
mPollCnt = 0;
}
}
void Console_c::Poll() {
mIoService.restart();
mIoService.poll();
}
void Console_c::CloseSocket() {
boost::system::error_code IgnoredError;
mConnectionSocket->close(IgnoredError);
mConnectionSocket = nullptr;
mReceiveBuf[0] = 0;
mAccepted = false;
AcceptIfNeeded(false);
}
void Console_c::PrimeForReceive() {
// Prime the socket to receive chars
boost::asio::async_read(
*mConnectionSocket,
boost::asio::buffer(mReceiveBuf),
boost::bind(&Console_c::ReadHandler, this, boost::asio::placeholders::error)
);
}
void Console_c::AcceptHandler(const boost::system::error_code& aError) {
// std::cout << "A" << std::flush;
if (aError) {
CloseSocket();
return;
}
try {
mAccepted = true;
mConnectionSocket->set_option(boost::asio::socket_base::keep_alive(true));
PrimeForReceive();
// If there's a pending transmission, send it
ConnectDetail();
// SendOptionString(TelnetCodes_e::DONT, TelnetOptions_e::OPT_ECHO, true);
SendOptionString(TelnetCodes_e::WILL, TelnetOptions_e::OPT_ECHO, true);
SendOptionString(TelnetCodes_e::WILL, TelnetOptions_e::SGA, true);
SendOptionString(TelnetCodes_e::WONT, TelnetOptions_e::LINEMODE, true);
// SendOptionString(TelnetCodes_e::DO, TelnetOptions_e::SGA, true);
SendFromQueue();
// mIoService.poll();
}
catch (boost::system::system_error &) {
CloseSocket();
}
}
void Console_c::ReadHandler(const boost::system::error_code& aError) {
// std::cout << "r" << std::flush;
if (aError) {
CloseSocket();
return;
}
CRAY_ASSERT(!mReceiveFull);
bool ReceiveFull = true;
// mLogger << setloglevel(LogLevel_IoTrace) << "Input char: " << DecPrinter(uint32_t(mReceiveBuf[0]) & 0xff) << " in state: " << int(mTelnetState) << std::endl;
// std::cout << " " << DecPrinter(uint32_t(mReceiveBuf[0]) & 0xff) << std::flush;
// std::cout << " RS:<" << DecPrinter(int(1) & 0xff, 2) << ", " << (mTelnetOptions[size_t(1)].OutstandingWillWont ? "OW " : "-- ") << (mTelnetOptions[size_t(1)].OutstandingDoDont ? "OD " : "-- ") << ">" << std::flush;
// std::cout << " RS:<" << DecPrinter(int(3) & 0xff, 2) << ", " << (mTelnetOptions[size_t(3)].OutstandingWillWont ? "OW " : "-- ") << (mTelnetOptions[size_t(3)].OutstandingDoDont ? "OD " : "-- ") << ">" << std::flush;
switch (mTelnetState) {
case TelnetStates_e::WaitIAC:
// std::cout << "-" << HexPrinter(int(mReceiveBuf[0]), 2) << "-" << std::flush;
if (mReceiveBuf[0] == char(TelnetCodes_e::IAC)) {
mTelnetState = TelnetStates_e::WaitAction;
ReceiveFull = false;
}
else {
// Eat LF from CRLF
if (mEatLF && mReceiveBuf[0] == 0x0a) {
ReceiveFull = false;
}
if (mEatNull && mReceiveBuf[0] == 0x00) {
ReceiveFull = false;
}
// Convert ^H to back-space
if (mMapBackspace && (mReceiveBuf[0] == 0x08 || mReceiveBuf[0] == 0x7f)) mReceiveBuf[0] = mBackspaceChar;
mTelnetState = TelnetStates_e::WaitIAC;
}
break;
case TelnetStates_e::WaitAction:
mTelnetAction = TelnetCodes_e(mReceiveBuf[0]);
switch (mTelnetAction) {
case TelnetCodes_e::DO:
case TelnetCodes_e::DONT:
case TelnetCodes_e::WILL:
case TelnetCodes_e::WONT:
mTelnetState = TelnetStates_e::WaitOption;
ReceiveFull = false;
break;
case TelnetCodes_e::NOP: // Eat keep-alive
ReceiveFull = false;
mTelnetState = TelnetStates_e::WaitIAC;
break;
default:
mTelnetState = TelnetStates_e::WaitIAC;
break;
}
break;
case TelnetStates_e::WaitOption:
TelnetOptions_e TelnetOption = TelnetOptions_e(mReceiveBuf[0]);
ReceiveFull = false;
// std::cout << " In:<" << DecPrinter(int(mTelnetAction) & 0xff, 2) << ", " << DecPrinter(int(TelnetOption) & 0xff, 2) << ", " << (mTelnetOptions[size_t(TelnetOption)].OutstandingWillWont ? "OW " : "-- ") << (mTelnetOptions[size_t(TelnetOption)].OutstandingDoDont ? "OD " : "-- ") << ">" << std::flush;
switch (mTelnetAction) {
case TelnetCodes_e::DO: {
switch (TelnetOption) {
case TelnetOptions_e::OPT_ECHO:
case TelnetOptions_e::SGA:
if (!mTelnetOptions[size_t(TelnetOption)].OutstandingWillWont) SendOptionString(TelnetCodes_e::WILL, TelnetOption, false);
mTelnetOptions[size_t(TelnetOption)].Do = true;
mTelnetOptions[size_t(TelnetOption)].Will = true;
mTelnetOptions[size_t(TelnetOption)].OutstandingWillWont = false;
mTelnetOptions[size_t(TelnetOption)].OutstandingDoDont = false;
break;
default:
if (!mTelnetOptions[size_t(TelnetOption)].OutstandingWillWont) SendOptionString(TelnetCodes_e::WONT, TelnetOption, false);
mTelnetOptions[size_t(TelnetOption)].Do = true;
mTelnetOptions[size_t(TelnetOption)].Will = false;
mTelnetOptions[size_t(TelnetOption)].OutstandingWillWont = false;
mTelnetOptions[size_t(TelnetOption)].OutstandingDoDont = false;
break;
}
}
break;
case TelnetCodes_e::DONT: {
switch (TelnetOption) {
case TelnetOptions_e::OPT_ECHO:
case TelnetOptions_e::SGA:
if (!mTelnetOptions[size_t(TelnetOption)].OutstandingWillWont) SendOptionString(TelnetCodes_e::WILL, TelnetOption, false);
mTelnetOptions[size_t(TelnetOption)].Do = false;
mTelnetOptions[size_t(TelnetOption)].Will = true;
mTelnetOptions[size_t(TelnetOption)].OutstandingWillWont = false;
mTelnetOptions[size_t(TelnetOption)].OutstandingDoDont = false;
break;
default:
if (!mTelnetOptions[size_t(TelnetOption)].OutstandingWillWont) SendOptionString(TelnetCodes_e::WONT, TelnetOption, false);
mTelnetOptions[size_t(TelnetOption)].Do = false;
mTelnetOptions[size_t(TelnetOption)].Will = false;
mTelnetOptions[size_t(TelnetOption)].OutstandingWillWont = false;
mTelnetOptions[size_t(TelnetOption)].OutstandingDoDont = false;
break;
}
}
break;
case TelnetCodes_e::WILL: {
switch (TelnetOption) {
case TelnetOptions_e::OPT_ECHO:
case TelnetOptions_e::SGA:
if (!mTelnetOptions[size_t(TelnetOption)].OutstandingDoDont) SendOptionString(TelnetCodes_e::DO, TelnetOption, false);
mTelnetOptions[size_t(TelnetOption)].Do = true;
mTelnetOptions[size_t(TelnetOption)].Will = true;
mTelnetOptions[size_t(TelnetOption)].OutstandingWillWont = false;
mTelnetOptions[size_t(TelnetOption)].OutstandingDoDont = false;
break;
default:
if (!mTelnetOptions[size_t(TelnetOption)].OutstandingDoDont) SendOptionString(TelnetCodes_e::DONT, TelnetOption, false);
mTelnetOptions[size_t(TelnetOption)].Do = false;
mTelnetOptions[size_t(TelnetOption)].Will = true;
mTelnetOptions[size_t(TelnetOption)].OutstandingWillWont = false;
mTelnetOptions[size_t(TelnetOption)].OutstandingDoDont = false;
break;
}
}
break;
case TelnetCodes_e::WONT: {
switch (TelnetOption) {
case TelnetOptions_e::OPT_ECHO:
case TelnetOptions_e::SGA:
if (!mTelnetOptions[size_t(TelnetOption)].OutstandingDoDont) SendOptionString(TelnetCodes_e::DO, TelnetOption, false);
mTelnetOptions[size_t(TelnetOption)].Do = true;
mTelnetOptions[size_t(TelnetOption)].Will = false;
mTelnetOptions[size_t(TelnetOption)].OutstandingWillWont = false;
mTelnetOptions[size_t(TelnetOption)].OutstandingDoDont = false;
break;
default:
if (!mTelnetOptions[size_t(TelnetOption)].OutstandingDoDont) SendOptionString(TelnetCodes_e::DONT, TelnetOption, false);
mTelnetOptions[size_t(TelnetOption)].Do = false;
mTelnetOptions[size_t(TelnetOption)].Will = false;
mTelnetOptions[size_t(TelnetOption)].OutstandingWillWont = false;
mTelnetOptions[size_t(TelnetOption)].OutstandingDoDont = false;
break;
}
}
break;
default:
CRAY_ASSERT(false);
break;
}
mTelnetState = TelnetStates_e::WaitIAC;
break;
}
mReceiveFull = ReceiveFull;
if (!mReceiveFull) PrimeForReceive();
}
void Console_c::WriteHandler(const boost::system::error_code& aError) {
// std::cout << "w" << std::flush;
if (aError) {
CloseSocket();
return;
}
if (!mTransmitQueue.empty()) {
bool DoNotify = mTransmitQueue.front().mDoNotify;
mTransmitQueue.pop();
SendFromQueue();
if (DoNotify) SendHandlerDetail();
}
}
void Console_c::AcceptIfNeeded(bool aSilent) {
if (mConnectionSocket == nullptr) {
if (!aSilent && !mDisableAutoTerminal) {
if (!mConsoleCommand.is_initialized()) {
std::cout << "please connect terminal to localhost:" << DecPrinter(mPort) << std::endl;
} else {
#if !defined(__CYGWIN__) && !defined(_WIN32)
std::stringstream PortStr;
PortStr << DecPrinter(mPort);
std::string CmdLine = mNewTerminalCommand.get_value_or("{cmd} &");
CmdLine = Replace(CmdLine, "{cmd}", mConsoleCommand.get());
CmdLine = Replace(CmdLine, "{host}", "localhost");
CmdLine = Replace(CmdLine, "{port}", PortStr.str());
if (!aSilent) std::cout << "Starting console application: " << CmdLine << std::endl;
int RetVal = system(CmdLine.c_str());
#else
std::string CmdLine = Replace(mConsoleCommand.get(), "{host}", "localhost");
std::stringstream PortStr;
PortStr << DecPrinter(mPort);
CmdLine = Replace(CmdLine, "{port}", PortStr.str());
if (!aSilent) std::cout << "Starting console application: " << CmdLine << std::endl;
char *CCmdLine = new char[CmdLine.length() + 1];
memcpy(CCmdLine, CmdLine.c_str(), CmdLine.length() + 1);
PROCESS_INFORMATION processInformation;
STARTUPINFOA startupInfo;
memset(&processInformation, 0, sizeof(processInformation));
memset(&startupInfo, 0, sizeof(startupInfo));
CreateProcessA(NULL, CCmdLine, NULL, NULL, TRUE, CREATE_NEW_CONSOLE, NULL, NULL, &startupInfo, &processInformation);
CloseHandle(processInformation.hProcess);
CloseHandle(processInformation.hThread);
delete[] CCmdLine;
#endif
}
}
try {
// std::cout << " CLEAR" << std::flush;
for (auto &TelnetOption : mTelnetOptions) {
TelnetOption.Do = false;
TelnetOption.Will = false;
TelnetOption.OutstandingDoDont = false;
TelnetOption.OutstandingWillWont = false;
}
mConnectionSocket = std::make_shared<boost::asio::ip::tcp::socket>(mIoService);
mAccepted = false;
mServerSocket->async_accept(*mConnectionSocket, boost::bind(&Console_c::AcceptHandler, this, boost::asio::placeholders::error));
mIoService.poll();
}
catch (boost::system::system_error &) {
CloseSocket();
}
}
}
boost::asio::io_context Console_c::mIoService;
#if defined(PARTIAL_DEBUG) && defined(_MSC_VER)
#pragma optimize ("", on)
#endif