Beispiel #1
0
static int
PipeClose2Proc(
    ClientData instanceData,	/* The pipe to close. */
    Tcl_Interp *interp,		/* For error reporting. */
    int flags)			/* Flags that indicate which side to close. */
{
    PipeState *pipePtr = instanceData;
    Tcl_Channel errChan;
    int errorCode, result;

    errorCode = 0;
    result = 0;

    if (((!flags) || (flags & TCL_CLOSE_READ)) && (pipePtr->inFile != NULL)) {
        if (TclpCloseFile(pipePtr->inFile) < 0) {
            errorCode = errno;
        } else {
            pipePtr->inFile = NULL;
        }
    }
    if (((!flags) || (flags & TCL_CLOSE_WRITE)) && (pipePtr->outFile != NULL)
            && (errorCode == 0)) {
        if (TclpCloseFile(pipePtr->outFile) < 0) {
            errorCode = errno;
        } else {
            pipePtr->outFile = NULL;
        }
    }

    /*
     * If half-closing, stop here.
     */

    if (flags) {
        return errorCode;
    }

    if (pipePtr->isNonBlocking || TclInExit()) {
        /*
         * If the channel is non-blocking or Tcl is being cleaned up, just
         * detach the children PIDs, reap them (important if we are in a
         * dynamic load module), and discard the errorFile.
         */

        Tcl_DetachPids(pipePtr->numPids, pipePtr->pidPtr);
        Tcl_ReapDetachedProcs();

        if (pipePtr->errorFile) {
            TclpCloseFile(pipePtr->errorFile);
        }
    } else {
        /*
         * Wrap the error file into a channel and give it to the cleanup
         * routine.
         */

        if (pipePtr->errorFile) {
            errChan = Tcl_MakeFileChannel(
                          INT2PTR(GetFd(pipePtr->errorFile)),
                          TCL_READABLE);
        } else {
            errChan = NULL;
        }
        result = TclCleanupChildren(interp, pipePtr->numPids, pipePtr->pidPtr,
                                    errChan);
    }

    if (pipePtr->numPids != 0) {
        ckfree(pipePtr->pidPtr);
    }
    ckfree(pipePtr);
    if (errorCode == 0) {
        return result;
    }
    return errorCode;
}
Beispiel #2
0
	/* ARGSUSED */
static int
PipeCloseProc(
    ClientData instanceData,	/* The pipe to close. */
    Tcl_Interp *interp)		/* For error reporting. */
{
    PipeState *pipePtr;
    Tcl_Channel errChan;
    int errorCode, result;

    errorCode = 0;
    result = 0;
    pipePtr = (PipeState *) instanceData;
    if (pipePtr->inFile) {
	if (TclpCloseFile(pipePtr->inFile) < 0) {
	    errorCode = errno;
	}
    }
    if (pipePtr->outFile) {
	if ((TclpCloseFile(pipePtr->outFile) < 0) && (errorCode == 0)) {
	    errorCode = errno;
	}
    }

    if (pipePtr->isNonBlocking || TclInExit()) {
	/*
	 * If the channel is non-blocking or Tcl is being cleaned up, just
	 * detach the children PIDs, reap them (important if we are in a
	 * dynamic load module), and discard the errorFile.
	 */

	Tcl_DetachPids(pipePtr->numPids, pipePtr->pidPtr);
	Tcl_ReapDetachedProcs();

	if (pipePtr->errorFile) {
	    TclpCloseFile(pipePtr->errorFile);
	}
    } else {
	/*
	 * Wrap the error file into a channel and give it to the cleanup
	 * routine.
	 */

	if (pipePtr->errorFile) {
	    errChan = Tcl_MakeFileChannel(
		(ClientData) INT2PTR(GetFd(pipePtr->errorFile)), TCL_READABLE);
	} else {
	    errChan = NULL;
	}
	result = TclCleanupChildren(interp, pipePtr->numPids, pipePtr->pidPtr,
		errChan);
    }

    if (pipePtr->numPids != 0) {
	ckfree((char *) pipePtr->pidPtr);
    }
    ckfree((char *) pipePtr);
    if (errorCode == 0) {
	return result;
    }
    return errorCode;
}
Beispiel #3
0
/*
 *---------------------------------------------------------------------------
 *
 * Blt_CreatePipeline --
 *
 *	Given an objc/objv array, instantiate a pipeline of processes as
 *	described by the objv.
 *
 * Results:
 *	The return value is a count of the number of new processes created, or
 *	-1 if an error occurred while creating the pipeline.  *pidArrayPtr is
 *	filled in with the address of a dynamically allocated array giving the
 *	ids of all of the processes.
 *
 *	It is up to the caller to free this array when it isn't needed
 *	anymore.
 *
 *	If stdinPipePtr isn't NULL, then *stdinPipePtr is filled with the file
 *	id for the input pipe for the pipeline (if any): the caller must
 *	eventually close this file.
 *
 *	If stdoutPipePtr isn't NULL, then *stdoutPipePtr is filled with the
 *	file id for the output pipe from the pipeline: the caller must close
 *	this file.
 *
 *	If stderrPipePtr isn't NULL, then *stderrPipePtr is filled with a file
 *	id that may be used to read error output after the pipeline completes.
 *
 * Side effects:
 *	Processes and pipes are created.
 *
 *---------------------------------------------------------------------------
 */
int
Blt_CreatePipeline(
    Tcl_Interp *interp,		/* Interpreter to use for error reporting. */
    int objc,			/* Number of entries in objv. */
    Tcl_Obj *const *objv,	/* Array of strings describing commands in
				 * pipeline plus I/O redirection with <, <<,
				 * >, etc.  Objv[objc] must be NULL. */
    ProcessId **pidArrayPtr,	/* (out) Word at *pidArrayPtr gets filled in
				 * with address of array of pids for processes
				 * in pipeline (first pid is first process in
				 * pipeline). */
    int *stdinPipePtr,		/* (out) If non-NULL, input to the pipeline
				 * comes from a pipe (unless overridden by
				 * redirection in the command).  The file id
				 * with which to write to this pipe is stored
				 * at *stdinPipePtr.  NULL means command
				 * specified its own input source. */
    int *stdoutPipePtr,		/* (out) If non-NULL, output to the pipeline
				 * goes to a pipe, unless overriden by
				 * redirection in the command.  The file id
				 * with which to read frome this pipe is
				 * stored at *stdoutPipePtr.  NULL means
				 * command specified its own output sink. */
    int *stderrPipePtr)		/* (out) If non-NULL, all stderr output from
				 * the pipeline will go to a temporary file
				 * created here, and a descriptor to read the
				 * file will be left at *stderrPipePtr.  The
				 * file will be removed already, so closing
				 * this descriptor will be the end of the
				 * file.  If this is NULL, then all stderr
				 * output goes to our stderr.  If the pipeline
				 * specifies redirection then the file will
				 * still be created but it will never get any
				 * data. */
{
    int *pids = NULL;		/* Points to malloc-ed array holding all the
				 * pids of child processes. */
    int nPids;			/* Actual number of processes that exist at
				 * *pids right now. */
    int cmdCount;		/* Count of number of distinct commands found
				 * in objc/objv. */
    char *inputLiteral = NULL;	/* If non-null, then this points to a string
				 * containing input data (specified via <<) to
				 * be piped to the first process in the
				 * pipeline. */
    char *p;
    int skip, lastBar, lastArg, i, j, atOK, flags, errorToOutput;
    Tcl_DString execBuffer;
    int pipeIn;
    int isOpen[3];
    int curFd[3];		/* If non-zero, then fd should be closed
    				 * when cleaning up. */
    int fd[3];
    
    char **argv;

    fd[0] = fd[1] = fd[2] = -1;
    isOpen[0] = isOpen[1] = isOpen[2] = FALSE;
    if (stdinPipePtr != NULL) {
	*stdinPipePtr = -1;
    }
    if (stdoutPipePtr != NULL) {
	*stdoutPipePtr = -1;
    }
    if (stderrPipePtr != NULL) {
	*stderrPipePtr = -1;
    }
    Tcl_DStringInit(&execBuffer);

    pipeIn = curFd[0] = curFd[1] = -1;
    nPids = 0;

    /*
     * First, scan through all the arguments to figure out the structure of
     * the pipeline.  Process all of the input and output redirection
     * arguments and remove them from the argument list in the pipeline.
     * Count the number of distinct processes (it's the number of "|"
     * arguments plus one) but don't remove the "|" arguments because they'll
     * be used in the second pass to seperate the individual child processes.
     *
     * Cannot start the child processes in this pass because the redirection
     * symbols may appear anywhere in the command line -- e.g., the '<' that
     * specifies the input to the entire pipe may appear at the very end of
     * the argument list.
     */

    /* Convert all the Tcl_Objs to strings. */
    argv = Blt_AssertMalloc((objc + 1) *  sizeof(char *));
    for (i = 0; i < objc; i++) {
	argv[i] = Tcl_GetString(objv[i]);
    }
    argv[i] = NULL;

    lastBar = -1;
    cmdCount = 1;
    for (i = 0; i < objc; i++) {
	skip = 0;
	p = argv[i];
	switch (*p++) {
	case '\\':
	    p++;
	    continue;

	case '|':
	    if (*p == '&') {
		p++;
	    }
	    if (*p == '\0') {
		if ((i == (lastBar + 1)) || (i == (objc - 1))) {
		    Tcl_AppendResult(interp, 
			"illegal use of | or |& in command", (char *)NULL);
		    goto error;
		}
	    }
	    lastBar = i;
	    cmdCount++;
	    break;

	case '<':
	    if (isOpen[0] != 0) {
		isOpen[0] = FALSE;
		CloseFile(fd[0]);
	    }
	    if (*p == '<') {
		fd[0] = -1;
		inputLiteral = p + 1;
		skip = 1;
		if (*inputLiteral == '\0') {
		    inputLiteral = argv[i + 1];
		    if (inputLiteral == NULL) {
			Tcl_AppendResult(interp, "can't specify \"", argv[i], 
				"\" as last word in command", (char *)NULL);
			goto error;
		    }
		    skip = 2;
		}
	    } else {
		inputLiteral = NULL;
		fd[0] = FileForRedirect(interp, p, argv[i], TRUE, argv[i + 1],
			O_RDONLY, &skip, &isOpen[0]);
		if (fd[0] < 0) {
		    goto error;
		}
	    }
	    break;

	case '>':
	    atOK = TRUE;
	    flags = O_WRONLY | O_CREAT | O_TRUNC;
	    errorToOutput = FALSE;
	    if (*p == '>') {
		p++;
		atOK = FALSE;
		flags = O_WRONLY | O_CREAT;
	    }
	    if (*p == '&') {
		if (isOpen[2] != 0) {
		    isOpen[2] = FALSE;
		    CloseFile(fd[2]);
		}
		errorToOutput = TRUE;
		p++;
	    }
	    if (isOpen[1] != 0) {
		isOpen[1] = FALSE;
		CloseFile(fd[1]);
	    }
	    fd[1] = FileForRedirect(interp, p, argv[i], atOK, argv[i + 1], 
		flags, &skip, &isOpen[1]);
	    if (fd[1] < 0) {
		goto error;
	    }
	    if (errorToOutput) {
		isOpen[2] = FALSE;
		fd[2] = fd[1];
	    }
	    break;

	case '2':
	    if (*p != '>') {
		break;
	    }
	    p++;
	    atOK = TRUE;
	    flags = O_WRONLY | O_CREAT | O_TRUNC;
	    if (*p == '>') {
		p++;
		atOK = FALSE;
		flags = O_WRONLY | O_CREAT;
	    }
	    if (isOpen[2] != 0) {
		isOpen[2] = FALSE;
		CloseFile(fd[2]);
	    }
	    fd[2] = FileForRedirect(interp, p, argv[i], atOK, argv[i + 1], 
		flags, &skip, &isOpen[2]);
	    if (fd[2] < 0) {
		goto error;
	    }
	    break;
	}

	if (skip != 0) {
	    for (j = i + skip; j < objc; j++) {
		argv[j - skip] = argv[j];
	    }
	    objc -= skip;
	    i -= 1;
	}
    }

    if (fd[0] == -1) {
	if (inputLiteral != NULL) {
	    /*
	     * The input for the first process is immediate data coming from
	     * Tcl.  Create a temporary file for it and put the data into the
	     * file.
	     */
	    fd[0] = CreateTempFile(inputLiteral);
	    if (fd[0] < 0) {
		Tcl_AppendResult(interp,
		    "can't create input file for command: ",
		    Tcl_PosixError(interp), (char *)NULL);
		goto error;
	    }
	    isOpen[0] = TRUE;
	} else if (stdinPipePtr != NULL) {
	    /*
	     * The input for the first process in the pipeline is to come from
	     * a pipe that can be written from by the caller.
	     */
	    if (CreatePipe(interp, &fd[0], stdinPipePtr) != TCL_OK) {
		goto error;
	    }
	    isOpen[0] = TRUE;
	} else {
	    /*
	     * The input for the first process comes from stdin.
	     */
	    fd[0] = 0;
	}
    }
    if (fd[1] == -1) {
	if (stdoutPipePtr != NULL) {
	    /*
	     * Output from the last process in the pipeline is to go to a pipe
	     * that can be read by the caller.
	     */
	    if (CreatePipe(interp, stdoutPipePtr, &fd[1]) != TCL_OK) {
		goto error;
	    }
	    isOpen[1] = TRUE;
	} else {
	    /*
	     * The output for the last process goes to stdout.
	     */
	    fd[1] = 1;
	}
    }
    if (fd[2] == -1) {
	if (stderrPipePtr != NULL) {
	    /*
	     * Stderr from the last process in the pipeline is to go to a pipe
	     * that can be read by the caller.
	     */
	    if (CreatePipe(interp, stderrPipePtr, &fd[2]) != TCL_OK) {
		goto error;
	    }
	    isOpen[2] = TRUE;
	} else {
	    /*
	     * Errors from the pipeline go to stderr.
	     */
	    fd[2] = 2;
	}
    }
    /*
     * Scan through the objc array, creating a process for each group of
     * arguments between the "|" characters.
     */

    Tcl_ReapDetachedProcs();
    pids = Blt_AssertMalloc(cmdCount * sizeof(int));
    curFd[0] = fd[0];

    lastArg = 0;		/* Suppress compiler warning */
    for (i = 0; i < objc; i = lastArg + 1) {
	int joinThisError;
	int pid;

	/*
	 * Convert the program name into native form.
	 */

	argv[i] = Tcl_TranslateFileName(interp, argv[i], &execBuffer);
	if (argv[i] == NULL) {
	    goto error;
	}
	/*
	 * Find the end of the curent segment of the pipeline.
	 */
	joinThisError = 0;
	for (lastArg = i + 1; lastArg < objc; lastArg++) {
	    if (argv[lastArg][0] == '|') {
		if (argv[lastArg][1] == '\0') {
		    break;
		}
		if ((argv[lastArg][1] == '&') && (argv[lastArg][2] == '\0')) {
		    joinThisError = 1;
		    break;
		}
	    }
	}
	argv[lastArg] = NULL;

	/*
	 * If this is the last segment, use the specified fd[1].  Otherwise
	 * create an intermediate pipe.  pipeIn will become the curInFile for
	 * the next segment of the pipe.
	 */
	if (lastArg == objc) {
	    curFd[1] = fd[1];
	} else {
	    if (CreatePipe(interp, &pipeIn, &curFd[1]) != TCL_OK) {
		goto error;
	    }
	}

	if (joinThisError != 0) {
	    curFd[2] = curFd[1];
	} else {
	    curFd[2] = fd[2];
	}

	if (CreateProcess(interp, lastArg - i, argv + i, curFd[0], curFd[1], 
		curFd[2], &pid) != TCL_OK) {
	    goto error;
	}
	Tcl_DStringFree(&execBuffer);

	pids[nPids] = pid;
	nPids++;

	/*
	 * Close off our copies of file descriptors that were set up for this
	 * child, then set up the input for the next child.
	 */
	if ((curFd[0] >= 0) && (curFd[0] != fd[0])) {
	    CloseFile(curFd[0]);
	}
	curFd[0] = pipeIn;
	pipeIn = -1;

	if ((curFd[1] >= 0) && (curFd[1] != fd[1])) {
	    CloseFile(curFd[1]);
	}
	curFd[1] = -1;
    }

    *pidArrayPtr = pids;

    /*
     * All done.  Cleanup open files lying around and then return.
     */

  cleanup:
    Tcl_DStringFree(&execBuffer);

    for (i = 0; i < 3; i++) {
	if (isOpen[i]) {
	    CloseFile(fd[i]);
	}
    }
    if (argv != NULL) {
	Blt_Free(argv);
    }
    return nPids;

    /*
     * An error occured.  There could have been extra files open, such as
     * pipes between children.  Clean them all up.  Detach any child processes
     * that have been created.
     */

  error:
    if (pipeIn >= 0) {
	CloseFile(pipeIn);
    }
    if ((curFd[2] >= 0) && (curFd[2] != fd[2])) {
	CloseFile(curFd[2]);
    }
    if ((curFd[1] >= 0) && (curFd[1] != fd[1])) {
	CloseFile(curFd[1]);
    }
    if ((curFd[0] >= 0) && (curFd[0] != fd[0])) {
	CloseFile(curFd[0]);
    }
    if ((stdinPipePtr != NULL) && (*stdinPipePtr >= 0)) {
	CloseFile(*stdinPipePtr);
	*stdinPipePtr = -1;
    }
    if ((stdoutPipePtr != NULL) && (*stdoutPipePtr >= 0)) {
	CloseFile(*stdoutPipePtr);
	*stdoutPipePtr = -1;
    }
    if ((stderrPipePtr != NULL) && (*stderrPipePtr >= 0)) {
	CloseFile(*stderrPipePtr);
	*stderrPipePtr = -1;
    }
    if (pids != NULL) {
	for (i = 0; i < nPids; i++) {
	    if (pids[i] != -1) {
		Tcl_DetachPids(1, (Tcl_Pid *)(pids + i));
	    }
	}
	Blt_Free(pids);
    }
    nPids = -1;
    goto cleanup;
}