| 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 |  */ | 
|---|