root/tcl-8.0/trunk/win/tclWinPipe.c @ 3700

Revision 3700, 67.4 KB (checked in by piers, 8 years ago)

Renamed ApplicationType? to ApplicationTypeTcl? because it clashes with Type defined defined in Windows <objid.h>

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
Line 
1/*
2 * tclWinPipe.c --
3 *
4 *      This file implements the Windows-specific exec pipeline functions,
5 *      the "pipe" channel driver, and the "pid" Tcl command.
6 *
7 * Copyright (c) 1996-1997 by Sun Microsystems, Inc.
8 *
9 * See the file "license.terms" for information on usage and redistribution
10 * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
11 *
12 * SCCS: @(#) tclWinPipe.c 1.49 97/11/06 17:33:03
13 */
14
15#include "tclWinInt.h"
16
17#include <dos.h>
18#include <fcntl.h>
19#include <io.h>
20#include <sys/stat.h>
21
22/*
23 * The following variable is used to tell whether this module has been
24 * initialized.
25 */
26
27static int initialized = 0;
28
29/*
30 * The following defines identify the various types of applications that
31 * run under windows.  There is special case code for the various types.
32 */
33
34#define APPL_NONE       0
35#define APPL_DOS        1
36#define APPL_WIN3X      2
37#define APPL_WIN32      3
38
39/*
40 * The following constants and structures are used to encapsulate the state
41 * of various types of files used in a pipeline.
42 */
43
44#define WIN32S_PIPE 1           /* Win32s emulated pipe. */
45#define WIN32S_TMPFILE 2        /* Win32s emulated temporary file. */
46#define WIN_FILE 3              /* Basic Win32 file. */
47
48/*
49 * This structure encapsulates the common state associated with all file
50 * types used in a pipeline.
51 */
52
53typedef struct WinFile {
54    int type;                   /* One of the file types defined above. */
55    HANDLE handle;              /* Open file handle. */
56} WinFile;
57
58/*
59 * The following structure is used to keep track of temporary files under
60 * Win32s and delete the disk file when the open handle is closed.
61 * The type field will be WIN32S_TMPFILE.
62 */
63
64typedef struct TmpFile {
65    WinFile file;               /* Common part. */
66    char name[MAX_PATH];        /* Name of temp file. */
67} TmpFile;
68
69/*
70 * The following structure represents a synchronous pipe under Win32s.
71 * The type field will be WIN32S_PIPE.  The handle field will refer to
72 * an open file when Tcl is reading from the "pipe", otherwise it is
73 * INVALID_HANDLE_VALUE.
74 */
75
76typedef struct WinPipe {
77    WinFile file;               /* Common part. */
78    struct WinPipe *otherPtr;   /* Pointer to the WinPipe structure that
79                                 * corresponds to the other end of this
80                                 * pipe. */
81    char *fileName;             /* The name of the staging file that gets
82                                 * the data written to this pipe.  Malloc'd.
83                                 * and shared by both ends of the pipe.  Only
84                                 * when both ends are freed will fileName be
85                                 * freed and the file it refers to deleted. */
86} WinPipe;
87
88/*
89 * This list is used to map from pids to process handles.
90 */
91
92typedef struct ProcInfo {
93    HANDLE hProcess;
94    DWORD dwProcessId;
95    struct ProcInfo *nextPtr;
96} ProcInfo;
97
98static ProcInfo *procList;
99
100/*
101 * State flags used in the PipeInfo structure below.
102 */
103
104#define PIPE_PENDING    (1<<0)  /* Message is pending in the queue. */
105#define PIPE_ASYNC      (1<<1)  /* Channel is non-blocking. */
106
107/*
108 * This structure describes per-instance data for a pipe based channel.
109 */
110
111typedef struct PipeInfo {
112    Tcl_Channel channel;        /* Pointer to channel structure. */
113    int validMask;              /* OR'ed combination of TCL_READABLE,
114                                 * TCL_WRITABLE, or TCL_EXCEPTION: indicates
115                                 * which operations are valid on the file. */
116    int watchMask;              /* OR'ed combination of TCL_READABLE,
117                                 * TCL_WRITABLE, or TCL_EXCEPTION: indicates
118                                 * which events should be reported. */
119    int flags;                  /* State flags, see above for a list. */
120    TclFile readFile;           /* Output from pipe. */
121    TclFile writeFile;          /* Input from pipe. */
122    TclFile errorFile;          /* Error output from pipe. */
123    int numPids;                /* Number of processes attached to pipe. */
124    Tcl_Pid *pidPtr;            /* Pids of attached processes. */
125    struct PipeInfo *nextPtr;   /* Pointer to next registered pipe. */
126} PipeInfo;
127
128/*
129 * The following pointer refers to the head of the list of pipes
130 * that are being watched for file events.
131 */
132
133static PipeInfo *firstPipePtr;
134
135/*
136 * The following structure is what is added to the Tcl event queue when
137 * pipe events are generated.
138 */
139
140typedef struct PipeEvent {
141    Tcl_Event header;           /* Information that is standard for
142                                 * all events. */
143    PipeInfo *infoPtr;          /* Pointer to pipe info structure.  Note
144                                 * that we still have to verify that the
145                                 * pipe exists before dereferencing this
146                                 * pointer. */
147} PipeEvent;
148
149/*
150 * Declarations for functions used only in this file.
151 */
152
153static int      ApplicationTypeTcl(Tcl_Interp *interp, const char *fileName,
154                    char *fullName);
155static void     BuildCommandLine(int argc, char **argv, Tcl_DString *linePtr);
156static void     CopyChannel(HANDLE dst, HANDLE src);
157static BOOL     HasConsole(void);
158static TclFile  MakeFile(HANDLE handle);
159static char *   MakeTempFile(Tcl_DString *namePtr);
160static int      PipeBlockModeProc(ClientData instanceData, int mode);
161static void     PipeCheckProc _ANSI_ARGS_((ClientData clientData,
162                    int flags));
163static int      PipeCloseProc(ClientData instanceData, Tcl_Interp *interp);
164static int      PipeEventProc(Tcl_Event *evPtr, int flags);
165static void     PipeExitHandler(ClientData clientData);
166static int      PipeGetHandleProc(ClientData instanceData, int direction,
167                    ClientData *handlePtr);
168static void     PipeInit(void);
169static int      PipeInputProc(ClientData instanceData, char *buf, int toRead,
170                    int *errorCode);
171static int      PipeOutputProc(ClientData instanceData, char *buf, int toWrite,
172                    int *errorCode);
173static void     PipeWatchProc(ClientData instanceData, int mask);
174static void     PipeSetupProc _ANSI_ARGS_((ClientData clientData,
175                    int flags));
176static int      TempFileName(char name[MAX_PATH]);
177
178/*
179 * This structure describes the channel type structure for command pipe
180 * based IO.
181 */
182
183static Tcl_ChannelType pipeChannelType = {
184    "pipe",                     /* Type name. */
185    PipeBlockModeProc,          /* Set blocking or non-blocking mode.*/
186    PipeCloseProc,              /* Close proc. */
187    PipeInputProc,              /* Input proc. */
188    PipeOutputProc,             /* Output proc. */
189    NULL,                       /* Seek proc. */
190    NULL,                       /* Set option proc. */
191    NULL,                       /* Get option proc. */
192    PipeWatchProc,              /* Set up notifier to watch the channel. */
193    PipeGetHandleProc,          /* Get an OS handle from channel. */
194};
195
196/*
197 *----------------------------------------------------------------------
198 *
199 * PipeInit --
200 *
201 *      This function initializes the static variables for this file.
202 *
203 * Results:
204 *      None.
205 *
206 * Side effects:
207 *      Creates a new event source.
208 *
209 *----------------------------------------------------------------------
210 */
211
212static void
213PipeInit()
214{
215    initialized = 1;
216    firstPipePtr = NULL;
217    procList = NULL;
218    Tcl_CreateEventSource(PipeSetupProc, PipeCheckProc, NULL);
219    Tcl_CreateExitHandler(PipeExitHandler, NULL);
220}
221
222/*
223 *----------------------------------------------------------------------
224 *
225 * PipeExitHandler --
226 *
227 *      This function is called to cleanup the pipe module before
228 *      Tcl is unloaded.
229 *
230 * Results:
231 *      None.
232 *
233 * Side effects:
234 *      Removes the pipe event source.
235 *
236 *----------------------------------------------------------------------
237 */
238
239static void
240PipeExitHandler(clientData)
241    ClientData clientData;      /* Old window proc */
242{
243    Tcl_DeleteEventSource(PipeSetupProc, PipeCheckProc, NULL);
244    initialized = 0;
245}
246
247/*
248 *----------------------------------------------------------------------
249 *
250 * PipeSetupProc --
251 *
252 *      This procedure is invoked before Tcl_DoOneEvent blocks waiting
253 *      for an event.
254 *
255 * Results:
256 *      None.
257 *
258 * Side effects:
259 *      Adjusts the block time if needed.
260 *
261 *----------------------------------------------------------------------
262 */
263
264void
265PipeSetupProc(data, flags)
266    ClientData data;            /* Not used. */
267    int flags;                  /* Event flags as passed to Tcl_DoOneEvent. */
268{
269    PipeInfo *infoPtr;
270    Tcl_Time blockTime = { 0, 0 };
271
272    if (!(flags & TCL_FILE_EVENTS)) {
273        return;
274    }
275   
276    /*
277     * Check to see if there is a watched pipe.  If so, poll.
278     */
279
280    for (infoPtr = firstPipePtr; infoPtr != NULL; infoPtr = infoPtr->nextPtr) {
281        if (infoPtr->watchMask) {
282            Tcl_SetMaxBlockTime(&blockTime);
283            break;
284        }
285    }
286}
287
288/*
289 *----------------------------------------------------------------------
290 *
291 * PipeCheckProc --
292 *
293 *      This procedure is called by Tcl_DoOneEvent to check the pipe
294 *      event source for events.
295 *
296 * Results:
297 *      None.
298 *
299 * Side effects:
300 *      May queue an event.
301 *
302 *----------------------------------------------------------------------
303 */
304
305static void
306PipeCheckProc(data, flags)
307    ClientData data;            /* Not used. */
308    int flags;                  /* Event flags as passed to Tcl_DoOneEvent. */
309{
310    PipeInfo *infoPtr;
311    PipeEvent *evPtr;
312
313    if (!(flags & TCL_FILE_EVENTS)) {
314        return;
315    }
316   
317    /*
318     * Queue events for any watched pipes that don't already have events
319     * queued.
320     */
321
322    for (infoPtr = firstPipePtr; infoPtr != NULL; infoPtr = infoPtr->nextPtr) {
323        if (infoPtr->watchMask && !(infoPtr->flags & PIPE_PENDING)) {
324            infoPtr->flags |= PIPE_PENDING;
325            evPtr = (PipeEvent *) ckalloc(sizeof(PipeEvent));
326            evPtr->header.proc = PipeEventProc;
327            evPtr->infoPtr = infoPtr;
328            Tcl_QueueEvent((Tcl_Event *) evPtr, TCL_QUEUE_TAIL);
329        }
330    }
331}
332
333/*
334 *----------------------------------------------------------------------
335 *
336 * MakeFile --
337 *
338 *      This function constructs a new TclFile from a given data and
339 *      type value.
340 *
341 * Results:
342 *      Returns a newly allocated WinFile as a TclFile.
343 *
344 * Side effects:
345 *      None.
346 *
347 *----------------------------------------------------------------------
348 */
349
350static TclFile
351MakeFile(handle)
352    HANDLE handle;              /* Type-specific data. */
353{
354    WinFile *filePtr;
355
356    filePtr = (WinFile *) ckalloc(sizeof(WinFile));
357    filePtr->type = WIN_FILE;
358    filePtr->handle = handle;
359
360    return (TclFile)filePtr;
361}
362
363/*
364 *----------------------------------------------------------------------
365 *
366 * TclpMakeFile --
367 *
368 *      Make a TclFile from a channel.
369 *
370 * Results:
371 *      Returns a new TclFile or NULL on failure.
372 *
373 * Side effects:
374 *      None.
375 *
376 *----------------------------------------------------------------------
377 */
378
379TclFile
380TclpMakeFile(channel, direction)
381    Tcl_Channel channel;        /* Channel to get file from. */
382    int direction;              /* Either TCL_READABLE or TCL_WRITABLE. */
383{
384    HANDLE handle;
385
386    if (Tcl_GetChannelHandle(channel, direction,
387            (ClientData *) &handle) == TCL_OK) {
388        return MakeFile(handle);
389    } else {
390        return (TclFile) NULL;
391    }
392}
393
394/*
395 *----------------------------------------------------------------------
396 *
397 * TempFileName --
398 *
399 *      Gets a temporary file name and deals with the fact that the
400 *      temporary file path provided by Windows may not actually exist
401 *      if the TMP or TEMP environment variables refer to a
402 *      non-existent directory.
403 *
404 * Results:   
405 *      0 if error, non-zero otherwise.  If non-zero is returned, the
406 *      name buffer will be filled with a name that can be used to
407 *      construct a temporary file.
408 *
409 * Side effects:
410 *      None.
411 *
412 *----------------------------------------------------------------------
413 */
414
415static int
416TempFileName(name)
417    char name[MAX_PATH];        /* Buffer in which name for temporary
418                                 * file gets stored. */
419{
420    if ((GetTempPath(MAX_PATH, name) == 0) ||
421            (GetTempFileName(name, "TCL", 0, name) == 0)) {
422        name[0] = '.';
423        name[1] = '\0';
424        if (GetTempFileName(name, "TCL", 0, name) == 0) {
425            return 0;
426        }
427    }
428    return 1;
429}
430
431/*
432 *----------------------------------------------------------------------
433 *
434 * TclpCreateTempFile --
435 *
436 *      This function opens a unique file with the property that it
437 *      will be deleted when its file handle is closed.  The temporary
438 *      file is created in the system temporary directory.
439 *
440 * Results:
441 *      Returns a valid TclFile, or NULL on failure.
442 *
443 * Side effects:
444 *      Creates a new temporary file.
445 *
446 *----------------------------------------------------------------------
447 */
448
449TclFile
450TclpCreateTempFile(contents, namePtr)
451    char *contents;             /* String to write into temp file, or NULL. */
452    Tcl_DString *namePtr;       /* If non-NULL, pointer to initialized
453                                 * DString that is filled with the name of
454                                 * the temp file that was created. */
455{
456    char name[MAX_PATH];
457    HANDLE handle;
458
459    if (TempFileName(name) == 0) {
460        return NULL;
461    }
462
463    handle = CreateFile(name, GENERIC_READ | GENERIC_WRITE, 0, NULL,
464            CREATE_ALWAYS, FILE_ATTRIBUTE_TEMPORARY|FILE_FLAG_DELETE_ON_CLOSE,
465            NULL);
466    if (handle == INVALID_HANDLE_VALUE) {
467        goto error;
468    }
469
470    /*
471     * Write the file out, doing line translations on the way.
472     */
473
474    if (contents != NULL) {
475        DWORD result, length;
476        char *p;
477       
478        for (p = contents; *p != '\0'; p++) {
479            if (*p == '\n') {
480                length = p - contents;
481                if (length > 0) {
482                    if (!WriteFile(handle, contents, length, &result, NULL)) {
483                        goto error;
484                    }
485                }
486                if (!WriteFile(handle, "\r\n", 2, &result, NULL)) {
487                    goto error;
488                }
489                contents = p+1;
490            }
491        }
492        length = p - contents;
493        if (length > 0) {
494            if (!WriteFile(handle, contents, length, &result, NULL)) {
495                goto error;
496            }
497        }
498    }
499
500    if (SetFilePointer(handle, 0, NULL, FILE_BEGIN) == 0xFFFFFFFF) {
501        goto error;
502    }
503
504    if (namePtr != NULL) {
505        Tcl_DStringAppend(namePtr, name, -1);
506    }
507
508    /*
509     * Under Win32s a file created with FILE_FLAG_DELETE_ON_CLOSE won't
510     * actually be deleted when it is closed, so we have to do it ourselves.
511     */
512
513    if (TclWinGetPlatformId() == VER_PLATFORM_WIN32s) {
514        TmpFile *tmpFilePtr = (TmpFile *) ckalloc(sizeof(TmpFile));
515        tmpFilePtr->file.type = WIN32S_TMPFILE;
516        tmpFilePtr->file.handle = handle;
517        strcpy(tmpFilePtr->name, name);
518        return (TclFile)tmpFilePtr;
519    } else {
520        return MakeFile(handle);
521    }
522
523  error:
524    TclWinConvertError(GetLastError());
525    CloseHandle(handle);
526    DeleteFile(name);
527    return NULL;
528}
529
530/*
531 *----------------------------------------------------------------------
532 *
533 * TclpOpenFile --
534 *
535 *      This function opens files for use in a pipeline.
536 *
537 * Results:
538 *      Returns a newly allocated TclFile structure containing the
539 *      file handle.
540 *
541 * Side effects:
542 *      None.
543 *
544 *----------------------------------------------------------------------
545 */
546
547TclFile
548TclpOpenFile(path, mode)
549    char *path;
550    int mode;
551{
552    HANDLE handle;
553    DWORD accessMode, createMode, shareMode, flags;
554    SECURITY_ATTRIBUTES sec;
555
556    /*
557     * Map the access bits to the NT access mode.
558     */
559
560    switch (mode & (O_RDONLY | O_WRONLY | O_RDWR)) {
561        case O_RDONLY:
562            accessMode = GENERIC_READ;
563            break;
564        case O_WRONLY:
565            accessMode = GENERIC_WRITE;
566            break;
567        case O_RDWR:
568            accessMode = (GENERIC_READ | GENERIC_WRITE);
569            break;
570        default:
571            TclWinConvertError(ERROR_INVALID_FUNCTION);
572            return NULL;
573    }
574
575    /*
576     * Map the creation flags to the NT create mode.
577     */
578
579    switch (mode & (O_CREAT | O_EXCL | O_TRUNC)) {
580        case (O_CREAT | O_EXCL):
581        case (O_CREAT | O_EXCL | O_TRUNC):
582            createMode = CREATE_NEW;
583            break;
584        case (O_CREAT | O_TRUNC):
585            createMode = CREATE_ALWAYS;
586            break;
587        case O_CREAT:
588            createMode = OPEN_ALWAYS;
589            break;
590        case O_TRUNC:
591        case (O_TRUNC | O_EXCL):
592            createMode = TRUNCATE_EXISTING;
593            break;
594        default:
595            createMode = OPEN_EXISTING;
596            break;
597    }
598
599    /*
600     * If the file is not being created, use the existing file attributes.
601     */
602
603    flags = 0;
604    if (!(mode & O_CREAT)) {
605        flags = GetFileAttributes(path);
606        if (flags == 0xFFFFFFFF) {
607            flags = 0;
608        }
609    }
610
611    /*
612     * Set up the security attributes so this file is not inherited by
613     * child processes.
614     */
615
616    sec.nLength = sizeof(sec);
617    sec.lpSecurityDescriptor = NULL;
618    sec.bInheritHandle = 0;
619
620    /*
621     * Set up the file sharing mode.  We want to allow simultaneous access.
622     */
623
624    shareMode = FILE_SHARE_READ | FILE_SHARE_WRITE;
625
626    /*
627     * Now we get to create the file.
628     */
629
630    handle = CreateFile(path, accessMode, shareMode, &sec, createMode, flags,
631            (HANDLE) NULL);
632    if (handle == INVALID_HANDLE_VALUE) {
633        DWORD err = GetLastError();
634        if ((err & 0xffffL) == ERROR_OPEN_FAILED) {
635            err = (mode & O_CREAT) ? ERROR_FILE_EXISTS : ERROR_FILE_NOT_FOUND;
636        }
637        TclWinConvertError(err);
638        return NULL;
639    }
640
641    /*
642     * Seek to the end of file if we are writing.
643     */
644
645    if (mode & O_WRONLY) {
646        SetFilePointer(handle, 0, NULL, FILE_END);
647    }
648
649    return MakeFile(handle);
650}
651
652/*
653 *----------------------------------------------------------------------
654 *
655 * TclpCreatePipe --
656 *
657 *      Creates an anonymous pipe.  Under Win32s, creates a temp file
658 *      that is used to simulate a pipe.
659 *
660 * Results:
661 *      Returns 1 on success, 0 on failure.
662 *
663 * Side effects:
664 *      Creates a pipe.
665 *
666 *----------------------------------------------------------------------
667 */
668
669int
670TclpCreatePipe(readPipe, writePipe)
671    TclFile *readPipe;  /* Location to store file handle for
672                                 * read side of pipe. */
673    TclFile *writePipe; /* Location to store file handle for
674                                 * write side of pipe. */
675{
676    HANDLE readHandle, writeHandle;
677
678    if (CreatePipe(&readHandle, &writeHandle, NULL, 0) != 0) {
679        *readPipe = MakeFile(readHandle);
680        *writePipe = MakeFile(writeHandle);
681        return 1;
682    }
683
684    if (TclWinGetPlatformId() == VER_PLATFORM_WIN32s) {
685        WinPipe *readPipePtr, *writePipePtr;
686        char buf[MAX_PATH];
687
688        if (TempFileName(buf) != 0) {
689            readPipePtr = (WinPipe *) ckalloc(sizeof(WinPipe));
690            writePipePtr = (WinPipe *) ckalloc(sizeof(WinPipe));
691
692            readPipePtr->file.type = WIN32S_PIPE;
693            readPipePtr->otherPtr = writePipePtr;
694            readPipePtr->fileName = strcpy(ckalloc(strlen(buf) + 1), buf);
695            readPipePtr->file.handle = INVALID_HANDLE_VALUE;
696            writePipePtr->file.type = WIN32S_PIPE;
697            writePipePtr->otherPtr = readPipePtr;
698            writePipePtr->fileName = readPipePtr->fileName;
699            writePipePtr->file.handle = INVALID_HANDLE_VALUE;
700
701            *readPipe = (TclFile)readPipePtr;
702            *writePipe = (TclFile)writePipePtr;
703
704            return 1;
705        }
706    }
707
708    TclWinConvertError(GetLastError());
709    return 0;
710}
711
712/*
713 *----------------------------------------------------------------------
714 *
715 * TclpCloseFile --
716 *
717 *      Closes a pipeline file handle.  These handles are created by
718 *      TclpOpenFile, TclpCreatePipe, or TclpMakeFile.
719 *
720 * Results:
721 *      0 on success, -1 on failure.
722 *
723 * Side effects:
724 *      The file is closed and deallocated.
725 *
726 *----------------------------------------------------------------------
727 */
728
729int
730TclpCloseFile(file)
731    TclFile file;       /* The file to close. */
732{
733    WinFile *filePtr = (WinFile *) file;
734    WinPipe *pipePtr;
735
736    switch (filePtr->type) {
737        case WIN_FILE:
738        case WIN32S_TMPFILE:
739            if (CloseHandle(filePtr->handle) == FALSE) {
740                TclWinConvertError(GetLastError());
741                ckfree((char *) filePtr);
742                return -1;
743            }
744            /*
745             * Simulate deleting the file on close for Win32s.
746             */
747
748            if (filePtr->type == WIN32S_TMPFILE) {
749                DeleteFile(((TmpFile*)filePtr)->name);
750            }
751            break;
752
753        case WIN32S_PIPE:
754            pipePtr = (WinPipe *) file;
755
756            if (pipePtr->otherPtr != NULL) {
757                pipePtr->otherPtr->otherPtr = NULL;
758            } else {
759                if (pipePtr->file.handle != INVALID_HANDLE_VALUE) {
760                    CloseHandle(pipePtr->file.handle);
761                }
762                DeleteFile(pipePtr->fileName);
763                ckfree((char *) pipePtr->fileName);
764            }
765            break;
766
767        default:
768            panic("Tcl_CloseFile: unexpected file type");
769    }
770
771    ckfree((char *) filePtr);
772    return 0;
773}
774
775/*
776 *--------------------------------------------------------------------------
777 *
778 * TclpGetProcess --
779 *
780 *      Given a process id return the Handle.
781 *
782 * Results:
783 *  Return the process handle, if the process id was unknown return 0
784 *
785 * Side effects:
786 *      None.
787 *
788 *--------------------------------------------------------------------------
789 */
790
791HANDLE
792TclpGetProcess(pid)
793    unsigned long pid; 
794{
795    ProcInfo *infoPtr;
796   
797    for (infoPtr = procList; infoPtr != NULL; infoPtr = infoPtr->nextPtr) {
798        if (infoPtr->dwProcessId == pid) {
799            return infoPtr->hProcess;
800        }
801    }
802    return 0;
803}
804
805/*
806 *--------------------------------------------------------------------------
807 *
808 * TclpGetPid --
809 *
810 *      Given a HANDLE to a child process, return the process id for that
811 *      child process.
812 *
813 * Results:
814 *      Returns the process id for the child process.  If the pid was not
815 *      known by Tcl, either because the pid was not created by Tcl or the
816 *      child process has already been reaped, -1 is returned.
817 *
818 * Side effects:
819 *      None.
820 *
821 *--------------------------------------------------------------------------
822 */
823
824unsigned long
825TclpGetPid(pid)
826    Tcl_Pid pid;                /* The HANDLE of the child process. */
827{
828    ProcInfo *infoPtr;
829   
830    for (infoPtr = procList; infoPtr != NULL; infoPtr = infoPtr->nextPtr) {
831        if (infoPtr->hProcess == (HANDLE) pid) {
832            return infoPtr->dwProcessId;
833        }
834    }
835    return (unsigned long) -1;
836}
837
838/*
839 *----------------------------------------------------------------------
840 *
841 * TclpCreateProcess --
842 *
843 *      Create a child process that has the specified files as its
844 *      standard input, output, and error.  The child process runs
845 *      synchronously under Win32s and asynchronously under Windows NT
846 *      and Windows 95, and runs with the same environment variables
847 *      as the creating process.
848 *
849 *      The complete Windows search path is searched to find the specified
850 *      executable.  If an executable by the given name is not found,
851 *      automatically tries appending ".com", ".exe", and ".bat" to the
852 *      executable name.
853 *
854 * Results:
855 *      The return value is TCL_ERROR and an error message is left in
856 *      interp->result if there was a problem creating the child
857 *      process.  Otherwise, the return value is TCL_OK and *pidPtr is
858 *      filled with the process id of the child process.
859 *
860 * Side effects:
861 *      A process is created.
862 *     
863 *----------------------------------------------------------------------
864 */
865
866int
867TclpCreateProcess(interp, argc, argv, inputFile, outputFile, errorFile,
868        pidPtr)
869    Tcl_Interp *interp;         /* Interpreter in which to leave errors that
870                                 * occurred when creating the child process.
871                                 * Error messages from the child process
872                                 * itself are sent to errorFile. */
873    int argc;                   /* Number of arguments in following array. */
874    char **argv;                /* Array of argument strings.  argv[0]
875                                 * contains the name of the executable
876                                 * converted to native format (using the
877                                 * Tcl_TranslateFileName call).  Additional
878                                 * arguments have not been converted. */
879    TclFile inputFile;          /* If non-NULL, gives the file to use as
880                                 * input for the child process.  If inputFile
881                                 * file is not readable or is NULL, the child
882                                 * will receive no standard input. */
883    TclFile outputFile;         /* If non-NULL, gives the file that
884                                 * receives output from the child process.  If
885                                 * outputFile file is not writeable or is
886                                 * NULL, output from the child will be
887                                 * discarded. */
888    TclFile errorFile;          /* If non-NULL, gives the file that
889                                 * receives errors from the child process.  If
890                                 * errorFile file is not writeable or is NULL,
891                                 * errors from the child will be discarded.
892                                 * errorFile may be the same as outputFile. */
893    Tcl_Pid *pidPtr;            /* If this procedure is successful, pidPtr
894                                 * is filled with the process id of the child
895                                 * process. */
896{
897    int result, applType, createFlags;
898    Tcl_DString cmdLine;
899    STARTUPINFO startInfo;
900    PROCESS_INFORMATION procInfo;
901    SECURITY_ATTRIBUTES secAtts;
902    HANDLE hProcess, h, inputHandle, outputHandle, errorHandle;
903    char execPath[MAX_PATH];
904    char *originalName;
905    WinFile *filePtr;
906
907    if (!initialized) {
908        PipeInit();
909    }
910
911    applType = ApplicationTypeTcl(interp, argv[0], execPath);
912    if (applType == APPL_NONE) {
913        return TCL_ERROR;
914    }
915    originalName = argv[0];
916    argv[0] = execPath;
917
918    result = TCL_ERROR;
919    Tcl_DStringInit(&cmdLine);
920
921    if (TclWinGetPlatformId() == VER_PLATFORM_WIN32s) {
922        /*
923         * Under Win32s, there are no pipes.  In order to simulate pipe
924         * behavior, the child processes are run synchronously and their
925         * I/O is redirected from/to temporary files before the next
926         * stage of the pipeline is started.
927         */
928
929        MSG msg;
930        DWORD status;
931        DWORD args[4];
932        void *trans[5];
933        char *inputFileName, *outputFileName;
934        Tcl_DString inputTempFile, outputTempFile;
935
936        BuildCommandLine(argc, argv, &cmdLine);
937
938        ZeroMemory(&startInfo, sizeof(startInfo));
939        startInfo.cb = sizeof(startInfo);
940
941        Tcl_DStringInit(&inputTempFile);
942        Tcl_DStringInit(&outputTempFile);
943        outputHandle = INVALID_HANDLE_VALUE;
944
945        inputFileName = NULL;
946        outputFileName = NULL;
947        if (inputFile != NULL) {
948            filePtr = (WinFile *) inputFile;
949            switch (filePtr->type) {
950                case WIN_FILE:
951                case WIN32S_TMPFILE: {
952                    h = INVALID_HANDLE_VALUE;
953                    inputFileName = MakeTempFile(&inputTempFile);
954                    if (inputFileName != NULL) {
955                        h = CreateFile(inputFileName, GENERIC_WRITE, 0,
956                                NULL, CREATE_ALWAYS, 0, NULL);
957                    }
958                    if (h == INVALID_HANDLE_VALUE) {
959                        Tcl_AppendResult(interp, "couldn't duplicate input handle: ",
960                                Tcl_PosixError(interp), (char *) NULL);
961                        goto end32s;
962                    }
963                    CopyChannel(h, filePtr->handle);
964                    CloseHandle(h);
965                    break;
966                }
967                case WIN32S_PIPE: {
968                    inputFileName = ((WinPipe*)inputFile)->fileName;
969                    break;
970                }
971            }
972        }
973        if (inputFileName == NULL) {
974            inputFileName = "nul";
975        }
976        if (outputFile != NULL) {
977            filePtr = (WinFile *)outputFile;
978            if (filePtr->type == WIN_FILE) {
979                outputFileName = MakeTempFile(&outputTempFile);
980                if (outputFileName == NULL) {
981                    Tcl_AppendResult(interp, "couldn't duplicate output handle: ",
982                            Tcl_PosixError(interp), (char *) NULL);
983                    goto end32s;
984                }
985                outputHandle = filePtr->handle;
986            } else if (filePtr->type == WIN32S_PIPE) {
987                outputFileName = ((WinPipe*)outputFile)->fileName;
988            }
989        }
990        if (outputFileName == NULL) {
991            outputFileName = "nul";
992        }
993
994        if (applType == APPL_DOS) {
995            args[0] = (DWORD) Tcl_DStringValue(&cmdLine);
996            args[1] = (DWORD) inputFileName;
997            args[2] = (DWORD) outputFileName;
998            trans[0] = &args[0];
999            trans[1] = &args[1];
1000            trans[2] = &args[2];
1001            trans[3] = NULL;
1002            if (TclWinSynchSpawn(args, 0, trans, pidPtr) != 0) {
1003                result = TCL_OK;
1004            }
1005        } else if (applType == APPL_WIN3X) {
1006            args[0] = (DWORD) Tcl_DStringValue(&cmdLine);
1007            trans[0] = &args[0];
1008            trans[1] = NULL;
1009            if (TclWinSynchSpawn(args, 1, trans, pidPtr) != 0) {
1010                result = TCL_OK;
1011            }
1012        } else {
1013            if (CreateProcess(NULL, Tcl_DStringValue(&cmdLine), NULL, NULL,
1014                    FALSE, DETACHED_PROCESS, NULL, NULL, &startInfo,
1015                    &procInfo) != 0) {
1016                CloseHandle(procInfo.hThread);
1017                while (1) {
1018                    if (GetExitCodeProcess(procInfo.hProcess, &status) == FALSE) {
1019                        break;
1020                    }
1021                    if (status != STILL_ACTIVE) {
1022                        break;
1023                    }
1024                    if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE) == TRUE) {
1025                        TranslateMessage(&msg);
1026                        DispatchMessage(&msg);
1027                    }
1028                }
1029                *pidPtr = (Tcl_Pid) procInfo.hProcess;
1030                if (*pidPtr != 0) {
1031                    ProcInfo *procPtr = (ProcInfo *) ckalloc(sizeof(ProcInfo));
1032                    procPtr->hProcess = procInfo.hProcess;
1033                    procPtr->dwProcessId = procInfo.dwProcessId;
1034                    procPtr->nextPtr = procList;
1035                    procList = procPtr;
1036                }
1037                result = TCL_OK;
1038            }
1039        }
1040        if (result != TCL_OK) {
1041            TclWinConvertError(GetLastError());
1042            Tcl_AppendResult(interp, "couldn't execute \"", originalName,
1043                    "\": ", Tcl_PosixError(interp), (char *) NULL);
1044        }
1045
1046        end32s:
1047        if (outputHandle != INVALID_HANDLE_VALUE) {
1048            /*
1049             * Now copy stuff from temp file to actual output handle. Don't
1050             * close outputHandle because it is associated with the output
1051             * file owned by the caller.
1052             */
1053
1054            h = CreateFile(outputFileName, GENERIC_READ, 0, NULL, OPEN_ALWAYS,
1055                    0, NULL);
1056            if (h != INVALID_HANDLE_VALUE) {
1057                CopyChannel(outputHandle, h);
1058            }
1059            CloseHandle(h);
1060        }
1061
1062        if (inputFileName == Tcl_DStringValue(&inputTempFile)) {
1063            DeleteFile(inputFileName);
1064        }
1065       
1066        if (outputFileName == Tcl_DStringValue(&outputTempFile)) {
1067            DeleteFile(outputFileName);
1068        }
1069
1070        Tcl_DStringFree(&inputTempFile);
1071        Tcl_DStringFree(&outputTempFile);
1072        Tcl_DStringFree(&cmdLine);
1073        return result;
1074    }
1075    hProcess = GetCurrentProcess();
1076
1077    /*
1078     * STARTF_USESTDHANDLES must be used to pass handles to child process.
1079     * Using SetStdHandle() and/or dup2() only works when a console mode
1080     * parent process is spawning an attached console mode child process.
1081     */
1082
1083    ZeroMemory(&startInfo, sizeof(startInfo));
1084    startInfo.cb = sizeof(startInfo);
1085    startInfo.dwFlags   = STARTF_USESTDHANDLES;
1086    startInfo.hStdInput = INVALID_HANDLE_VALUE;
1087    startInfo.hStdOutput= INVALID_HANDLE_VALUE;
1088    startInfo.hStdError = INVALID_HANDLE_VALUE;
1089
1090    secAtts.nLength = sizeof(SECURITY_ATTRIBUTES);
1091    secAtts.lpSecurityDescriptor = NULL;
1092    secAtts.bInheritHandle = TRUE;
1093
1094    /*
1095     * We have to check the type of each file, since we cannot duplicate
1096     * some file types. 
1097     */
1098
1099    inputHandle = INVALID_HANDLE_VALUE;
1100    if (inputFile != NULL) {
1101        filePtr = (WinFile *)inputFile;
1102        if (filePtr->type == WIN_FILE) {
1103            inputHandle = filePtr->handle;
1104        }
1105    }
1106    outputHandle = INVALID_HANDLE_VALUE;
1107    if (outputFile != NULL) {
1108        filePtr = (WinFile *)outputFile;
1109        if (filePtr->type == WIN_FILE) {
1110            outputHandle = filePtr->handle;
1111        }
1112    }
1113    errorHandle = INVALID_HANDLE_VALUE;
1114    if (errorFile != NULL) {
1115        filePtr = (WinFile *)errorFile;
1116        if (filePtr->type == WIN_FILE) {
1117            errorHandle = filePtr->handle;
1118        }
1119    }
1120
1121    /*
1122     * Duplicate all the handles which will be passed off as stdin, stdout
1123     * and stderr of the child process. The duplicate handles are set to
1124     * be inheritable, so the child process can use them.
1125     */
1126
1127    if (inputHandle == INVALID_HANDLE_VALUE) {
1128        /*
1129         * If handle was not set, stdin should return immediate EOF.
1130         * Under Windows95, some applications (both 16 and 32 bit!)
1131         * cannot read from the NUL device; they read from console
1132         * instead.  When running tk, this is fatal because the child
1133         * process would hang forever waiting for EOF from the unmapped
1134         * console window used by the helper application.
1135         *
1136         * Fortunately, the helper application detects a closed pipe
1137         * as an immediate EOF and can pass that information to the
1138         * child process.
1139         */
1140
1141        if (CreatePipe(&startInfo.hStdInput, &h, &secAtts, 0) != FALSE) {
1142            CloseHandle(h);
1143        }
1144    } else {
1145        DuplicateHandle(hProcess, inputHandle, hProcess, &startInfo.hStdInput,
1146                0, TRUE, DUPLICATE_SAME_ACCESS);
1147    }
1148    if (startInfo.hStdInput == INVALID_HANDLE_VALUE) {
1149        TclWinConvertError(GetLastError());
1150        Tcl_AppendResult(interp, "couldn't duplicate input handle: ",
1151                Tcl_PosixError(interp), (char *) NULL);
1152        goto end;
1153    }
1154
1155    if (outputHandle == INVALID_HANDLE_VALUE) {
1156        /*
1157         * If handle was not set, output should be sent to an infinitely
1158         * deep sink.  Under Windows 95, some 16 bit applications cannot
1159         * have stdout redirected to NUL; they send their output to
1160         * the console instead.  Some applications, like "more" or "dir /p",
1161         * when outputting multiple pages to the console, also then try and
1162         * read from the console to go the next page.  When running tk, this
1163         * is fatal because the child process would hang forever waiting
1164         * for input from the unmapped console window used by the helper
1165         * application.
1166         *
1167         * Fortunately, the helper application will detect a closed pipe
1168         * as a sink.
1169         */
1170
1171        if ((TclWinGetPlatformId() == VER_PLATFORM_WIN32_WINDOWS)
1172                && (applType == APPL_DOS)) {
1173            if (CreatePipe(&h, &startInfo.hStdOutput, &secAtts, 0) != FALSE) {
1174                CloseHandle(h);
1175            }
1176        } else {
1177            startInfo.hStdOutput = CreateFile("NUL:", GENERIC_WRITE, 0,
1178                    &secAtts, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
1179        }
1180    } else {
1181        DuplicateHandle(hProcess, outputHandle, hProcess, &startInfo.hStdOutput,
1182                0, TRUE, DUPLICATE_SAME_ACCESS);
1183    }
1184    if (startInfo.hStdOutput == INVALID_HANDLE_VALUE) {
1185        TclWinConvertError(GetLastError());
1186        Tcl_AppendResult(interp, "couldn't duplicate output handle: ",
1187                Tcl_PosixError(interp), (char *) NULL);
1188        goto end;
1189    }
1190
1191    if (errorHandle == INVALID_HANDLE_VALUE) {
1192        /*
1193         * If handle was not set, errors should be sent to an infinitely
1194         * deep sink.
1195         */
1196
1197        startInfo.hStdError = CreateFile("NUL:", GENERIC_WRITE, 0,
1198                &secAtts, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1199    } else {
1200        DuplicateHandle(hProcess, errorHandle, hProcess, &startInfo.hStdError,
1201                0, TRUE, DUPLICATE_SAME_ACCESS);
1202    }
1203    if (startInfo.hStdError == INVALID_HANDLE_VALUE) {
1204        TclWinConvertError(GetLastError());
1205        Tcl_AppendResult(interp, "couldn't duplicate error handle: ",
1206                Tcl_PosixError(interp), (char *) NULL);
1207        goto end;
1208    }
1209    /*
1210     * If we do not have a console window, then we must run DOS and
1211     * WIN32 console mode applications as detached processes. This tells
1212     * the loader that the child application should not inherit the
1213     * console, and that it should not create a new console window for
1214     * the child application.  The child application should get its stdio
1215     * from the redirection handles provided by this application, and run
1216     * in the background.
1217     *
1218     * If we are starting a GUI process, they don't automatically get a
1219     * console, so it doesn't matter if they are started as foreground or
1220     * detached processes.  The GUI window will still pop up to the
1221     * foreground.
1222     */
1223
1224    if (TclWinGetPlatformId() == VER_PLATFORM_WIN32_NT) {
1225        if (HasConsole()) {
1226            createFlags = 0;
1227        } else if (applType == APPL_DOS) {
1228            /*
1229             * Under NT, 16-bit DOS applications will not run unless they
1230             * can be attached to a console.  If we are running without a
1231             * console, run the 16-bit program as an normal process inside
1232             * of a hidden console application, and then run that hidden
1233             * console as a detached process.
1234             */
1235
1236            startInfo.wShowWindow = SW_HIDE;
1237            startInfo.dwFlags |= STARTF_USESHOWWINDOW;
1238            createFlags = CREATE_NEW_CONSOLE;
1239            Tcl_DStringAppend(&cmdLine, "cmd.exe /c ", -1);
1240        } else {
1241            createFlags = DETACHED_PROCESS;
1242        }
1243    } else {
1244        if (HasConsole()) {
1245            createFlags = 0;
1246        } else {
1247            createFlags = DETACHED_PROCESS;
1248        }
1249       
1250        if (applType == APPL_DOS) {
1251            /*
1252             * Under Windows 95, 16-bit DOS applications do not work well
1253             * with pipes:
1254             *
1255             * 1. EOF on a pipe between a detached 16-bit DOS application
1256             * and another application is not seen at the other
1257             * end of the pipe, so the listening process blocks forever on
1258             * reads.  This inablity to detect EOF happens when either a
1259             * 16-bit app or the 32-bit app is the listener. 
1260             *
1261             * 2. If a 16-bit DOS application (detached or not) blocks when
1262             * writing to a pipe, it will never wake up again, and it
1263             * eventually brings the whole system down around it.
1264             *
1265             * The 16-bit application is run as a normal process inside
1266             * of a hidden helper console app, and this helper may be run
1267             * as a detached process.  If any of the stdio handles is
1268             * a pipe, the helper application accumulates information
1269             * into temp files and forwards it to or from the DOS
1270             * application as appropriate.  This means that DOS apps
1271             * must receive EOF from a stdin pipe before they will actually
1272             * begin, and must finish generating stdout or stderr before
1273             * the data will be sent to the next stage of the pipe.
1274             *
1275             * The helper app should be located in the same directory as
1276             * the tcl dll.
1277             */
1278
1279            if (createFlags != 0) {
1280                startInfo.wShowWindow = SW_HIDE;
1281                startInfo.dwFlags |= STARTF_USESHOWWINDOW;
1282                createFlags = CREATE_NEW_CONSOLE;
1283            }
1284            Tcl_DStringAppend(&cmdLine, "tclpip" STRINGIFY(TCL_MAJOR_VERSION)
1285                    STRINGIFY(TCL_MINOR_VERSION) ".dll ", -1);
1286        }
1287    }
1288   
1289    /*
1290     * cmdLine gets the full command line used to invoke the executable,
1291     * including the name of the executable itself.  The command line
1292     * arguments in argv[] are stored in cmdLine separated by spaces.
1293     * Special characters in individual arguments from argv[] must be
1294     * quoted when being stored in cmdLine.
1295     *
1296     * When calling any application, bear in mind that arguments that
1297     * specify a path name are not converted.  If an argument contains
1298     * forward slashes as path separators, it may or may not be
1299     * recognized as a path name, depending on the program.  In general,
1300     * most applications accept forward slashes only as option
1301     * delimiters and backslashes only as paths.
1302     *
1303     * Additionally, when calling a 16-bit dos or windows application,
1304     * all path names must use the short, cryptic, path format (e.g.,
1305     * using ab~1.def instead of "a b.default"). 
1306     */
1307
1308    BuildCommandLine(argc, argv, &cmdLine);
1309
1310    if (!CreateProcess(NULL, Tcl_DStringValue(&cmdLine), NULL, NULL, TRUE,
1311            createFlags, NULL, NULL, &startInfo, &procInfo)) {
1312        TclWinConvertError(GetLastError());
1313        Tcl_AppendResult(interp, "couldn't execute \"", originalName,
1314                "\": ", Tcl_PosixError(interp), (char *) NULL);
1315        goto end;
1316    }
1317
1318    if (applType == APPL_DOS) {
1319        WaitForSingleObject(hProcess, 50);
1320    }
1321
1322    /*
1323     * "When an application spawns a process repeatedly, a new thread
1324     * instance will be created for each process but the previous
1325     * instances may not be cleaned up.  This results in a significant
1326     * virtual memory loss each time the process is spawned.  If there
1327     * is a WaitForInputIdle() call between CreateProcess() and
1328     * CloseHandle(), the problem does not occur." PSS ID Number: Q124121
1329     */
1330
1331    WaitForInputIdle(procInfo.hProcess, 5000);
1332    CloseHandle(procInfo.hThread);
1333
1334    *pidPtr = (Tcl_Pid) procInfo.hProcess;
1335    if (*pidPtr != 0) {
1336        ProcInfo *procPtr = (ProcInfo *) ckalloc(sizeof(ProcInfo));
1337        procPtr->hProcess = procInfo.hProcess;
1338        procPtr->dwProcessId = procInfo.dwProcessId;
1339        procPtr->nextPtr = procList;
1340        procList = procPtr;
1341    }
1342    result = TCL_OK;
1343
1344    end:
1345    Tcl_DStringFree(&cmdLine);
1346    if (startInfo.hStdInput != INVALID_HANDLE_VALUE) {
1347        CloseHandle(startInfo.hStdInput);
1348    }
1349    if (startInfo.hStdOutput != INVALID_HANDLE_VALUE) {
1350        CloseHandle(startInfo.hStdOutput);
1351    }
1352    if (startInfo.hStdError != INVALID_HANDLE_VALUE) {
1353        CloseHandle(startInfo.hStdError);
1354    }
1355    return result;
1356}
1357
1358
1359/*
1360 *----------------------------------------------------------------------
1361 *
1362 * HasConsole --
1363 *
1364 *      Determines whether the current application is attached to a
1365 *      console.
1366 *
1367 * Results:
1368 *      Returns TRUE if this application has a console, else FALSE.
1369 *
1370 * Side effects:
1371 *      None.
1372 *
1373 *----------------------------------------------------------------------
1374 */
1375
1376static BOOL
1377HasConsole()
1378{
1379    HANDLE handle = CreateFile("CONOUT$", GENERIC_WRITE, FILE_SHARE_WRITE,
1380            NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1381
1382    if (handle != INVALID_HANDLE_VALUE) {
1383        CloseHandle(handle);
1384        return TRUE;
1385    } else {
1386        return FALSE;
1387    }
1388}
1389
1390/*
1391 *--------------------------------------------------------------------
1392 *
1393 * ApplicationTypeTcl --
1394 *
1395 *      Search for the specified program and identify if it refers to a DOS,
1396 *      Windows 3.X, or Win32 program.  Used to determine how to invoke
1397 *      a program, or if it can even be invoked.
1398 *
1399 *      It is possible to almost positively identify DOS and Windows
1400 *      applications that contain the appropriate magic numbers.  However,
1401 *      DOS .com files do not seem to contain a magic number; if the program
1402 *      name ends with .com and could not be identified as a Windows .com
1403 *      file, it will be assumed to be a DOS application, even if it was
1404 *      just random data.  If the program name does not end with .com, no
1405 *      such assumption is made.
1406 *
1407 *      The Win32 procedure GetBinaryType incorrectly identifies any
1408 *      junk file that ends with .exe as a dos executable and some
1409 *      executables that don't end with .exe as not executable.  Plus it
1410 *      doesn't exist under win95, so I won't feel bad about reimplementing
1411 *      functionality.
1412 *
1413 * Results:
1414 *      The return value is one of APPL_DOS, APPL_WIN3X, or APPL_WIN32
1415 *      if the filename referred to the corresponding application type.
1416 *      If the file name could not be found or did not refer to any known
1417 *      application type, APPL_NONE is returned and an error message is
1418 *      left in interp.  .bat files are identified as APPL_DOS.
1419 *
1420 * Side effects:
1421 *      None.
1422 *
1423 *----------------------------------------------------------------------
1424 */
1425
1426static int
1427ApplicationTypeTcl(interp, originalName, fullPath)
1428    Tcl_Interp *interp;         /* Interp, for error message. */
1429    const char *originalName;   /* Name of the application to find. */
1430    char fullPath[MAX_PATH];    /* Filled with complete path to
1431                                 * application. */
1432{
1433    int applType, i;
1434    HANDLE hFile;
1435    char *ext, *rest;
1436    char buf[2];
1437    DWORD read;
1438    IMAGE_DOS_HEADER header;
1439    static char extensions[][5] = {"", ".com", ".exe", ".bat"};
1440
1441    /* Look for the program as an external program.  First try the name
1442     * as it is, then try adding .com, .exe, and .bat, in that order, to
1443     * the name, looking for an executable.
1444     *
1445     * Using the raw SearchPath() procedure doesn't do quite what is
1446     * necessary.  If the name of the executable already contains a '.'
1447     * character, it will not try appending the specified extension when
1448     * searching (in other words, SearchPath will not find the program
1449     * "a.b.exe" if the arguments specified "a.b" and ".exe").   
1450     * So, first look for the file as it is named.  Then manually append
1451     * the extensions, looking for a match. 
1452     */
1453
1454    applType = APPL_NONE;
1455    for (i = 0; i < (int) (sizeof(extensions) / sizeof(extensions[0])); i++) {
1456        lstrcpyn(fullPath, originalName, MAX_PATH - 5);
1457        lstrcat(fullPath, extensions[i]);
1458       
1459        SearchPath(NULL, fullPath, NULL, MAX_PATH, fullPath, &rest);
1460
1461        /*
1462         * Ignore matches on directories or data files, return if identified
1463         * a known type.
1464         */
1465
1466        if (GetFileAttributes(fullPath) & FILE_ATTRIBUTE_DIRECTORY) {
1467            continue;
1468        }
1469
1470        ext = strrchr(fullPath, '.');
1471        if ((ext != NULL) && (strcmpi(ext, ".bat") == 0)) {
1472            applType = APPL_DOS;
1473            break;
1474        }
1475
1476        hFile = CreateFile(fullPath, GENERIC_READ, FILE_SHARE_READ, NULL,
1477                OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1478        if (hFile == INVALID_HANDLE_VALUE) {
1479            continue;
1480        }
1481
1482        header.e_magic = 0;
1483        ReadFile(hFile, (void *) &header, sizeof(header), &read, NULL);
1484        if (header.e_magic != IMAGE_DOS_SIGNATURE) {
1485            /*
1486             * Doesn't have the magic number for relocatable executables.  If
1487             * filename ends with .com, assume it's a DOS application anyhow.
1488             * Note that we didn't make this assumption at first, because some
1489             * supposed .com files are really 32-bit executables with all the
1490             * magic numbers and everything. 
1491             */
1492
1493            CloseHandle(hFile);
1494            if ((ext != NULL) && (strcmpi(ext, ".com") == 0)) {
1495                applType = APPL_DOS;
1496                break;
1497            }
1498            continue;
1499        }
1500        if (header.e_lfarlc != sizeof(header)) {
1501            /*
1502             * All Windows 3.X and Win32 and some DOS programs have this value
1503             * set here.  If it doesn't, assume that since it already had the
1504             * other magic number it was a DOS application.
1505             */
1506
1507            CloseHandle(hFile);
1508            applType = APPL_DOS;
1509            break;
1510        }
1511
1512        /*
1513         * The DWORD at header.e_lfanew points to yet another magic number.
1514         */
1515
1516        buf[0] = '\0';
1517        SetFilePointer(hFile, header.e_lfanew, NULL, FILE_BEGIN);
1518        ReadFile(hFile, (void *) buf, 2, &read, NULL);
1519        CloseHandle(hFile);
1520
1521        if ((buf[0] == 'N') && (buf[1] == 'E')) {
1522            applType = APPL_WIN3X;
1523        } else if ((buf[0] == 'P') && (buf[1] == 'E')) {
1524            applType = APPL_WIN32;
1525        } else {
1526            /*
1527             * Strictly speaking, there should be a test that there
1528             * is an 'L' and 'E' at buf[0..1], to identify the type as
1529             * DOS, but of course we ran into a DOS executable that
1530             * _doesn't_ have the magic number -- specifically, one
1531             * compiled using the Lahey Fortran90 compiler.
1532             */
1533
1534            applType = APPL_DOS;
1535        }
1536        break;
1537    }
1538
1539    if (applType == APPL_NONE) {
1540        TclWinConvertError(GetLastError());
1541        Tcl_AppendResult(interp, "couldn't execute \"", originalName,
1542                "\": ", Tcl_PosixError(interp), (char *) NULL);
1543        return APPL_NONE;
1544    }
1545
1546    if ((applType == APPL_DOS) || (applType == APPL_WIN3X)) {
1547        /*
1548         * Replace long path name of executable with short path name for
1549         * 16-bit applications.  Otherwise the application may not be able
1550         * to correctly parse its own command line to separate off the
1551         * application name from the arguments.
1552         */
1553
1554        GetShortPathName(fullPath, fullPath, MAX_PATH);
1555    }
1556    return applType;
1557}
1558
1559/*   
1560 *----------------------------------------------------------------------
1561 *
1562 * BuildCommandLine --
1563 *
1564 *      The command line arguments are stored in linePtr separated
1565 *      by spaces, in a form that CreateProcess() understands.  Special
1566 *      characters in individual arguments from argv[] must be quoted
1567 *      when being stored in cmdLine.
1568 *
1569 * Results:
1570 *      None.
1571 *
1572 * Side effects:
1573 *      None.
1574 *
1575 *----------------------------------------------------------------------
1576 */
1577
1578static void
1579BuildCommandLine(argc, argv, linePtr)
1580    int argc;                   /* Number of arguments. */
1581    char **argv;                /* Argument strings. */
1582    Tcl_DString *linePtr;       /* Initialized Tcl_DString that receives the
1583                                 * command line. */
1584{
1585    char *start, *special;
1586    int quote, i;
1587
1588    for (i = 0; i < argc; i++) {
1589        if (i > 0) {
1590            Tcl_DStringAppend(linePtr, " ", 1);
1591        }
1592
1593        quote = 0;
1594        if (argv[i][0] == '\0') {
1595            quote = 1;
1596        } else {
1597            for (start = argv[i]; *start != '\0'; start++) {
1598                if (isspace(*start)) {
1599                    quote = 1;
1600                    break;
1601                }
1602            }
1603        }
1604        if (quote) {
1605            Tcl_DStringAppend(linePtr, "\"", 1);
1606        }
1607
1608        start = argv[i];           
1609        for (special = argv[i]; ; ) {
1610            if ((*special == '\\') &&
1611                    (special[1] == '\\' || special[1] == '"')) {
1612                Tcl_DStringAppend(linePtr, start, special - start);
1613                start = special;
1614                while (1) {
1615                    special++;
1616                    if (*special == '"') {
1617                        /*
1618                         * N backslashes followed a quote -> insert
1619                         * N * 2 + 1 backslashes then a quote.
1620                         */
1621
1622                        Tcl_DStringAppend(linePtr, start, special - start);
1623                        break;
1624                    }
1625                    if (*special != '\\') {
1626                        break;
1627                    }
1628                }
1629                Tcl_DStringAppend(linePtr, start, special - start);
1630                start = special;
1631            }
1632            if (*special == '"') {
1633                Tcl_DStringAppend(linePtr, start, special - start);
1634                Tcl_DStringAppend(linePtr, "\\\"", 2);
1635                start = special + 1;
1636            }
1637            if (*special == '\0') {
1638                break;
1639            }
1640            special++;
1641        }
1642        Tcl_DStringAppend(linePtr, start, special - start);
1643        if (quote) {
1644            Tcl_DStringAppend(linePtr, "\"", 1);
1645        }
1646    }
1647}
1648
1649/*
1650 *----------------------------------------------------------------------
1651 *
1652 * MakeTempFile --
1653 *
1654 *      Helper function for TclpCreateProcess under Win32s.  Makes a
1655 *      temporary file that _won't_ go away automatically when it's file
1656 *      handle is closed.  Used for simulated pipes, which are written
1657 *      in one pass and reopened and read in the next pass.
1658 *
1659 * Results:
1660 *      namePtr is filled with the name of the temporary file.
1661 *
1662 * Side effects:
1663 *      A temporary file with the name specified by namePtr is created. 
1664 *      The caller is responsible for deleting this temporary file.
1665 *
1666 *----------------------------------------------------------------------
1667 */
1668
1669static char *
1670MakeTempFile(namePtr)
1671    Tcl_DString *namePtr;       /* Initialized Tcl_DString that is filled
1672                                 * with the name of the temporary file that
1673                                 * was created. */
1674{
1675    char name[MAX_PATH];
1676
1677    if (TempFileName(name) == 0) {
1678        return NULL;
1679    }
1680
1681    Tcl_DStringAppend(namePtr, name, -1);
1682    return Tcl_DStringValue(namePtr);
1683}
1684
1685/*
1686 *----------------------------------------------------------------------
1687 *
1688 * CopyChannel --
1689 *
1690 *      Helper function used by TclpCreateProcess under Win32s.  Copies
1691 *      what remains of source file to destination file; source file
1692 *      pointer need not be positioned at the beginning of the file if
1693 *      all of source file is not desired, but data is copied up to end
1694 *      of source file.
1695 *
1696 * Results:
1697 *      None.
1698 *
1699 * Side effects:
1700 *      None.
1701 *
1702 *----------------------------------------------------------------------
1703 */
1704
1705static void
1706CopyChannel(dst, src)
1707    HANDLE dst;                 /* Destination file. */
1708    HANDLE src;                 /* Source file. */
1709{
1710    char buf[8192];
1711    DWORD dwRead, dwWrite;
1712
1713    while (ReadFile(src, buf, sizeof(buf), &dwRead, NULL) != FALSE) {
1714        if (dwRead == 0) {
1715            break;
1716        }
1717        if (WriteFile(dst, buf, dwRead, &dwWrite, NULL) == FALSE) {
1718            break;
1719        }
1720    }
1721}
1722
1723/*
1724 *----------------------------------------------------------------------
1725 *
1726 * TclpCreateCommandChannel --
1727 *
1728 *      This function is called by Tcl_OpenCommandChannel to perform
1729 *      the platform specific channel initialization for a command
1730 *      channel.
1731 *
1732 * Results:
1733 *      Returns a new channel or NULL on failure.
1734 *
1735 * Side effects:
1736 *      Allocates a new channel.
1737 *
1738 *----------------------------------------------------------------------
1739 */
1740
1741Tcl_Channel
1742TclpCreateCommandChannel(readFile, writeFile, errorFile, numPids, pidPtr)
1743    TclFile readFile;           /* If non-null, gives the file for reading. */
1744    TclFile writeFile;          /* If non-null, gives the file for writing. */
1745    TclFile errorFile;          /* If non-null, gives the file where errors
1746                                 * can be read. */
1747    int numPids;                /* The number of pids in the pid array. */
1748    Tcl_Pid *pidPtr;            /* An array of process identifiers. */
1749{
1750    char channelName[20];
1751    int channelId;
1752    PipeInfo *infoPtr = (PipeInfo *) ckalloc((unsigned) sizeof(PipeInfo));
1753
1754    if (!initialized) {
1755        PipeInit();
1756    }
1757
1758    infoPtr->watchMask = 0;
1759    infoPtr->flags = 0;
1760    infoPtr->readFile = readFile;
1761    infoPtr->writeFile = writeFile;
1762    infoPtr->errorFile = errorFile;
1763    infoPtr->numPids = numPids;
1764    infoPtr->pidPtr = pidPtr;
1765
1766    /*
1767     * Use one of the fds associated with the channel as the
1768     * channel id.
1769     */
1770
1771    if (readFile) {
1772        WinPipe *pipePtr = (WinPipe *) readFile;
1773        if (pipePtr->file.type == WIN32S_PIPE
1774                && pipePtr->file.handle == INVALID_HANDLE_VALUE) {
1775            pipePtr->file.handle = CreateFile(pipePtr->fileName, GENERIC_READ,
1776                    0, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
1777        }
1778        channelId = (int) pipePtr->file.handle;
1779    } else if (writeFile) {
1780        channelId = (int) ((WinFile*)writeFile)->handle;
1781    } else if (errorFile) {
1782        channelId = (int) ((WinFile*)errorFile)->handle;
1783    } else {
1784        channelId = 0;
1785    }
1786
1787    infoPtr->validMask = 0;
1788    if (readFile != NULL) {
1789        infoPtr->validMask |= TCL_READABLE;
1790    }
1791    if (writeFile != NULL) {
1792        infoPtr->validMask |= TCL_WRITABLE;
1793    }
1794
1795    /*
1796     * For backward compatibility with previous versions of Tcl, we
1797     * use "file%d" as the base name for pipes even though it would
1798     * be more natural to use "pipe%d".
1799     */
1800
1801    sprintf(channelName, "file%d", channelId);
1802    infoPtr->channel = Tcl_CreateChannel(&pipeChannelType, channelName,
1803            (ClientData) infoPtr, infoPtr->validMask);
1804
1805    /*
1806     * Pipes have AUTO translation mode on Windows and ^Z eof char, which
1807     * means that a ^Z will be appended to them at close. This is needed
1808     * for Windows programs that expect a ^Z at EOF.
1809     */
1810
1811    Tcl_SetChannelOption((Tcl_Interp *) NULL, infoPtr->channel,
1812            "-translation", "auto");
1813    Tcl_SetChannelOption((Tcl_Interp *) NULL, infoPtr->channel,
1814            "-eofchar", "\032 {}");
1815    return infoPtr->channel;
1816}
1817
1818/*
1819 *----------------------------------------------------------------------
1820 *
1821 * TclGetAndDetachPids --
1822 *
1823 *      Stores a list of the command PIDs for a command channel in
1824 *      interp->result.
1825 *
1826 * Results:
1827 *      None.
1828 *
1829 * Side effects:
1830 *      Modifies interp->result.
1831 *
1832 *----------------------------------------------------------------------
1833 */
1834
1835void
1836TclGetAndDetachPids(interp, chan)
1837    Tcl_Interp *interp;
1838    Tcl_Channel chan;
1839{
1840    PipeInfo *pipePtr;
1841    Tcl_ChannelType *chanTypePtr;
1842    int i;
1843    char buf[20];
1844
1845    /*
1846     * Punt if the channel is not a command channel.
1847     */
1848
1849    chanTypePtr = Tcl_GetChannelType(chan);
1850    if (chanTypePtr != &pipeChannelType) {
1851        return;
1852    }
1853
1854    pipePtr = (PipeInfo *) Tcl_GetChannelInstanceData(chan);
1855    for (i = 0; i < pipePtr->numPids; i++) {
1856        sprintf(buf, "%lu", TclpGetPid(pipePtr->pidPtr[i]));
1857        Tcl_AppendElement(interp, buf);
1858        Tcl_DetachPids(1, &(pipePtr->pidPtr[i]));
1859    }
1860    if (pipePtr->numPids > 0) {
1861        ckfree((char *) pipePtr->pidPtr);
1862        pipePtr->numPids = 0;
1863    }
1864}
1865
1866/*
1867 *----------------------------------------------------------------------
1868 *
1869 * PipeBlockModeProc --
1870 *
1871 *      Set blocking or non-blocking mode on channel.
1872 *
1873 * Results:
1874 *      0 if successful, errno when failed.
1875 *
1876 * Side effects:
1877 *      Sets the device into blocking or non-blocking mode.
1878 *
1879 *----------------------------------------------------------------------
1880 */
1881
1882static int
1883PipeBlockModeProc(instanceData, mode)
1884    ClientData instanceData;    /* Instance data for channel. */
1885    int mode;                   /* TCL_MODE_BLOCKING or
1886                                 * TCL_MODE_NONBLOCKING. */
1887{
1888    PipeInfo *infoPtr = (PipeInfo *) instanceData;
1889   
1890    /*
1891     * Pipes on Windows can not be switched between blocking and nonblocking,
1892     * hence we have to emulate the behavior. This is done in the input
1893     * function by checking against a bit in the state. We set or unset the
1894     * bit here to cause the input function to emulate the correct behavior.
1895     */
1896
1897    if (mode == TCL_MODE_NONBLOCKING) {
1898        infoPtr->flags |= PIPE_ASYNC;
1899    } else {
1900        infoPtr->flags &= ~(PIPE_ASYNC);
1901    }
1902    return 0;
1903}
1904
1905/*
1906 *----------------------------------------------------------------------
1907 *
1908 * PipeCloseProc --
1909 *
1910 *      Closes a pipe based IO channel.
1911 *
1912 * Results:
1913 *      0 on success, errno otherwise.
1914 *
1915 * Side effects:
1916 *      Closes the physical channel.
1917 *
1918 *----------------------------------------------------------------------
1919 */
1920
1921static int
1922PipeCloseProc(instanceData, interp)
1923    ClientData instanceData;    /* Pointer to PipeInfo structure. */
1924    Tcl_Interp *interp;         /* For error reporting. */
1925{
1926    PipeInfo *pipePtr = (PipeInfo *) instanceData;
1927    Tcl_Channel errChan;
1928    int errorCode, result;
1929    PipeInfo *infoPtr, **nextPtrPtr;
1930
1931    /*
1932     * Remove the file from the list of watched files.
1933     */
1934
1935    for (nextPtrPtr = &firstPipePtr, infoPtr = *nextPtrPtr; infoPtr != NULL;
1936            nextPtrPtr = &infoPtr->nextPtr, infoPtr = *nextPtrPtr) {
1937        if (infoPtr == (PipeInfo *)pipePtr) {
1938            *nextPtrPtr = infoPtr->nextPtr;
1939            break;
1940        }
1941    }
1942
1943    errorCode = 0;
1944    if (pipePtr->readFile != NULL) {
1945        if (TclpCloseFile(pipePtr->readFile) != 0) {
1946            errorCode = errno;
1947        }
1948    }
1949    if (pipePtr->writeFile != NULL) {
1950        if (TclpCloseFile(pipePtr->writeFile) != 0) {
1951            if (errorCode == 0) {
1952                errorCode = errno;
1953            }
1954        }
1955    }
1956   
1957    /*
1958     * Wrap the error file into a channel and give it to the cleanup
1959     * routine.  If we are running in Win32s, just delete the error file
1960     * immediately, because it was never used.
1961     */
1962
1963    if (pipePtr->errorFile) {
1964        WinFile *filePtr;
1965        OSVERSIONINFO os;
1966
1967        os.dwOSVersionInfoSize = sizeof(os);
1968        GetVersionEx(&os);
1969        if (os.dwPlatformId == VER_PLATFORM_WIN32s) {
1970            TclpCloseFile(pipePtr->errorFile);
1971            errChan = NULL;
1972        } else {
1973            filePtr = (WinFile*)pipePtr->errorFile;
1974            errChan = Tcl_MakeFileChannel((ClientData) filePtr->handle,
1975                    TCL_READABLE);
1976        }
1977    } else {
1978        errChan = NULL;
1979    }
1980    result = TclCleanupChildren(interp, pipePtr->numPids, pipePtr->pidPtr,
1981            errChan);
1982    if (pipePtr->numPids > 0) {
1983        ckfree((char *) pipePtr->pidPtr);
1984    }
1985    ckfree((char*) pipePtr);
1986
1987    if (errorCode == 0) {
1988        return result;
1989    }
1990    return errorCode;
1991}
1992
1993/*
1994 *----------------------------------------------------------------------
1995 *
1996 * PipeInputProc --
1997 *
1998 *      Reads input from the IO channel into the buffer given. Returns
1999 *      count of how many bytes were actually read, and an error indication.
2000 *
2001 * Results:
2002 *      A count of how many bytes were read is returned and an error
2003 *      indication is returned in an output argument.
2004 *
2005 * Side effects:
2006 *      Reads input from the actual channel.
2007 *
2008 *----------------------------------------------------------------------
2009 */
2010
2011static int
2012PipeInputProc(instanceData, buf, bufSize, errorCode)
2013    ClientData instanceData;            /* Pipe state. */
2014    char *buf;                          /* Where to store data read. */
2015    int bufSize;                        /* How much space is available
2016                                         * in the buffer? */
2017    int *errorCode;                     /* Where to store error code. */
2018{
2019    PipeInfo *infoPtr = (PipeInfo *) instanceData;
2020    WinFile *filePtr = (WinFile*) infoPtr->readFile;
2021    DWORD count;
2022    DWORD bytesRead;
2023
2024    *errorCode = 0;
2025    if (filePtr->type == WIN32S_PIPE) {
2026        if (((WinPipe *)filePtr)->otherPtr != NULL) {
2027            panic("PipeInputProc: child process isn't finished writing");
2028        }
2029        if (filePtr->handle == INVALID_HANDLE_VALUE) {
2030            filePtr->handle = CreateFile(((WinPipe *)filePtr)->fileName,
2031                    GENERIC_READ, 0, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL,
2032                    NULL);
2033        }
2034        if (filePtr->handle == INVALID_HANDLE_VALUE) {
2035            goto error;
2036        }
2037    } else {
2038        /*
2039         * Pipes will block until the requested number of bytes has been
2040         * read.  To avoid blocking unnecessarily, we look ahead and only
2041         * read as much as is available.
2042         */
2043
2044        if (PeekNamedPipe(filePtr->handle, (LPVOID) NULL, (DWORD) 0,
2045                (LPDWORD) NULL, &count, (LPDWORD) NULL) == TRUE) {
2046            if ((count != 0) && ((DWORD) bufSize > count)) {
2047                bufSize = (int) count;
2048
2049                /*
2050                 * This code is commented out because on Win95 we don't get
2051                 * notifier of eof on a pipe unless we try to read it.
2052                 * The correct solution is to move to threads.
2053                 */
2054
2055/*          } else if ((count == 0) && (infoPtr->flags & PIPE_ASYNC)) { */
2056/*              errno = *errorCode = EAGAIN; */
2057/*              return -1; */
2058            } else if ((count == 0) && !(infoPtr->flags & PIPE_ASYNC)) {
2059                bufSize = 1;
2060            }
2061        } else {
2062            goto error;
2063        }
2064    }
2065
2066    /*
2067     * Note that we will block on reads from a console buffer until a
2068     * full line has been entered.  The only way I know of to get
2069     * around this is to write a console driver.  We should probably
2070     * do this at some point, but for now, we just block.
2071     */
2072
2073    if (ReadFile(filePtr->handle, (LPVOID) buf, (DWORD) bufSize, &bytesRead,
2074            (LPOVERLAPPED) NULL) == FALSE) {
2075        goto error;
2076    }
2077   
2078    return bytesRead;
2079
2080    error:
2081    TclWinConvertError(GetLastError());
2082    if (errno == EPIPE) {
2083        return 0;
2084    }
2085    *errorCode = errno;
2086    return -1;
2087}
2088
2089/*
2090 *----------------------------------------------------------------------
2091 *
2092 * PipeOutputProc --
2093 *
2094 *      Writes the given output on the IO channel. Returns count of how
2095 *      many characters were actually written, and an error indication.
2096 *
2097 * Results:
2098 *      A count of how many characters were written is returned and an
2099 *      error indication is returned in an output argument.
2100 *
2101 * Side effects:
2102 *      Writes output on the actual channel.
2103 *
2104 *----------------------------------------------------------------------
2105 */
2106
2107static int
2108PipeOutputProc(instanceData, buf, toWrite, errorCode)
2109    ClientData instanceData;            /* Pipe state. */
2110    char *buf;                          /* The data buffer. */
2111    int toWrite;                        /* How many bytes to write? */
2112    int *errorCode;                     /* Where to store error code. */
2113{
2114    PipeInfo *infoPtr = (PipeInfo *) instanceData;
2115    WinFile *filePtr = (WinFile*) infoPtr->writeFile;
2116    DWORD bytesWritten;
2117   
2118    *errorCode = 0;
2119    if (WriteFile(filePtr->handle, (LPVOID) buf, (DWORD) toWrite,
2120            &bytesWritten, (LPOVERLAPPED) NULL) == FALSE) {
2121        TclWinConvertError(GetLastError());
2122        if (errno == EPIPE) {
2123            return 0;
2124        }
2125        *errorCode = errno;
2126        return -1;
2127    }
2128    return bytesWritten;
2129}
2130
2131/*
2132 *----------------------------------------------------------------------
2133 *
2134 * PipeEventProc --
2135 *
2136 *      This function is invoked by Tcl_ServiceEvent when a file event
2137 *      reaches the front of the event queue.  This procedure invokes
2138 *      Tcl_NotifyChannel on the pipe.
2139 *
2140 * Results:
2141 *      Returns 1 if the event was handled, meaning it should be removed
2142 *      from the queue.  Returns 0 if the event was not handled, meaning
2143 *      it should stay on the queue.  The only time the event isn't
2144 *      handled is if the TCL_FILE_EVENTS flag bit isn't set.
2145 *
2146 * Side effects:
2147 *      Whatever the notifier callback does.
2148 *
2149 *----------------------------------------------------------------------
2150 */
2151
2152static int
2153PipeEventProc(evPtr, flags)
2154    Tcl_Event *evPtr;           /* Event to service. */
2155    int flags;                  /* Flags that indicate what events to
2156                                 * handle, such as TCL_FILE_EVENTS. */
2157{
2158    PipeEvent *pipeEvPtr = (PipeEvent *)evPtr;
2159    PipeInfo *infoPtr;
2160    WinFile *filePtr;
2161    int mask;
2162/*    DWORD count;*/
2163
2164    if (!(flags & TCL_FILE_EVENTS)) {
2165        return 0;
2166    }
2167
2168    /*
2169     * Search through the list of watched pipes for the one whose handle
2170     * matches the event.  We do this rather than simply dereferencing
2171     * the handle in the event so that pipes can be deleted while the
2172     * event is in the queue.
2173     */
2174
2175    for (infoPtr = firstPipePtr; infoPtr != NULL; infoPtr = infoPtr->nextPtr) {
2176        if (pipeEvPtr->infoPtr == infoPtr) {
2177            infoPtr->flags &= ~(PIPE_PENDING);
2178            break;
2179        }
2180    }
2181
2182    /*
2183     * Remove stale events.
2184     */
2185
2186    if (!infoPtr) {
2187        return 1;
2188    }
2189
2190    /*
2191     * If we aren't on Win32s, check to see if the pipe is readable.  Note
2192     * that we can't tell if a pipe is writable, so we always report it
2193     * as being writable.
2194     */
2195
2196    filePtr = (WinFile*) ((PipeInfo*)infoPtr)->readFile;
2197    if (filePtr->type != WIN32S_PIPE) {
2198
2199        /*
2200         * On windows 95, PeekNamedPipe returns 0 on eof so we can't
2201         * distinguish underflow from eof.  The correct solution is to
2202         * switch to the threaded implementation.
2203         */
2204        mask = TCL_WRITABLE|TCL_READABLE;
2205/*      if (PeekNamedPipe(filePtr->handle, (LPVOID) NULL, (DWORD) 0, */
2206/*              (LPDWORD) NULL, &count, (LPDWORD) NULL) == TRUE) { */
2207/*          if (count != 0) { */
2208/*              mask |= TCL_READABLE; */
2209/*          } */
2210/*      } else { */
2211
2212            /*
2213             * If the pipe has been closed by the other side, then
2214             * mark the pipe as readable, but not writable.
2215             */
2216
2217/*          if (GetLastError() == ERROR_BROKEN_PIPE) { */
2218/*              mask = TCL_READABLE; */
2219/*          } */
2220/*      } */
2221    } else {
2222        mask = TCL_READABLE | TCL_WRITABLE;
2223    }
2224
2225    /*
2226     * Inform the channel of the events.
2227     */
2228
2229    Tcl_NotifyChannel(infoPtr->channel, infoPtr->watchMask & mask);
2230    return 1;
2231}
2232
2233/*
2234 *----------------------------------------------------------------------
2235 *
2236 * PipeWatchProc --
2237 *
2238 *      Called by the notifier to set up to watch for events on this
2239 *      channel.
2240 *
2241 * Results:
2242 *      None.
2243 *
2244 * Side effects:
2245 *      None.
2246 *
2247 *----------------------------------------------------------------------
2248 */
2249
2250static void
2251PipeWatchProc(instanceData, mask)
2252    ClientData instanceData;            /* Pipe state. */
2253    int mask;                           /* What events to watch for; OR-ed
2254                                         * combination of TCL_READABLE,
2255                                         * TCL_WRITABLE and TCL_EXCEPTION. */
2256{
2257    PipeInfo **nextPtrPtr, *ptr;
2258    PipeInfo *infoPtr = (PipeInfo *) instanceData;
2259    int oldMask = infoPtr->watchMask;
2260
2261    /*
2262     * For now, we just send a message to ourselves so we can poll the
2263     * channel for readable events.
2264     */
2265
2266    infoPtr->watchMask = mask & infoPtr->validMask;
2267    if (infoPtr->watchMask) {
2268        Tcl_Time blockTime = { 0, 0 };
2269        if (!oldMask) {
2270            infoPtr->nextPtr = firstPipePtr;
2271            firstPipePtr = infoPtr;
2272        }
2273        Tcl_SetMaxBlockTime(&blockTime);
2274    } else {
2275        if (oldMask) {
2276            /*
2277             * Remove the pipe from the list of watched pipes.
2278             */
2279
2280            for (nextPtrPtr = &firstPipePtr, ptr = *nextPtrPtr;
2281                 ptr != NULL;
2282                 nextPtrPtr = &ptr->nextPtr, ptr = *nextPtrPtr) {
2283                if (infoPtr == ptr) {
2284                    *nextPtrPtr = ptr->nextPtr;
2285                    break;
2286                }
2287            }
2288        }
2289    }
2290}
2291
2292/*
2293 *----------------------------------------------------------------------
2294 *
2295 * PipeGetHandleProc --
2296 *
2297 *      Called from Tcl_GetChannelHandle to retrieve OS handles from
2298 *      inside a command pipeline based channel.
2299 *
2300 * Results:
2301 *      Returns TCL_OK with the fd in handlePtr, or TCL_ERROR if
2302 *      there is no handle for the specified direction.
2303 *
2304 * Side effects:
2305 *      None.
2306 *
2307 *----------------------------------------------------------------------
2308 */
2309
2310static int
2311PipeGetHandleProc(instanceData, direction, handlePtr)
2312    ClientData instanceData;    /* The pipe state. */
2313    int direction;              /* TCL_READABLE or TCL_WRITABLE */
2314    ClientData *handlePtr;      /* Where to store the handle.  */
2315{
2316    PipeInfo *infoPtr = (PipeInfo *) instanceData;
2317    WinFile *filePtr;
2318
2319    if (direction == TCL_READABLE && infoPtr->readFile) {
2320        filePtr = (WinFile*) infoPtr->readFile;
2321        if (filePtr->type == WIN32S_PIPE) {
2322            if (filePtr->handle == INVALID_HANDLE_VALUE) {
2323                filePtr->handle = CreateFile(((WinPipe *)filePtr)->fileName,
2324                        GENERIC_READ, 0, NULL, OPEN_ALWAYS,
2325                        FILE_ATTRIBUTE_NORMAL, NULL);
2326            }
2327            if (filePtr->handle == INVALID_HANDLE_VALUE) {
2328                return TCL_ERROR;
2329            }
2330        }
2331        *handlePtr = (ClientData) filePtr->handle;
2332        return TCL_OK;
2333    }
2334    if (direction == TCL_WRITABLE && infoPtr->writeFile) {
2335        filePtr = (WinFile*) infoPtr->writeFile;
2336        *handlePtr = (ClientData) filePtr->handle;
2337        return TCL_OK;
2338    }
2339    return TCL_ERROR;
2340}
2341
2342/*
2343 *----------------------------------------------------------------------
2344 *
2345 * Tcl_WaitPid --
2346 *
2347 *      Emulates the waitpid system call.
2348 *
2349 * Results:
2350 *      Returns 0 if the process is still alive, -1 on an error, or
2351 *      the pid on a clean close. 
2352 *
2353 * Side effects:
2354 *      Unless WNOHANG is set and the wait times out, the process
2355 *      information record will be deleted and the process handle
2356 *      will be closed.
2357 *
2358 *----------------------------------------------------------------------
2359 */
2360
2361Tcl_Pid
2362Tcl_WaitPid(pid, statPtr, options)
2363    Tcl_Pid pid;
2364    int *statPtr;
2365    int options;
2366{
2367    ProcInfo *infoPtr, **prevPtrPtr;
2368    int flags;
2369    Tcl_Pid result;
2370    DWORD ret;
2371
2372    if (!initialized) {
2373        PipeInit();
2374    }
2375
2376    /*
2377     * If no pid is specified, do nothing.
2378     */
2379   
2380    if (pid == 0) {
2381        *statPtr = 0;
2382        return 0;
2383    }
2384
2385    /*
2386     * Find the process on the process list.
2387     */
2388
2389    prevPtrPtr = &procList;
2390    for (infoPtr = procList; infoPtr != NULL;
2391            prevPtrPtr = &infoPtr->nextPtr, infoPtr = infoPtr->nextPtr) {
2392         if (infoPtr->hProcess == (HANDLE) pid) {
2393            break;
2394        }
2395    }
2396
2397    /*
2398     * If the pid is not one of the processes we know about (we started it)
2399     * then do nothing.
2400     */
2401   
2402    if (infoPtr == NULL) {
2403        *statPtr = 0;
2404        return 0;
2405    }
2406
2407    /*
2408     * Officially "wait" for it to finish. We either poll (WNOHANG) or
2409     * wait for an infinite amount of time.
2410     */
2411   
2412    if (options & WNOHANG) {
2413        flags = 0;
2414    } else {
2415        flags = INFINITE;
2416    }
2417    ret = WaitForSingleObject(infoPtr->hProcess, flags);
2418    if (ret == WAIT_TIMEOUT) {
2419        *statPtr = 0;
2420        if (options & WNOHANG) {
2421            return 0;
2422        } else {
2423            result = 0;
2424        }
2425    } else if (ret != WAIT_FAILED) {
2426        GetExitCodeProcess(infoPtr->hProcess, (DWORD*)statPtr);
2427        *statPtr = ((*statPtr << 8) & 0xff00);
2428        result = pid;
2429    } else {
2430        errno = ECHILD;
2431        *statPtr = ECHILD;
2432        result = (Tcl_Pid) -1;
2433    }
2434
2435    /*
2436     * Remove the process from the process list and close the process handle.
2437     */
2438
2439    CloseHandle(infoPtr->hProcess);
2440    *prevPtrPtr = infoPtr->nextPtr;
2441    ckfree((char*)infoPtr);
2442
2443    return result;
2444}
2445
2446/*
2447 *----------------------------------------------------------------------
2448 *
2449 * Tcl_PidObjCmd --
2450 *
2451 *      This procedure is invoked to process the "pid" Tcl command.
2452 *      See the user documentation for details on what it does.
2453 *
2454 * Results:
2455 *      A standard Tcl result.
2456 *
2457 * Side effects:
2458 *      See the user documentation.
2459 *
2460 *----------------------------------------------------------------------
2461 */
2462
2463        /* ARGSUSED */
2464int
2465Tcl_PidObjCmd(dummy, interp, objc, objv)
2466    ClientData dummy;           /* Not used. */
2467    Tcl_Interp *interp;         /* Current interpreter. */
2468    int objc;                   /* Number of arguments. */
2469    Tcl_Obj *CONST *objv;       /* Argument strings. */
2470{
2471    Tcl_Channel chan;
2472    Tcl_ChannelType *chanTypePtr;
2473    PipeInfo *pipePtr;
2474    int i;
2475    Tcl_Obj *resultPtr;
2476    char buf[20];
2477
2478    if (objc > 2) {
2479        Tcl_WrongNumArgs(interp, 1, objv, "?channelId?");
2480        return TCL_ERROR;
2481    }
2482    if (objc == 1) {
2483        resultPtr = Tcl_GetObjResult(interp);
2484        sprintf(buf, "%lu", (unsigned long) getpid());
2485        Tcl_SetStringObj(resultPtr, buf, -1);
2486    } else {
2487        chan = Tcl_GetChannel(interp, Tcl_GetStringFromObj(objv[1], NULL),
2488                NULL);
2489        if (chan == (Tcl_Channel) NULL) {
2490            return TCL_ERROR;
2491        }
2492        chanTypePtr = Tcl_GetChannelType(chan);
2493        if (chanTypePtr != &pipeChannelType) {
2494            return TCL_OK;
2495        }
2496
2497        pipePtr = (PipeInfo *) Tcl_GetChannelInstanceData(chan);
2498        resultPtr = Tcl_GetObjResult(interp);
2499        for (i = 0; i < pipePtr->numPids; i++) {
2500            sprintf(buf, "%lu", TclpGetPid(pipePtr->pidPtr[i]));
2501            Tcl_ListObjAppendElement(/*interp*/ NULL, resultPtr,
2502                    Tcl_NewStringObj(buf, -1));
2503        }
2504    }
2505    return TCL_OK;
2506}
Note: See TracBrowser for help on using the browser.