1
0
mirror of https://github.com/winscp/winscp.git synced 2026-10-24 02:28:55 +03:00
Files
winscp/source/console/Main.cpp
Martin Prikryl d487c586c2 Bug fix: Script sometimes does not abort after receiving no answer for prompt, when running in batch mode.
See Bug 623

Source commit: 0b01777b9cd4a541cfbaafbc14b9d7ee080451e4
2023-08-25 01:13:44 +02:00

1054 lines
29 KiB
C++

//---------------------------------------------------------------------------
#include <stdexcept>
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <io.h>
#include <fcntl.h>
#include "Console.h"
#define MAX_ATTEMPTS 10
//---------------------------------------------------------------------------
#define LENOF(x) ( (sizeof((x))) / (sizeof(*(x))))
//---------------------------------------------------------------------------
using namespace std;
HANDLE ConsoleInput = NULL;
HANDLE ConsoleOutput = NULL;
HANDLE ConsoleStandardOutput = NULL;
HANDLE ConsoleErrorOutput = NULL;
TConsoleCommStruct::TInitEvent::STDINOUT OutputFormat;
TConsoleCommStruct::TInitEvent::STDINOUT InputFormat;
HANDLE Child = NULL;
HANDLE CancelEvent = NULL;
HANDLE InputTimerEvent = NULL;
bool SupportsUtf8ConsoleOutput = false;
unsigned int OutputType = FILE_TYPE_UNKNOWN;
unsigned int InputType = FILE_TYPE_UNKNOWN;
enum { RESULT_GLOBAL_ERROR = 1, RESULT_INIT_ERROR = 2, RESULT_PROCESSING_ERROR = 3,
RESULT_UNKNOWN_ERROR = 4 };
const wchar_t* CONSOLE_CHILD_PARAM = L"consolechild";
//---------------------------------------------------------------------------
inline TConsoleCommStruct* GetCommStruct(HANDLE FileMapping)
{
TConsoleCommStruct* Result;
Result = static_cast<TConsoleCommStruct*>(MapViewOfFile(FileMapping,
FILE_MAP_ALL_ACCESS, 0, 0, 0));
if (Result == NULL)
{
throw runtime_error("Cannot open mapping object.");
}
return Result;
}
//---------------------------------------------------------------------------
inline void FreeCommStruct(TConsoleCommStruct* CommStruct)
{
UnmapViewOfFile(CommStruct);
}
//---------------------------------------------------------------------------
void InitializeConsole(wchar_t* InstanceName, HANDLE& RequestEvent, HANDLE& ResponseEvent,
HANDLE& CancelEvent, HANDLE& FileMapping, HANDLE& Job)
{
unsigned int Process = GetCurrentProcessId();
int Attempts = 0;
wchar_t Name[MAX_PATH];
bool UniqEvent;
do
{
if (Attempts > MAX_ATTEMPTS)
{
throw runtime_error("Cannot find unique name for event object.");
}
int InstanceNumber;
#ifdef CONSOLE_TEST
InstanceNumber = 1;
#else
InstanceNumber = random(1000);
#endif
swprintf(InstanceName, L"_%u_%d", Process, InstanceNumber);
swprintf(Name, L"%s%s", CONSOLE_EVENT_REQUEST, InstanceName);
HANDLE EventHandle = OpenEvent(EVENT_ALL_ACCESS, false, Name);
UniqEvent = (EventHandle == NULL);
if (!UniqEvent)
{
CloseHandle(EventHandle);
}
Attempts++;
}
while (!UniqEvent);
RequestEvent = CreateEvent(NULL, false, false, Name);
if (RequestEvent == NULL)
{
throw runtime_error("Cannot create request event object.");
}
swprintf(Name, L"%s%s", CONSOLE_EVENT_RESPONSE, InstanceName);
ResponseEvent = CreateEvent(NULL, false, false, Name);
if (ResponseEvent == NULL)
{
throw runtime_error("Cannot create response event object.");
}
swprintf(Name, L"%s%s", CONSOLE_EVENT_CANCEL, InstanceName);
CancelEvent = CreateEvent(NULL, false, false, Name);
if (CancelEvent == NULL)
{
throw runtime_error("Cannot create cancel event object.");
}
swprintf(Name, L"%s%s", CONSOLE_MAPPING, InstanceName);
FileMapping = CreateFileMapping((HANDLE)0xFFFFFFFF, NULL, PAGE_READWRITE,
0, sizeof(TConsoleCommStruct), Name);
if (FileMapping == NULL)
{
throw runtime_error("Cannot create mapping object.");
}
swprintf(Name, L"%s%s", CONSOLE_JOB, InstanceName);
Job = CreateJobObject(NULL, Name);
if (Job == NULL)
{
throw runtime_error("Cannot create job object.");
}
JOBOBJECT_EXTENDED_LIMIT_INFORMATION ExtendedLimitInformation;
memset(&ExtendedLimitInformation, 0, sizeof(ExtendedLimitInformation));
ExtendedLimitInformation.BasicLimitInformation.LimitFlags =
JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
if (SetInformationJobObject(Job, JobObjectExtendedLimitInformation,
&ExtendedLimitInformation, sizeof(ExtendedLimitInformation)) == 0)
{
CloseHandle(Job);
Job = NULL;
}
TConsoleCommStruct* CommStruct = GetCommStruct(FileMapping);
CommStruct->Size = sizeof(TConsoleCommStruct);
CommStruct->Version = TConsoleCommStruct::CurrentVersion;
CommStruct->Event = TConsoleCommStruct::NONE;
FreeCommStruct(CommStruct);
}
//---------------------------------------------------------------------------
// duplicated in Common.cpp
bool __fastcall CutToken(const wchar_t*& Str, wchar_t* Token)
{
bool Result;
// inspired by Putty's sftp_getcmd() from PSFTP.C
int Length = wcslen(Str);
int Index = 0;
while ((Index < Length) &&
((Str[Index] == L' ') || (Str[Index] == L'\t')))
{
Index++;
}
if (Index < Length)
{
bool Quoting = false;
while (Index < Length)
{
if (!Quoting && ((Str[Index] == L' ') || (Str[Index] == L'\t')))
{
break;
}
else if ((Str[Index] == L'"') && (Index + 1 < Length) &&
(Str[Index + 1] == L'"'))
{
Index += 2;
*Token = L'"';
Token++;
}
else if (Str[Index] == L'"')
{
Index++;
Quoting = !Quoting;
}
else
{
*Token = Str[Index];
Token++;
Index++;
}
}
if (Index < Length)
{
Index++;
}
Str += Index;
Result = true;
}
else
{
Result = false;
Str += Length;
}
*Token = L'\0';
return Result;
}
//---------------------------------------------------------------------------
char* WideStringToString(const wchar_t* Message)
{
char* Buffer;
int Size = WideCharToMultiByte(CP_UTF8, 0, Message, -1, 0, 0, 0, 0);
if (Size > 0)
{
Buffer = new char[(Size * 2) + 1];
if (WideCharToMultiByte(CP_UTF8, 0, Message, -1, Buffer, Size, 0, 0) > 0)
{
Buffer[Size] = '\0';
}
else
{
delete[] Buffer;
Buffer = NULL;
}
}
return Buffer;
}
//---------------------------------------------------------------------------
void GetProductVersion(wchar_t* ProductVersion)
{
wchar_t Buffer[MAX_PATH];
DWORD ModuleNameLen = GetModuleFileName(NULL, Buffer, MAX_PATH);
if ((ModuleNameLen == 0) || (ModuleNameLen == MAX_PATH))
{
throw runtime_error("Error retrieving executable name.");
}
ProductVersion[0] = '\0';
unsigned long Handle;
unsigned int Size = GetFileVersionInfoSize(Buffer, &Handle);
if (Size > 0)
{
void * VersionInfo = new char[Size];
VS_FIXEDFILEINFO* FixedFileInfo;
unsigned int Length;
if (GetFileVersionInfo(Buffer, Handle, Size, VersionInfo))
{
if (VerQueryValue(VersionInfo, L"\\", (void**)&FixedFileInfo, &Length))
{
int ProductMajor = HIWORD(FixedFileInfo->dwProductVersionMS);
int ProductMinor = LOWORD(FixedFileInfo->dwProductVersionMS);
int ProductBuild = HIWORD(FixedFileInfo->dwProductVersionLS);
if ((ProductMajor >= 1) && (ProductMajor <= 99) &&
(ProductMinor >= 0) && (ProductMinor <= 99) &&
(ProductBuild >= 0) && (ProductBuild <= 99))
{
wsprintf(ProductVersion, L"%d.%d.%d", ProductMajor, ProductMinor, ProductBuild);
}
}
}
delete[] VersionInfo;
}
if (ProductVersion[0] == L'\0')
{
throw runtime_error("Error retrieving product version.");
}
}
//---------------------------------------------------------------------------
void InitializeChild(const wchar_t* CommandLine, const wchar_t* InstanceName, HANDLE& Child)
{
int SkipParam = 0;
wchar_t ChildPath[MAX_PATH] = L"";
size_t CommandLineLen = wcslen(CommandLine);
wchar_t* Buffer = new wchar_t[(CommandLineLen > MAX_PATH ? CommandLineLen : MAX_PATH) + 1];
int Count = 0;
const wchar_t* P = CommandLine;
while (CutToken(P, Buffer))
{
if ((wcschr(L"-/", Buffer[0]) != NULL) &&
(wcsncmpi(Buffer + 1, CONSOLE_CHILD_PARAM, wcslen(CONSOLE_CHILD_PARAM)) == 0) &&
(Buffer[wcslen(CONSOLE_CHILD_PARAM) + 1] == L'='))
{
SkipParam = Count;
wcscpy(ChildPath, Buffer + 1 + wcslen(CONSOLE_CHILD_PARAM) + 1);
}
++Count;
}
if (wcslen(ChildPath) == 0)
{
DWORD ModuleNameLen = GetModuleFileName(NULL, Buffer, MAX_PATH);
if ((ModuleNameLen == 0) || (ModuleNameLen == MAX_PATH))
{
throw runtime_error("Error retrieving executable name.");
}
const wchar_t* LastDelimiter = wcsrchr(Buffer, L'\\');
const wchar_t* AppFileName;
if (LastDelimiter != NULL)
{
wcsncpy(ChildPath, Buffer, LastDelimiter - Buffer + 1);
ChildPath[LastDelimiter - Buffer + 1] = L'\0';
AppFileName = LastDelimiter + 1;
}
else
{
ChildPath[0] = L'\0';
AppFileName = Buffer;
}
const wchar_t* ExtensionStart = wcsrchr(AppFileName, L'.');
if (ExtensionStart != NULL)
{
wchar_t* End = ChildPath + wcslen(ChildPath);
wcsncpy(End, AppFileName, ExtensionStart - AppFileName);
*(End + (ExtensionStart - AppFileName)) = L'\0';
}
else
{
wcscat(ChildPath, AppFileName);
}
wcscat(ChildPath, L".exe");
}
wchar_t ProductVersion[32];
GetProductVersion(ProductVersion);
wchar_t* Parameters = new wchar_t[(CommandLineLen * 2) + 100 + (Count * 3) + 1];
wsprintf(Parameters, L"\"%s\" /console=%s /consoleinstance=%s ", ChildPath, ProductVersion, InstanceName);
P = CommandLine;
// skip executable path
CutToken(P, Buffer);
int i = 1;
while (CutToken(P, Buffer))
{
if (i != SkipParam)
{
wcscat(Parameters, L"\"");
wchar_t* P2 = Parameters + wcslen(Parameters);
const wchar_t* P3 = Buffer;
const wchar_t* BufferEnd = Buffer + wcslen(Buffer) + 1;
while (P3 != BufferEnd)
{
*P2 = *P3;
++P2;
if (*P3 == L'"')
{
*P2 = L'"';
++P2;
}
++P3;
}
wcscat(Parameters, L"\" ");
}
++i;
}
delete[] Buffer;
STARTUPINFO StartupInfo = { sizeof(STARTUPINFO) };
PROCESS_INFORMATION ProcessInfomation;
BOOL Result =
CreateProcess(ChildPath, Parameters, NULL, NULL, false, 0, NULL, NULL,
&StartupInfo, &ProcessInfomation);
delete[] Parameters;
if (Result)
{
Child = ProcessInfomation.hProcess;
}
else
{
size_t Len = MAX_PATH + 1024;
DWORD Error = GetLastError();
wchar_t * Buffer = NULL;
Len += FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER, 0, Error, 0, (LPTSTR)&Buffer, 0, NULL);
wchar_t* Message = new wchar_t[Len];
wsprintf(Message, L"Cannot start WinSCP application \"%s\".", ChildPath);
if (Buffer != NULL)
{
wcscat(Message, L"\n");
wcscat(Message, Buffer);
LocalFree(Buffer);
}
char* MessageString = WideStringToString(Message);
delete[] Message;
std::string ErrorString(MessageString);
delete[] MessageString;
throw runtime_error(ErrorString);
}
}
//---------------------------------------------------------------------------
void FinalizeChild(HANDLE Child)
{
if (Child != NULL)
{
TerminateProcess(Child, 0);
CloseHandle(Child);
}
}
//---------------------------------------------------------------------------
void FinalizeConsole(const wchar_t* /*InstanceName*/, HANDLE RequestEvent,
HANDLE ResponseEvent, HANDLE CancelEvent, HANDLE FileMapping, HANDLE Job)
{
CloseHandle(RequestEvent);
CloseHandle(ResponseEvent);
CloseHandle(CancelEvent);
CloseHandle(FileMapping);
if (Job != NULL)
{
CloseHandle(Job);
}
}
//---------------------------------------------------------------------------
static wchar_t LastFromBeginning[sizeof(TConsoleCommStruct::TPrintEvent)] = L""; //???
//---------------------------------------------------------------------------
inline void Flush()
{
if ((OutputType == FILE_TYPE_DISK) || (OutputType == FILE_TYPE_PIPE))
{
fflush(stdout);
}
}
//---------------------------------------------------------------------------
void PrintException(const exception& e)
{
if (ConsoleOutput != NULL)
{
unsigned long Written;
WriteFile(ConsoleOutput, e.what(), strlen(e.what()), &Written, NULL);
}
else
{
puts(e.what());
}
}
//---------------------------------------------------------------------------
void Print(const wchar_t* Message)
{
char* Buffer = WideStringToString(Message);
if (Buffer != NULL)
{
char* Ptr = Buffer;
while ((Ptr = strchr(Ptr, '\n')) != NULL)
{
memmove(Ptr + 1, Ptr, strlen(Ptr) + 1);
*Ptr = '\r';
Ptr += 2;
}
unsigned long Written;
WriteFile(ConsoleOutput, Buffer, strlen(Buffer), &Written, NULL);
delete[] Buffer;
}
}
//---------------------------------------------------------------------------
void Print(bool FromBeginning, const wchar_t* Message)
{
size_t Len = wcslen(Message);
if ((OutputType == FILE_TYPE_DISK) || (OutputType == FILE_TYPE_PIPE))
{
if (FromBeginning && (Message[0] != L'\n'))
{
wcscpy(LastFromBeginning, Message);
}
else
{
if (LastFromBeginning[0] != L'\0')
{
Print(LastFromBeginning);
LastFromBeginning[0] = L'\0';
}
if (FromBeginning && (Message[0] == L'\n'))
{
Print(L"\n");
wcscpy(LastFromBeginning, Message + 1);
}
else
{
Print(Message);
}
Flush();
}
}
else
{
unsigned long Written;
if (FromBeginning)
{
WriteConsole(ConsoleOutput, L"\r", 1, &Written, NULL);
}
bool WriteResult =
WriteConsole(ConsoleOutput, Message, Len, &Written, NULL);
int Error = GetLastError();
// The current console font does not support some characters in the message,
// fall back to ansi-writting
if (!WriteResult && (Error == ERROR_GEN_FAILURE))
{
int Size = WideCharToMultiByte(CP_ACP, 0, Message, -1, 0, 0, 0, 0);
if (Size > 0)
{
char* Buffer = new char[Size];
if (WideCharToMultiByte(CP_ACP, 0, Message, -1, Buffer, Size, 0, 0) > 0)
{
WriteConsoleA(ConsoleOutput, Buffer, strlen(Buffer), &Written, NULL);
}
delete[] Buffer;
}
}
}
}
//---------------------------------------------------------------------------
inline void ProcessPrintEvent(TConsoleCommStruct::TPrintEvent& Event)
{
Print(Event.FromBeginning, Event.Message);
}
//---------------------------------------------------------------------------
void CancelInput()
{
SetEvent(CancelEvent);
}
//---------------------------------------------------------------------------
void BreakInput()
{
FlushConsoleInputBuffer(ConsoleInput);
// Signal cancel first, otherwise the main thread can get Enter before the event is set
CancelInput();
INPUT_RECORD InputRecord;
memset(&InputRecord, 0, sizeof(InputRecord));
InputRecord.EventType = KEY_EVENT;
InputRecord.Event.KeyEvent.bKeyDown = true;
InputRecord.Event.KeyEvent.wRepeatCount = 1;
InputRecord.Event.KeyEvent.uChar.UnicodeChar = L'\r';
unsigned long Written;
WriteConsoleInput(ConsoleInput, &InputRecord, 1, &Written);
}
//---------------------------------------------------------------------------
DWORD WINAPI InputTimerThreadProc(void* Parameter)
{
unsigned int Timer = reinterpret_cast<unsigned int>(Parameter);
unsigned int Remaining = Timer;
const unsigned int Step = 1000;
const int FirstKey = VK_LBUTTON; // 0x01
const int LastKey = VK_OEM_CLEAR; // 0xFE
// reset key state
for (int Key = FirstKey; Key <= LastKey; Key++)
{
GetAsyncKeyState(Key);
}
while (Remaining > 0)
{
unsigned long WaitResult = WaitForSingleObject(InputTimerEvent, Step);
if (WaitResult == WAIT_OBJECT_0)
{
// input entered
Remaining = 0;
}
else if (WaitResult == WAIT_TIMEOUT)
{
bool Input = false;
for (int Key = FirstKey; Key <= LastKey; Key++)
{
if ((GetAsyncKeyState(Key) & 0x01) != 0)
{
Input = true;
// Finishing the loop nevertheless to reset state of all keys
}
}
if (Input)
{
// If we have new input, reset timer
Remaining = Timer;
}
else if (Remaining > Step)
{
Remaining -= Step;
}
else
{
BreakInput();
Remaining = 0;
}
}
else
{
// abort input on (unlikely) error
BreakInput();
Remaining = 0;
}
}
return 0;
}
//---------------------------------------------------------------------------
void ProcessInputEvent(TConsoleCommStruct::TInputEvent& Event)
{
if ((InputType == FILE_TYPE_DISK) || (InputType == FILE_TYPE_PIPE))
{
unsigned long Bytes = 0;
unsigned long Read;
bool Result;
char Ch;
char Buf[LENOF(Event.Str) * 3];
while (((Result = (ReadFile(ConsoleInput, &Ch, 1, &Read, NULL) != 0)) != false) &&
(Read > 0) && (Bytes < LENOF(Buf) - 1) && (Ch != '\n'))
{
if (Ch != '\r')
{
Buf[Bytes] = Ch;
Bytes++;
}
}
Buf[Bytes] = L'\0';
MultiByteToWideChar(CP_UTF8, 0, Buf, -1, Event.Str, LENOF(Event.Str) - 1);
Event.Str[LENOF(Event.Str) - 1] = L'\0';
Print(false, Event.Str);
Print(false, L"\n");
Event.Result = ((Result && (Read > 0)) || (Bytes > 0));
}
else
{
unsigned long PrevMode, NewMode;
GetConsoleMode(ConsoleInput, &PrevMode);
NewMode = PrevMode | ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT;
if (Event.Echo)
{
NewMode |= ENABLE_ECHO_INPUT;
}
else
{
NewMode &= ~ENABLE_ECHO_INPUT;
}
SetConsoleMode(ConsoleInput, NewMode);
HANDLE InputTimerThread = NULL;
try
{
if (Event.Timer > 0)
{
unsigned long ThreadId;
InputTimerEvent = CreateEvent(NULL, false, false, NULL);
InputTimerThread = CreateThread(NULL, 0, InputTimerThreadProc,
reinterpret_cast<void *>(Event.Timer), 0, &ThreadId);
}
unsigned long Read;
Event.Result = ReadConsole(ConsoleInput, Event.Str, LENOF(Event.Str) - 1, &Read, NULL);
Event.Str[Read] = L'\0';
bool PendingCancel = (WaitForSingleObject(CancelEvent, 0) == WAIT_OBJECT_0);
if (PendingCancel || !Event.Echo)
{
WriteFile(ConsoleOutput, "\n", 1, NULL, NULL);
Flush();
}
if (PendingCancel || (Read == 0))
{
Event.Result = false;
}
}
__finally
{
if (InputTimerThread != NULL)
{
SetEvent(InputTimerEvent);
WaitForSingleObject(InputTimerThread, 100);
CloseHandle(InputTimerEvent);
InputTimerEvent = NULL;
CloseHandle(InputTimerThread);
}
SetConsoleMode(ConsoleInput, PrevMode);
}
}
}
//---------------------------------------------------------------------------
void ProcessChoiceEvent(TConsoleCommStruct::TChoiceEvent& Event)
{
// note that if output is redirected to file, input is still FILE_TYPE_CHAR
if ((InputType == FILE_TYPE_DISK) || (InputType == FILE_TYPE_PIPE))
{
if (Event.Timeouting)
{
Sleep(Event.Timer);
Event.Result = Event.Timeouted;
}
else
{
Event.Result = Event.Break;
}
}
else
{
Event.Result = 0;
unsigned long PrevMode, NewMode;
GetConsoleMode(ConsoleInput, &PrevMode);
NewMode = (PrevMode | ENABLE_PROCESSED_INPUT) & ~(ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT);
SetConsoleMode(ConsoleInput, NewMode);
unsigned int ATimer = Event.Timer;
try
{
do
{
unsigned long Read;
INPUT_RECORD Record;
if ((PeekConsoleInput(ConsoleInput, &Record, 1, &Read) != 0) &&
(Read == 1))
{
if ((ReadConsoleInput(ConsoleInput, &Record, 1, &Read) != 0) &&
(Read == 1))
{
bool PendingCancel = (WaitForSingleObject(CancelEvent, 0) == WAIT_OBJECT_0);
if (PendingCancel)
{
Event.Result = Event.Break;
}
else if ((Record.EventType == KEY_EVENT) &&
Record.Event.KeyEvent.bKeyDown)
{
// This happens when Shift key is pressed
if (Record.Event.KeyEvent.uChar.UnicodeChar != 0)
{
wchar_t CStr[2];
CStr[0] = Record.Event.KeyEvent.uChar.UnicodeChar;
CStr[1] = L'\0';
CharUpperBuff(CStr, 1);
wchar_t C = CStr[0];
if (C == 27)
{
Event.Result = Event.Cancel;
}
else if ((wcschr(Event.Options, C) != NULL) &&
((Record.Event.KeyEvent.dwControlKeyState &
(LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED | LEFT_ALT_PRESSED |
RIGHT_ALT_PRESSED)) == 0))
{
Event.Result = wcschr(Event.Options, C) - Event.Options + 1;
}
}
}
}
}
if (Event.Result == 0)
{
unsigned int TimerSlice = 50;
Sleep(TimerSlice);
if (Event.Timer > 0)
{
if (ATimer > TimerSlice)
{
ATimer -= TimerSlice;
}
else
{
Event.Result = Event.Timeouted;
}
}
}
}
while (Event.Result == 0);
SetConsoleMode(ConsoleInput, PrevMode);
}
catch(...)
{
SetConsoleMode(ConsoleInput, PrevMode);
throw;
}
}
}
//---------------------------------------------------------------------------
inline void ProcessTitleEvent(TConsoleCommStruct::TTitleEvent& Event)
{
SetConsoleTitle(Event.Title);
}
//---------------------------------------------------------------------------
inline void ProcessInitEvent(TConsoleCommStruct::TInitEvent& Event)
{
if (Event.UseStdErr)
{
ConsoleOutput = ConsoleErrorOutput;
}
OutputFormat = Event.OutputFormat;
if (OutputFormat != TConsoleCommStruct::TInitEvent::OFF)
{
setmode(fileno(stdout), O_BINARY);
}
InputFormat = Event.InputFormat;
if (InputFormat != TConsoleCommStruct::TInitEvent::OFF)
{
setmode(fileno(stdin), O_BINARY);
}
OutputType = GetFileType(ConsoleOutput);
// Until now we should not have printed anything.
// Only in case of a fatal failure, we might have printed a pure ASCII error messages (and never got here).
if ((OutputType == FILE_TYPE_DISK) || (OutputType == FILE_TYPE_PIPE) ||
SupportsUtf8ConsoleOutput)
{
SetConsoleOutputCP(CP_UTF8);
}
else
{
SetConsoleOutputCP(CP_ACP);
}
Event.InputType = InputType;
Event.OutputType = OutputType;
// default anyway
Event.WantsProgress = false;
}
//---------------------------------------------------------------------------
inline void ProcessTransferOutEvent(TConsoleCommStruct::TTransferEvent& Event)
{
if (OutputFormat == TConsoleCommStruct::TInitEvent::BINARY)
{
fwrite(Event.Data, 1, Event.Len, stdout);
}
else if (OutputFormat == TConsoleCommStruct::TInitEvent::CHUNKED)
{
fprintf(stdout, "%x\r\n", static_cast<int>(Event.Len));
fwrite(Event.Data, 1, Event.Len, stdout);
fputs("\r\n", stdout);
}
}
//---------------------------------------------------------------------------
inline void ProcessTransferInEvent(TConsoleCommStruct::TTransferEvent& Event)
{
size_t Read = fread(Event.Data, 1, Event.Len, stdin);
if (Read != Event.Len)
{
if (ferror(stdin))
{
Event.Error = true;
}
else
{
Event.Len = Read;
}
}
}
//---------------------------------------------------------------------------
void ProcessEvent(HANDLE ResponseEvent, HANDLE FileMapping)
{
TConsoleCommStruct* CommStruct = GetCommStruct(FileMapping);
try
{
if (CommStruct->Version != TConsoleCommStruct::CurrentVersionConfirmed)
{
throw runtime_error("Incompatible console protocol version");
}
switch (CommStruct->Event)
{
case TConsoleCommStruct::PRINT:
ProcessPrintEvent(CommStruct->PrintEvent);
break;
case TConsoleCommStruct::INPUT:
ProcessInputEvent(CommStruct->InputEvent);
break;
case TConsoleCommStruct::CHOICE:
ProcessChoiceEvent(CommStruct->ChoiceEvent);
break;
case TConsoleCommStruct::TITLE:
ProcessTitleEvent(CommStruct->TitleEvent);
break;
case TConsoleCommStruct::INIT:
ProcessInitEvent(CommStruct->InitEvent);
break;
case TConsoleCommStruct::TRANSFEROUT:
ProcessTransferOutEvent(CommStruct->TransferEvent);
break;
case TConsoleCommStruct::TRANSFERIN:
ProcessTransferInEvent(CommStruct->TransferEvent);
break;
default:
throw runtime_error("Unknown event");
}
FreeCommStruct(CommStruct);
SetEvent(ResponseEvent);
}
catch(...)
{
FreeCommStruct(CommStruct);
throw;
}
}
//---------------------------------------------------------------------------
BOOL WINAPI HandlerRoutine(DWORD CtrlType)
{
if ((CtrlType == CTRL_C_EVENT) || (CtrlType == CTRL_BREAK_EVENT))
{
CancelInput();
return true;
}
else
{
FinalizeChild(Child);
return false;
}
}
//---------------------------------------------------------------------------
#pragma argsused
int wmain(int /*argc*/, wchar_t* /*argv*/[])
{
unsigned long Result = RESULT_UNKNOWN_ERROR;
try
{
randomize();
OSVERSIONINFO VersionInfo;
VersionInfo.dwOSVersionInfoSize = sizeof(VersionInfo);
GetVersionEx(&VersionInfo);
ConsoleInput = GetStdHandle(STD_INPUT_HANDLE);
InputType = GetFileType(ConsoleInput);
SetConsoleCtrlHandler(HandlerRoutine, true);
unsigned int SavedConsoleCP = GetConsoleCP();
unsigned int SavedConsoleOutputCP = GetConsoleOutputCP();
ConsoleStandardOutput = GetStdHandle(STD_OUTPUT_HANDLE);
ConsoleOutput = ConsoleStandardOutput;
ConsoleErrorOutput = GetStdHandle(STD_ERROR_HANDLE);
SupportsUtf8ConsoleOutput =
((VersionInfo.dwMajorVersion == 6) && (VersionInfo.dwMinorVersion >= 1)) ||
(VersionInfo.dwMajorVersion > 6);
if ((InputType == FILE_TYPE_DISK) || (InputType == FILE_TYPE_PIPE) ||
SupportsUtf8ConsoleOutput)
{
SetConsoleCP(CP_UTF8);
}
else
{
SetConsoleCP(CP_ACP);
}
wchar_t InstanceName[MAX_PATH];
HANDLE RequestEvent, ResponseEvent, FileMapping, Job;
InitializeConsole(InstanceName, RequestEvent, ResponseEvent,
CancelEvent, FileMapping, Job);
wchar_t SavedTitle[512];
GetConsoleTitle(SavedTitle, LENOF(SavedTitle));
try
{
#ifndef CONSOLE_TEST
InitializeChild(GetCommandLine(), InstanceName, Child);
#endif
try
{
bool Continue = true;
do
{
HANDLE Handles[2];
Handles[0] = RequestEvent;
Handles[1] = Child;
unsigned int HandleCount;
#ifndef CONSOLE_TEST
HandleCount = 2;
#else
HandleCount = 1;
#endif
unsigned long WaitResult =
WaitForMultipleObjects(HandleCount, Handles, false, INFINITE);
switch (WaitResult)
{
case WAIT_OBJECT_0:
ProcessEvent(ResponseEvent, FileMapping);
break;
case WAIT_OBJECT_0 + 1:
GetExitCodeProcess(Child, &Result);
CloseHandle(Child);
Child = NULL;
Continue = false;
break;
default:
throw runtime_error("Error waiting for communication from child process.");
}
}
while (Continue);
// flush pending progress message
Print(false, L"");
}
catch(const exception& e)
{
PrintException(e);
Result = RESULT_PROCESSING_ERROR;
}
#ifndef CONSOLE_TEST
FinalizeChild(Child);
#endif
SetConsoleTitle(SavedTitle);
SetConsoleCP(SavedConsoleCP);
SetConsoleOutputCP(SavedConsoleOutputCP);
}
catch(const exception& e)
{
PrintException(e);
Result = RESULT_INIT_ERROR;
}
FinalizeConsole(InstanceName, RequestEvent, ResponseEvent,
CancelEvent, FileMapping, Job);
}
catch(const exception& e)
{
PrintException(e);
Result = RESULT_GLOBAL_ERROR;
}
return Result;
}
//---------------------------------------------------------------------------