1/*
2Title:      Poly/ML Start-up code.
3
4Copyright (c) 2000, 2015, 2018, 2019 David C. J. Matthews
5
6This library is free software; you can redistribute it and/or
7modify it under the terms of the GNU Lesser General Public
8License version 2.1 as published by the Free Software Foundation.
9
10This library is distributed in the hope that it will be useful,
11but WITHOUT ANY WARRANTY; without even the implied warranty of
12MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13Lesser General Public License for more details.
14
15You should have received a copy of the GNU Lesser General Public
16License along with this library; if not, write to the Free Software
17Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
18
19*/
20
21// This was previously part of the Console and Winguiconsole file.
22
23#ifdef HAVE_CONFIG_H
24#include "config.h"
25#elif defined(_WIN32)
26#include "winconfig.h"
27#else
28#error "No configuration file"
29#endif
30
31#ifdef HAVE_STDIO_H
32#include <stdio.h>
33#endif
34
35#ifdef HAVE_WINDOWS_H
36#include <winsock2.h> // Include first to avoid conflicts
37#include <windows.h>
38#endif
39
40#ifdef HAVE_TCHAR_H
41#include <tchar.h>
42#endif
43
44#ifdef HAVE_IO_H
45#include <io.h>
46#endif
47
48#ifdef HAVE_FCNTL_H
49#include <fcntl.h>
50#endif
51
52#ifdef HAVE_ERRNO_H
53#include <errno.h>
54#endif
55
56#ifdef HAVE_ASSERT_H
57#include <assert.h>
58#define ASSERT(x)   assert(x)
59#else
60#define ASSERT(x)
61#endif
62
63#include "PolyControl.h"
64#include "mpoly.h"
65#include "sighandler.h" // For RequestConsoleInterrupt
66#include "winguiconsole.h"
67#include "../polyexports.h"
68#include "processes.h"
69#include "io_internal.h"
70#include "winstartup.h"
71
72
73#ifdef _MSC_VER
74// Don't tell me about ISO C++ changes.
75#pragma warning(disable:4996)
76#endif
77
78HINSTANCE hApplicationInstance;     // Application instance (exported)
79
80static HWND hDDEWindow;     // Window to handle DDE requests from ML thread.
81
82static LPTSTR*  lpArgs = 0; // Argument list.
83static int      nArgs = 0;
84static int initDDEControl(const TCHAR *lpszName);
85static void uninitDDEControl(void);
86static DWORD dwDDEInstance;
87
88// useConsole is read in diagnostics to indicate whether to
89// put up a message box.
90bool useConsole = false;
91
92// The streams set by PolyWinMain.  These are used by winbasicio.
93WinStream *standardInput, *standardOutput, *standardError;
94
95// Default DDE service name.
96#define POLYMLSERVICE   _T("PolyML")
97
98#ifdef UNICODE
99#define DDECODEPAGE CP_WINUNICODE
100#else
101#define DDECODEPAGE CP_WINANSI
102#endif
103
104/* Messages interpreted by the main window thread. */
105//#define WM_ADDTEXT      WM_APP
106#define WM_DDESTART     (WM_APP+1)
107#define WM_DDESTOP      (WM_APP+2)
108#define WM_DDEEXEC      (WM_APP+3)
109#define WM_DDESERVINIT      (WM_APP+4)
110
111/* DDE requests.  DDE uses an internal window for communication and so all
112DDE operations on a particular instance handle have to be performed by
113the same thread.  That thread also has to check and process the message
114queue.  The previous version did this by having the ML thread make the
115DDE calls and processed the message list in an "interrupt" routine.
116That complicates the Windows interface so now the ML thread sends messages
117to the main window thread to do the work. */
118HCONV StartDDEConversation(TCHAR *serviceName, TCHAR *topicName)
119{
120    return (HCONV)SendMessage(hDDEWindow, WM_DDESTART, (WPARAM)serviceName, (LPARAM)topicName);
121}
122
123void CloseDDEConversation(HCONV hConv)
124{
125    SendMessage(hDDEWindow, WM_DDESTOP, 0, (LPARAM)hConv);
126}
127
128LRESULT ExecuteDDE(char *command, HCONV hConv)
129{
130    return SendMessage(hDDEWindow, WM_DDEEXEC, (WPARAM)hConv, (LPARAM)command);
131}
132
133// This is called by the main Poly/ML thread after the arguments have been processed.
134void SetupDDEHandler(const TCHAR *lpszServiceName)
135{
136    SendMessage(hDDEWindow, WM_DDESERVINIT, 0, (LPARAM)lpszServiceName);
137}
138
139LRESULT CALLBACK DDEWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
140{
141    switch (uMsg)
142    {
143    case WM_DDESERVINIT:
144        return initDDEControl((const TCHAR*)lParam);
145
146    case WM_DDESTART:
147    {
148        HCONV hcDDEConv;
149        HSZ hszServiceName, hszTopicName;
150        TCHAR *serviceName = (TCHAR*)wParam;
151        TCHAR *topicName = (TCHAR*)lParam;
152        hszServiceName =
153            DdeCreateStringHandle(dwDDEInstance, serviceName, DDECODEPAGE);
154        hszTopicName =
155            DdeCreateStringHandle(dwDDEInstance, topicName, DDECODEPAGE);
156        hcDDEConv =
157            DdeConnect(dwDDEInstance, hszServiceName, hszTopicName, NULL);
158        DdeFreeStringHandle(dwDDEInstance, hszServiceName);
159        DdeFreeStringHandle(dwDDEInstance, hszTopicName);
160        if (hcDDEConv == 0)
161        {
162            // UINT nErr = DdeGetLastError(dwDDEInstance);
163            return 0;
164        }
165        return (LRESULT)hcDDEConv;
166    }
167
168    case WM_DDESTOP:
169        DdeDisconnect((HCONV)lParam);
170        return 0;
171
172    case WM_DDEEXEC:
173    {
174        HDDEDATA res;
175        LPSTR   command = (LPSTR)lParam;
176        res = DdeClientTransaction((LPBYTE)command, (DWORD)(strlen(command) + 1),
177            (HCONV)wParam, 0L, 0, XTYP_EXECUTE, TIMEOUT_ASYNC, NULL);
178        if (res != 0)
179        {
180            DdeFreeDataHandle(res);
181            // Succeeded - return true;
182            return 1;
183        }
184        else if (DdeGetLastError(dwDDEInstance) == DMLERR_BUSY)
185            // If it's busy return false.
186            return 0;
187        else return -1; // An error
188    }
189
190    default:
191        return DefWindowProc(hwnd, uMsg, wParam, lParam);
192    }
193}
194
195static DWORD WINAPI MainThrdProc(LPVOID lpParameter)
196// This thread simply continues with the rest of the ML
197// initialistion.
198{
199    exportDescription *exports = (exportDescription *)lpParameter;
200    return polymain(nArgs, lpArgs, exports);
201}
202
203// A wrapper for WinInOutStream for use with standard input that records the
204// original file type.
205class WinStdInPipeStream : public WinInOutStream
206{
207public:
208    WinStdInPipeStream(int kind) : fKind(kind) {}
209    virtual int fileKind() {
210        return fKind;
211    }
212protected:
213    int fKind;
214};
215
216// Thread to copy everything from the input to the output.
217class CopyThread {
218public:
219    CopyThread() :
220        hInput(INVALID_HANDLE_VALUE), hOutput(INVALID_HANDLE_VALUE) {}
221
222    bool RunCopy(HANDLE hIn, HANDLE hOut);
223private:
224    ~CopyThread();
225
226    void threadFunction(void);
227    HANDLE hInput, hOutput;
228    static DWORD WINAPI copyThread(LPVOID lpParameter);
229};
230
231CopyThread::~CopyThread()
232{
233    if (hOutput != INVALID_HANDLE_VALUE) CloseHandle(hOutput);
234    if (hInput != INVALID_HANDLE_VALUE) CloseHandle(hInput);
235}
236
237// Static thread function.  Deletes the CopyThread object when the copying is complete.
238// That closes the handles.
239DWORD WINAPI CopyThread::copyThread(LPVOID lpParameter)
240{
241    CopyThread *cp = (CopyThread *)lpParameter;
242    cp->threadFunction();
243    delete cp;
244    return 0;
245}
246
247void CopyThread::threadFunction()
248{
249    char buffer[4096];
250
251    while (true) {
252        DWORD dwRead;
253        if (!ReadFile(hInput, buffer, sizeof(buffer), &dwRead, NULL))
254            return;
255
256        if (dwRead == 0) // End-of-stream
257        {
258            DWORD dwErr = GetLastError();
259            // If we are reading from the (Windows) console and the user presses ctrl-C we
260            // may get a ERROR_OPERATION_ABORTED error.
261            if (dwErr == ERROR_OPERATION_ABORTED)
262            {
263                SetLastError(0); // Reset this.  We may have a normal EOF next.
264                continue;
265            }
266            // Normal exit.  Indicate EOF
267            return;
268        }
269
270        char *b = buffer;
271        do {
272            DWORD dwWritten;
273            if (!WriteFile(hOutput, b, dwRead, &dwWritten, NULL))
274                return;
275            b += dwWritten;
276            dwRead -= dwWritten;
277        } while (dwRead != 0);
278    }
279}
280
281// Set up the copying.  It closes the handles when it has finished.
282bool CopyThread::RunCopy(HANDLE hIn, HANDLE hOut)
283{
284    DWORD dwInId;
285    hInput = hIn;
286    hOutput = hOut;
287    HANDLE hInThread = CreateThread(NULL, 0, copyThread, this, 0, &dwInId);
288    if (hInThread == NULL) return false;
289    CloseHandle(hInThread);
290    return true;
291}
292
293// Called with various control events if the input stream is a console.
294static BOOL WINAPI CtrlHandler(DWORD dwCtrlType)
295{
296    if (dwCtrlType == CTRL_C_EVENT)
297    {
298        RequestConsoleInterrupt();
299        return TRUE;
300    }
301    return FALSE;
302}
303
304// Main entry point.  Called from WinMain with a pointer to the ML code.
305int PolyWinMain(
306    HINSTANCE hInstance,
307    HINSTANCE hPrevInstance,
308    LPSTR lpCmdLineUnused,
309    int nCmdShow,
310    exportDescription *exports
311)
312{
313    DWORD dwInId, dwRes;
314
315    SetErrorMode(0); // Force a proper error report
316
317    hApplicationInstance = hInstance;
318
319    // If we already have standard input and standard output we
320    // don't replace them, otherwise we create a window and pipes
321    // to connect it.  We use _get_osfhandle here because that
322    // checks for handles passed in in the STARTUPINFO as well as
323    // those inherited as standard handles.
324    HANDLE hStdInHandle = (HANDLE)_get_osfhandle(fileno(stdin));
325    HANDLE hStdOutHandle = (HANDLE)_get_osfhandle(fileno(stdout));
326    HANDLE hStdErrHandle = (HANDLE)_get_osfhandle(fileno(stderr));
327
328    // If we don't have standard output we're going to create a console for output.
329    // We also use this for input except if we've provided standard input but not standard ouput.
330    useConsole = hStdOutHandle == INVALID_HANDLE_VALUE;
331
332    // Do we have stdin?  If we do we need to create a pipe to buffer the input.
333    if (hStdInHandle != INVALID_HANDLE_VALUE)
334    {
335        // We have to capture the original type here.  hStdInHandle itself will
336        // be closed when we set the new stdin and hOldStdin could be closed by
337        // the pipe input thread almost immediately if the input is a small file.
338        int originalKind = WinStream::fileTypeOfHandle(hStdInHandle);
339
340        TCHAR pipeName[MAX_PATH];
341        newPipeName(pipeName);
342        HANDLE hOutputPipe =
343            CreateNamedPipe(pipeName, PIPE_ACCESS_OUTBOUND | FILE_FLAG_FIRST_PIPE_INSTANCE,
344                PIPE_READMODE_BYTE | PIPE_WAIT | PIPE_REJECT_REMOTE_CLIENTS, 1, 4096, 4096, 0, NULL);
345        if (hOutputPipe == INVALID_HANDLE_VALUE)
346            return 1;
347
348        HANDLE hNewStdin =
349            CreateFile(pipeName, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL);
350        if (hNewStdin == INVALID_HANDLE_VALUE)
351            return 1;
352        // Copy everything from the original standard input to the pipe.
353        CopyThread *copyStdin = new CopyThread;
354        if (!copyStdin->RunCopy(hStdInHandle, hOutputPipe))
355            return 1;
356
357        SetConsoleCtrlHandler(CtrlHandler, TRUE); // May fail if there's no console.
358                                                  // Leave the original stdIn.
359
360        WinStdInPipeStream *stndIn = new WinStdInPipeStream(originalKind);
361        stndIn->openHandle(hNewStdin, OPENREAD, true);
362        standardInput = stndIn;
363        CloseHandle(hNewStdin); // Duplicated
364    }
365    else
366    {
367        // There was no standard input.  If we didn't have standard output either and are using
368        // our GUI console use that for input.  Otherwise open a stream on "NUL"
369        if (useConsole)
370            standardInput = createConsoleStream();
371        else
372        {
373            WinInOutStream *inStream = new WinInOutStream;
374            // We can't use openFile here because we don't have a taskData yet.
375            HANDLE hNewStdin =
376                CreateFile(_T("NUL"), GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL);
377            inStream->openHandle(hNewStdin, OPENREAD, true);
378            standardInput = inStream;
379        }
380    }
381
382    if (useConsole)
383    {
384        HANDLE hWriteToScreen = createConsoleWindow(nCmdShow);
385        if (hWriteToScreen == INVALID_HANDLE_VALUE)
386            return 1;
387        // hWriteToScreen is used as stdout .
388        // hReadFromML is what the screen thread reads from.
389        WinInOutStream *stdOut = new WinInOutStream;
390        // Safe since we duplicate the handle.
391        stdOut->openHandle(hWriteToScreen, OPENWRITE, true);
392        standardOutput = stdOut; // Pass to winbasicio
393                                 // Replace the standard Windows handles.  This may be used in OS.Process.system.
394        SetStdHandle(STD_OUTPUT_HANDLE, hWriteToScreen);
395        // A few RTS modules use stdio for output, primarily objsize and diagnostics.
396        // Doing this causes the stdIn package to close hWriteToScreen during shut-down.
397        polyStdout = _fdopen(_open_osfhandle((INT_PTR)hWriteToScreen, _O_TEXT), "wt"); // == stdout
398
399        if (hStdErrHandle == INVALID_HANDLE_VALUE)
400        {
401            // If we didn't have stderr write any stderr output to our console.
402            WinInOutStream *stdErr = new WinInOutStream;
403            stdErr->openHandle(hWriteToScreen, OPENWRITE, true);
404            standardError = stdErr;
405            HANDLE hStderr;
406            // We definitely need a duplicate for stdio since it closes each stream on exit
407            // We may also inherit it in OS.Process.system so make it inheritable.
408            if (!DuplicateHandle(GetCurrentProcess(), hWriteToScreen, GetCurrentProcess(), &hStderr, 0, TRUE, DUPLICATE_SAME_ACCESS))
409                return 1;
410            SetStdHandle(STD_ERROR_HANDLE, hStderr);
411            // Used in a few cases for diagnostics.
412            polyStderr = _fdopen(_open_osfhandle((INT_PTR)hStderr, _O_TEXT), "wt");
413        }
414    }
415    else
416    {
417        // Create a copy stream
418        TCHAR pipeName[MAX_PATH];
419        newPipeName(pipeName);
420        HANDLE hInputPipe =
421            CreateNamedPipe(pipeName, PIPE_ACCESS_INBOUND | FILE_FLAG_FIRST_PIPE_INSTANCE,
422                PIPE_READMODE_BYTE | PIPE_WAIT | PIPE_REJECT_REMOTE_CLIENTS, 1, 4096, 4096, 0, NULL);
423        if (hInputPipe == INVALID_HANDLE_VALUE)
424            return 1;
425        HANDLE hNewStdout =
426            CreateFile(pipeName, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL);
427        if (hNewStdout == INVALID_HANDLE_VALUE)
428            return 1;
429        // Copy everything from the original standard input to the pipe.
430        CopyThread *copyStdout = new CopyThread;
431        if (!copyStdout->RunCopy(hInputPipe, hStdOutHandle))
432            return 1;
433        WinInOutStream *standOut = new WinInOutStream;
434        standOut->openHandle(hNewStdout, OPENWRITE, true);
435        standardOutput = standOut;
436        // Leave the existing stdout and use it for diagnostics if necessary.
437        polyStdout = stdout;
438
439        if (hStdErrHandle == INVALID_HANDLE_VALUE)
440        {
441            // We had a stdout but not stderr.  We could choose to direct stderr output
442            // to the provided stdout but maybe that's not what the user wants.  Instead
443            // we open one on NUL.
444            polyStderr = fopen("NUL", "wt");
445            HANDLE hStdErr = (HANDLE)_get_osfhandle(fileno(polyStderr));
446            SetStdHandle(STD_ERROR_HANDLE, hStdErr);
447            hStdErrHandle = hStdErr;
448        }
449        // Set up a stream for writes from ML.
450        WinInOutStream *stdErr = new WinInOutStream;
451        stdErr->openHandle(hStdErrHandle, OPENWRITE, true);
452        standardError = stdErr;
453    }
454
455    // Set nArgs and lpArgs to the command line arguments.
456    // Convert the command line into Unix-style arguments.  There isn't a
457    // CommandLineToArgvA function so we have to use the Unicode version and
458    // convert the results.
459    {
460        // Get the unicode args
461        LPWSTR *uniArgs = CommandLineToArgvW(GetCommandLineW(), &nArgs);
462#ifdef UNICODE
463        lpArgs = uniArgs;
464#else
465        if (uniArgs != NULL)
466        {
467            lpArgs = (LPSTR*)calloc(nArgs, sizeof(LPSTR));
468            if (lpArgs != 0)
469            {
470                for (int i = 0; i < nArgs; i++)
471                {
472                    // See how much space will be needed
473                    int space =
474                        WideCharToMultiByte(CP_ACP, 0, uniArgs[i], -1, NULL, 0, NULL, NULL);
475                    if (space == 0) break; // Failed for some reason
476                                           // Allocate the space then do the conversion
477                    LPSTR buff = (LPSTR)malloc(space);
478                    if (buff == 0) break;
479                    int result =
480                        WideCharToMultiByte(CP_ACP, 0, uniArgs[i], -1, buff, space, NULL, NULL);
481                    if (result == 0) { free(buff); break; }
482                    lpArgs[i] = buff;
483                }
484            }
485            LocalFree(uniArgs);
486        }
487#endif
488    }
489
490    // Create an internal hidden window to handle DDE requests from the ML thread.
491    {
492        WNDCLASSEX wndClass;
493        ATOM atClass;
494        memset(&wndClass, 0, sizeof(wndClass));
495        wndClass.cbSize = sizeof(wndClass);
496        wndClass.lpfnWndProc = DDEWndProc;
497        wndClass.hInstance = hInstance;
498        wndClass.lpszClassName = _T("PolyMLDDEWindowClass");
499
500        if ((atClass = RegisterClassEx(&wndClass)) == 0) return 1;
501
502        hDDEWindow = CreateWindow(
503            (LPTSTR)(intptr_t)atClass,
504            _T("Poly/ML-DDE"),
505            WS_OVERLAPPEDWINDOW,
506            CW_USEDEFAULT,
507            CW_USEDEFAULT,
508            CW_USEDEFAULT,
509            CW_USEDEFAULT,
510            NULL,
511            NULL,   // handle to menu or child-window identifier
512            hInstance,
513            NULL     // pointer to window-creation data
514        );
515    }
516
517    // Call the main program to do the rest of the initialisation.
518    HANDLE hMainThread = CreateThread(NULL, 0, MainThrdProc, exports, 0, &dwInId);
519
520    // Enter the main message loop.
521    while (MsgWaitForMultipleObjects(1, &hMainThread,
522        FALSE, INFINITE, QS_ALLINPUT) == WAIT_OBJECT_0 + 1)
523    {
524        MSG Msg;
525        while (PeekMessage(&Msg, NULL, 0, 0, PM_REMOVE))
526        {
527            TranslateMessage(&Msg);
528            DispatchMessage(&Msg);
529        }
530    }
531
532    if (!GetExitCodeThread(hMainThread, &dwRes)) dwRes = 0;
533
534    uninitDDEControl();
535    DestroyWindow(hDDEWindow);
536
537    return dwRes;
538}
539
540HDDEDATA CALLBACK DdeCallback(UINT uType, UINT uFmt, HCONV hconv,
541    HSZ hsz1, HSZ hsz2, HDDEDATA hdata,
542    ULONG_PTR dwData1, ULONG_PTR dwData2)
543{
544    switch (uType)
545    {
546    case XTYP_CONNECT:
547        // Client connecting.  For the moment we ignore
548        // the topic.
549        return (HDDEDATA)TRUE;
550
551    case XTYP_EXECUTE:
552    {
553        // See what the message is.  The only ones we
554        // handle are interrupt and terminate.
555        TCHAR buff[256];
556        buff[0] = 0;
557        DdeGetData(hdata, (LPBYTE)buff, sizeof(buff), 0);
558        if (lstrcmpi(buff, _T(POLYINTERRUPT)) == 0)
559        {
560            RequestConsoleInterrupt();
561            return (HDDEDATA)DDE_FACK;
562        }
563        if (lstrcmpi(buff, _T(POLYTERMINATE)) == 0)
564        {
565            processes->RequestProcessExit(0);
566            return (HDDEDATA)DDE_FACK;
567        }
568        return (HDDEDATA)DDE_FNOTPROCESSED;
569    }
570
571    default:
572        return (HDDEDATA)NULL;
573    }
574}
575
576static int initDDEControl(const TCHAR *lpszName)
577{
578    // Start the DDE service.  This receives remote requests.
579    if (DdeInitialize(&dwDDEInstance, DdeCallback,
580        APPCLASS_STANDARD | CBF_FAIL_ADVISES | CBF_FAIL_POKES |
581        CBF_FAIL_REQUESTS | CBF_SKIP_ALLNOTIFICATIONS, 0)
582        != DMLERR_NO_ERROR)
583        return 0;
584
585    // If we were given a service name we register that,
586    // otherwise we use the default name.
587    if (lpszName == 0) lpszName = POLYMLSERVICE;
588    HSZ hszServiceName = DdeCreateStringHandle(dwDDEInstance, lpszName, DDECODEPAGE);
589    if (hszServiceName == 0) return 0;
590
591    DdeNameService(dwDDEInstance, hszServiceName, 0L, DNS_REGISTER);
592
593    DdeFreeStringHandle(dwDDEInstance, hszServiceName);
594    return 1;
595}
596
597static void uninitDDEControl(void)
598{
599    // Unregister our name(s).
600    DdeNameService(dwDDEInstance, 0L, 0L, DNS_UNREGISTER);
601    //  DdeAbandonTransaction(dwDDEInstance, 0L, 0L);
602    // Unitialise DDE.
603    DdeUninitialize(dwDDEInstance);
604}
605