tclwinpipe.c

来自「tcl是工具命令语言」· C语言 代码 · 共 2,329 行 · 第 1/5 页

C
2,329
字号
    static char extensions[][5] = {"", ".com", ".exe", ".bat"};    /* Look for the program as an external program.  First try the name     * as it is, then try adding .com, .exe, and .bat, in that order, to     * the name, looking for an executable.     *     * Using the raw SearchPath() procedure doesn't do quite what is      * necessary.  If the name of the executable already contains a '.'      * character, it will not try appending the specified extension when     * searching (in other words, SearchPath will not find the program      * "a.b.exe" if the arguments specified "a.b" and ".exe").        * So, first look for the file as it is named.  Then manually append      * the extensions, looking for a match.       */    applType = APPL_NONE;    Tcl_DStringInit(&nameBuf);    Tcl_DStringAppend(&nameBuf, originalName, -1);    nameLen = Tcl_DStringLength(&nameBuf);    for (i = 0; i < (int) (sizeof(extensions) / sizeof(extensions[0])); i++) {	Tcl_DStringSetLength(&nameBuf, nameLen);	Tcl_DStringAppend(&nameBuf, extensions[i], -1);        nativeName = Tcl_WinUtfToTChar(Tcl_DStringValue(&nameBuf), 		Tcl_DStringLength(&nameBuf), &ds);	found = (*tclWinProcs->searchPathProc)(NULL, nativeName, NULL, 		MAX_PATH, nativeFullPath, &rest);	Tcl_DStringFree(&ds);	if (found == 0) {	    continue;	}	/*	 * Ignore matches on directories or data files, return if identified	 * a known type.	 */	attr = (*tclWinProcs->getFileAttributesProc)((TCHAR *) nativeFullPath);	if ((attr == 0xffffffff) || (attr & FILE_ATTRIBUTE_DIRECTORY)) {	    continue;	}	strcpy(fullName, Tcl_WinTCharToUtf((TCHAR *) nativeFullPath, -1, &ds));	Tcl_DStringFree(&ds);	ext = strrchr(fullName, '.');	if ((ext != NULL) && (stricmp(ext, ".bat") == 0)) {	    applType = APPL_DOS;	    break;	}		hFile = (*tclWinProcs->createFileProc)((TCHAR *) nativeFullPath, 		GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 		FILE_ATTRIBUTE_NORMAL, NULL);	if (hFile == INVALID_HANDLE_VALUE) {	    continue;	}	header.e_magic = 0;	ReadFile(hFile, (void *) &header, sizeof(header), &read, NULL);	if (header.e_magic != IMAGE_DOS_SIGNATURE) {	    /* 	     * Doesn't have the magic number for relocatable executables.  If 	     * filename ends with .com, assume it's a DOS application anyhow.	     * Note that we didn't make this assumption at first, because some	     * supposed .com files are really 32-bit executables with all the	     * magic numbers and everything.  	     */	    CloseHandle(hFile);	    if ((ext != NULL) && (stricmp(ext, ".com") == 0)) {		applType = APPL_DOS;		break;	    }	    continue;	}	if (header.e_lfarlc != sizeof(header)) {	    /* 	     * All Windows 3.X and Win32 and some DOS programs have this value	     * set here.  If it doesn't, assume that since it already had the 	     * other magic number it was a DOS application.	     */	    CloseHandle(hFile);	    applType = APPL_DOS;	    break;	}	/* 	 * The DWORD at header.e_lfanew points to yet another magic number.	 */	buf[0] = '\0';	SetFilePointer(hFile, header.e_lfanew, NULL, FILE_BEGIN);	ReadFile(hFile, (void *) buf, 2, &read, NULL);	CloseHandle(hFile);	if ((buf[0] == 'N') && (buf[1] == 'E')) {	    applType = APPL_WIN3X;	} else if ((buf[0] == 'P') && (buf[1] == 'E')) {	    applType = APPL_WIN32;	} else {	    /*	     * Strictly speaking, there should be a test that there	     * is an 'L' and 'E' at buf[0..1], to identify the type as 	     * DOS, but of course we ran into a DOS executable that 	     * _doesn't_ have the magic number -- specifically, one	     * compiled using the Lahey Fortran90 compiler.	     */	    applType = APPL_DOS;	}	break;    }    Tcl_DStringFree(&nameBuf);    if (applType == APPL_NONE) {	TclWinConvertError(GetLastError());	Tcl_AppendResult(interp, "couldn't execute \"", originalName,		"\": ", Tcl_PosixError(interp), (char *) NULL);	return APPL_NONE;    }    if ((applType == APPL_DOS) || (applType == APPL_WIN3X)) {	/* 	 * Replace long path name of executable with short path name for 	 * 16-bit applications.  Otherwise the application may not be able	 * to correctly parse its own command line to separate off the 	 * application name from the arguments.	 */	(*tclWinProcs->getShortPathNameProc)((TCHAR *) nativeFullPath, 		nativeFullPath, MAX_PATH);	strcpy(fullName, Tcl_WinTCharToUtf((TCHAR *) nativeFullPath, -1, &ds));	Tcl_DStringFree(&ds);    }    return applType;}/*     *---------------------------------------------------------------------- * * BuildCommandLine -- * *	The command line arguments are stored in linePtr separated *	by spaces, in a form that CreateProcess() understands.  Special  *	characters in individual arguments from argv[] must be quoted  *	when being stored in cmdLine. * * Results: *	None. * * Side effects: *	None. * *---------------------------------------------------------------------- */static voidBuildCommandLine(    CONST char *executable,	/* Full path of executable (including 				 * extension).  Replacement for argv[0]. */    int argc,			/* Number of arguments. */    CONST char **argv,		/* Argument strings in UTF. */    Tcl_DString *linePtr)	/* Initialized Tcl_DString that receives the				 * command line (TCHAR). */{    CONST char *arg, *start, *special;    int quote, i;    Tcl_DString ds;    Tcl_DStringInit(&ds);    /*     * Prime the path.     */        Tcl_DStringAppend(&ds, Tcl_DStringValue(linePtr), -1);        for (i = 0; i < argc; i++) {	if (i == 0) {	    arg = executable;	} else {	    arg = argv[i];	    Tcl_DStringAppend(&ds, " ", 1);	}	quote = 0;	if (arg[0] == '\0') {	    quote = 1;	} else {	    for (start = arg; *start != '\0'; start++) {		if (isspace(*start)) { /* INTL: ISO space. */		    quote = 1;		    break;		}	    }	}	if (quote) {	    Tcl_DStringAppend(&ds, "\"", 1);	}	start = arg;	    	for (special = arg; ; ) {	    if ((*special == '\\') && 		    (special[1] == '\\' || special[1] == '"')) {		Tcl_DStringAppend(&ds, start, special - start);		start = special;		while (1) {		    special++;		    if (*special == '"') {			/* 			 * N backslashes followed a quote -> insert 			 * N * 2 + 1 backslashes then a quote.			 */			Tcl_DStringAppend(&ds, start, special - start);			break;		    }		    if (*special != '\\') {			break;		    }		}		Tcl_DStringAppend(&ds, start, special - start);		start = special;	    }	    if (*special == '"') {		Tcl_DStringAppend(&ds, start, special - start);		Tcl_DStringAppend(&ds, "\\\"", 2);		start = special + 1;	    }	    if (*special == '{') {		Tcl_DStringAppend(&ds, start, special - start);		Tcl_DStringAppend(&ds, "\\{", 2);		start = special + 1;	    }	    if (*special == '\0') {		break;	    }	    special++;	}	Tcl_DStringAppend(&ds, start, special - start);	if (quote) {	    Tcl_DStringAppend(&ds, "\"", 1);	}    }    Tcl_DStringFree(linePtr);    Tcl_WinUtfToTChar(Tcl_DStringValue(&ds), Tcl_DStringLength(&ds), linePtr);    Tcl_DStringFree(&ds);}/* *---------------------------------------------------------------------- * * TclpCreateCommandChannel -- * *	This function is called by Tcl_OpenCommandChannel to perform *	the platform specific channel initialization for a command *	channel. * * Results: *	Returns a new channel or NULL on failure. * * Side effects: *	Allocates a new channel. * *---------------------------------------------------------------------- */Tcl_ChannelTclpCreateCommandChannel(    TclFile readFile,		/* If non-null, gives the file for reading. */    TclFile writeFile,		/* If non-null, gives the file for writing. */    TclFile errorFile,		/* If non-null, gives the file where errors				 * can be read. */    int numPids,		/* The number of pids in the pid array. */    Tcl_Pid *pidPtr)		/* An array of process identifiers. */{    char channelName[16 + TCL_INTEGER_SPACE];    int channelId;    DWORD id;    PipeInfo *infoPtr = (PipeInfo *) ckalloc((unsigned) sizeof(PipeInfo));    PipeInit();    infoPtr->watchMask = 0;    infoPtr->flags = 0;    infoPtr->readFlags = 0;    infoPtr->readFile = readFile;    infoPtr->writeFile = writeFile;    infoPtr->errorFile = errorFile;    infoPtr->numPids = numPids;    infoPtr->pidPtr = pidPtr;    infoPtr->writeBuf = 0;    infoPtr->writeBufLen = 0;    infoPtr->writeError = 0;    /*     * Use one of the fds associated with the channel as the     * channel id.     */    if (readFile) {	channelId = (int) ((WinFile*)readFile)->handle;    } else if (writeFile) {	channelId = (int) ((WinFile*)writeFile)->handle;    } else if (errorFile) {	channelId = (int) ((WinFile*)errorFile)->handle;    } else {	channelId = 0;    }    infoPtr->validMask = 0;    infoPtr->threadId = Tcl_GetCurrentThread();    if (readFile != NULL) {	/*	 * Start the background reader thread.	 */	infoPtr->readable = CreateEvent(NULL, TRUE, TRUE, NULL);	infoPtr->startReader = CreateEvent(NULL, FALSE, FALSE, NULL);	infoPtr->stopReader = CreateEvent(NULL, TRUE, FALSE, NULL);	infoPtr->readThread = CreateThread(NULL, 256, PipeReaderThread,		infoPtr, 0, &id);	SetThreadPriority(infoPtr->readThread, THREAD_PRIORITY_HIGHEST);         infoPtr->validMask |= TCL_READABLE;    } else {	infoPtr->readThread = 0;    }    if (writeFile != NULL) {	/*	 * Start the background writer thread.	 */	infoPtr->writable = CreateEvent(NULL, TRUE, TRUE, NULL);	infoPtr->startWriter = CreateEvent(NULL, FALSE, FALSE, NULL);	infoPtr->stopWriter = CreateEvent(NULL, TRUE, FALSE, NULL);	infoPtr->writeThread = CreateThread(NULL, 256, PipeWriterThread,		infoPtr, 0, &id);	SetThreadPriority(infoPtr->readThread, THREAD_PRIORITY_HIGHEST);         infoPtr->validMask |= TCL_WRITABLE;    }    /*     * For backward compatibility with previous versions of Tcl, we     * use "file%d" as the base name for pipes even though it would     * be more natural to use "pipe%d".     * Use the pointer to keep the channel names unique, in case     * channels share handles (stdin/stdout).     */    wsprintfA(channelName, "file%lx", infoPtr);    infoPtr->channel = Tcl_CreateChannel(&pipeChannelType, channelName,            (ClientData) infoPtr, infoPtr->validMask);    /*     * Pipes have AUTO translation mode on Windows and ^Z eof char, which     * means that a ^Z will be appended to them at close. This is needed     * for Windows programs that expect a ^Z at EOF.     */    Tcl_SetChannelOption((Tcl_Interp *) NULL, infoPtr->channel,	    "-translation", "auto");    Tcl_SetChannelOption((Tcl_Interp *) NULL, infoPtr->channel,	    "-eofchar", "\032 {}");    return infoPtr->channel;}/* *---------------------------------------------------------------------- * * TclGetAndDetachPids -- * *	Stores a list of the command PIDs for a command channel in *	the interp's result. * * Results: *	None. * * Side effects: *	Modifies the interp's result. * *---------------------------------------------------------------------- */voidTclGetAndDetachPids(    Tcl_Interp *interp,    Tcl_Channel chan){    PipeInfo *pipePtr;    Tcl_ChannelType *chanTypePtr;    int i;    char buf[TCL_INTEGER_SPACE];    /*     * Punt if the channel is not a command channel.     */    chanTypePtr = Tcl_GetChannelType(chan);    if (chanTypePtr != &pipeChannelType) {        return;    }    pipePtr = (PipeInfo *) Tcl_GetChannelInstanceData(chan);    for (i = 0; i < pipePtr->numPids; i++) {        wsprintfA(buf, "%lu", TclpGetPid(pipePtr->pidPtr[i]));        Tcl_AppendElement(interp, buf);        Tcl_DetachPids(1, &(pipePtr->pidPtr[i]));    }    if (pipePtr->numPids > 0) {        ckfree((char *) pipePtr->pidPtr);        pipePtr->numPids = 0;    }}/* *---------------------------------------------------------------------- * * PipeBlockModeProc -- * *	Set blocking or non-blocking mode on channel. * * Results: *	0 if successful, errno when failed. * * Side effects: *	Sets the device into blocking or non-blocking mode. * *---------------------------------------------------------------------- */static intPipeBlockModeProc(    ClientData instanceData,	/* Instance data for channel. */    int mode)			/* TCL_MODE_BLOCKING or                                 * TCL_MODE_NONBLOCKING. */{    PipeInfo *infoPtr = (PipeInfo *) instanceData;        /*     * Pipes on Windows can not be switched between blocking and nonblocking,     * hence we have to emulate the behavior. This is done in the input     * function by checking against a bit in the state. We set or unset the     * bit here to cause the input function to emulate the correct behavior.     */    if (mode == TCL_MODE_NONBLOCKING) {	infoPtr->flags |= PIPE_ASYNC;    } else {	infoPtr->flags &= ~(PIPE_ASYNC);    }    return 0;}/* *---------------------------------------------------------------------- * * PipeClose2Proc -- * *	Closes a pipe based IO channel. * * Results: *	0 on success, errno otherwise. *

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?