static int join(Ejs *ejs, EjsObj *workers, int timeout) { MprTicks mark; int64 dispatcherMark; int result, remaining; mprDebug("ejs worker", 5, "Worker.join: joining %d", ejs->joining); mark = mprGetTicks(); remaining = timeout; dispatcherMark = mprGetEventMark(ejs->dispatcher); do { ejs->joining = !reapJoins(ejs, workers); if (!ejs->joining) { break; } if (mprShouldAbortRequests()) { ejsThrowStateError(ejs, "Program instructed to exit"); break; } mprWaitForEvent(ejs->dispatcher, remaining, dispatcherMark); remaining = (int) mprGetRemainingTicks(mark, timeout); dispatcherMark = mprGetEventMark(ejs->dispatcher); } while (remaining > 0 && !ejs->exception); if (ejs->exception) { return 0; } result = (ejs->joining) ? MPR_ERR_TIMEOUT: 0; ejs->joining = 0; mprDebug("ejs worker", 7, "Worker.join: result %d", result); return result; }
/* Parse the CGI output first line */ static bool parseFirstCgiResponse(Cgi *cgi, HttpPacket *packet) { MprBuf *buf; char *protocol, *status, *msg; buf = packet->content; protocol = getCgiToken(buf, " "); if (protocol == 0 || protocol[0] == '\0') { httpError(cgi->conn, HTTP_CODE_BAD_GATEWAY, "Bad CGI HTTP protocol response"); return 0; } if (strncmp(protocol, "HTTP/1.", 7) != 0) { httpError(cgi->conn, HTTP_CODE_BAD_GATEWAY, "Unsupported CGI protocol"); return 0; } status = getCgiToken(buf, " "); if (status == 0 || *status == '\0') { httpError(cgi->conn, HTTP_CODE_BAD_GATEWAY, "Bad CGI header response"); return 0; } msg = getCgiToken(buf, "\n"); mprNop(msg); mprDebug("http cgi", 4, "CGI response status: %s %s %s", protocol, status, msg); return 1; }
/* Trace output first part of output received from the cgi process */ static void traceCGIData(MprCmd *cmd, char *src, ssize size) { char dest[512]; int index, i; if (mprGetLogLevel() >= 5) { mprDebug("http cgi", 5, "CGI: process wrote (leading %zd bytes) => \n", min(sizeof(dest), size)); for (index = 0; index < size; ) { for (i = 0; i < (sizeof(dest) - 1) && index < size; i++) { dest[i] = src[index]; index++; } dest[i] = '\0'; mprDebug("http cgi", 5, "%s", dest); } } }
void ejsShowBlockScope(Ejs *ejs, EjsBlock *block) { #if ME_DEBUG EjsNamespace *nsp; MprList *namespaces; int nextNsp; mprDebug("ejs vm", 6, "\n Block scope"); for (; block; block = block->scope) { mprDebug("ejs vm", 6, " Block \"%s\" 0x%08x", mprGetName(block), block); namespaces = &block->namespaces; if (namespaces) { for (nextNsp = 0; (nsp = (EjsNamespace*) mprGetNextItem(namespaces, &nextNsp)) != 0; ) { mprDebug("ejs vm", 6, " \"%@\"", nsp->value); } } } #endif }
static int reportResponse(HttpConn *conn, cchar *url) { HttpRx *rx; MprOff bytesRead; char *responseHeaders; int status; if (mprShouldAbortRequests(conn)) { return 0; } app->status = status = httpGetStatus(conn); bytesRead = httpGetContentLength(conn); if (bytesRead < 0 && conn->rx) { bytesRead = conn->rx->bytesRead; } mprDebug("http", 6, "Response status %d, elapsed %lld", status, mprGetTicks() - conn->started); if (conn->error) { app->success = 0; } if (conn->rx) { if (app->showHeaders) { responseHeaders = httpGetHeaders(conn); rx = conn->rx; mprPrintf("%s %d %s\n", conn->protocol, status, rx->statusMessage); if (responseHeaders) { mprPrintf("%s\n", responseHeaders); } } else if (app->showStatus) { mprPrintf("%d\n", status); } } if (status < 0) { mprLog("error http", 0, "Cannot process request for \"%s\" %s", url, httpGetError(conn)); return MPR_ERR_CANT_READ; } else if (status == 0 && conn->protocol == 0) { /* Ignore */; } else if (!(200 <= status && status <= 206) && !(301 <= status && status <= 304)) { if (!app->zeroOnErrors) { app->success = 0; } if (!app->showStatus) { mprLog("error http", 0, "Cannot process request for \"%s\" (%d) %s", url, status, httpGetError(conn)); return MPR_ERR_CANT_READ; } } mprLock(app->mutex); app->fetchCount++; if (app->verbose && app->noout) { trace(conn, url, app->fetchCount, app->method, status, bytesRead); } mprUnlock(app->mutex); return 0; }
PUBLIC int mprUnloadModule(MprModule *mp) { mprDebug("mpr", 5, "Unloading native module %s from %s", mp->name, mp->path); if (mprStopModule(mp) < 0) { return MPR_ERR_NOT_READY; } #if ME_COMPILER_HAS_DYN_LOAD if (mp->flags & MPR_MODULE_LOADED) { if (mprUnloadNativeModule(mp) != 0) { mprLog("error mpr", 0, "Cannot unload module %s", mp->name); } } #endif mprRemoveItem(MPR->moduleService->modules, mp); return 0; }
/* Start a worker thread. This is called by eval() and load(). Not by preload() or by Worker(). It always joins. */ static EjsObj *startWorker(Ejs *ejs, EjsWorker *outsideWorker, int timeout) { EjsWorker *insideWorker; Ejs *inside; EjsString *result; assert(ejs); assert(outsideWorker); assert(!outsideWorker->inside); assert(outsideWorker->state == EJS_WORKER_BEGIN); assert(outsideWorker->pair); assert(outsideWorker->pair->ejs); mprDebug("ejs worker", 5, "Worker.startWorker"); if (outsideWorker->state > EJS_WORKER_BEGIN) { ejsThrowStateError(ejs, "Worker has already started"); return 0; } insideWorker = outsideWorker->pair; assert(insideWorker->inside); assert(insideWorker->state == EJS_WORKER_BEGIN); inside = insideWorker->ejs; outsideWorker->state = EJS_WORKER_STARTED; if (mprCreateEvent(inside->dispatcher, "workerMain", 0, (MprEventProc) workerMain, insideWorker, 0) < 0) { ejsThrowStateError(ejs, "Cannot create worker event"); return 0; } if (timeout == 0) { return ESV(undefined); } if (timeout < 0) { timeout = MAXINT; } if (join(ejs, (EjsObj*) outsideWorker, timeout) < 0) { return ESV(undefined); } result = ejsToJSON(inside, inside->result, NULL); if (result == 0) { return ESV(null); } return ejsDeserialize(ejs, result); }
/* Stop the command WARNING: Called from the finalizer. Must not block or lock. */ PUBLIC int mprStopCmd(MprCmd *cmd, int signal) { mprDebug("mpr cmd", 6, "cmd: stop"); if (signal < 0) { signal = SIGTERM; } cmd->stopped = 1; if (cmd->pid) { #if ME_WIN_LIKE return TerminateProcess(cmd->process, 2) == 0; #elif VXWORKS return taskDelete(cmd->pid); #else return kill(cmd->pid, signal); #endif } return 0; }
/* Gather the child's exit status. WARNING: this may be called with a false-positive, ie. SIGCHLD will get invoked for all process deaths and not just when this cmd has completed. */ static void reapCmd(MprCmd *cmd, bool finalizing) { if (cmd->pid == 0) { return; } #if ME_UNIX_LIKE { int status, rc; status = 0; if ((rc = waitpid(cmd->pid, &status, WNOHANG | __WALL)) < 0) { mprLog("error mpr cmd", 0, "Waitpid failed for pid %d, errno %d", cmd->pid, errno); } else if (rc == cmd->pid) { if (!WIFSTOPPED(status)) { if (WIFEXITED(status)) { cmd->status = WEXITSTATUS(status); mprDebug("mpr cmd", 6, "Process exited pid %d, status %d", cmd->pid, cmd->status); } else if (WIFSIGNALED(status)) { cmd->status = WTERMSIG(status); } cmd->pid = 0; if (cmd->signal) { mprRemoveSignalHandler(cmd->signal); cmd->signal = 0; } } } else { mprDebug("mpr cmd", 6, "Still running pid %d, thread %s", cmd->pid, mprGetCurrentThreadName()); } } #endif #if VXWORKS /* The command exit status (cmd->status) is set in cmdTaskEntry */ if (!cmd->stopped) { if (semTake(cmd->exitCond, MPR_TIMEOUT_STOP_TASK) != OK) { mprLog("error mpr cmd", 0, "Child %s did not exit, errno %d", cmd->program, errno); return; } } semDelete(cmd->exitCond); cmd->exitCond = 0; cmd->pid = 0; #endif #if ME_WIN_LIKE { int status, rc; status = 0; if (GetExitCodeProcess(cmd->process, (ulong*) &status) == 0) { mprLog("error mpr cmd", 0, "GetExitProcess error"); return; } if (status != STILL_ACTIVE) { cmd->status = status; rc = CloseHandle(cmd->process); assert(rc != 0); rc = CloseHandle(cmd->thread); assert(rc != 0); cmd->process = 0; cmd->thread = 0; cmd->pid = 0; } } #endif if (cmd->pid == 0) { if (cmd->eofCount >= cmd->requiredEof) { completeCommand(cmd); } mprDebug("mpr cmd", 6, "Process reaped: status %d, pid %d, eof %d / %d", cmd->status, cmd->pid, cmd->eofCount, cmd->requiredEof); if (cmd->callback) { (cmd->callback)(cmd, -1, cmd->callbackData); } } }
/* Build the command arguments. NOTE: argv is untrusted input. */ static void buildArgs(HttpConn *conn, MprCmd *cmd, int *argcp, cchar ***argvp) { HttpRx *rx; HttpTx *tx; char **argv; char *indexQuery, *cp, *tok; cchar *actionProgram, *fileName; size_t len; int argc, argind, i; rx = conn->rx; tx = conn->tx; fileName = tx->filename; assert(fileName); actionProgram = 0; argind = 0; argc = *argcp; if (tx->ext) { actionProgram = mprGetMimeProgram(rx->route->mimeTypes, tx->ext); if (actionProgram != 0) { argc++; } /* This is an Apache compatible hack for PHP 5.3 */ mprAddKey(rx->headers, "REDIRECT_STATUS", itos(HTTP_CODE_MOVED_TEMPORARILY)); } /* Count the args for ISINDEX queries. Only valid if there is not a "=" in the query. If this is so, then we must not have these args in the query env also? */ indexQuery = rx->parsedUri->query; if (indexQuery && !strchr(indexQuery, '=')) { argc++; for (cp = indexQuery; *cp; cp++) { if (*cp == '+') { argc++; } } } else { indexQuery = 0; } #if ME_WIN_LIKE || VXWORKS { char *bangScript, *cmdBuf, *program, *cmdScript; /* On windows we attempt to find an executable matching the fileName. We look for *.exe, *.bat and also do unix style processing "#!/program" */ findExecutable(conn, &program, &cmdScript, &bangScript, fileName); assert(program); if (cmdScript) { /* Cmd/Batch script (.bat | .cmd) Convert the command to the form where there are 4 elements in argv that cmd.exe can interpret. argv[0] = cmd.exe argv[1] = /Q argv[2] = /C argv[3] = ""script" args ..." */ argc = 4; len = (argc + 1) * sizeof(char*); argv = (char**) mprAlloc(len); memset(argv, 0, len); argv[argind++] = program; /* Duped in findExecutable */ argv[argind++] = "/Q"; argv[argind++] = "/C"; len = strlen(cmdScript) + 2 + 1; cmdBuf = mprAlloc(len); fmt(cmdBuf, len, "\"%s\"", cmdScript); argv[argind++] = cmdBuf; mprSetCmdDir(cmd, cmdScript); /* program will get freed when argv[] gets freed */ } else if (bangScript) { /* Script used "#!/program". NOTE: this may be overridden by a mime Action directive. */ argc++; /* Adding bangScript arg */ len = (argc + 1) * sizeof(char*); argv = (char**) mprAlloc(len); memset(argv, 0, len); argv[argind++] = program; /* Will get freed when argv[] is freed */ argv[argind++] = bangScript; /* Will get freed when argv[] is freed */ mprSetCmdDir(cmd, bangScript); } else { /* Either unknown extension or .exe (.out) program. */ len = (argc + 1) * sizeof(char*); argv = (char**) mprAlloc(len); memset(argv, 0, len); if (actionProgram) { argv[argind++] = sclone(actionProgram); } argv[argind++] = program; } } #else len = (argc + 1) * sizeof(char*); argv = mprAlloc(len); memset(argv, 0, len); if (actionProgram) { argv[argind++] = sclone(actionProgram); } // OPT - why clone all these string? argv[argind++] = sclone(fileName); #endif /* ISINDEX queries. Only valid if there is not a "=" in the query. If this is so, then we must not have these args in the query env also? FUTURE - should query vars be set in the env? */ if (indexQuery) { indexQuery = sclone(indexQuery); cp = stok(indexQuery, "+", &tok); while (cp) { argv[argind++] = mprEscapeCmd(mprUriDecode(cp), 0); cp = stok(NULL, "+", &tok); } } assert(argind <= argc); argv[argind] = 0; *argcp = argc; *argvp = (cchar**) argv; mprDebug("http cgi", 5, "CGI: command:"); for (i = 0; i < argind; i++) { mprDebug("http cgi", 5, " argv[%d] = %s", i, argv[i]); } }
static int issueRequest(HttpConn *conn, cchar *url, MprList *files) { HttpRx *rx; HttpUri *target, *location; char *redirect; cchar *msg, *sep, *authType; int count, redirectCount, rc; httpSetRetries(conn, app->retries); httpSetTimeout(conn, app->timeout, app->timeout); authType = conn->authType; for (redirectCount = count = 0; count <= conn->retries && redirectCount < 10 && !mprShouldAbortRequests(conn); count++) { if (prepRequest(conn, files, count) < 0) { return MPR_ERR_CANT_OPEN; } if (sendRequest(conn, app->method, url, files) < 0) { return MPR_ERR_CANT_WRITE; } if ((rc = httpWait(conn, HTTP_STATE_PARSED, conn->limits->requestTimeout)) == 0) { if (httpNeedRetry(conn, &redirect)) { if (redirect) { httpRemoveHeader(conn, "Host"); location = httpCreateUri(redirect, 0); if (!location || !location->valid) { httpError(conn, HTTP_ABORT, "Invalid location URI"); break; } target = httpJoinUri(conn->tx->parsedUri, 1, &location); url = httpUriToString(target, HTTP_COMPLETE_URI); count = 0; } if (conn->rx && conn->rx->status == HTTP_CODE_UNAUTHORIZED && authType && smatch(authType, conn->authType)) { /* Supplied authentication details and failed */ break; } redirectCount++; count--; } else { break; } } else if (!conn->error) { if (rc == MPR_ERR_TIMEOUT) { httpError(conn, HTTP_ABORT | HTTP_CODE_REQUEST_TIMEOUT, "Inactive request timed out, exceeded request timeout %lld", app->timeout); } else { httpError(conn, HTTP_ABORT | HTTP_CODE_COMMS_ERROR, "Connection I/O error"); } } if ((rx = conn->rx) != 0) { if (rx->status == HTTP_CODE_REQUEST_TOO_LARGE || rx->status == HTTP_CODE_REQUEST_URL_TOO_LARGE || rx->status == HTTP_CODE_NOT_ACCEPTABLE || (rx->status == HTTP_CODE_UNAUTHORIZED && conn->username == 0)) { /* No point retrying */ break; } if (conn->sock->flags & MPR_SOCKET_CERT_ERROR) { break; } } mprDebug("http", 4, "retry %d of %d for: %s %s", count, conn->retries, app->method, url); } if (conn->error) { msg = (conn->errorMsg) ? conn->errorMsg : ""; sep = (msg && *msg) ? "\n" : ""; mprLog("error http", 0, "Failed \"%s\" request for %s%s%s", app->method, url, sep, msg); return MPR_ERR_CANT_CONNECT; } return 0; }
/* Process a message sent from postMessage. This may run inside the worker or outside in the parent depending on the direction of the message. But it ALWAYS runs in the appropriate thread for the interpreter. */ static int doMessage(Message *msg, MprEvent *mprEvent) { Ejs *ejs; EjsObj *event, *frame; EjsWorker *worker; EjsFunction *callback; EjsObj *argv[1]; worker = msg->worker; worker->gotMessage = 1; ejs = worker->ejs; assert(!ejs->exception); event = 0; ejsBlockGC(ejs); callback = ejsGetProperty(ejs, worker, msg->callbackSlot); switch (msg->callbackSlot) { case ES_Worker_onerror: event = ejsCreateObj(ejs, ESV(ErrorEvent), 0); break; case ES_Worker_onclose: case ES_Worker_onmessage: event = ejsCreateObj(ejs, ESV(Event), 0); break; default: assert(msg->callbackSlot == 0); return 0; } worker->event = event; if (msg->data) { ejsSetProperty(ejs, event, ES_Event_data, ejsCreateStringFromAsc(ejs, msg->data)); } if (msg->message) { ejsSetProperty(ejs, event, ES_ErrorEvent_message, msg->message); } if (msg->stack) { ejsSetProperty(ejs, event, ES_ErrorEvent_stack, msg->stack); if ((frame = ejsGetProperty(ejs, msg->stack, 0)) != 0 && !ejsIs(ejs, frame, Void)) { ejsSetProperty(ejs, event, ES_ErrorEvent_filename, ejsGetPropertyByName(ejs, frame, EN("filename"))); ejsSetProperty(ejs, event, ES_ErrorEvent_lineno, ejsGetPropertyByName(ejs, frame, EN("lineno"))); } } assert(!ejs->exception); if (callback == 0 || ejsIs(ejs, callback, Null)) { if (msg->callbackSlot == ES_Worker_onmessage) { mprDebug("ejs worker", 6, "Discard message as no onmessage handler defined for worker"); } else if (msg->callbackSlot == ES_Worker_onerror) { if (ejsIs(ejs, msg->message, String)) { ejsThrowError(ejs, "Exception in Worker: %@", ejsToString(ejs, msg->message)); } else { ejsThrowError(ejs, "Exception in Worker: %s", ejsGetErrorMsg(worker->pair->ejs, 1)); } } else { /* Ignore onclose message */ } } else if (!ejsIsFunction(ejs, callback)) { ejsThrowTypeError(ejs, "Worker callback %s is not a function", msg->callback); } else { assert(!ejs->exception); argv[0] = event; ejsRunFunction(ejs, callback, worker, 1, argv); } if (msg->callbackSlot == ES_Worker_onclose) { assert(!worker->inside); worker->state = EJS_WORKER_COMPLETE; mprDebug("ejs worker", 5, "Worker.doMessage: complete"); /* Worker and insider interpreter are now eligible for garbage collection */ removeWorker(worker); } mprSignalDispatcher(ejs->dispatcher); worker->event = 0; return 0; }