| [25] | 1 | /* | 
|---|
 | 2 |  * tclPipe.c -- | 
|---|
 | 3 |  * | 
|---|
 | 4 |  *      This file contains the generic portion of the command channel driver | 
|---|
 | 5 |  *      as well as various utility routines used in managing subprocesses. | 
|---|
 | 6 |  * | 
|---|
 | 7 |  * Copyright (c) 1997 by Sun Microsystems, Inc. | 
|---|
 | 8 |  * | 
|---|
 | 9 |  * See the file "license.terms" for information on usage and redistribution of | 
|---|
 | 10 |  * this file, and for a DISCLAIMER OF ALL WARRANTIES. | 
|---|
 | 11 |  * | 
|---|
 | 12 |  * RCS: @(#) $Id: tclPipe.c,v 1.19 2007/04/20 06:10:58 kennykb Exp $ | 
|---|
 | 13 |  */ | 
|---|
 | 14 |  | 
|---|
 | 15 | #include "tclInt.h" | 
|---|
 | 16 |  | 
|---|
 | 17 | /* | 
|---|
 | 18 |  * A linked list of the following structures is used to keep track of child | 
|---|
 | 19 |  * processes that have been detached but haven't exited yet, so we can make | 
|---|
 | 20 |  * sure that they're properly "reaped" (officially waited for) and don't lie | 
|---|
 | 21 |  * around as zombies cluttering the system. | 
|---|
 | 22 |  */ | 
|---|
 | 23 |  | 
|---|
 | 24 | typedef struct Detached { | 
|---|
 | 25 |     Tcl_Pid pid;                /* Id of process that's been detached but | 
|---|
 | 26 |                                  * isn't known to have exited. */ | 
|---|
 | 27 |     struct Detached *nextPtr;   /* Next in list of all detached processes. */ | 
|---|
 | 28 | } Detached; | 
|---|
 | 29 |  | 
|---|
 | 30 | static Detached *detList = NULL;/* List of all detached proceses. */ | 
|---|
 | 31 | TCL_DECLARE_MUTEX(pipeMutex)    /* Guard access to detList. */ | 
|---|
 | 32 |  | 
|---|
 | 33 | /* | 
|---|
 | 34 |  * Declarations for local functions defined in this file: | 
|---|
 | 35 |  */ | 
|---|
 | 36 |  | 
|---|
 | 37 | static TclFile          FileForRedirect(Tcl_Interp *interp, CONST char *spec, | 
|---|
 | 38 |                             int atOk, CONST char *arg, CONST char *nextArg, | 
|---|
 | 39 |                             int flags, int *skipPtr, int *closePtr, | 
|---|
 | 40 |                             int *releasePtr); | 
|---|
 | 41 |  | 
|---|
 | 42 | /* | 
|---|
 | 43 |  *---------------------------------------------------------------------- | 
|---|
 | 44 |  * | 
|---|
 | 45 |  * FileForRedirect -- | 
|---|
 | 46 |  * | 
|---|
 | 47 |  *      This function does much of the work of parsing redirection operators. | 
|---|
 | 48 |  *      It handles "@" if specified and allowed, and a file name, and opens | 
|---|
 | 49 |  *      the file if necessary. | 
|---|
 | 50 |  * | 
|---|
 | 51 |  * Results: | 
|---|
 | 52 |  *      The return value is the descriptor number for the file. If an error | 
|---|
 | 53 |  *      occurs then NULL is returned and an error message is left in the | 
|---|
 | 54 |  *      interp's result. Several arguments are side-effected; see the argument | 
|---|
 | 55 |  *      list below for details. | 
|---|
 | 56 |  * | 
|---|
 | 57 |  * Side effects: | 
|---|
 | 58 |  *      None. | 
|---|
 | 59 |  * | 
|---|
 | 60 |  *---------------------------------------------------------------------- | 
|---|
 | 61 |  */ | 
|---|
 | 62 |  | 
|---|
 | 63 | static TclFile | 
|---|
 | 64 | FileForRedirect( | 
|---|
 | 65 |     Tcl_Interp *interp,         /* Intepreter to use for error reporting. */ | 
|---|
 | 66 |     CONST char *spec,           /* Points to character just after redirection | 
|---|
 | 67 |                                  * character. */ | 
|---|
 | 68 |     int atOK,                   /* Non-zero means that '@' notation can be | 
|---|
 | 69 |                                  * used to specify a channel, zero means that | 
|---|
 | 70 |                                  * it isn't. */ | 
|---|
 | 71 |     CONST char *arg,            /* Pointer to entire argument containing spec: | 
|---|
 | 72 |                                  * used for error reporting. */ | 
|---|
 | 73 |     CONST char *nextArg,        /* Next argument in argc/argv array, if needed | 
|---|
 | 74 |                                  * for file name or channel name. May be | 
|---|
 | 75 |                                  * NULL. */ | 
|---|
 | 76 |     int flags,                  /* Flags to use for opening file or to specify | 
|---|
 | 77 |                                  * mode for channel. */ | 
|---|
 | 78 |     int *skipPtr,               /* Filled with 1 if redirection target was in | 
|---|
 | 79 |                                  * spec, 2 if it was in nextArg. */ | 
|---|
 | 80 |     int *closePtr,              /* Filled with one if the caller should close | 
|---|
 | 81 |                                  * the file when done with it, zero | 
|---|
 | 82 |                                  * otherwise. */ | 
|---|
 | 83 |     int *releasePtr) | 
|---|
 | 84 | { | 
|---|
 | 85 |     int writing = (flags & O_WRONLY); | 
|---|
 | 86 |     Tcl_Channel chan; | 
|---|
 | 87 |     TclFile file; | 
|---|
 | 88 |  | 
|---|
 | 89 |     *skipPtr = 1; | 
|---|
 | 90 |     if ((atOK != 0) && (*spec == '@')) { | 
|---|
 | 91 |         spec++; | 
|---|
 | 92 |         if (*spec == '\0') { | 
|---|
 | 93 |             spec = nextArg; | 
|---|
 | 94 |             if (spec == NULL) { | 
|---|
 | 95 |                 goto badLastArg; | 
|---|
 | 96 |             } | 
|---|
 | 97 |             *skipPtr = 2; | 
|---|
 | 98 |         } | 
|---|
 | 99 |         chan = Tcl_GetChannel(interp, spec, NULL); | 
|---|
 | 100 |         if (chan == (Tcl_Channel) NULL) { | 
|---|
 | 101 |             return NULL; | 
|---|
 | 102 |         } | 
|---|
 | 103 |         file = TclpMakeFile(chan, writing ? TCL_WRITABLE : TCL_READABLE); | 
|---|
 | 104 |         if (file == NULL) { | 
|---|
 | 105 |             Tcl_AppendResult(interp, "channel \"", Tcl_GetChannelName(chan), | 
|---|
 | 106 |                     "\" wasn't opened for ", | 
|---|
 | 107 |                     ((writing) ? "writing" : "reading"), NULL); | 
|---|
 | 108 |             return NULL; | 
|---|
 | 109 |         } | 
|---|
 | 110 |         *releasePtr = 1; | 
|---|
 | 111 |         if (writing) { | 
|---|
 | 112 |             /* | 
|---|
 | 113 |              * Be sure to flush output to the file, so that anything written | 
|---|
 | 114 |              * by the child appears after stuff we've already written. | 
|---|
 | 115 |              */ | 
|---|
 | 116 |  | 
|---|
 | 117 |             Tcl_Flush(chan); | 
|---|
 | 118 |         } | 
|---|
 | 119 |     } else { | 
|---|
 | 120 |         CONST char *name; | 
|---|
 | 121 |         Tcl_DString nameString; | 
|---|
 | 122 |  | 
|---|
 | 123 |         if (*spec == '\0') { | 
|---|
 | 124 |             spec = nextArg; | 
|---|
 | 125 |             if (spec == NULL) { | 
|---|
 | 126 |                 goto badLastArg; | 
|---|
 | 127 |             } | 
|---|
 | 128 |             *skipPtr = 2; | 
|---|
 | 129 |         } | 
|---|
 | 130 |         name = Tcl_TranslateFileName(interp, spec, &nameString); | 
|---|
 | 131 |         if (name == NULL) { | 
|---|
 | 132 |             return NULL; | 
|---|
 | 133 |         } | 
|---|
 | 134 |         file = TclpOpenFile(name, flags); | 
|---|
 | 135 |         Tcl_DStringFree(&nameString); | 
|---|
 | 136 |         if (file == NULL) { | 
|---|
 | 137 |             Tcl_AppendResult(interp, "couldn't ", | 
|---|
 | 138 |                     ((writing) ? "write" : "read"), " file \"", spec, "\": ", | 
|---|
 | 139 |                     Tcl_PosixError(interp), NULL); | 
|---|
 | 140 |             return NULL; | 
|---|
 | 141 |         } | 
|---|
 | 142 |         *closePtr = 1; | 
|---|
 | 143 |     } | 
|---|
 | 144 |     return file; | 
|---|
 | 145 |  | 
|---|
 | 146 |   badLastArg: | 
|---|
 | 147 |     Tcl_AppendResult(interp, "can't specify \"", arg, | 
|---|
 | 148 |             "\" as last word in command", NULL); | 
|---|
 | 149 |     return NULL; | 
|---|
 | 150 | } | 
|---|
 | 151 |  | 
|---|
 | 152 | /* | 
|---|
 | 153 |  *---------------------------------------------------------------------- | 
|---|
 | 154 |  * | 
|---|
 | 155 |  * Tcl_DetachPids -- | 
|---|
 | 156 |  * | 
|---|
 | 157 |  *      This function is called to indicate that one or more child processes | 
|---|
 | 158 |  *      have been placed in background and will never be waited for; they | 
|---|
 | 159 |  *      should eventually be reaped by Tcl_ReapDetachedProcs. | 
|---|
 | 160 |  * | 
|---|
 | 161 |  * Results: | 
|---|
 | 162 |  *      None. | 
|---|
 | 163 |  * | 
|---|
 | 164 |  * Side effects: | 
|---|
 | 165 |  *      None. | 
|---|
 | 166 |  * | 
|---|
 | 167 |  *---------------------------------------------------------------------- | 
|---|
 | 168 |  */ | 
|---|
 | 169 |  | 
|---|
 | 170 | void | 
|---|
 | 171 | Tcl_DetachPids( | 
|---|
 | 172 |     int numPids,                /* Number of pids to detach: gives size of | 
|---|
 | 173 |                                  * array pointed to by pidPtr. */ | 
|---|
 | 174 |     Tcl_Pid *pidPtr)            /* Array of pids to detach. */ | 
|---|
 | 175 | { | 
|---|
 | 176 |     register Detached *detPtr; | 
|---|
 | 177 |     int i; | 
|---|
 | 178 |  | 
|---|
 | 179 |     Tcl_MutexLock(&pipeMutex); | 
|---|
 | 180 |     for (i = 0; i < numPids; i++) { | 
|---|
 | 181 |         detPtr = (Detached *) ckalloc(sizeof(Detached)); | 
|---|
 | 182 |         detPtr->pid = pidPtr[i]; | 
|---|
 | 183 |         detPtr->nextPtr = detList; | 
|---|
 | 184 |         detList = detPtr; | 
|---|
 | 185 |     } | 
|---|
 | 186 |     Tcl_MutexUnlock(&pipeMutex); | 
|---|
 | 187 |  | 
|---|
 | 188 | } | 
|---|
 | 189 |  | 
|---|
 | 190 | /* | 
|---|
 | 191 |  *---------------------------------------------------------------------- | 
|---|
 | 192 |  * | 
|---|
 | 193 |  * Tcl_ReapDetachedProcs -- | 
|---|
 | 194 |  * | 
|---|
 | 195 |  *      This function checks to see if any detached processes have exited and, | 
|---|
 | 196 |  *      if so, it "reaps" them by officially waiting on them. It should be | 
|---|
 | 197 |  *      called "occasionally" to make sure that all detached processes are | 
|---|
 | 198 |  *      eventually reaped. | 
|---|
 | 199 |  * | 
|---|
 | 200 |  * Results: | 
|---|
 | 201 |  *      None. | 
|---|
 | 202 |  * | 
|---|
 | 203 |  * Side effects: | 
|---|
 | 204 |  *      Processes are waited on, so that they can be reaped by the system. | 
|---|
 | 205 |  * | 
|---|
 | 206 |  *---------------------------------------------------------------------- | 
|---|
 | 207 |  */ | 
|---|
 | 208 |  | 
|---|
 | 209 | void | 
|---|
 | 210 | Tcl_ReapDetachedProcs(void) | 
|---|
 | 211 | { | 
|---|
 | 212 |     register Detached *detPtr; | 
|---|
 | 213 |     Detached *nextPtr, *prevPtr; | 
|---|
 | 214 |     int status; | 
|---|
 | 215 |     Tcl_Pid pid; | 
|---|
 | 216 |  | 
|---|
 | 217 |     Tcl_MutexLock(&pipeMutex); | 
|---|
 | 218 |     for (detPtr = detList, prevPtr = NULL; detPtr != NULL; ) { | 
|---|
 | 219 |         pid = Tcl_WaitPid(detPtr->pid, &status, WNOHANG); | 
|---|
 | 220 |         if ((pid == 0) || ((pid == (Tcl_Pid) -1) && (errno != ECHILD))) { | 
|---|
 | 221 |             prevPtr = detPtr; | 
|---|
 | 222 |             detPtr = detPtr->nextPtr; | 
|---|
 | 223 |             continue; | 
|---|
 | 224 |         } | 
|---|
 | 225 |         nextPtr = detPtr->nextPtr; | 
|---|
 | 226 |         if (prevPtr == NULL) { | 
|---|
 | 227 |             detList = detPtr->nextPtr; | 
|---|
 | 228 |         } else { | 
|---|
 | 229 |             prevPtr->nextPtr = detPtr->nextPtr; | 
|---|
 | 230 |         } | 
|---|
 | 231 |         ckfree((char *) detPtr); | 
|---|
 | 232 |         detPtr = nextPtr; | 
|---|
 | 233 |     } | 
|---|
 | 234 |     Tcl_MutexUnlock(&pipeMutex); | 
|---|
 | 235 | } | 
|---|
 | 236 |  | 
|---|
 | 237 | /* | 
|---|
 | 238 |  *---------------------------------------------------------------------- | 
|---|
 | 239 |  * | 
|---|
 | 240 |  * TclCleanupChildren -- | 
|---|
 | 241 |  * | 
|---|
 | 242 |  *      This is a utility function used to wait for child processes to exit, | 
|---|
 | 243 |  *      record information about abnormal exits, and then collect any stderr | 
|---|
 | 244 |  *      output generated by them. | 
|---|
 | 245 |  * | 
|---|
 | 246 |  * Results: | 
|---|
 | 247 |  *      The return value is a standard Tcl result. If anything at weird | 
|---|
 | 248 |  *      happened with the child processes, TCL_ERROR is returned and a message | 
|---|
 | 249 |  *      is left in the interp's result. | 
|---|
 | 250 |  * | 
|---|
 | 251 |  * Side effects: | 
|---|
 | 252 |  *      If the last character of the interp's result is a newline, then it is | 
|---|
 | 253 |  *      removed unless keepNewline is non-zero. File errorId gets closed, and | 
|---|
 | 254 |  *      pidPtr is freed back to the storage allocator. | 
|---|
 | 255 |  * | 
|---|
 | 256 |  *---------------------------------------------------------------------- | 
|---|
 | 257 |  */ | 
|---|
 | 258 |  | 
|---|
 | 259 | int | 
|---|
 | 260 | TclCleanupChildren( | 
|---|
 | 261 |     Tcl_Interp *interp,         /* Used for error messages. */ | 
|---|
 | 262 |     int numPids,                /* Number of entries in pidPtr array. */ | 
|---|
 | 263 |     Tcl_Pid *pidPtr,            /* Array of process ids of children. */ | 
|---|
 | 264 |     Tcl_Channel errorChan)      /* Channel for file containing stderr output | 
|---|
 | 265 |                                  * from pipeline. NULL means there isn't any | 
|---|
 | 266 |                                  * stderr output. */ | 
|---|
 | 267 | { | 
|---|
 | 268 |     int result = TCL_OK; | 
|---|
 | 269 |     int i, abnormalExit, anyErrorInfo; | 
|---|
 | 270 |     Tcl_Pid pid; | 
|---|
 | 271 |     WAIT_STATUS_TYPE waitStatus; | 
|---|
 | 272 |     CONST char *msg; | 
|---|
 | 273 |     unsigned long resolvedPid; | 
|---|
 | 274 |  | 
|---|
 | 275 |     abnormalExit = 0; | 
|---|
 | 276 |     for (i = 0; i < numPids; i++) { | 
|---|
 | 277 |         /* | 
|---|
 | 278 |          * We need to get the resolved pid before we wait on it as the windows | 
|---|
 | 279 |          * implimentation of Tcl_WaitPid deletes the information such that any | 
|---|
 | 280 |          * following calls to TclpGetPid fail. | 
|---|
 | 281 |          */ | 
|---|
 | 282 |  | 
|---|
 | 283 |         resolvedPid = TclpGetPid(pidPtr[i]); | 
|---|
 | 284 |         pid = Tcl_WaitPid(pidPtr[i], (int *) &waitStatus, 0); | 
|---|
 | 285 |         if (pid == (Tcl_Pid) -1) { | 
|---|
 | 286 |             result = TCL_ERROR; | 
|---|
 | 287 |             if (interp != NULL) { | 
|---|
 | 288 |                 msg = Tcl_PosixError(interp); | 
|---|
 | 289 |                 if (errno == ECHILD) { | 
|---|
 | 290 |                     /* | 
|---|
 | 291 |                      * This changeup in message suggested by Mark Diekhans to | 
|---|
 | 292 |                      * remind people that ECHILD errors can occur on some | 
|---|
 | 293 |                      * systems if SIGCHLD isn't in its default state. | 
|---|
 | 294 |                      */ | 
|---|
 | 295 |  | 
|---|
 | 296 |                     msg = | 
|---|
 | 297 |                         "child process lost (is SIGCHLD ignored or trapped?)"; | 
|---|
 | 298 |                 } | 
|---|
 | 299 |                 Tcl_AppendResult(interp, "error waiting for process to exit: ", | 
|---|
 | 300 |                         msg, NULL); | 
|---|
 | 301 |             } | 
|---|
 | 302 |             continue; | 
|---|
 | 303 |         } | 
|---|
 | 304 |  | 
|---|
 | 305 |         /* | 
|---|
 | 306 |          * Create error messages for unusual process exits. An extra newline | 
|---|
 | 307 |          * gets appended to each error message, but it gets removed below (in | 
|---|
 | 308 |          * the same fashion that an extra newline in the command's output is | 
|---|
 | 309 |          * removed). | 
|---|
 | 310 |          */ | 
|---|
 | 311 |  | 
|---|
 | 312 |         if (!WIFEXITED(waitStatus) || (WEXITSTATUS(waitStatus) != 0)) { | 
|---|
 | 313 |             char msg1[TCL_INTEGER_SPACE], msg2[TCL_INTEGER_SPACE]; | 
|---|
 | 314 |  | 
|---|
 | 315 |             result = TCL_ERROR; | 
|---|
 | 316 |             sprintf(msg1, "%lu", resolvedPid); | 
|---|
 | 317 |             if (WIFEXITED(waitStatus)) { | 
|---|
 | 318 |                 if (interp != (Tcl_Interp *) NULL) { | 
|---|
 | 319 |                     sprintf(msg2, "%lu", | 
|---|
 | 320 |                             (unsigned long) WEXITSTATUS(waitStatus)); | 
|---|
 | 321 |                     Tcl_SetErrorCode(interp, "CHILDSTATUS", msg1, msg2, NULL); | 
|---|
 | 322 |                 } | 
|---|
 | 323 |                 abnormalExit = 1; | 
|---|
 | 324 |             } else if (interp != NULL) { | 
|---|
 | 325 |                 CONST char *p; | 
|---|
 | 326 |  | 
|---|
 | 327 |                 if (WIFSIGNALED(waitStatus)) { | 
|---|
 | 328 |                     p = Tcl_SignalMsg((int) (WTERMSIG(waitStatus))); | 
|---|
 | 329 |                     Tcl_SetErrorCode(interp, "CHILDKILLED", msg1, | 
|---|
 | 330 |                             Tcl_SignalId((int) (WTERMSIG(waitStatus))), p, | 
|---|
 | 331 |                             NULL); | 
|---|
 | 332 |                     Tcl_AppendResult(interp, "child killed: ", p, "\n", NULL); | 
|---|
 | 333 |                 } else if (WIFSTOPPED(waitStatus)) { | 
|---|
 | 334 |                     p = Tcl_SignalMsg((int) (WSTOPSIG(waitStatus))); | 
|---|
 | 335 |                     Tcl_SetErrorCode(interp, "CHILDSUSP", msg1, | 
|---|
 | 336 |                             Tcl_SignalId((int) (WSTOPSIG(waitStatus))), p, | 
|---|
 | 337 |                             NULL); | 
|---|
 | 338 |                     Tcl_AppendResult(interp, "child suspended: ", p, "\n", | 
|---|
 | 339 |                             NULL); | 
|---|
 | 340 |                 } else { | 
|---|
 | 341 |                     Tcl_AppendResult(interp, | 
|---|
 | 342 |                             "child wait status didn't make sense\n", NULL); | 
|---|
 | 343 |                 } | 
|---|
 | 344 |             } | 
|---|
 | 345 |         } | 
|---|
 | 346 |     } | 
|---|
 | 347 |  | 
|---|
 | 348 |     /* | 
|---|
 | 349 |      * Read the standard error file. If there's anything there, then return an | 
|---|
 | 350 |      * error and add the file's contents to the result string. | 
|---|
 | 351 |      */ | 
|---|
 | 352 |  | 
|---|
 | 353 |     anyErrorInfo = 0; | 
|---|
 | 354 |     if (errorChan != NULL) { | 
|---|
 | 355 |         /* | 
|---|
 | 356 |          * Make sure we start at the beginning of the file. | 
|---|
 | 357 |          */ | 
|---|
 | 358 |  | 
|---|
 | 359 |         if (interp != NULL) { | 
|---|
 | 360 |             int count; | 
|---|
 | 361 |             Tcl_Obj *objPtr; | 
|---|
 | 362 |  | 
|---|
 | 363 |             Tcl_Seek(errorChan, (Tcl_WideInt)0, SEEK_SET); | 
|---|
 | 364 |             objPtr = Tcl_NewObj(); | 
|---|
 | 365 |             count = Tcl_ReadChars(errorChan, objPtr, -1, 0); | 
|---|
 | 366 |             if (count < 0) { | 
|---|
 | 367 |                 result = TCL_ERROR; | 
|---|
 | 368 |                 Tcl_DecrRefCount(objPtr); | 
|---|
 | 369 |                 Tcl_ResetResult(interp); | 
|---|
 | 370 |                 Tcl_AppendResult(interp, "error reading stderr output file: ", | 
|---|
 | 371 |                         Tcl_PosixError(interp), NULL); | 
|---|
 | 372 |             } else if (count > 0) { | 
|---|
 | 373 |                 anyErrorInfo = 1; | 
|---|
 | 374 |                 Tcl_SetObjResult(interp, objPtr); | 
|---|
 | 375 |                 result = TCL_ERROR; | 
|---|
 | 376 |             } else { | 
|---|
 | 377 |                 Tcl_DecrRefCount(objPtr); | 
|---|
 | 378 |             } | 
|---|
 | 379 |         } | 
|---|
 | 380 |         Tcl_Close(NULL, errorChan); | 
|---|
 | 381 |     } | 
|---|
 | 382 |  | 
|---|
 | 383 |     /* | 
|---|
 | 384 |      * If a child exited abnormally but didn't output any error information at | 
|---|
 | 385 |      * all, generate an error message here. | 
|---|
 | 386 |      */ | 
|---|
 | 387 |  | 
|---|
 | 388 |     if ((abnormalExit != 0) && (anyErrorInfo == 0) && (interp != NULL)) { | 
|---|
 | 389 |         Tcl_AppendResult(interp, "child process exited abnormally", NULL); | 
|---|
 | 390 |     } | 
|---|
 | 391 |     return result; | 
|---|
 | 392 | } | 
|---|
 | 393 |  | 
|---|
 | 394 | /* | 
|---|
 | 395 |  *---------------------------------------------------------------------- | 
|---|
 | 396 |  * | 
|---|
 | 397 |  * TclCreatePipeline -- | 
|---|
 | 398 |  * | 
|---|
 | 399 |  *      Given an argc/argv array, instantiate a pipeline of processes as | 
|---|
 | 400 |  *      described by the argv. | 
|---|
 | 401 |  * | 
|---|
 | 402 |  *      This function is unofficially exported for use by BLT. | 
|---|
 | 403 |  * | 
|---|
 | 404 |  * Results: | 
|---|
 | 405 |  *      The return value is a count of the number of new processes created, or | 
|---|
 | 406 |  *      -1 if an error occurred while creating the pipeline. *pidArrayPtr is | 
|---|
 | 407 |  *      filled in with the address of a dynamically allocated array giving the | 
|---|
 | 408 |  *      ids of all of the processes. It is up to the caller to free this array | 
|---|
 | 409 |  *      when it isn't needed anymore. If inPipePtr is non-NULL, *inPipePtr is | 
|---|
 | 410 |  *      filled in with the file id for the input pipe for the pipeline (if | 
|---|
 | 411 |  *      any): the caller must eventually close this file. If outPipePtr isn't | 
|---|
 | 412 |  *      NULL, then *outPipePtr is filled in with the file id for the output | 
|---|
 | 413 |  *      pipe from the pipeline: the caller must close this file. If errFilePtr | 
|---|
 | 414 |  *      isn't NULL, then *errFilePtr is filled with a file id that may be used | 
|---|
 | 415 |  *      to read error output after the pipeline completes. | 
|---|
 | 416 |  * | 
|---|
 | 417 |  * Side effects: | 
|---|
 | 418 |  *      Processes and pipes are created. | 
|---|
 | 419 |  * | 
|---|
 | 420 |  *---------------------------------------------------------------------- | 
|---|
 | 421 |  */ | 
|---|
 | 422 |  | 
|---|
 | 423 | int | 
|---|
 | 424 | TclCreatePipeline( | 
|---|
 | 425 |     Tcl_Interp *interp,         /* Interpreter to use for error reporting. */ | 
|---|
 | 426 |     int argc,                   /* Number of entries in argv. */ | 
|---|
 | 427 |     CONST char **argv,          /* Array of strings describing commands in | 
|---|
 | 428 |                                  * pipeline plus I/O redirection with <, <<, | 
|---|
 | 429 |                                  * >, etc. Argv[argc] must be NULL. */ | 
|---|
 | 430 |     Tcl_Pid **pidArrayPtr,      /* Word at *pidArrayPtr gets filled in with | 
|---|
 | 431 |                                  * address of array of pids for processes in | 
|---|
 | 432 |                                  * pipeline (first pid is first process in | 
|---|
 | 433 |                                  * pipeline). */ | 
|---|
 | 434 |     TclFile *inPipePtr,         /* If non-NULL, input to the pipeline comes | 
|---|
 | 435 |                                  * from a pipe (unless overridden by | 
|---|
 | 436 |                                  * redirection in the command). The file id | 
|---|
 | 437 |                                  * with which to write to this pipe is stored | 
|---|
 | 438 |                                  * at *inPipePtr. NULL means command specified | 
|---|
 | 439 |                                  * its own input source. */ | 
|---|
 | 440 |     TclFile *outPipePtr,        /* If non-NULL, output to the pipeline goes to | 
|---|
 | 441 |                                  * a pipe, unless overriden by redirection in | 
|---|
 | 442 |                                  * the command. The file id with which to read | 
|---|
 | 443 |                                  * frome this pipe is stored at *outPipePtr. | 
|---|
 | 444 |                                  * NULL means command specified its own output | 
|---|
 | 445 |                                  * sink. */ | 
|---|
 | 446 |     TclFile *errFilePtr)        /* If non-NULL, all stderr output from the | 
|---|
 | 447 |                                  * pipeline will go to a temporary file | 
|---|
 | 448 |                                  * created here, and a descriptor to read the | 
|---|
 | 449 |                                  * file will be left at *errFilePtr. The file | 
|---|
 | 450 |                                  * will be removed already, so closing this | 
|---|
 | 451 |                                  * descriptor will be the end of the file. If | 
|---|
 | 452 |                                  * this is NULL, then all stderr output goes | 
|---|
 | 453 |                                  * to our stderr. If the pipeline specifies | 
|---|
 | 454 |                                  * redirection then the file will still be | 
|---|
 | 455 |                                  * created but it will never get any data. */ | 
|---|
 | 456 | { | 
|---|
 | 457 |     Tcl_Pid *pidPtr = NULL;     /* Points to malloc-ed array holding all the | 
|---|
 | 458 |                                  * pids of child processes. */ | 
|---|
 | 459 |     int numPids;                /* Actual number of processes that exist at | 
|---|
 | 460 |                                  * *pidPtr right now. */ | 
|---|
 | 461 |     int cmdCount;               /* Count of number of distinct commands found | 
|---|
 | 462 |                                  * in argc/argv. */ | 
|---|
 | 463 |     CONST char *inputLiteral = NULL; | 
|---|
 | 464 |                                 /* If non-null, then this points to a string | 
|---|
 | 465 |                                  * containing input data (specified via <<) to | 
|---|
 | 466 |                                  * be piped to the first process in the | 
|---|
 | 467 |                                  * pipeline. */ | 
|---|
 | 468 |     TclFile inputFile = NULL;   /* If != NULL, gives file to use as input for | 
|---|
 | 469 |                                  * first process in pipeline (specified via < | 
|---|
 | 470 |                                  * or <@). */ | 
|---|
 | 471 |     int inputClose = 0;         /* If non-zero, then inputFile should be | 
|---|
 | 472 |                                  * closed when cleaning up. */ | 
|---|
 | 473 |     int inputRelease = 0; | 
|---|
 | 474 |     TclFile outputFile = NULL;  /* Writable file for output from last command | 
|---|
 | 475 |                                  * in pipeline (could be file or pipe). NULL | 
|---|
 | 476 |                                  * means use stdout. */ | 
|---|
 | 477 |     int outputClose = 0;        /* If non-zero, then outputFile should be | 
|---|
 | 478 |                                  * closed when cleaning up. */ | 
|---|
 | 479 |     int outputRelease = 0; | 
|---|
 | 480 |     TclFile errorFile = NULL;   /* Writable file for error output from all | 
|---|
 | 481 |                                  * commands in pipeline. NULL means use | 
|---|
 | 482 |                                  * stderr. */ | 
|---|
 | 483 |     int errorClose = 0;         /* If non-zero, then errorFile should be | 
|---|
 | 484 |                                  * closed when cleaning up. */ | 
|---|
 | 485 |     int errorRelease = 0; | 
|---|
 | 486 |     CONST char *p; | 
|---|
 | 487 |     CONST char *nextArg; | 
|---|
 | 488 |     int skip, lastBar, lastArg, i, j, atOK, flags, needCmd, errorToOutput = 0; | 
|---|
 | 489 |     Tcl_DString execBuffer; | 
|---|
 | 490 |     TclFile pipeIn; | 
|---|
 | 491 |     TclFile curInFile, curOutFile, curErrFile; | 
|---|
 | 492 |     Tcl_Channel channel; | 
|---|
 | 493 |  | 
|---|
 | 494 |     if (inPipePtr != NULL) { | 
|---|
 | 495 |         *inPipePtr = NULL; | 
|---|
 | 496 |     } | 
|---|
 | 497 |     if (outPipePtr != NULL) { | 
|---|
 | 498 |         *outPipePtr = NULL; | 
|---|
 | 499 |     } | 
|---|
 | 500 |     if (errFilePtr != NULL) { | 
|---|
 | 501 |         *errFilePtr = NULL; | 
|---|
 | 502 |     } | 
|---|
 | 503 |  | 
|---|
 | 504 |     Tcl_DStringInit(&execBuffer); | 
|---|
 | 505 |  | 
|---|
 | 506 |     pipeIn = NULL; | 
|---|
 | 507 |     curInFile = NULL; | 
|---|
 | 508 |     curOutFile = NULL; | 
|---|
 | 509 |     numPids = 0; | 
|---|
 | 510 |  | 
|---|
 | 511 |     /* | 
|---|
 | 512 |      * First, scan through all the arguments to figure out the structure of | 
|---|
 | 513 |      * the pipeline. Process all of the input and output redirection arguments | 
|---|
 | 514 |      * and remove them from the argument list in the pipeline. Count the | 
|---|
 | 515 |      * number of distinct processes (it's the number of "|" arguments plus | 
|---|
 | 516 |      * one) but don't remove the "|" arguments because they'll be used in the | 
|---|
 | 517 |      * second pass to seperate the individual child processes. Cannot start | 
|---|
 | 518 |      * the child processes in this pass because the redirection symbols may | 
|---|
 | 519 |      * appear anywhere in the command line - e.g., the '<' that specifies the | 
|---|
 | 520 |      * input to the entire pipe may appear at the very end of the argument | 
|---|
 | 521 |      * list. | 
|---|
 | 522 |      */ | 
|---|
 | 523 |  | 
|---|
 | 524 |     lastBar = -1; | 
|---|
 | 525 |     cmdCount = 1; | 
|---|
 | 526 |     needCmd = 1; | 
|---|
 | 527 |     for (i = 0; i < argc; i++) { | 
|---|
 | 528 |         errorToOutput = 0; | 
|---|
 | 529 |         skip = 0; | 
|---|
 | 530 |         p = argv[i]; | 
|---|
 | 531 |         switch (*p++) { | 
|---|
 | 532 |         case '|': | 
|---|
 | 533 |             if (*p == '&') { | 
|---|
 | 534 |                 p++; | 
|---|
 | 535 |             } | 
|---|
 | 536 |             if (*p == '\0') { | 
|---|
 | 537 |                 if ((i == (lastBar + 1)) || (i == (argc - 1))) { | 
|---|
 | 538 |                     Tcl_SetResult(interp, "illegal use of | or |& in command", | 
|---|
 | 539 |                             TCL_STATIC); | 
|---|
 | 540 |                     goto error; | 
|---|
 | 541 |                 } | 
|---|
 | 542 |             } | 
|---|
 | 543 |             lastBar = i; | 
|---|
 | 544 |             cmdCount++; | 
|---|
 | 545 |             needCmd = 1; | 
|---|
 | 546 |             break; | 
|---|
 | 547 |  | 
|---|
 | 548 |         case '<': | 
|---|
 | 549 |             if (inputClose != 0) { | 
|---|
 | 550 |                 inputClose = 0; | 
|---|
 | 551 |                 TclpCloseFile(inputFile); | 
|---|
 | 552 |             } | 
|---|
 | 553 |             if (inputRelease != 0) { | 
|---|
 | 554 |                 inputRelease = 0; | 
|---|
 | 555 |                 TclpReleaseFile(inputFile); | 
|---|
 | 556 |             } | 
|---|
 | 557 |             if (*p == '<') { | 
|---|
 | 558 |                 inputFile = NULL; | 
|---|
 | 559 |                 inputLiteral = p + 1; | 
|---|
 | 560 |                 skip = 1; | 
|---|
 | 561 |                 if (*inputLiteral == '\0') { | 
|---|
 | 562 |                     inputLiteral = ((i + 1) == argc) ? NULL : argv[i + 1]; | 
|---|
 | 563 |                     if (inputLiteral == NULL) { | 
|---|
 | 564 |                         Tcl_AppendResult(interp, "can't specify \"", argv[i], | 
|---|
 | 565 |                                 "\" as last word in command", NULL); | 
|---|
 | 566 |                         goto error; | 
|---|
 | 567 |                     } | 
|---|
 | 568 |                     skip = 2; | 
|---|
 | 569 |                 } | 
|---|
 | 570 |             } else { | 
|---|
 | 571 |                 nextArg = ((i + 1) == argc) ? NULL : argv[i + 1]; | 
|---|
 | 572 |                 inputLiteral = NULL; | 
|---|
 | 573 |                 inputFile = FileForRedirect(interp, p, 1, argv[i], nextArg, | 
|---|
 | 574 |                         O_RDONLY, &skip, &inputClose, &inputRelease); | 
|---|
 | 575 |                 if (inputFile == NULL) { | 
|---|
 | 576 |                     goto error; | 
|---|
 | 577 |                 } | 
|---|
 | 578 |             } | 
|---|
 | 579 |             break; | 
|---|
 | 580 |  | 
|---|
 | 581 |         case '>': | 
|---|
 | 582 |             atOK = 1; | 
|---|
 | 583 |             flags = O_WRONLY | O_CREAT | O_TRUNC; | 
|---|
 | 584 |             if (*p == '>') { | 
|---|
 | 585 |                 p++; | 
|---|
 | 586 |                 atOK = 0; | 
|---|
 | 587 |  | 
|---|
 | 588 |                 /* | 
|---|
 | 589 |                  * Note that the O_APPEND flag only has an effect on POSIX | 
|---|
 | 590 |                  * platforms. On Windows, we just have to carry on regardless. | 
|---|
 | 591 |                  */ | 
|---|
 | 592 |  | 
|---|
 | 593 |                 flags = O_WRONLY | O_CREAT | O_APPEND; | 
|---|
 | 594 |             } | 
|---|
 | 595 |             if (*p == '&') { | 
|---|
 | 596 |                 if (errorClose != 0) { | 
|---|
 | 597 |                     errorClose = 0; | 
|---|
 | 598 |                     TclpCloseFile(errorFile); | 
|---|
 | 599 |                 } | 
|---|
 | 600 |                 errorToOutput = 1; | 
|---|
 | 601 |                 p++; | 
|---|
 | 602 |             } | 
|---|
 | 603 |  | 
|---|
 | 604 |             /* | 
|---|
 | 605 |              * Close the old output file, but only if the error file is not | 
|---|
 | 606 |              * also using it. | 
|---|
 | 607 |              */ | 
|---|
 | 608 |  | 
|---|
 | 609 |             if (outputClose != 0) { | 
|---|
 | 610 |                 outputClose = 0; | 
|---|
 | 611 |                 if (errorFile == outputFile) { | 
|---|
 | 612 |                     errorClose = 1; | 
|---|
 | 613 |                 } else { | 
|---|
 | 614 |                     TclpCloseFile(outputFile); | 
|---|
 | 615 |                 } | 
|---|
 | 616 |             } | 
|---|
 | 617 |             if (outputRelease != 0) { | 
|---|
 | 618 |                 outputRelease = 0; | 
|---|
 | 619 |                 if (errorFile == outputFile) { | 
|---|
 | 620 |                     errorRelease = 1; | 
|---|
 | 621 |                 } else { | 
|---|
 | 622 |                     TclpReleaseFile(outputFile); | 
|---|
 | 623 |                 } | 
|---|
 | 624 |             } | 
|---|
 | 625 |             nextArg = ((i + 1) == argc) ? NULL : argv[i + 1]; | 
|---|
 | 626 |             outputFile = FileForRedirect(interp, p, atOK, argv[i], nextArg, | 
|---|
 | 627 |                     flags, &skip, &outputClose, &outputRelease); | 
|---|
 | 628 |             if (outputFile == NULL) { | 
|---|
 | 629 |                 goto error; | 
|---|
 | 630 |             } | 
|---|
 | 631 |             if (errorToOutput) { | 
|---|
 | 632 |                 if (errorClose != 0) { | 
|---|
 | 633 |                     errorClose = 0; | 
|---|
 | 634 |                     TclpCloseFile(errorFile); | 
|---|
 | 635 |                 } | 
|---|
 | 636 |                 if (errorRelease != 0) { | 
|---|
 | 637 |                     errorRelease = 0; | 
|---|
 | 638 |                     TclpReleaseFile(errorFile); | 
|---|
 | 639 |                 } | 
|---|
 | 640 |                 errorFile = outputFile; | 
|---|
 | 641 |             } | 
|---|
 | 642 |             break; | 
|---|
 | 643 |  | 
|---|
 | 644 |         case '2': | 
|---|
 | 645 |             if (*p != '>') { | 
|---|
 | 646 |                 break; | 
|---|
 | 647 |             } | 
|---|
 | 648 |             p++; | 
|---|
 | 649 |             atOK = 1; | 
|---|
 | 650 |             flags = O_WRONLY | O_CREAT | O_TRUNC; | 
|---|
 | 651 |             if (*p == '>') { | 
|---|
 | 652 |                 p++; | 
|---|
 | 653 |                 atOK = 0; | 
|---|
 | 654 |                 flags = O_WRONLY | O_CREAT; | 
|---|
 | 655 |             } | 
|---|
 | 656 |             if (errorClose != 0) { | 
|---|
 | 657 |                 errorClose = 0; | 
|---|
 | 658 |                 TclpCloseFile(errorFile); | 
|---|
 | 659 |             } | 
|---|
 | 660 |             if (errorRelease != 0) { | 
|---|
 | 661 |                 errorRelease = 0; | 
|---|
 | 662 |                 TclpReleaseFile(errorFile); | 
|---|
 | 663 |             } | 
|---|
 | 664 |             if (atOK && p[0] == '@' && p[1] == '1' && p[2] == '\0') { | 
|---|
 | 665 |                 /* | 
|---|
 | 666 |                  * Special case handling of 2>@1 to redirect stderr to the | 
|---|
 | 667 |                  * exec/open output pipe as well. This is meant for the end of | 
|---|
 | 668 |                  * the command string, otherwise use |& between commands. | 
|---|
 | 669 |                  */ | 
|---|
 | 670 |  | 
|---|
 | 671 |                 if (i != argc-1) { | 
|---|
 | 672 |                     Tcl_AppendResult(interp, "must specify \"", argv[i], | 
|---|
 | 673 |                             "\" as last word in command", NULL); | 
|---|
 | 674 |                     goto error; | 
|---|
 | 675 |                 } | 
|---|
 | 676 |                 errorFile = outputFile; | 
|---|
 | 677 |                 errorToOutput = 2; | 
|---|
 | 678 |                 skip = 1; | 
|---|
 | 679 |             } else { | 
|---|
 | 680 |                 nextArg = ((i + 1) == argc) ? NULL : argv[i + 1]; | 
|---|
 | 681 |                 errorFile = FileForRedirect(interp, p, atOK, argv[i], | 
|---|
 | 682 |                         nextArg, flags, &skip, &errorClose, &errorRelease); | 
|---|
 | 683 |                 if (errorFile == NULL) { | 
|---|
 | 684 |                     goto error; | 
|---|
 | 685 |                 } | 
|---|
 | 686 |             } | 
|---|
 | 687 |             break; | 
|---|
 | 688 |  | 
|---|
 | 689 |         default: | 
|---|
 | 690 |           /* Got a command word, not a redirection */ | 
|---|
 | 691 |           needCmd = 0; | 
|---|
 | 692 |           break; | 
|---|
 | 693 |         } | 
|---|
 | 694 |  | 
|---|
 | 695 |         if (skip != 0) { | 
|---|
 | 696 |             for (j = i + skip; j < argc; j++) { | 
|---|
 | 697 |                 argv[j - skip] = argv[j]; | 
|---|
 | 698 |             } | 
|---|
 | 699 |             argc -= skip; | 
|---|
 | 700 |             i -= 1; | 
|---|
 | 701 |         } | 
|---|
 | 702 |     } | 
|---|
 | 703 |  | 
|---|
 | 704 |     if (needCmd) { | 
|---|
 | 705 |         /* We had a bar followed only by redirections. */ | 
|---|
 | 706 |  | 
|---|
 | 707 |         Tcl_SetResult(interp, | 
|---|
 | 708 |                       "illegal use of | or |& in command", | 
|---|
 | 709 |                       TCL_STATIC); | 
|---|
 | 710 |         goto error; | 
|---|
 | 711 |     } | 
|---|
 | 712 |  | 
|---|
 | 713 |     if (inputFile == NULL) { | 
|---|
 | 714 |         if (inputLiteral != NULL) { | 
|---|
 | 715 |             /* | 
|---|
 | 716 |              * The input for the first process is immediate data coming from | 
|---|
 | 717 |              * Tcl. Create a temporary file for it and put the data into the | 
|---|
 | 718 |              * file. | 
|---|
 | 719 |              */ | 
|---|
 | 720 |  | 
|---|
 | 721 |             inputFile = TclpCreateTempFile(inputLiteral); | 
|---|
 | 722 |             if (inputFile == NULL) { | 
|---|
 | 723 |                 Tcl_AppendResult(interp, | 
|---|
 | 724 |                         "couldn't create input file for command: ", | 
|---|
 | 725 |                         Tcl_PosixError(interp), NULL); | 
|---|
 | 726 |                 goto error; | 
|---|
 | 727 |             } | 
|---|
 | 728 |             inputClose = 1; | 
|---|
 | 729 |         } else if (inPipePtr != NULL) { | 
|---|
 | 730 |             /* | 
|---|
 | 731 |              * The input for the first process in the pipeline is to come from | 
|---|
 | 732 |              * a pipe that can be written from by the caller. | 
|---|
 | 733 |              */ | 
|---|
 | 734 |  | 
|---|
 | 735 |             if (TclpCreatePipe(&inputFile, inPipePtr) == 0) { | 
|---|
 | 736 |                 Tcl_AppendResult(interp, | 
|---|
 | 737 |                         "couldn't create input pipe for command: ", | 
|---|
 | 738 |                         Tcl_PosixError(interp), NULL); | 
|---|
 | 739 |                 goto error; | 
|---|
 | 740 |             } | 
|---|
 | 741 |             inputClose = 1; | 
|---|
 | 742 |         } else { | 
|---|
 | 743 |             /* | 
|---|
 | 744 |              * The input for the first process comes from stdin. | 
|---|
 | 745 |              */ | 
|---|
 | 746 |  | 
|---|
 | 747 |             channel = Tcl_GetStdChannel(TCL_STDIN); | 
|---|
 | 748 |             if (channel != NULL) { | 
|---|
 | 749 |                 inputFile = TclpMakeFile(channel, TCL_READABLE); | 
|---|
 | 750 |                 if (inputFile != NULL) { | 
|---|
 | 751 |                     inputRelease = 1; | 
|---|
 | 752 |                 } | 
|---|
 | 753 |             } | 
|---|
 | 754 |         } | 
|---|
 | 755 |     } | 
|---|
 | 756 |  | 
|---|
 | 757 |     if (outputFile == NULL) { | 
|---|
 | 758 |         if (outPipePtr != NULL) { | 
|---|
 | 759 |             /* | 
|---|
 | 760 |              * Output from the last process in the pipeline is to go to a pipe | 
|---|
 | 761 |              * that can be read by the caller. | 
|---|
 | 762 |              */ | 
|---|
 | 763 |  | 
|---|
 | 764 |             if (TclpCreatePipe(outPipePtr, &outputFile) == 0) { | 
|---|
 | 765 |                 Tcl_AppendResult(interp, | 
|---|
 | 766 |                         "couldn't create output pipe for command: ", | 
|---|
 | 767 |                         Tcl_PosixError(interp), NULL); | 
|---|
 | 768 |                 goto error; | 
|---|
 | 769 |             } | 
|---|
 | 770 |             outputClose = 1; | 
|---|
 | 771 |         } else { | 
|---|
 | 772 |             /* | 
|---|
 | 773 |              * The output for the last process goes to stdout. | 
|---|
 | 774 |              */ | 
|---|
 | 775 |  | 
|---|
 | 776 |             channel = Tcl_GetStdChannel(TCL_STDOUT); | 
|---|
 | 777 |             if (channel) { | 
|---|
 | 778 |                 outputFile = TclpMakeFile(channel, TCL_WRITABLE); | 
|---|
 | 779 |                 if (outputFile != NULL) { | 
|---|
 | 780 |                     outputRelease = 1; | 
|---|
 | 781 |                 } | 
|---|
 | 782 |             } | 
|---|
 | 783 |         } | 
|---|
 | 784 |     } | 
|---|
 | 785 |  | 
|---|
 | 786 |     if (errorFile == NULL) { | 
|---|
 | 787 |         if (errorToOutput == 2) { | 
|---|
 | 788 |             /* | 
|---|
 | 789 |              * Handle 2>@1 special case at end of cmd line. | 
|---|
 | 790 |              */ | 
|---|
 | 791 |  | 
|---|
 | 792 |             errorFile = outputFile; | 
|---|
 | 793 |         } else if (errFilePtr != NULL) { | 
|---|
 | 794 |             /* | 
|---|
 | 795 |              * Set up the standard error output sink for the pipeline, if | 
|---|
 | 796 |              * requested. Use a temporary file which is opened, then deleted. | 
|---|
 | 797 |              * Could potentially just use pipe, but if it filled up it could | 
|---|
 | 798 |              * cause the pipeline to deadlock: we'd be waiting for processes | 
|---|
 | 799 |              * to complete before reading stderr, and processes couldn't | 
|---|
 | 800 |              * complete because stderr was backed up. | 
|---|
 | 801 |              */ | 
|---|
 | 802 |  | 
|---|
 | 803 |             errorFile = TclpCreateTempFile(NULL); | 
|---|
 | 804 |             if (errorFile == NULL) { | 
|---|
 | 805 |                 Tcl_AppendResult(interp, | 
|---|
 | 806 |                         "couldn't create error file for command: ", | 
|---|
 | 807 |                         Tcl_PosixError(interp), NULL); | 
|---|
 | 808 |                 goto error; | 
|---|
 | 809 |             } | 
|---|
 | 810 |             *errFilePtr = errorFile; | 
|---|
 | 811 |         } else { | 
|---|
 | 812 |             /* | 
|---|
 | 813 |              * Errors from the pipeline go to stderr. | 
|---|
 | 814 |              */ | 
|---|
 | 815 |  | 
|---|
 | 816 |             channel = Tcl_GetStdChannel(TCL_STDERR); | 
|---|
 | 817 |             if (channel) { | 
|---|
 | 818 |                 errorFile = TclpMakeFile(channel, TCL_WRITABLE); | 
|---|
 | 819 |                 if (errorFile != NULL) { | 
|---|
 | 820 |                     errorRelease = 1; | 
|---|
 | 821 |                 } | 
|---|
 | 822 |             } | 
|---|
 | 823 |         } | 
|---|
 | 824 |     } | 
|---|
 | 825 |  | 
|---|
 | 826 |     /* | 
|---|
 | 827 |      * Scan through the argc array, creating a process for each group of | 
|---|
 | 828 |      * arguments between the "|" characters. | 
|---|
 | 829 |      */ | 
|---|
 | 830 |  | 
|---|
 | 831 |     Tcl_ReapDetachedProcs(); | 
|---|
 | 832 |     pidPtr = (Tcl_Pid *) ckalloc((unsigned) (cmdCount * sizeof(Tcl_Pid))); | 
|---|
 | 833 |  | 
|---|
 | 834 |     curInFile = inputFile; | 
|---|
 | 835 |  | 
|---|
 | 836 |     for (i = 0; i < argc; i = lastArg + 1) { | 
|---|
 | 837 |         int result, joinThisError; | 
|---|
 | 838 |         Tcl_Pid pid; | 
|---|
 | 839 |         CONST char *oldName; | 
|---|
 | 840 |  | 
|---|
 | 841 |         /* | 
|---|
 | 842 |          * Convert the program name into native form. | 
|---|
 | 843 |          */ | 
|---|
 | 844 |  | 
|---|
 | 845 |         if (Tcl_TranslateFileName(interp, argv[i], &execBuffer) == NULL) { | 
|---|
 | 846 |             goto error; | 
|---|
 | 847 |         } | 
|---|
 | 848 |  | 
|---|
 | 849 |         /* | 
|---|
 | 850 |          * Find the end of the current segment of the pipeline. | 
|---|
 | 851 |          */ | 
|---|
 | 852 |  | 
|---|
 | 853 |         joinThisError = 0; | 
|---|
 | 854 |         for (lastArg = i; lastArg < argc; lastArg++) { | 
|---|
 | 855 |             if (argv[lastArg][0] != '|') { | 
|---|
 | 856 |                 continue; | 
|---|
 | 857 |             } | 
|---|
 | 858 |             if (argv[lastArg][1] == '\0') { | 
|---|
 | 859 |                 break; | 
|---|
 | 860 |             } | 
|---|
 | 861 |             if ((argv[lastArg][1] == '&') && (argv[lastArg][2] == '\0')) { | 
|---|
 | 862 |                 joinThisError = 1; | 
|---|
 | 863 |                 break; | 
|---|
 | 864 |             } | 
|---|
 | 865 |         } | 
|---|
 | 866 |  | 
|---|
 | 867 |         /* | 
|---|
 | 868 |          * If this is the last segment, use the specified outputFile. | 
|---|
 | 869 |          * Otherwise create an intermediate pipe. pipeIn will become the | 
|---|
 | 870 |          * curInFile for the next segment of the pipe. | 
|---|
 | 871 |          */ | 
|---|
 | 872 |  | 
|---|
 | 873 |         if (lastArg == argc) { | 
|---|
 | 874 |             curOutFile = outputFile; | 
|---|
 | 875 |         } else { | 
|---|
 | 876 |             argv[lastArg] = NULL; | 
|---|
 | 877 |             if (TclpCreatePipe(&pipeIn, &curOutFile) == 0) { | 
|---|
 | 878 |                 Tcl_AppendResult(interp, "couldn't create pipe: ", | 
|---|
 | 879 |                         Tcl_PosixError(interp), NULL); | 
|---|
 | 880 |                 goto error; | 
|---|
 | 881 |             } | 
|---|
 | 882 |         } | 
|---|
 | 883 |  | 
|---|
 | 884 |         if (joinThisError != 0) { | 
|---|
 | 885 |             curErrFile = curOutFile; | 
|---|
 | 886 |         } else { | 
|---|
 | 887 |             curErrFile = errorFile; | 
|---|
 | 888 |         } | 
|---|
 | 889 |  | 
|---|
 | 890 |         /* | 
|---|
 | 891 |          * Restore argv[i], since a caller wouldn't expect the contents of | 
|---|
 | 892 |          * argv to be modified. | 
|---|
 | 893 |          */ | 
|---|
 | 894 |  | 
|---|
 | 895 |         oldName = argv[i]; | 
|---|
 | 896 |         argv[i] = Tcl_DStringValue(&execBuffer); | 
|---|
 | 897 |         result = TclpCreateProcess(interp, lastArg - i, argv + i, | 
|---|
 | 898 |                 curInFile, curOutFile, curErrFile, &pid); | 
|---|
 | 899 |         argv[i] = oldName; | 
|---|
 | 900 |         if (result != TCL_OK) { | 
|---|
 | 901 |             goto error; | 
|---|
 | 902 |         } | 
|---|
 | 903 |         Tcl_DStringFree(&execBuffer); | 
|---|
 | 904 |  | 
|---|
 | 905 |         pidPtr[numPids] = pid; | 
|---|
 | 906 |         numPids++; | 
|---|
 | 907 |  | 
|---|
 | 908 |         /* | 
|---|
 | 909 |          * Close off our copies of file descriptors that were set up for this | 
|---|
 | 910 |          * child, then set up the input for the next child. | 
|---|
 | 911 |          */ | 
|---|
 | 912 |  | 
|---|
 | 913 |         if ((curInFile != NULL) && (curInFile != inputFile)) { | 
|---|
 | 914 |             TclpCloseFile(curInFile); | 
|---|
 | 915 |         } | 
|---|
 | 916 |         curInFile = pipeIn; | 
|---|
 | 917 |         pipeIn = NULL; | 
|---|
 | 918 |  | 
|---|
 | 919 |         if ((curOutFile != NULL) && (curOutFile != outputFile)) { | 
|---|
 | 920 |             TclpCloseFile(curOutFile); | 
|---|
 | 921 |         } | 
|---|
 | 922 |         curOutFile = NULL; | 
|---|
 | 923 |     } | 
|---|
 | 924 |  | 
|---|
 | 925 |     *pidArrayPtr = pidPtr; | 
|---|
 | 926 |  | 
|---|
 | 927 |     /* | 
|---|
 | 928 |      * All done. Cleanup open files lying around and then return. | 
|---|
 | 929 |      */ | 
|---|
 | 930 |  | 
|---|
 | 931 |   cleanup: | 
|---|
 | 932 |     Tcl_DStringFree(&execBuffer); | 
|---|
 | 933 |  | 
|---|
 | 934 |     if (inputClose) { | 
|---|
 | 935 |         TclpCloseFile(inputFile); | 
|---|
 | 936 |     } else if (inputRelease) { | 
|---|
 | 937 |         TclpReleaseFile(inputFile); | 
|---|
 | 938 |     } | 
|---|
 | 939 |     if (outputClose) { | 
|---|
 | 940 |         TclpCloseFile(outputFile); | 
|---|
 | 941 |     } else if (outputRelease) { | 
|---|
 | 942 |         TclpReleaseFile(outputFile); | 
|---|
 | 943 |     } | 
|---|
 | 944 |     if (errorClose) { | 
|---|
 | 945 |         TclpCloseFile(errorFile); | 
|---|
 | 946 |     } else if (errorRelease) { | 
|---|
 | 947 |         TclpReleaseFile(errorFile); | 
|---|
 | 948 |     } | 
|---|
 | 949 |     return numPids; | 
|---|
 | 950 |  | 
|---|
 | 951 |     /* | 
|---|
 | 952 |      * An error occurred. There could have been extra files open, such as | 
|---|
 | 953 |      * pipes between children. Clean them all up. Detach any child processes | 
|---|
 | 954 |      * that have been created. | 
|---|
 | 955 |      */ | 
|---|
 | 956 |  | 
|---|
 | 957 |   error: | 
|---|
 | 958 |     if (pipeIn != NULL) { | 
|---|
 | 959 |         TclpCloseFile(pipeIn); | 
|---|
 | 960 |     } | 
|---|
 | 961 |     if ((curOutFile != NULL) && (curOutFile != outputFile)) { | 
|---|
 | 962 |         TclpCloseFile(curOutFile); | 
|---|
 | 963 |     } | 
|---|
 | 964 |     if ((curInFile != NULL) && (curInFile != inputFile)) { | 
|---|
 | 965 |         TclpCloseFile(curInFile); | 
|---|
 | 966 |     } | 
|---|
 | 967 |     if ((inPipePtr != NULL) && (*inPipePtr != NULL)) { | 
|---|
 | 968 |         TclpCloseFile(*inPipePtr); | 
|---|
 | 969 |         *inPipePtr = NULL; | 
|---|
 | 970 |     } | 
|---|
 | 971 |     if ((outPipePtr != NULL) && (*outPipePtr != NULL)) { | 
|---|
 | 972 |         TclpCloseFile(*outPipePtr); | 
|---|
 | 973 |         *outPipePtr = NULL; | 
|---|
 | 974 |     } | 
|---|
 | 975 |     if ((errFilePtr != NULL) && (*errFilePtr != NULL)) { | 
|---|
 | 976 |         TclpCloseFile(*errFilePtr); | 
|---|
 | 977 |         *errFilePtr = NULL; | 
|---|
 | 978 |     } | 
|---|
 | 979 |     if (pidPtr != NULL) { | 
|---|
 | 980 |         for (i = 0; i < numPids; i++) { | 
|---|
 | 981 |             if (pidPtr[i] != (Tcl_Pid) -1) { | 
|---|
 | 982 |                 Tcl_DetachPids(1, &pidPtr[i]); | 
|---|
 | 983 |             } | 
|---|
 | 984 |         } | 
|---|
 | 985 |         ckfree((char *) pidPtr); | 
|---|
 | 986 |     } | 
|---|
 | 987 |     numPids = -1; | 
|---|
 | 988 |     goto cleanup; | 
|---|
 | 989 | } | 
|---|
 | 990 |  | 
|---|
 | 991 | /* | 
|---|
 | 992 |  *---------------------------------------------------------------------- | 
|---|
 | 993 |  * | 
|---|
 | 994 |  * Tcl_OpenCommandChannel -- | 
|---|
 | 995 |  * | 
|---|
 | 996 |  *      Opens an I/O channel to one or more subprocesses specified by argc and | 
|---|
 | 997 |  *      argv. The flags argument determines the disposition of the stdio | 
|---|
 | 998 |  *      handles. If the TCL_STDIN flag is set then the standard input for the | 
|---|
 | 999 |  *      first subprocess will be tied to the channel: writing to the channel | 
|---|
 | 1000 |  *      will provide input to the subprocess. If TCL_STDIN is not set, then | 
|---|
 | 1001 |  *      standard input for the first subprocess will be the same as this | 
|---|
 | 1002 |  *      application's standard input. If TCL_STDOUT is set then standard | 
|---|
 | 1003 |  *      output from the last subprocess can be read from the channel; | 
|---|
 | 1004 |  *      otherwise it goes to this application's standard output. If TCL_STDERR | 
|---|
 | 1005 |  *      is set, standard error output for all subprocesses is returned to the | 
|---|
 | 1006 |  *      channel and results in an error when the channel is closed; otherwise | 
|---|
 | 1007 |  *      it goes to this application's standard error. If TCL_ENFORCE_MODE is | 
|---|
 | 1008 |  *      not set, then argc and argv can redirect the stdio handles to override | 
|---|
 | 1009 |  *      TCL_STDIN, TCL_STDOUT, and TCL_STDERR; if it is set, then it is an | 
|---|
 | 1010 |  *      error for argc and argv to override stdio channels for which | 
|---|
 | 1011 |  *      TCL_STDIN, TCL_STDOUT, and TCL_STDERR have been set. | 
|---|
 | 1012 |  * | 
|---|
 | 1013 |  * Results: | 
|---|
 | 1014 |  *      A new command channel, or NULL on failure with an error message left | 
|---|
 | 1015 |  *      in interp. | 
|---|
 | 1016 |  * | 
|---|
 | 1017 |  * Side effects: | 
|---|
 | 1018 |  *      Creates processes, opens pipes. | 
|---|
 | 1019 |  * | 
|---|
 | 1020 |  *---------------------------------------------------------------------- | 
|---|
 | 1021 |  */ | 
|---|
 | 1022 |  | 
|---|
 | 1023 | Tcl_Channel | 
|---|
 | 1024 | Tcl_OpenCommandChannel( | 
|---|
 | 1025 |     Tcl_Interp *interp,         /* Interpreter for error reporting. Can NOT be | 
|---|
 | 1026 |                                  * NULL. */ | 
|---|
 | 1027 |     int argc,                   /* How many arguments. */ | 
|---|
 | 1028 |     CONST char **argv,          /* Array of arguments for command pipe. */ | 
|---|
 | 1029 |     int flags)                  /* Or'ed combination of TCL_STDIN, TCL_STDOUT, | 
|---|
 | 1030 |                                  * TCL_STDERR, and TCL_ENFORCE_MODE. */ | 
|---|
 | 1031 | { | 
|---|
 | 1032 |     TclFile *inPipePtr, *outPipePtr, *errFilePtr; | 
|---|
 | 1033 |     TclFile inPipe, outPipe, errFile; | 
|---|
 | 1034 |     int numPids; | 
|---|
 | 1035 |     Tcl_Pid *pidPtr; | 
|---|
 | 1036 |     Tcl_Channel channel; | 
|---|
 | 1037 |  | 
|---|
 | 1038 |     inPipe = outPipe = errFile = NULL; | 
|---|
 | 1039 |  | 
|---|
 | 1040 |     inPipePtr = (flags & TCL_STDIN) ? &inPipe : NULL; | 
|---|
 | 1041 |     outPipePtr = (flags & TCL_STDOUT) ? &outPipe : NULL; | 
|---|
 | 1042 |     errFilePtr = (flags & TCL_STDERR) ? &errFile : NULL; | 
|---|
 | 1043 |  | 
|---|
 | 1044 |     numPids = TclCreatePipeline(interp, argc, argv, &pidPtr, inPipePtr, | 
|---|
 | 1045 |             outPipePtr, errFilePtr); | 
|---|
 | 1046 |  | 
|---|
 | 1047 |     if (numPids < 0) { | 
|---|
 | 1048 |         goto error; | 
|---|
 | 1049 |     } | 
|---|
 | 1050 |  | 
|---|
 | 1051 |     /* | 
|---|
 | 1052 |      * Verify that the pipes that were created satisfy the readable/writable | 
|---|
 | 1053 |      * constraints. | 
|---|
 | 1054 |      */ | 
|---|
 | 1055 |  | 
|---|
 | 1056 |     if (flags & TCL_ENFORCE_MODE) { | 
|---|
 | 1057 |         if ((flags & TCL_STDOUT) && (outPipe == NULL)) { | 
|---|
 | 1058 |             Tcl_AppendResult(interp, "can't read output from command:" | 
|---|
 | 1059 |                     " standard output was redirected", NULL); | 
|---|
 | 1060 |             goto error; | 
|---|
 | 1061 |         } | 
|---|
 | 1062 |         if ((flags & TCL_STDIN) && (inPipe == NULL)) { | 
|---|
 | 1063 |             Tcl_AppendResult(interp, "can't write input to command:" | 
|---|
 | 1064 |                     " standard input was redirected", NULL); | 
|---|
 | 1065 |             goto error; | 
|---|
 | 1066 |         } | 
|---|
 | 1067 |     } | 
|---|
 | 1068 |  | 
|---|
 | 1069 |     channel = TclpCreateCommandChannel(outPipe, inPipe, errFile, | 
|---|
 | 1070 |             numPids, pidPtr); | 
|---|
 | 1071 |  | 
|---|
 | 1072 |     if (channel == (Tcl_Channel) NULL) { | 
|---|
 | 1073 |         Tcl_AppendResult(interp, "pipe for command could not be created", | 
|---|
 | 1074 |                 NULL); | 
|---|
 | 1075 |         goto error; | 
|---|
 | 1076 |     } | 
|---|
 | 1077 |     return channel; | 
|---|
 | 1078 |  | 
|---|
 | 1079 |   error: | 
|---|
 | 1080 |     if (numPids > 0) { | 
|---|
 | 1081 |         Tcl_DetachPids(numPids, pidPtr); | 
|---|
 | 1082 |         ckfree((char *) pidPtr); | 
|---|
 | 1083 |     } | 
|---|
 | 1084 |     if (inPipe != NULL) { | 
|---|
 | 1085 |         TclpCloseFile(inPipe); | 
|---|
 | 1086 |     } | 
|---|
 | 1087 |     if (outPipe != NULL) { | 
|---|
 | 1088 |         TclpCloseFile(outPipe); | 
|---|
 | 1089 |     } | 
|---|
 | 1090 |     if (errFile != NULL) { | 
|---|
 | 1091 |         TclpCloseFile(errFile); | 
|---|
 | 1092 |     } | 
|---|
 | 1093 |     return NULL; | 
|---|
 | 1094 | } | 
|---|
 | 1095 |  | 
|---|
 | 1096 | /* | 
|---|
 | 1097 |  * Local Variables: | 
|---|
 | 1098 |  * mode: c | 
|---|
 | 1099 |  * c-basic-offset: 4 | 
|---|
 | 1100 |  * fill-column: 78 | 
|---|
 | 1101 |  * End: | 
|---|
 | 1102 |  */ | 
|---|