static void browserToCgiService(HttpQueue *q) { HttpConn *conn; HttpPacket *packet; Cgi *cgi; MprCmd *cmd; MprBuf *buf; ssize rc, len; int err; if ((cgi = q->queueData) == 0) { return; } assert(q == cgi->writeq); cmd = cgi->cmd; assert(cmd); conn = cgi->conn; for (packet = httpGetPacket(q); packet; packet = httpGetPacket(q)) { if ((buf = packet->content) == 0) { /* End packet */ continue; } len = mprGetBufLength(buf); rc = mprWriteCmd(cmd, MPR_CMD_STDIN, mprGetBufStart(buf), len); if (rc < 0) { err = mprGetError(); if (err == EINTR) { continue; } else if (err == EAGAIN || err == EWOULDBLOCK) { httpPutBackPacket(q, packet); break; } mprLog(2, "CGI: write to gateway failed for %d bytes, rc %d, errno %d", len, rc, mprGetOsError()); mprCloseCmdFd(cmd, MPR_CMD_STDIN); httpDiscardQueueData(q, 1); httpError(conn, HTTP_CODE_BAD_GATEWAY, "Cannot write body data to CGI gateway"); break; } mprTrace(6, "CGI: browserToCgiService %d/%d, qmax %d", rc, len, q->max); mprAdjustBufStart(buf, rc); if (mprGetBufLength(buf) > 0) { httpPutBackPacket(q, packet); break; } } if (q->count > 0) { /* Wait for writable event so cgiCallback can recall this routine */ mprEnableCmdEvents(cmd, MPR_CMD_STDIN); } else if (conn->rx->eof) { mprCloseCmdFd(cmd, MPR_CMD_STDIN); } else { mprDisableCmdEvents(cmd, MPR_CMD_STDIN); } }
/* * Default callback routine for the mprRunCmd routines. Uses may supply their own callback instead of this routine. * The callback is run whenever there is I/O to read/write to the CGI gateway. */ static int cmdCallback(MprCmd *cmd, int channel, void *data) { MprBuf *buf; int len, space; /* * Note: stdin, stdout and stderr are named from the client's perspective */ buf = 0; switch (channel) { case MPR_CMD_STDIN: return 0; case MPR_CMD_STDOUT: buf = cmd->stdoutBuf; break; case MPR_CMD_STDERR: buf = cmd->stderrBuf; break; } /* * Read and aggregate the result into a single string */ space = mprGetBufSpace(buf); if (space < (MPR_BUFSIZE / 4)) { if (mprGrowBuf(buf, MPR_BUFSIZE) < 0) { mprCloseCmdFd(cmd, channel); return 0; } space = mprGetBufSpace(buf); } len = mprReadCmdPipe(cmd, channel, mprGetBufEnd(buf), space); if (len <= 0) { if (len == 0 || (len < 0 && !(errno == EAGAIN || errno == EWOULDBLOCK))) { if (channel == MPR_CMD_STDOUT && cmd->flags & MPR_CMD_ERR) { /* * Now that stdout is complete, enable stderr to receive an EOF or any error output. * This is serialized to eliminate both stdin and stdout events on different threads at the same time. * Do before closing as the stderr event may come on another thread and we want to ensure avoid locking. */ mprCloseCmdFd(cmd, channel); } else { mprCloseCmdFd(cmd, channel); } return 0; } } else { mprAdjustBufEnd(buf, len); } return 0; }
/* Default callback routine for the mprRunCmd routines. Uses may supply their own callback instead of this routine. The callback is run whenever there is I/O to read/write to the CGI gateway. */ static void defaultCmdCallback(MprCmd *cmd, int channel, void *data) { MprBuf *buf; ssize len, space; int errCode; /* Note: stdin, stdout and stderr are named from the client's perspective */ buf = 0; switch (channel) { case MPR_CMD_STDIN: return; case MPR_CMD_STDOUT: buf = cmd->stdoutBuf; break; case MPR_CMD_STDERR: buf = cmd->stderrBuf; break; default: /* Child death notification */ return; } /* Read and aggregate the result into a single string */ space = mprGetBufSpace(buf); if (space < (ME_BUFSIZE / 4)) { if (mprGrowBuf(buf, ME_BUFSIZE) < 0) { mprCloseCmdFd(cmd, channel); return; } space = mprGetBufSpace(buf); } len = mprReadCmd(cmd, channel, mprGetBufEnd(buf), space); errCode = mprGetError(); if (len <= 0) { if (len == 0 || (len < 0 && !(errCode == EAGAIN || errCode == EWOULDBLOCK))) { mprCloseCmdFd(cmd, channel); return; } } else { mprAdjustBufEnd(buf, len); } mprAddNullToBuf(buf); mprEnableCmdEvents(cmd, channel); }
static void writeToCGI(MaQueue *q) { MaConn *conn; MaPacket *packet; MprCmd *cmd; MprBuf *buf; int len, rc, err; cmd = (MprCmd*) q->pair->queueData; mprAssert(cmd); conn = q->conn; for (packet = maGet(q); packet && !conn->requestFailed; packet = maGet(q)) { buf = packet->content; len = mprGetBufLength(buf); mprAssert(len > 0); rc = mprWriteCmdPipe(cmd, MPR_CMD_STDIN, mprGetBufStart(buf), len); mprLog(q, 5, "CGI: write %d bytes to gateway. Rc rc %d, errno %d", len, rc, mprGetOsError()); if (rc < 0) { err = mprGetError(); if (err == EINTR) { continue; } else if (err == EAGAIN || err == EWOULDBLOCK) { break; } mprLog(q, 2, "CGI: write to gateway failed for %d bytes, rc %d, errno %d", len, rc, mprGetOsError()); mprCloseCmdFd(cmd, MPR_CMD_STDIN); maFailRequest(conn, MPR_HTTP_CODE_BAD_GATEWAY, "Can't write body data to CGI gateway"); break; } else { mprLog(q, 5, "CGI: write to gateway %d bytes asked to write %d", rc, len); mprAdjustBufStart(buf, rc); if (mprGetBufLength(buf) > 0) { maPutBack(q, packet); } else { maFreePacket(q, packet); } } } }
/* This routine runs after all incoming data has been received */ static void runCgi(MaQueue *q) { MaResponse *resp; MaConn *conn; MprCmd *cmd; conn = q->conn; resp = conn->response; cmd = (MprCmd*) q->queueData; if (cmd == 0) { startCgi(q); cmd = (MprCmd*) q->queueData; if (q->pair->count > 0) { writeToCGI(q->pair); } } /* Close the CGI program's stdin. This will allow it to exit if it was expecting input data. */ mprCloseCmdFd(cmd, MPR_CMD_STDIN); if (conn->requestFailed) { maPutForService(q, maCreateEndPacket(q), 1); return; } while (mprWaitForCmd(cmd, 1000) < 0) { if (mprGetElapsedTime(cmd, cmd->lastActivity) >= conn->host->timeout) { break; } } if (cmd->pid == 0) { maPutForService(q, maCreateEndPacket(q), 1); } else { mprStopCmd(cmd); mprReapCmd(cmd, MPR_TIMEOUT_STOP_TASK); cmd->status = 255; } }
PUBLIC void mprFinalizeCmd(MprCmd *cmd) { assert(cmd); mprCloseCmdFd(cmd, MPR_CMD_STDIN); }
static void readFromCgi(Cgi *cgi, int channel) { HttpConn *conn; HttpPacket *packet; HttpTx *tx; HttpQueue *q, *writeq; MprCmd *cmd; ssize nbytes; int err; cmd = cgi->cmd; conn = cgi->conn; tx = conn->tx; q = cgi->readq; writeq = conn->writeq; assert(conn->sock); assert(conn->state > HTTP_STATE_BEGIN); if (tx->finalized) { mprCloseCmdFd(cmd, channel); } while (mprGetCmdFd(cmd, channel) >= 0 && !tx->finalized && writeq->count < writeq->max) { if ((packet = cgi->headers) != 0) { if (mprGetBufSpace(packet->content) < ME_MAX_BUFFER && mprGrowBuf(packet->content, ME_MAX_BUFFER) < 0) { break; } } else if ((packet = httpCreateDataPacket(ME_MAX_BUFFER)) == 0) { break; } nbytes = mprReadCmd(cmd, channel, mprGetBufEnd(packet->content), ME_MAX_BUFFER); if (nbytes < 0) { err = mprGetError(); if (err == EINTR) { continue; } else if (err == EAGAIN || err == EWOULDBLOCK) { break; } mprCloseCmdFd(cmd, channel); break; } else if (nbytes == 0) { mprCloseCmdFd(cmd, channel); break; } else { traceData(cmd, mprGetBufEnd(packet->content), nbytes); mprAdjustBufEnd(packet->content, nbytes); } if (channel == MPR_CMD_STDERR) { mprLog("error cgi", 0, "CGI failed uri=\"%s\", details: %s", conn->rx->uri, mprGetBufStart(packet->content)); httpSetStatus(conn, HTTP_CODE_SERVICE_UNAVAILABLE); cgi->seenHeader = 1; } if (!cgi->seenHeader) { if (!parseCgiHeaders(cgi, packet)) { cgi->headers = packet; return; } cgi->headers = 0; cgi->seenHeader = 1; } if (!tx->finalizedOutput && httpGetPacketLength(packet) > 0) { /* Put the data to the CGI readq, then cgiToBrowserService will take care of it */ httpPutPacket(q, packet); } } }
static void cgiEvent(MaQueue *q, MprCmd *cmd, int channel) { MaConn *conn; MaResponse *resp; MprBuf *buf; int space, nbytes, err; mprLog(cmd, 6, "CGI callback channel %d", channel); buf = 0; conn = q->conn; resp = conn->response; mprAssert(resp); cmd->lastActivity = mprGetTime(cmd); switch (channel) { case MPR_CMD_STDIN: writeToCGI(q->pair); return; case MPR_CMD_STDOUT: buf = cmd->stdoutBuf; break; case MPR_CMD_STDERR: buf = cmd->stderrBuf; break; } mprAssert(buf); mprResetBufIfEmpty(buf); /* Come here for CGI stdout, stderr events. ie. reading data from the CGI program. */ while (mprGetCmdFd(cmd, channel) >= 0) { /* Read as much data from the CGI as possible */ do { if ((space = mprGetBufSpace(buf)) == 0) { mprGrowBuf(buf, MA_BUFSIZE); if ((space = mprGetBufSpace(buf)) == 0) { break; } } nbytes = mprReadCmdPipe(cmd, channel, mprGetBufEnd(buf), space); mprLog(q, 5, "CGI: read from gateway %d on channel %d. errno %d", nbytes, channel, nbytes >= 0 ? 0 : mprGetOsError()); if (nbytes < 0) { err = mprGetError(); if (err == EINTR) { continue; } else if (err == EAGAIN || err == EWOULDBLOCK) { break; } mprLog(cmd, 5, "CGI read error %d for %", mprGetError(), (channel == MPR_CMD_STDOUT) ? "stdout" : "stderr"); mprCloseCmdFd(cmd, channel); } else if (nbytes == 0) { /* This may reap the terminated child and thus clear cmd->process if both stderr and stdout are closed. */ mprLog(cmd, 5, "CGI EOF for %s", (channel == MPR_CMD_STDOUT) ? "stdout" : "stderr"); mprCloseCmdFd(cmd, channel); break; } else { mprLog(cmd, 5, "CGI read %d bytes from %s", nbytes, (channel == MPR_CMD_STDOUT) ? "stdout" : "stderr"); mprAdjustBufEnd(buf, nbytes); traceData(cmd, mprGetBufStart(buf), nbytes); } } while ((space = mprGetBufSpace(buf)) > 0); if (mprGetBufLength(buf) == 0) { return; } if (channel == MPR_CMD_STDERR) { /* If we have an error message, send that to the client */ if (mprGetBufLength(buf) > 0) { mprAddNullToBuf(buf); mprLog(conn, 4, mprGetBufStart(buf)); if (writeToClient(q, cmd, buf, channel) < 0) { return; } maSetResponseCode(conn, MPR_HTTP_CODE_SERVICE_UNAVAILABLE); cmd->userFlags |= MA_CGI_SEEN_HEADER; cmd->status = 0; } } else { if (!(cmd->userFlags & MA_CGI_SEEN_HEADER) && !parseHeader(conn, cmd)) { return; } if (cmd->userFlags & MA_CGI_SEEN_HEADER) { if (writeToClient(q, cmd, buf, channel) < 0) { return; } } } } }
static void readFromCgi(Cgi *cgi, int channel) { HttpConn *conn; HttpPacket *packet; HttpTx *tx; HttpQueue *q, *writeq; MprCmd *cmd; ssize nbytes; int err; cmd = cgi->cmd; conn = cgi->conn; tx = conn->tx; q = cgi->readq; writeq = conn->writeq; assert(conn->sock); assert(conn->state > HTTP_STATE_BEGIN); if (tx->finalized) { mprCloseCmdFd(cmd, channel); } while (mprGetCmdFd(cmd, channel) >= 0 && !tx->finalized && writeq->count < writeq->max) { if ((packet = cgi->headers) != 0) { if (mprGetBufSpace(packet->content) < ME_MAX_BUFFER && mprGrowBuf(packet->content, ME_MAX_BUFFER) < 0) { break; } } else if ((packet = httpCreateDataPacket(ME_MAX_BUFFER)) == 0) { break; } nbytes = mprReadCmd(cmd, channel, mprGetBufEnd(packet->content), ME_MAX_BUFFER); if (nbytes < 0) { err = mprGetError(); if (err == EINTR) { continue; } else if (err == EAGAIN || err == EWOULDBLOCK) { break; } mprTrace(6, "CGI: Gateway read error %d for %s", err, (channel == MPR_CMD_STDOUT) ? "stdout" : "stderr"); mprCloseCmdFd(cmd, channel); break; } else if (nbytes == 0) { mprTrace(6, "CGI: Gateway EOF for %s, pid %d", (channel == MPR_CMD_STDOUT) ? "stdout" : "stderr", cmd->pid); mprCloseCmdFd(cmd, channel); break; } else { mprTrace(6, "CGI: Gateway read %d bytes from %s", nbytes, (channel == MPR_CMD_STDOUT) ? "stdout" : "stderr"); traceData(cmd, mprGetBufEnd(packet->content), nbytes); mprAdjustBufEnd(packet->content, nbytes); } if (channel == MPR_CMD_STDERR) { // FUTURE - should be an option to keep going despite stderr output mprError("CGI: Error for \"%s\"\n\n%s", conn->rx->uri, mprGetBufStart(packet->content)); httpSetStatus(conn, HTTP_CODE_SERVICE_UNAVAILABLE); cgi->seenHeader = 1; } if (!cgi->seenHeader) { if (!parseCgiHeaders(cgi, packet)) { cgi->headers = packet; return; } cgi->headers = 0; cgi->seenHeader = 1; } if (!tx->finalizedOutput && httpGetPacketLength(packet) > 0) { /* Put the data to the CGI readq, then cgiToBrowserService will take care of it */ httpPutPacket(q, packet); } } }
/* * This routine runs a command and waits for its completion. Stdoutput and Stderr are returned in *out and *err * respectively. The command returns the exit status of the command. * Valid flags are: * MPR_CMD_NEW_SESSION Create a new session on Unix * MPR_CMD_SHOW Show the commands window on Windows * MPR_CMD_IN Connect to stdin */ int mprRunCmdV(MprCmd *cmd, int argc, char **argv, char **out, char **err, int flags) { int rc, status; if (err) { *err = 0; flags |= MPR_CMD_ERR; } else { flags &= ~MPR_CMD_ERR; } if (out) { *out = 0; flags |= MPR_CMD_OUT; } else { flags &= ~MPR_CMD_OUT; } if (flags & MPR_CMD_OUT) { mprFree(cmd->stdoutBuf); cmd->stdoutBuf = mprCreateBuf(cmd, MPR_BUFSIZE, -1); } if (flags & MPR_CMD_ERR) { mprFree(cmd->stderrBuf); cmd->stderrBuf = mprCreateBuf(cmd, MPR_BUFSIZE, -1); } mprSetCmdCallback(cmd, cmdCallback, NULL); lock(cmd); rc = mprStartCmd(cmd, argc, argv, NULL, flags); /* * Close the pipe connected to the client's stdin */ if (cmd->files[MPR_CMD_STDIN].fd >= 0) { mprCloseCmdFd(cmd, MPR_CMD_STDIN); } if (rc < 0) { if (err) { if (rc == MPR_ERR_CANT_ACCESS) { *err = mprAsprintf(cmd, -1, "Can't access command %s", cmd->program); } else if (MPR_ERR_CANT_OPEN) { *err = mprAsprintf(cmd, -1, "Can't open standard I/O for command %s", cmd->program); } else if (rc == MPR_ERR_CANT_CREATE) { *err = mprAsprintf(cmd, -1, "Can't create process for %s", cmd->program); } } unlock(cmd); return rc; } if (cmd->flags & MPR_CMD_DETACH) { unlock(cmd); return 0; } unlock(cmd); if (mprWaitForCmd(cmd, -1) < 0) { return MPR_ERR_NOT_READY; } lock(cmd); if (mprGetCmdExitStatus(cmd, &status) < 0) { unlock(cmd); return MPR_ERR; } if (err && flags & MPR_CMD_ERR) { mprAddNullToBuf(cmd->stderrBuf); *err = mprGetBufStart(cmd->stderrBuf); } if (out && flags & MPR_CMD_OUT) { mprAddNullToBuf(cmd->stdoutBuf); *out = mprGetBufStart(cmd->stdoutBuf); } unlock(cmd); return status; }