diff options
Diffstat (limited to 'generic/tclPipe.c')
| -rw-r--r-- | generic/tclPipe.c | 805 | 
1 files changed, 439 insertions, 366 deletions
| diff --git a/generic/tclPipe.c b/generic/tclPipe.c index dcfabdf..83fb818 100644 --- a/generic/tclPipe.c +++ b/generic/tclPipe.c @@ -1,62 +1,56 @@ -/*  +/*   * tclPipe.c --   * - *	This file contains the generic portion of the command channel - *	driver as well as various utility routines used in managing - *	subprocesses. + *	This file contains the generic portion of the command channel driver + *	as well as various utility routines used in managing subprocesses.   *   * Copyright (c) 1997 by Sun Microsystems, Inc.   * - * See the file "license.terms" for information on usage and redistribution - * of this file, and for a DISCLAIMER OF ALL WARRANTIES. - * - * RCS: @(#) $Id: tclPipe.c,v 1.5 2002/01/25 20:40:55 dgp Exp $ + * See the file "license.terms" for information on usage and redistribution of + * this file, and for a DISCLAIMER OF ALL WARRANTIES.   */  #include "tclInt.h" -#include "tclPort.h"  /* - * A linked list of the following structures is used to keep track - * of child processes that have been detached but haven't exited - * yet, so we can make sure that they're properly "reaped" (officially - * waited for) and don't lie around as zombies cluttering the - * system. + * A linked list of the following structures is used to keep track of child + * processes that have been detached but haven't exited yet, so we can make + * sure that they're properly "reaped" (officially waited for) and don't lie + * around as zombies cluttering the system.   */  typedef struct Detached { -    Tcl_Pid pid;			/* Id of process that's been detached -					 * but isn't known to have exited. */ -    struct Detached *nextPtr;		/* Next in list of all detached -					 * processes. */ +    Tcl_Pid pid;		/* Id of process that's been detached but +				 * isn't known to have exited. */ +    struct Detached *nextPtr;	/* Next in list of all detached processes. */  } Detached; -static Detached *detList = NULL;	/* List of all detached proceses. */ -TCL_DECLARE_MUTEX(pipeMutex)		/* Guard access to detList. */ +static Detached *detList = NULL;/* List of all detached proceses. */ +TCL_DECLARE_MUTEX(pipeMutex)	/* Guard access to detList. */  /* - * Declarations for local procedures defined in this file: + * Declarations for local functions defined in this file:   */ -static TclFile	FileForRedirect _ANSI_ARGS_((Tcl_Interp *interp, -	            CONST char *spec, int atOk, CONST char *arg,  -		    CONST char *nextArg, int flags, int *skipPtr, -		    int *closePtr, int *releasePtr)); +static TclFile		FileForRedirect(Tcl_Interp *interp, const char *spec, +			    int atOk, const char *arg, const char *nextArg, +			    int flags, int *skipPtr, int *closePtr, +			    int *releasePtr);  /*   *----------------------------------------------------------------------   *   * FileForRedirect --   * - *	This procedure does much of the work of parsing redirection - *	operators.  It handles "@" if specified and allowed, and a file - *	name, and opens the file if necessary. + *	This function does much of the work of parsing redirection operators. + *	It handles "@" if specified and allowed, and a file name, and opens + *	the file if necessary.   *   * Results: - *	The return value is the descriptor number for the file.  If an - *	error occurs then NULL is returned and an error message is left - *	in the interp's result.  Several arguments are side-effected; see - *	the argument list below for details. + *	The return value is the descriptor number for the file. If an error + *	occurs then NULL is returned and an error message is left in the + *	interp's result. Several arguments are side-effected; see the argument + *	list below for details.   *   * Side effects:   *	None. @@ -65,34 +59,33 @@ static TclFile	FileForRedirect _ANSI_ARGS_((Tcl_Interp *interp,   */  static TclFile -FileForRedirect(interp, spec, atOK, arg, nextArg, flags, skipPtr, closePtr, -	releasePtr) -    Tcl_Interp *interp;		/* Intepreter to use for error reporting. */ -    CONST char *spec;			/* Points to character just after -				 * redirection character. */ -    CONST char *arg;		/* Pointer to entire argument containing  -				 * spec:  used for error reporting. */ -    int atOK;			/* Non-zero means that '@' notation can be  +FileForRedirect( +    Tcl_Interp *interp,		/* Intepreter to use for error reporting. */ +    const char *spec,		/* Points to character just after redirection +				 * character. */ +    int atOK,			/* Non-zero means that '@' notation can be  				 * used to specify a channel, zero means that  				 * it isn't. */ -    CONST char *nextArg;	/* Next argument in argc/argv array, if needed  -				 * for file name or channel name.  May be  +    const char *arg,		/* Pointer to entire argument containing spec: +				 * used for error reporting. */ +    const char *nextArg,	/* Next argument in argc/argv array, if needed +				 * for file name or channel name. May be  				 * NULL. */ -    int flags;			/* Flags to use for opening file or to  -				 * specify mode for channel. */ -    int *skipPtr;		/* Filled with 1 if redirection target was -				 * in spec, 2 if it was in nextArg. */ -    int *closePtr;		/* Filled with one if the caller should  -				 * close the file when done with it, zero +    int flags,			/* Flags to use for opening file or to specify +				 * mode for channel. */ +    int *skipPtr,		/* Filled with 1 if redirection target was in +				 * spec, 2 if it was in nextArg. */ +    int *closePtr,		/* Filled with one if the caller should close +				 * the file when done with it, zero  				 * otherwise. */ -    int *releasePtr; +    int *releasePtr)  {      int writing = (flags & O_WRONLY);      Tcl_Channel chan;      TclFile file;      *skipPtr = 1; -    if ((atOK != 0)  && (*spec == '@')) { +    if ((atOK != 0) && (*spec == '@')) {  	spec++;  	if (*spec == '\0') {  	    spec = nextArg; @@ -101,30 +94,38 @@ FileForRedirect(interp, spec, atOK, arg, nextArg, flags, skipPtr, closePtr,  	    }  	    *skipPtr = 2;  	} -        chan = Tcl_GetChannel(interp, spec, NULL); -        if (chan == (Tcl_Channel) NULL) { -            return NULL; -        } +	chan = Tcl_GetChannel(interp, spec, NULL); +	if (chan == (Tcl_Channel) NULL) { +	    return NULL; +	}  	file = TclpMakeFile(chan, writing ? TCL_WRITABLE : TCL_READABLE); -        if (file == NULL) { -            Tcl_AppendResult(interp, "channel \"", Tcl_GetChannelName(chan), -                    "\" wasn't opened for ", -                    ((writing) ? "writing" : "reading"), (char *) NULL); -            return NULL; -        } +	if (file == NULL) { +	    Tcl_Obj *msg; + +	    Tcl_GetChannelError(chan, &msg); +	    if (msg) { +		Tcl_SetObjResult(interp, msg); +	    } else { +		Tcl_SetObjResult(interp, Tcl_ObjPrintf( +			"channel \"%s\" wasn't opened for %s", +			Tcl_GetChannelName(chan), +			((writing) ? "writing" : "reading"))); +		Tcl_SetErrorCode(interp, "TCL", "OPERATION", "EXEC", +			"BADCHAN", NULL); +	    } +	    return NULL; +	}  	*releasePtr = 1;  	if (writing) { -  	    /* -	     * Be sure to flush output to the file, so that anything -	     * written by the child appears after stuff we've already -	     * written. +	     * Be sure to flush output to the file, so that anything written +	     * by the child appears after stuff we've already written.  	     */ -            Tcl_Flush(chan); +	    Tcl_Flush(chan);  	}      } else { -	CONST char *name; +	const char *name;  	Tcl_DString nameString;  	if (*spec == '\0') { @@ -135,25 +136,26 @@ FileForRedirect(interp, spec, atOK, arg, nextArg, flags, skipPtr, closePtr,  	    *skipPtr = 2;  	}  	name = Tcl_TranslateFileName(interp, spec, &nameString); -	if (name != NULL) { -	    file = TclpOpenFile(name, flags); -	} else { -	    file = NULL; +	if (name == NULL) { +	    return NULL;  	} +	file = TclpOpenFile(name, flags);  	Tcl_DStringFree(&nameString);  	if (file == NULL) { -	    Tcl_AppendResult(interp, "couldn't ", -		    ((writing) ? "write" : "read"), " file \"", spec, "\": ", -		    Tcl_PosixError(interp), (char *) NULL); +	    Tcl_SetObjResult(interp, Tcl_ObjPrintf( +		    "couldn't %s file \"%s\": %s", +		    (writing ? "write" : "read"), spec, +		    Tcl_PosixError(interp)));  	    return NULL;  	} -        *closePtr = 1; +	*closePtr = 1;      }      return file; -    badLastArg: -    Tcl_AppendResult(interp, "can't specify \"", arg, -	    "\" as last word in command", (char *) NULL); +  badLastArg: +    Tcl_SetObjResult(interp, Tcl_ObjPrintf( +	    "can't specify \"%s\" as last word in command", arg)); +    Tcl_SetErrorCode(interp, "TCL", "OPERATION", "EXEC", "SYNTAX", NULL);      return NULL;  } @@ -162,10 +164,9 @@ FileForRedirect(interp, spec, atOK, arg, nextArg, flags, skipPtr, closePtr,   *   * Tcl_DetachPids --   * - *	This procedure is called to indicate that one or more child - *	processes have been placed in background and will never be - *	waited for;  they should eventually be reaped by - *	Tcl_ReapDetachedProcs. + *	This function is called to indicate that one or more child processes + *	have been placed in background and will never be waited for; they + *	should eventually be reaped by Tcl_ReapDetachedProcs.   *   * Results:   *	None. @@ -177,17 +178,17 @@ FileForRedirect(interp, spec, atOK, arg, nextArg, flags, skipPtr, closePtr,   */  void -Tcl_DetachPids(numPids, pidPtr) -    int numPids;		/* Number of pids to detach:  gives size -				 * of array pointed to by pidPtr. */ -    Tcl_Pid *pidPtr;		/* Array of pids to detach. */ +Tcl_DetachPids( +    int numPids,		/* Number of pids to detach: gives size of +				 * array pointed to by pidPtr. */ +    Tcl_Pid *pidPtr)		/* Array of pids to detach. */  {      register Detached *detPtr;      int i;      Tcl_MutexLock(&pipeMutex);      for (i = 0; i < numPids; i++) { -	detPtr = (Detached *) ckalloc(sizeof(Detached)); +	detPtr = ckalloc(sizeof(Detached));  	detPtr->pid = pidPtr[i];  	detPtr->nextPtr = detList;  	detList = detPtr; @@ -201,23 +202,22 @@ Tcl_DetachPids(numPids, pidPtr)   *   * Tcl_ReapDetachedProcs --   * - *	This procedure checks to see if any detached processes have - *	exited and, if so, it "reaps" them by officially waiting on - *	them.  It should be called "occasionally" to make sure that - *	all detached processes are eventually reaped. + *	This function checks to see if any detached processes have exited and, + *	if so, it "reaps" them by officially waiting on them. It should be + *	called "occasionally" to make sure that all detached processes are + *	eventually reaped.   *   * Results:   *	None.   *   * Side effects: - *	Processes are waited on, so that they can be reaped by the - *	system. + *	Processes are waited on, so that they can be reaped by the system.   *   *----------------------------------------------------------------------   */  void -Tcl_ReapDetachedProcs() +Tcl_ReapDetachedProcs(void)  {      register Detached *detPtr;      Detached *nextPtr, *prevPtr; @@ -238,7 +238,7 @@ Tcl_ReapDetachedProcs()  	} else {  	    prevPtr->nextPtr = detPtr->nextPtr;  	} -	ckfree((char *) detPtr); +	ckfree(detPtr);  	detPtr = nextPtr;      }      Tcl_MutexUnlock(&pipeMutex); @@ -249,138 +249,137 @@ Tcl_ReapDetachedProcs()   *   * TclCleanupChildren --   * - *	This is a utility procedure used to wait for child processes - *	to exit, record information about abnormal exits, and then - *	collect any stderr output generated by them. + *	This is a utility function used to wait for child processes to exit, + *	record information about abnormal exits, and then collect any stderr + *	output generated by them.   *   * Results: - *	The return value is a standard Tcl result.  If anything at - *	weird happened with the child processes, TCL_ERROR is returned - *	and a message is left in the interp's result. + *	The return value is a standard Tcl result. If anything at weird + *	happened with the child processes, TCL_ERROR is returned and a message + *	is left in the interp's result.   *   * Side effects: - *	If the last character of the interp's result is a newline, then it - *	is removed unless keepNewline is non-zero.  File errorId gets - *	closed, and pidPtr is freed back to the storage allocator. + *	If the last character of the interp's result is a newline, then it is + *	removed unless keepNewline is non-zero. File errorId gets closed, and + *	pidPtr is freed back to the storage allocator.   *   *----------------------------------------------------------------------   */  int -TclCleanupChildren(interp, numPids, pidPtr, errorChan) -    Tcl_Interp *interp;		/* Used for error messages. */ -    int numPids;		/* Number of entries in pidPtr array. */ -    Tcl_Pid *pidPtr;		/* Array of process ids of children. */ -    Tcl_Channel errorChan;	/* Channel for file containing stderr output -				 * from pipeline.  NULL means there isn't any +TclCleanupChildren( +    Tcl_Interp *interp,		/* Used for error messages. */ +    int numPids,		/* Number of entries in pidPtr array. */ +    Tcl_Pid *pidPtr,		/* Array of process ids of children. */ +    Tcl_Channel errorChan)	/* Channel for file containing stderr output +				 * from pipeline. NULL means there isn't any  				 * stderr output. */  {      int result = TCL_OK;      int i, abnormalExit, anyErrorInfo;      Tcl_Pid pid; -    WAIT_STATUS_TYPE waitStatus; -    CONST char *msg; +    int waitStatus; +    const char *msg; +    unsigned long resolvedPid;      abnormalExit = 0;      for (i = 0; i < numPids; i++) { -        pid = Tcl_WaitPid(pidPtr[i], (int *) &waitStatus, 0); +	/* +	 * We need to get the resolved pid before we wait on it as the windows +	 * implementation of Tcl_WaitPid deletes the information such that any +	 * following calls to TclpGetPid fail. +	 */ + +	resolvedPid = TclpGetPid(pidPtr[i]); +	pid = Tcl_WaitPid(pidPtr[i], &waitStatus, 0);  	if (pid == (Tcl_Pid) -1) {  	    result = TCL_ERROR; -            if (interp != (Tcl_Interp *) NULL) { -                msg = Tcl_PosixError(interp); -                if (errno == ECHILD) { +	    if (interp != NULL) { +		msg = Tcl_PosixError(interp); +		if (errno == ECHILD) {  		    /* -                     * This changeup in message suggested by Mark Diekhans -                     * to remind people that ECHILD errors can occur on -                     * some systems if SIGCHLD isn't in its default state. -                     */ - -                    msg = -                        "child process lost (is SIGCHLD ignored or trapped?)"; -                } -                Tcl_AppendResult(interp, "error waiting for process to exit: ", -                        msg, (char *) NULL); -            } +		     * This changeup in message suggested by Mark Diekhans to +		     * remind people that ECHILD errors can occur on some +		     * systems if SIGCHLD isn't in its default state. +		     */ + +		    msg = +			"child process lost (is SIGCHLD ignored or trapped?)"; +		} +		Tcl_SetObjResult(interp, Tcl_ObjPrintf( +			"error waiting for process to exit: %s", msg)); +	    }  	    continue;  	}  	/* -	 * Create error messages for unusual process exits.  An -	 * extra newline gets appended to each error message, but -	 * it gets removed below (in the same fashion that an -	 * extra newline in the command's output is removed). +	 * Create error messages for unusual process exits. An extra newline +	 * gets appended to each error message, but it gets removed below (in +	 * the same fashion that an extra newline in the command's output is +	 * removed).  	 */  	if (!WIFEXITED(waitStatus) || (WEXITSTATUS(waitStatus) != 0)) {  	    char msg1[TCL_INTEGER_SPACE], msg2[TCL_INTEGER_SPACE];  	    result = TCL_ERROR; -	    TclFormatInt(msg1, (long) TclpGetPid(pid)); +	    sprintf(msg1, "%lu", resolvedPid);  	    if (WIFEXITED(waitStatus)) { -                if (interp != (Tcl_Interp *) NULL) { -		    TclFormatInt(msg2, WEXITSTATUS(waitStatus)); -                    Tcl_SetErrorCode(interp, "CHILDSTATUS", msg1, msg2, -                            (char *) NULL); -                } +		if (interp != NULL) { +		    sprintf(msg2, "%u", WEXITSTATUS(waitStatus)); +		    Tcl_SetErrorCode(interp, "CHILDSTATUS", msg1, msg2, NULL); +		}  		abnormalExit = 1; -	    } else if (WIFSIGNALED(waitStatus)) { -                if (interp != (Tcl_Interp *) NULL) { -                    CONST char *p; -                     -                    p = Tcl_SignalMsg((int) (WTERMSIG(waitStatus))); -                    Tcl_SetErrorCode(interp, "CHILDKILLED", msg1, -                            Tcl_SignalId((int) (WTERMSIG(waitStatus))), p, -                            (char *) NULL); -                    Tcl_AppendResult(interp, "child killed: ", p, "\n", -                            (char *) NULL); -                } -	    } else if (WIFSTOPPED(waitStatus)) { -                if (interp != (Tcl_Interp *) NULL) { -                    CONST char *p; - -                    p = Tcl_SignalMsg((int) (WSTOPSIG(waitStatus))); -                    Tcl_SetErrorCode(interp, "CHILDSUSP", msg1, -                            Tcl_SignalId((int) (WSTOPSIG(waitStatus))), -                            p, (char *) NULL); -                    Tcl_AppendResult(interp, "child suspended: ", p, "\n", -                            (char *) NULL); -                } -	    } else { -                if (interp != (Tcl_Interp *) NULL) { -                    Tcl_AppendResult(interp, -                            "child wait status didn't make sense\n", -                            (char *) NULL); -                } +	    } else if (interp != NULL) { +		const char *p; + +		if (WIFSIGNALED(waitStatus)) { +		    p = Tcl_SignalMsg(WTERMSIG(waitStatus)); +		    Tcl_SetErrorCode(interp, "CHILDKILLED", msg1, +			    Tcl_SignalId(WTERMSIG(waitStatus)), p, NULL); +		    Tcl_SetObjResult(interp, Tcl_ObjPrintf( +			    "child killed: %s\n", p)); +		} else if (WIFSTOPPED(waitStatus)) { +		    p = Tcl_SignalMsg(WSTOPSIG(waitStatus)); +		    Tcl_SetErrorCode(interp, "CHILDSUSP", msg1, +			    Tcl_SignalId(WSTOPSIG(waitStatus)), p, NULL); +		    Tcl_SetObjResult(interp, Tcl_ObjPrintf( +			    "child suspended: %s\n", p)); +		} else { +		    Tcl_SetObjResult(interp, Tcl_NewStringObj( +			    "child wait status didn't make sense\n", -1)); +		    Tcl_SetErrorCode(interp, "TCL", "OPERATION", "EXEC", +			    "ODDWAITRESULT", msg1, NULL); +		}  	    }  	}      }      /* -     * Read the standard error file.  If there's anything there, -     * then return an error and add the file's contents to the result -     * string. +     * Read the standard error file. If there's anything there, then return an +     * error and add the file's contents to the result string.       */      anyErrorInfo = 0;      if (errorChan != NULL) { -  	/*  	 * Make sure we start at the beginning of the file.  	 */ -        if (interp != NULL) { +	if (interp != NULL) {  	    int count;  	    Tcl_Obj *objPtr; -	     -	    Tcl_Seek(errorChan, 0L, SEEK_SET); + +	    Tcl_Seek(errorChan, (Tcl_WideInt)0, SEEK_SET);  	    objPtr = Tcl_NewObj();  	    count = Tcl_ReadChars(errorChan, objPtr, -1, 0);  	    if (count < 0) {  		result = TCL_ERROR;  		Tcl_DecrRefCount(objPtr);  		Tcl_ResetResult(interp); -		Tcl_AppendResult(interp, "error reading stderr output file: ", -			Tcl_PosixError(interp), NULL); +		Tcl_SetObjResult(interp, Tcl_ObjPrintf( +			"error reading stderr output file: %s", +			Tcl_PosixError(interp)));  	    } else if (count > 0) {  		anyErrorInfo = 1;  		Tcl_SetObjResult(interp, objPtr); @@ -393,13 +392,13 @@ TclCleanupChildren(interp, numPids, pidPtr, errorChan)      }      /* -     * If a child exited abnormally but didn't output any error information -     * at all, generate an error message here. +     * If a child exited abnormally but didn't output any error information at +     * all, generate an error message here.       */      if ((abnormalExit != 0) && (anyErrorInfo == 0) && (interp != NULL)) { -	Tcl_AppendResult(interp, "child process exited abnormally", -		(char *) NULL); +	Tcl_SetObjResult(interp, Tcl_NewStringObj( +		"child process exited abnormally", -1));      }      return result;  } @@ -409,25 +408,23 @@ TclCleanupChildren(interp, numPids, pidPtr, errorChan)   *   * TclCreatePipeline --   * - *	Given an argc/argv array, instantiate a pipeline of processes - *	as described by the argv. + *	Given an argc/argv array, instantiate a pipeline of processes as + *	described by the argv.   * - *	This procedure is unofficially exported for use by BLT. + *	This function is unofficially exported for use by BLT.   *   * 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 inPipePtr is non-NULL, *inPipePtr is filled in - *	with the file id for the input pipe for the pipeline (if any): - *	the caller must eventually close this file.  If outPipePtr - *	isn't NULL, then *outPipePtr is filled in with the file id - *	for the output pipe from the pipeline:  the caller must close - *	this file.  If errFilePtr isn't NULL, then *errFilePtr is filled - *	with a file id that may be used to read error output after the - *	pipeline completes. + *	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 inPipePtr is non-NULL, *inPipePtr is + *	filled in with the file id for the input pipe for the pipeline (if + *	any): the caller must eventually close this file. If outPipePtr isn't + *	NULL, then *outPipePtr is filled in with the file id for the output + *	pipe from the pipeline: the caller must close this file. If errFilePtr + *	isn't NULL, then *errFilePtr 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. @@ -436,71 +433,71 @@ TclCleanupChildren(interp, numPids, pidPtr, errorChan)   */  int -TclCreatePipeline(interp, argc, argv, pidArrayPtr, inPipePtr, -	outPipePtr, errFilePtr) -    Tcl_Interp *interp;		/* Interpreter to use for error reporting. */ -    int argc;			/* Number of entries in argv. */ -    CONST char **argv;		/* Array of strings describing commands in -				 * pipeline plus I/O redirection with <, -				 * <<,  >, etc.  Argv[argc] must be NULL. */ -    Tcl_Pid **pidArrayPtr;	/* Word at *pidArrayPtr gets filled in with -				 * address of array of pids for processes -				 * in pipeline (first pid is first process -				 * in pipeline). */ -    TclFile *inPipePtr;		/* If non-NULL, input to the pipeline comes +TclCreatePipeline( +    Tcl_Interp *interp,		/* Interpreter to use for error reporting. */ +    int argc,			/* Number of entries in argv. */ +    const char **argv,		/* Array of strings describing commands in +				 * pipeline plus I/O redirection with <, <<, +				 * >, etc. Argv[argc] must be NULL. */ +    Tcl_Pid **pidArrayPtr,	/* Word at *pidArrayPtr gets filled in with +				 * address of array of pids for processes in +				 * pipeline (first pid is first process in +				 * pipeline). */ +    TclFile *inPipePtr,		/* 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 *inPipePtr.  NULL means command -				 * specified its own input source. */ -    TclFile *outPipePtr;	/* 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 -				 * *outPipePtr.  NULL means command specified -				 * its own output sink. */ -    TclFile *errFilePtr;	/* If non-NULL, all stderr output from the +				 * redirection in the command). The file id +				 * with which to write to this pipe is stored +				 * at *inPipePtr. NULL means command specified +				 * its own input source. */ +    TclFile *outPipePtr,	/* 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 *outPipePtr. +				 * NULL means command specified its own output +				 * sink. */ +    TclFile *errFilePtr)	/* 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 *errFilePtr. -				 * 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. */ +				 * created here, and a descriptor to read the +				 * file will be left at *errFilePtr. 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. */  { -    Tcl_Pid *pidPtr = NULL;	/* Points to malloc-ed array holding all -				 * the pids of child processes. */ -    int numPids;		/* Actual number of processes that exist -				 * at *pidPtr right now. */ -    int cmdCount;		/* Count of number of distinct commands -				 * found in argc/argv. */ -    CONST 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. */ +    Tcl_Pid *pidPtr = NULL;	/* Points to malloc-ed array holding all the +				 * pids of child processes. */ +    int numPids;		/* Actual number of processes that exist at +				 * *pidPtr right now. */ +    int cmdCount;		/* Count of number of distinct commands found +				 * in argc/argv. */ +    const 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. */      TclFile inputFile = NULL;	/* If != NULL, gives file to use as input for  				 * first process in pipeline (specified via <  				 * or <@). */ -    int inputClose = 0;		/* If non-zero, then inputFile should be  -    				 * closed when cleaning up. */ +    int inputClose = 0;		/* If non-zero, then inputFile should be +				 * closed when cleaning up. */      int inputRelease = 0;      TclFile outputFile = NULL;	/* Writable file for output from last command -				 * in pipeline (could be file or pipe).  NULL +				 * in pipeline (could be file or pipe). NULL  				 * means use stdout. */ -    int outputClose = 0;	/* If non-zero, then outputFile should be  -    				 * closed when cleaning up. */ +    int outputClose = 0;	/* If non-zero, then outputFile should be +				 * closed when cleaning up. */      int outputRelease = 0;      TclFile errorFile = NULL;	/* Writable file for error output from all -				 * commands in pipeline.  NULL means use +				 * commands in pipeline. NULL means use  				 * stderr. */ -    int errorClose = 0;		/* If non-zero, then errorFile should be  -    				 * closed when cleaning up. */ +    int errorClose = 0;		/* If non-zero, then errorFile should be +				 * closed when cleaning up. */      int errorRelease = 0; -    CONST char *p; -    int skip, lastBar, lastArg, i, j, atOK, flags, errorToOutput; +    const char *p; +    const char *nextArg; +    int skip, lastBar, lastArg, i, j, atOK, flags, needCmd, errorToOutput = 0;      Tcl_DString execBuffer;      TclFile pipeIn;      TclFile curInFile, curOutFile, curErrFile; @@ -517,29 +514,31 @@ TclCreatePipeline(interp, argc, argv, pidArrayPtr, inPipePtr,      }      Tcl_DStringInit(&execBuffer); -     +      pipeIn = NULL;      curInFile = NULL;      curOutFile = NULL;      numPids = 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. +     * 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.       */      lastBar = -1;      cmdCount = 1; +    needCmd = 1;      for (i = 0; i < argc; i++) { -        skip = 0; +	errorToOutput = 0; +	skip = 0;  	p = argv[i];  	switch (*p++) {  	case '|': @@ -548,14 +547,16 @@ TclCreatePipeline(interp, argc, argv, pidArrayPtr, inPipePtr,  	    }  	    if (*p == '\0') {  		if ((i == (lastBar + 1)) || (i == (argc - 1))) { -		    Tcl_SetResult(interp, -			    "illegal use of | or |& in command", -			    TCL_STATIC); +		    Tcl_SetObjResult(interp, Tcl_NewStringObj( +			    "illegal use of | or |& in command", -1)); +		    Tcl_SetErrorCode(interp, "TCL", "OPERATION", "EXEC", +			    "PIPESYNTAX", NULL);  		    goto error;  		}  	    }  	    lastBar = i;  	    cmdCount++; +	    needCmd = 1;  	    break;  	case '<': @@ -572,18 +573,22 @@ TclCreatePipeline(interp, argc, argv, pidArrayPtr, inPipePtr,  		inputLiteral = p + 1;  		skip = 1;  		if (*inputLiteral == '\0') { -		    inputLiteral = argv[i + 1]; +		    inputLiteral = ((i + 1) == argc) ? NULL : argv[i + 1];  		    if (inputLiteral == NULL) { -			Tcl_AppendResult(interp, "can't specify \"", argv[i], -				"\" as last word in command", (char *) NULL); +			Tcl_SetObjResult(interp, Tcl_ObjPrintf( +				"can't specify \"%s\" as last word in command", +				argv[i])); +			Tcl_SetErrorCode(interp, "TCL", "OPERATION", "EXEC", +				"PIPESYNTAX", NULL);  			goto error;  		    }  		    skip = 2;  		}  	    } else { +		nextArg = ((i + 1) == argc) ? NULL : argv[i + 1];  		inputLiteral = NULL; -		inputFile = FileForRedirect(interp, p, 1, argv[i],  -			argv[i + 1], O_RDONLY, &skip, &inputClose, &inputRelease); +		inputFile = FileForRedirect(interp, p, 1, argv[i], nextArg, +			O_RDONLY, &skip, &inputClose, &inputRelease);  		if (inputFile == NULL) {  		    goto error;  		} @@ -593,11 +598,16 @@ TclCreatePipeline(interp, argc, argv, pidArrayPtr, inPipePtr,  	case '>':  	    atOK = 1;  	    flags = O_WRONLY | O_CREAT | O_TRUNC; -	    errorToOutput = 0;  	    if (*p == '>') {  		p++;  		atOK = 0; -		flags = O_WRONLY | O_CREAT; + +		/* +		 * Note that the O_APPEND flag only has an effect on POSIX +		 * platforms. On Windows, we just have to carry on regardless. +		 */ + +		flags = O_WRONLY | O_CREAT | O_APPEND;  	    }  	    if (*p == '&') {  		if (errorClose != 0) { @@ -609,8 +619,8 @@ TclCreatePipeline(interp, argc, argv, pidArrayPtr, inPipePtr,  	    }  	    /* -	     * Close the old output file, but only if the error file is -	     * not also using it. +	     * Close the old output file, but only if the error file is not +	     * also using it.  	     */  	    if (outputClose != 0) { @@ -629,8 +639,9 @@ TclCreatePipeline(interp, argc, argv, pidArrayPtr, inPipePtr,  		    TclpReleaseFile(outputFile);  		}  	    } -	    outputFile = FileForRedirect(interp, p, atOK, argv[i],  -		    argv[i + 1], flags, &skip, &outputClose, &outputRelease); +	    nextArg = ((i + 1) == argc) ? NULL : argv[i + 1]; +	    outputFile = FileForRedirect(interp, p, atOK, argv[i], nextArg, +		    flags, &skip, &outputClose, &outputRelease);  	    if (outputFile == NULL) {  		goto error;  	    } @@ -667,12 +678,41 @@ TclCreatePipeline(interp, argc, argv, pidArrayPtr, inPipePtr,  		errorRelease = 0;  		TclpReleaseFile(errorFile);  	    } -	    errorFile = FileForRedirect(interp, p, atOK, argv[i],  -		    argv[i + 1], flags, &skip, &errorClose, &errorRelease); -	    if (errorFile == NULL) { -		goto error; +	    if (atOK && p[0] == '@' && p[1] == '1' && p[2] == '\0') { +		/* +		 * Special case handling of 2>@1 to redirect stderr to the +		 * exec/open output pipe as well. This is meant for the end of +		 * the command string, otherwise use |& between commands. +		 */ + +		if (i != argc-1) { +		    Tcl_SetObjResult(interp, Tcl_ObjPrintf( +			    "must specify \"%s\" as last word in command", +			    argv[i])); +		    Tcl_SetErrorCode(interp, "TCL", "OPERATION", "EXEC", +			    "PIPESYNTAX", NULL); +		    goto error; +		} +		errorFile = outputFile; +		errorToOutput = 2; +		skip = 1; +	    } else { +		nextArg = ((i + 1) == argc) ? NULL : argv[i + 1]; +		errorFile = FileForRedirect(interp, p, atOK, argv[i], +			nextArg, flags, &skip, &errorClose, &errorRelease); +		if (errorFile == NULL) { +		    goto error; +		}  	    }  	    break; + +	default: +	    /* +	     * Got a command word, not a redirection. +	     */ + +	    needCmd = 0; +	    break;  	}  	if (skip != 0) { @@ -684,31 +724,44 @@ TclCreatePipeline(interp, argc, argv, pidArrayPtr, inPipePtr,  	}      } +    if (needCmd) { +	/* +	 * We had a bar followed only by redirections. +	 */ + +	Tcl_SetObjResult(interp, Tcl_NewStringObj( +		"illegal use of | or |& in command", -1)); +	Tcl_SetErrorCode(interp, "TCL", "OPERATION", "EXEC", "PIPESYNTAX", +		NULL); +	goto error; +    } +      if (inputFile == NULL) {  	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 +	     * Tcl. Create a temporary file for it and put the data into the  	     * file.  	     */ +  	    inputFile = TclpCreateTempFile(inputLiteral);  	    if (inputFile == NULL) { -		Tcl_AppendResult(interp, -			"couldn't create input file for command: ", -			Tcl_PosixError(interp), (char *) NULL); +		Tcl_SetObjResult(interp, Tcl_ObjPrintf( +			"couldn't create input file for command: %s", +			Tcl_PosixError(interp)));  		goto error;  	    }  	    inputClose = 1;  	} else if (inPipePtr != NULL) {  	    /* -	     * The input for the first process in the pipeline is to -	     * come from a pipe that can be written from by the caller. +	     * The input for the first process in the pipeline is to come from +	     * a pipe that can be written from by the caller.  	     */  	    if (TclpCreatePipe(&inputFile, inPipePtr) == 0) { -		Tcl_AppendResult(interp,  -			"couldn't create input pipe for command: ", -			Tcl_PosixError(interp), (char *) NULL); +		Tcl_SetObjResult(interp, Tcl_ObjPrintf( +			"couldn't create input pipe for command: %s", +			Tcl_PosixError(interp)));  		goto error;  	    }  	    inputClose = 1; @@ -730,14 +783,14 @@ TclCreatePipeline(interp, argc, argv, pidArrayPtr, inPipePtr,      if (outputFile == NULL) {  	if (outPipePtr != NULL) {  	    /* -	     * Output from the last process in the pipeline is to go to a -	     * pipe that can be read by the caller. +	     * Output from the last process in the pipeline is to go to a pipe +	     * that can be read by the caller.  	     */  	    if (TclpCreatePipe(outPipePtr, &outputFile) == 0) { -		Tcl_AppendResult(interp,  -			"couldn't create output pipe for command: ", -			Tcl_PosixError(interp), (char *) NULL); +		Tcl_SetObjResult(interp, Tcl_ObjPrintf( +			"couldn't create output pipe for command: %s", +			Tcl_PosixError(interp)));  		goto error;  	    }  	    outputClose = 1; @@ -757,21 +810,27 @@ TclCreatePipeline(interp, argc, argv, pidArrayPtr, inPipePtr,      }      if (errorFile == NULL) { -	if (errFilePtr != NULL) { +	if (errorToOutput == 2) { +	    /* +	     * Handle 2>@1 special case at end of cmd line. +	     */ + +	    errorFile = outputFile; +	} else if (errFilePtr != NULL) {  	    /*  	     * Set up the standard error output sink for the pipeline, if -	     * requested.  Use a temporary file which is opened, then deleted. +	     * requested. Use a temporary file which is opened, then deleted.  	     * Could potentially just use pipe, but if it filled up it could -	     * cause the pipeline to deadlock:  we'd be waiting for processes -	     * to complete before reading stderr, and processes couldn't  +	     * cause the pipeline to deadlock: we'd be waiting for processes +	     * to complete before reading stderr, and processes couldn't  	     * complete because stderr was backed up.  	     */  	    errorFile = TclpCreateTempFile(NULL);  	    if (errorFile == NULL) { -		Tcl_AppendResult(interp, -			"couldn't create error file for command: ", -			Tcl_PosixError(interp), (char *) NULL); +		Tcl_SetObjResult(interp, Tcl_ObjPrintf( +			"couldn't create error file for command: %s", +			Tcl_PosixError(interp)));  		goto error;  	    }  	    *errFilePtr = errorFile; @@ -789,24 +848,24 @@ TclCreatePipeline(interp, argc, argv, pidArrayPtr, inPipePtr,  	    }  	}      } -	 +      /* -     * Scan through the argc array, creating a process for each -     * group of arguments between the "|" characters. +     * Scan through the argc array, creating a process for each group of +     * arguments between the "|" characters.       */      Tcl_ReapDetachedProcs(); -    pidPtr = (Tcl_Pid *) ckalloc((unsigned) (cmdCount * sizeof(Tcl_Pid))); +    pidPtr = ckalloc(cmdCount * sizeof(Tcl_Pid));      curInFile = inputFile; -    for (i = 0; i < argc; i = lastArg + 1) {  +    for (i = 0; i < argc; i = lastArg + 1) {  	int result, joinThisError;  	Tcl_Pid pid; -	CONST char *oldName; +	const char *oldName;  	/* -	 * Convert the program name into native form.  +	 * Convert the program name into native form.  	 */  	if (Tcl_TranslateFileName(interp, argv[i], &execBuffer) == NULL) { @@ -819,30 +878,31 @@ TclCreatePipeline(interp, argc, argv, pidArrayPtr, inPipePtr,  	joinThisError = 0;  	for (lastArg = i; lastArg < argc; lastArg++) { -	    if (argv[lastArg][0] == '|') {  -		if (argv[lastArg][1] == '\0') {  -		    break; -		} -		if ((argv[lastArg][1] == '&') && (argv[lastArg][2] == '\0')) { -		    joinThisError = 1; -		    break; -		} +	    if (argv[lastArg][0] != '|') { +		continue; +	    } +	    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 outputFile. -	 * Otherwise create an intermediate pipe.  pipeIn will become the +	 * Otherwise create an intermediate pipe. pipeIn will become the  	 * curInFile for the next segment of the pipe.  	 */ -	if (lastArg == argc) {  +	if (lastArg == argc) {  	    curOutFile = outputFile;  	} else { +	    argv[lastArg] = NULL;  	    if (TclpCreatePipe(&pipeIn, &curOutFile) == 0) { -		Tcl_AppendResult(interp, "couldn't create pipe: ", -			Tcl_PosixError(interp), (char *) NULL); +		Tcl_SetObjResult(interp, Tcl_ObjPrintf( +			"couldn't create pipe: %s", Tcl_PosixError(interp)));  		goto error;  	    }  	} @@ -857,7 +917,7 @@ TclCreatePipeline(interp, argc, argv, pidArrayPtr, inPipePtr,  	 * Restore argv[i], since a caller wouldn't expect the contents of  	 * argv to be modified.  	 */ -	  +  	oldName = argv[i];  	argv[i] = Tcl_DStringValue(&execBuffer);  	result = TclpCreateProcess(interp, lastArg - i, argv + i, @@ -872,8 +932,8 @@ TclCreatePipeline(interp, argc, argv, pidArrayPtr, inPipePtr,  	numPids++;  	/* -	 * Close off our copies of file descriptors that were set up for -	 * this child, then set up the input for the next child. +	 * Close off our copies of file descriptors that were set up for this +	 * child, then set up the input for the next child.  	 */  	if ((curInFile != NULL) && (curInFile != inputFile)) { @@ -891,10 +951,10 @@ TclCreatePipeline(interp, argc, argv, pidArrayPtr, inPipePtr,      *pidArrayPtr = pidPtr;      /* -     * All done.  Cleanup open files lying around and then return. +     * All done. Cleanup open files lying around and then return.       */ -cleanup: +  cleanup:      Tcl_DStringFree(&execBuffer);      if (inputClose) { @@ -915,12 +975,12 @@ cleanup:      return numPids;      /* -     * An error occurred.  There could have been extra files open, such -     * as pipes between children.  Clean them all up.  Detach any child -     * processes that have been created. +     * An error occurred. 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: +  error:      if (pipeIn != NULL) {  	TclpCloseFile(pipeIn);      } @@ -948,7 +1008,7 @@ error:  		Tcl_DetachPids(1, &pidPtr[i]);  	    }  	} -	ckfree((char *) pidPtr); +	ckfree(pidPtr);      }      numPids = -1;      goto cleanup; @@ -959,28 +1019,26 @@ error:   *   * Tcl_OpenCommandChannel --   * - *	Opens an I/O channel to one or more subprocesses specified - *	by argc and argv.  The flags argument determines the - *	disposition of the stdio handles.  If the TCL_STDIN flag is - *	set then the standard input for the first subprocess will - *	be tied to the channel:  writing to the channel will provide - *	input to the subprocess.  If TCL_STDIN is not set, then - *	standard input for the first subprocess will be the same as - *	this application's standard input.  If TCL_STDOUT is set then - *	standard output from the last subprocess can be read from the - *	channel;  otherwise it goes to this application's standard - *	output.  If TCL_STDERR is set, standard error output for all - *	subprocesses is returned to the channel and results in an error - *	when the channel is closed;  otherwise it goes to this - *	application's standard error.  If TCL_ENFORCE_MODE is not set, - *	then argc and argv can redirect the stdio handles to override - *	TCL_STDIN, TCL_STDOUT, and TCL_STDERR;  if it is set, then it  - *	is an error for argc and argv to override stdio channels for - *	which TCL_STDIN, TCL_STDOUT, and TCL_STDERR have been set. + *	Opens an I/O channel to one or more subprocesses specified by argc and + *	argv. The flags argument determines the disposition of the stdio + *	handles. If the TCL_STDIN flag is set then the standard input for the + *	first subprocess will be tied to the channel: writing to the channel + *	will provide input to the subprocess. If TCL_STDIN is not set, then + *	standard input for the first subprocess will be the same as this + *	application's standard input. If TCL_STDOUT is set then standard + *	output from the last subprocess can be read from the channel; + *	otherwise it goes to this application's standard output. If TCL_STDERR + *	is set, standard error output for all subprocesses is returned to the + *	channel and results in an error when the channel is closed; otherwise + *	it goes to this application's standard error. If TCL_ENFORCE_MODE is + *	not set, then argc and argv can redirect the stdio handles to override + *	TCL_STDIN, TCL_STDOUT, and TCL_STDERR; if it is set, then it is an + *	error for argc and argv to override stdio channels for which + *	TCL_STDIN, TCL_STDOUT, and TCL_STDERR have been set.   *   * Results: - *	A new command channel, or NULL on failure with an error - *	message left in interp. + *	A new command channel, or NULL on failure with an error message left + *	in interp.   *   * Side effects:   *	Creates processes, opens pipes. @@ -989,12 +1047,12 @@ error:   */  Tcl_Channel -Tcl_OpenCommandChannel(interp, argc, argv, flags) -    Tcl_Interp *interp;		/* Interpreter for error reporting. Can -                                 * NOT be NULL. */ -    int argc;			/* How many arguments. */ -    CONST char **argv;		/* Array of arguments for command pipe. */ -    int flags;			/* Or'ed combination of TCL_STDIN, TCL_STDOUT, +Tcl_OpenCommandChannel( +    Tcl_Interp *interp,		/* Interpreter for error reporting. Can NOT be +				 * NULL. */ +    int argc,			/* How many arguments. */ +    const char **argv,		/* Array of arguments for command pipe. */ +    int flags)			/* Or'ed combination of TCL_STDIN, TCL_STDOUT,  				 * TCL_STDERR, and TCL_ENFORCE_MODE. */  {      TclFile *inPipePtr, *outPipePtr, *errFilePtr; @@ -1008,46 +1066,53 @@ Tcl_OpenCommandChannel(interp, argc, argv, flags)      inPipePtr = (flags & TCL_STDIN) ? &inPipe : NULL;      outPipePtr = (flags & TCL_STDOUT) ? &outPipe : NULL;      errFilePtr = (flags & TCL_STDERR) ? &errFile : NULL; -     +      numPids = TclCreatePipeline(interp, argc, argv, &pidPtr, inPipePtr, -            outPipePtr, errFilePtr); +	    outPipePtr, errFilePtr);      if (numPids < 0) {  	goto error;      }      /* -     * Verify that the pipes that were created satisfy the -     * readable/writable constraints.  +     * Verify that the pipes that were created satisfy the readable/writable +     * constraints.       */      if (flags & TCL_ENFORCE_MODE) {  	if ((flags & TCL_STDOUT) && (outPipe == NULL)) { -	    Tcl_AppendResult(interp, "can't read output from command:", -		    " standard output was redirected", (char *) NULL); +	    Tcl_SetObjResult(interp, Tcl_NewStringObj( +		    "can't read output from command:" +		    " standard output was redirected", -1)); +	    Tcl_SetErrorCode(interp, "TCL", "OPERATION", "EXEC", +		    "BADREDIRECT", NULL);  	    goto error;  	}  	if ((flags & TCL_STDIN) && (inPipe == NULL)) { -	    Tcl_AppendResult(interp, "can't write input to command:", -		    " standard input was redirected", (char *) NULL); +	    Tcl_SetObjResult(interp, Tcl_NewStringObj( +		    "can't write input to command:" +		    " standard input was redirected", -1)); +	    Tcl_SetErrorCode(interp, "TCL", "OPERATION", "EXEC", +		    "BADREDIRECT", NULL);  	    goto error;  	}      } -     +      channel = TclpCreateCommandChannel(outPipe, inPipe, errFile,  	    numPids, pidPtr); -    if (channel == (Tcl_Channel) NULL) { -        Tcl_AppendResult(interp, "pipe for command could not be created", -                (char *) NULL); +    if (channel == NULL) { +	Tcl_SetObjResult(interp, Tcl_NewStringObj( +		"pipe for command could not be created", -1)); +	Tcl_SetErrorCode(interp, "TCL", "OPERATION", "EXEC", "NOPIPE", NULL);  	goto error;      }      return channel; -error: +  error:      if (numPids > 0) {  	Tcl_DetachPids(numPids, pidPtr); -	ckfree((char *) pidPtr); +	ckfree(pidPtr);      }      if (inPipe != NULL) {  	TclpCloseFile(inPipe); @@ -1060,3 +1125,11 @@ error:      }      return NULL;  } + +/* + * Local Variables: + * mode: c + * c-basic-offset: 4 + * fill-column: 78 + * End: + */ | 
