static int JsiFreeWaitInfoTable(Jsi_Interp *interp, void *privData) { struct WaitInfoTable *table = privData; Jsi_Free(table->info); Jsi_Free(table); return JSI_OK; }
void jsi_PstateFree(jsi_Pstate *ps) { /* TODO: when do we free opcodes */ jsi_PstateClear(ps); Jsi_Free(ps->lexer); if (ps->opcodes) jsi_FreeOpcodes(ps->opcodes); if (ps->hPtr) Jsi_HashEntryDelete(ps->hPtr); Jsi_HashDelete(ps->argsTbl); Jsi_HashDelete(ps->strTbl); Jsi_HashDelete(ps->fastVarTbl); MEMCLEAR(ps); Jsi_Free(ps); }
static int JsiOpenForWrite(Jsi_Interp *interp, const char *filename, int append) { char *fn = Jsi_FileRealpathStr(interp, filename, NULL); int rc = open(fn, O_WRONLY | O_CREAT | (append ? O_APPEND : O_TRUNC), 0666); Jsi_Free(fn); return rc; }
static int JsiOpenForRead(Jsi_Interp *interp, const char *filename) { char *fn = Jsi_FileRealpathStr(interp, filename, NULL); int rc = open(fn, O_RDONLY, 0); Jsi_Free(fn); return rc; }
static int JsiCleanupChildren(Jsi_Interp *interp, int numPids, pidtype *pidPtr, fdtype errorId, Jsi_DString *dStr, Jsi_DString *cStr, int *code) { struct WaitInfoTable *table = jsi_ExecCmdData(interp); int result = JSI_OK; int i, exitCode = 256; char buf[1000]; Jsi_DString sStr; Jsi_DSInit(&sStr); for (i = 0; i < numPids; i++) { int waitStatus = 0; if (cStr) { if (i==0) Jsi_DSAppend(cStr, (Jsi_DSLength(cStr)>1?", ":""), "children: [", NULL); else Jsi_DSAppend(&sStr, ", ", NULL); } if (JsiWaitForProcess(table, pidPtr[i], &waitStatus) != JSI_BAD_PID) { // if (JsiCheckWaitStatus(interp, pidPtr[i], waitStatus, dStr?&sStr:0) != JSI_OK) //result = JSI_ERROR; // TODO: we don't error out on non-zero return code. Find way to return it. int es = WEXITSTATUS(waitStatus); if (WIFEXITED(waitStatus)) { if (i==0) exitCode = es; else if (es>exitCode) exitCode = es; } if (cStr) { pidtype pid = pidPtr[i]; if (WIFEXITED(waitStatus)) sprintf(buf, "{type:\"exit\", pid:%ld, exitCode:%d}", (long)pid, es); else sprintf(buf, "{type:\"%s\", pid:%ld, signal: %d}", (WIFSIGNALED(waitStatus) ? "killed" : "suspended"), (long)pid, WTERMSIG(waitStatus)); Jsi_DSAppend(&sStr, buf, NULL); } } } if (i>0 && cStr) { if (exitCode != 256) sprintf(buf, ", exitCode: %d", exitCode); Jsi_DSAppend(cStr, Jsi_DSValue(&sStr), "]", buf, NULL); } Jsi_DSFree(&sStr); Jsi_Free(pidPtr); /* * Read the standard error file. If there's anything there, * then add the file's contents to the result * string. */ if (errorId != JSI_BAD_FD) { JsiRewindFd(errorId); if (JsiAppendStreamToString(interp, errorId, dStr) != JSI_OK) { result = JSI_ERROR; } } *code = exitCode; return result; }
static fdtype JsiOpenForWrite(Jsi_Interp *interp, const char *filename, int append) { char *fn = Jsi_FileRealpathStr(interp, filename, NULL); int rc = CreateFile(fn, append ? FILE_APPEND_DATA : GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, JsiStdSecAttrs(), append ? OPEN_ALWAYS : CREATE_ALWAYS, 0, (HANDLE) NULL); Jsi_Free(fn); return rc; }
static fdtype JsiOpenForRead(Jsi_Interp *interp, const char *filename) { char *fn = Jsi_FileRealpathStr(interp, filename, NULL); int rc = CreateFile(filename, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, JsiStdSecAttrs(), OPEN_EXISTING, 0, NULL); Jsi_Free(fn); return rc; }
static void FileNormalize(Jsi_Interp *interp, Jsi_Value *path) { char *np, *cp = Jsi_ValueString(interp, path, 0); if (!cp) return; np = Jsi_FileRealpath(interp, path, NULL); if (np && Jsi_Strcmp(cp, np)) Jsi_ValueMakeString(interp, path, np); else if (np) Jsi_Free(np); }
/* * Frees any allocated space and sets the DString back to empty such that it is safe to exit the scope. * Alternatively, the DString may be immediately reused. However in that case a call to Jsi_DSReset might * be more appropriate. */ void Jsi_DSFree(Jsi_DString *dsPtr) { if (!dsPtr->str) return; if (dsPtr->str != dsPtr->staticSpace && dsPtr->spaceAvl>0) Jsi_Free(dsPtr->str); dsPtr->str = NULL; dsPtr->staticSpace[0] = 0; dsPtr->spaceAvl = dsPtr->staticSize; }
int Jsi_UserObjUnregister(Jsi_Interp *interp, Jsi_UserObjReg *udreg) { Jsi_HashEntry *hPtr; if (interp->deleting) return JSI_ERROR; hPtr = Jsi_HashEntryFind(interp->userdataTbl, udreg->name); if (hPtr == NULL) return JSI_ERROR; Jsi_HashEntryDelete(hPtr); UserObjReg* ptr = Jsi_HashValueGet(hPtr); SIGASSERT(ptr, USER_REG); Jsi_Free(ptr); return JSI_OK; }
void jsi_UserObjFree(Jsi_Interp *interp, Jsi_UserObj *uobj) { Jsi_UserObjReg *udr =uobj->reg; if (interp != uobj->interp) { Jsi_LogError("UDID bad interp"); return; } if (uobj->hPtr) Jsi_HashEntryDelete(uobj->hPtr); if (udr->freefun) { udr->freefun(interp, uobj->data); } MEMCLEAR(uobj); Jsi_Free(uobj); }
int jsi_UserObjDelete(Jsi_Interp *interp, void *data) { UserObjReg* ptr = data; SIGASSERT(ptr, USER_REG); Jsi_Hash *tblPtr = ptr->hashPtr; Jsi_HashEntry *hPtr; Jsi_HashSearch search; for (hPtr = Jsi_HashEntryFirst(tblPtr, &search); hPtr != NULL; hPtr = Jsi_HashEntryNext(&search)) { void *dptr; Jsi_Obj *obj = Jsi_HashValueGet(hPtr); assert(obj && obj->ot == JSI_OT_USEROBJ); dptr = obj->d.uobj->data; Jsi_HashEntryDelete(hPtr); if (!dptr) continue; if (ptr->reg->freefun) ptr->reg->freefun(interp, dptr); } Jsi_HashDelete(tblPtr); Jsi_Free(ptr); return JSI_OK; }
static int TestSqlite(Jsi_Interp *interp, int level) { /* SETUP AND DB CREATION */ Jsi_Db *jdb; if (!interp) jdb = Jsi_DbNew("~/mytest.db", 0); else { #ifndef JSI_LITE_ONLY if (JSI_OK != Jsi_EvalString(interp, "var mydb = new Sqlite('/tmp/mytest.db');", 0)) return JSI_ERROR; jdb = Jsi_UserObjDataFromVar(interp, "mydb"); if (!jdb) return JSI_ERROR; #endif } sqlite3 *db = Jsi_DbHandle(interp, jdb); sqlite3_exec(db, "DROP TABLE IF EXISTS mytable;", 0, 0, 0); sqlite3_exec(db, "CREATE TABLE mytable (max FLOAT, name, desc, id INT, myTime INT, mark, markSet);", 0, 0, 0); /* NOW STAGE DATA TO AND FROM STRUCT. */ int i, n, res = JSI_OK; MyData mydata = {.id=99, .max=100.0, .mark=MARK_A, .markSet=6, .name="maryjane"}; mydata.myTime = time(NULL)*1000LL; // or use Jsi_DateTime() for milliseconds; Jsi_DSSet(&mydata.desc, "Some stuff"); /* Quickly store 10 rows into the database */ sqlite3_exec(db, "BEGIN", 0, 0, 0); for (i=0; i<10; i++) { mydata.id++; mydata.max--; n = Jsi_DbQuery(jdb, MyOptions, &mydata, 1, "INSERT INTO mytable %s", 0); assert(n==1); } sqlite3_exec(db, "COMMIT", 0, 0, 0); /* Read the last inserted row into mydata2. */ MyData mydata2 = {}; mydata2.rowid = mydata.rowid; n = Jsi_DbQuery(jdb, MyOptions, &mydata2, 1, "SELECT id,name FROM mytable WHERE rowid=:rowid", 0); assert(n==1); n = Jsi_DbQuery(jdb, MyOptions, &mydata2, 1, "SELECT %s FROM mytable WHERE rowid=:rowid", 0); assert(n==1); /* Modify all fields of last inserted row. */ mydata.max = -1; mydata.myTime = Jsi_DateTime(); strcpy(mydata.name, "billybob"); n = Jsi_DbQuery(jdb, MyOptions, &mydata, 1, "UPDATE mytable SET %s WHERE id=:id", 0); assert(n==1); /* Modify specific columns for half of inserted rows. */ mydata.id = 105; n = Jsi_DbQuery(jdb, MyOptions, &mydata, 1, "UPDATE mytable SET name=:name, max=:max WHERE id<:id", 0); assert(n==5); /* Delete item id 105. */ n = Jsi_DbQuery(jdb, MyOptions, &mydata, 1, "DELETE FROM mytable WHERE id=:id", 0); assert(n==1); /* ARRAY OF STRUCTS. */ int num = 10, cnt; MyData mydatas[10] = {}; cnt = Jsi_DbQuery(jdb, MyOptions, mydatas, num, "SELECT %s FROM mytable", 0); assert(cnt==9); for (i=0; i<cnt; i++) mydatas[i].id += i; n = Jsi_DbQuery(jdb, MyOptions, mydatas, cnt, "UPDATE mytable SET %s WHERE rowid = :rowid", 0); assert(n==9); /* Update only the dirty rows. */ for (i=1; i<=3; i++) { mydatas[i].isdirty = 1; mydatas[i].id += 100*i; } n = Jsi_DbQuery(jdb, MyOptions, mydatas, cnt, "UPDATE mytable SET %s WHERE rowid = :rowid", JSI_DB_DIRTY_ONLY); assert(n==3); /* ARRAY OF POINTERS TO STRUCTS. */ num = 3; static MyData *mdPtr[3] = {}; /* Fixed length */ n = Jsi_DbQuery(jdb, MyOptions, mdPtr, num, "SELECT %s FROM mytable", JSI_DB_PTRS); assert(n==3); printf("%f\n", mdPtr[0]->max); MyData **dynPtr = NULL; /* Variable length */ n = Jsi_DbQuery(jdb, MyOptions, &dynPtr, 0, "SELECT %s FROM mytable WHERE rowid < 5", JSI_DB_PTR_PTRS); assert(n==4); n = Jsi_DbQuery(jdb, MyOptions, &dynPtr, n, "SELECT %s FROM mytable LIMIT 1000", JSI_DB_PTR_PTRS); assert(n==9); n = Jsi_DbQuery(jdb, MyOptions, &dynPtr, n, NULL, JSI_DB_PTR_PTRS|JSI_DB_MEMFREE); assert(!dynPtr); /* Multi-bind interface, to bind vars from other struct(s). */ Jsi_DbMultipleBind binds[] = { { ':', MyOptions, mdPtr, num }, { '$', MyOptions, &mydata }, {} }; mydata.max = -1; n = Jsi_DbQuery(jdb, NULL, binds, 0, "SELECT %s FROM mytable WHERE max=$max", JSI_DB_PTRS); assert(n==3); /* Make mdPtr available as "mydata" to Javascript info.cdata() */ #ifndef JSI_LITE_ONLY Jsi_CDataRegister(interp, "mydata", MyOptions, mdPtr, num, JSI_DB_PTRS); #endif jdbPtr = jdb; if (level==2) { ExecMyData(mydatas, n, "SELECT %s FROM mytable;", 0); ExecMySemi(mdPtr, n, "SELECT %s FROM mytable;", 0); ExecMyDyn(&dynPtr, n, "SELECT %s FROM mytable;", 0); } /* Load test: insert/select/update 1,000,000 rows. */ if (level==1) { Jsi_Wide stim, etim; int bnum = 1000000; MyData *big = Jsi_Calloc(bnum, sizeof(MyData)), *b = big; //b->max = 2; b->id=99; b->max=100.0; b->mark=MARK_A; b->markSet=6; *b = mydata; printf("TESTING %d ROWS\n", bnum); stim = Jsi_DateTime(); b[0].id = 0; for (i=1; i<bnum; i++) { big[i] = *b; big[i].id = i; } etim = Jsi_DateTime(); printf("INIT C: %8.3f secs\n", ((etim-stim)/1000.0)); sqlite3_exec(db, "DELETE FROM mytable", 0, 0, 0); stim=etim; n = ExecMyData(big, bnum, "INSERT INTO mytable %s", 0); assert(n==bnum); etim = Jsi_DateTime(); i=(int)(etim-stim); printf("%8.3f sec, %8d rows/sec INSERT %d ROWS\n", i/1000.0, bnum*1000/i, n); stim=etim; memset(big, 0, num*sizeof(MyData)); n = ExecMyData(big, bnum, "SELECT %s FROM mytable", 0); assert(n==bnum); etim = Jsi_DateTime(); i=(int)(etim-stim); printf("%8.3f sec, %8d rows/sec SELECT %d ROWS \n", i/1000.0, bnum*1000/i, bnum); for (i=0; i<bnum; i++) { if (b[i].id != i) printf("FAILED: Data[%d].id: %d\n", i, b[i].id); b[i].id = i+1; } stim=etim; n = ExecMyData(big, bnum, "UPDATE mytable SET id=:id where rowid=:rowid", 0); assert(n==bnum); etim = Jsi_DateTime(); i=(int)(etim-stim); printf("%8.3f sec, %8d rows/sec UPDATE %d ROWS, 1 FIELD\n", i/1000.0, bnum*1000/i, n); for (i=0; i<bnum; i++) { if (b[i].id != (i+1)) printf("FAILED: Data[%d].id: %d\n", i, b[i].id); b[i].id = i+2; if ((i%10)==0) b[i].isdirty = 1; } stim=etim; n = ExecMyData(big, bnum, "UPDATE mytable SET %s where rowid=:rowid", 0); assert(n==bnum); etim = Jsi_DateTime(); i=(int)(etim-stim); printf("%8.3f sec, %8d rows/sec UPDATE %d ROWS, ALL FIELDS\n", i/1000.0, n*1000/i, n); stim=etim; n = ExecMyData(big, bnum, "UPDATE mytable SET %s where rowid=:rowid", JSI_DB_DIRTY_ONLY); assert(n==(bnum/10)); etim = Jsi_DateTime(); i=(int)(etim-stim); printf("%8.3f sec, %8d rows/sec UPDATE %d DIRTY ROWS\n", i/1000.0, (int)(n*1000.0/i), n); stim=etim; for (i=0; i<bnum; i++) { if ((i%1000)==0) b[i].isdirty = 1; } n = ExecMyData(big, bnum, "UPDATE mytable SET %s where rowid=:rowid", JSI_DB_DIRTY_ONLY); assert(n==(bnum/1000)); etim = Jsi_DateTime(); i=(int)(etim-stim); printf("%8.3f sec, %8d rows/sec UPDATE %d DIRTY ROWS\n", i/1000.0, n*1000/i, n); stim=etim; for (i=0; i<bnum; i++) { if ((i%100000)==0) b[i].isdirty = 1; } n = ExecMyData(big, bnum, "UPDATE mytable SET %s where rowid=:rowid", JSI_DB_DIRTY_ONLY); assert(n==(bnum/100000)); etim = Jsi_DateTime(); i=(int)(etim-stim); printf("%8.3f sec, %8d rows/sec UPDATE %d DIRTY ROWS\n", i/1000.0, n*1000/i, n); Jsi_Free(big); } // Why we need wrappers: following is an error that the compiler does not detect! if (0) Jsi_DbQuery(jdb, MyOptions, "a bad struct", n, "", 0); return res; } int main(int argc, char *argv[]) { #ifdef JSI_LITE_ONLY TestSqlite(NULL, argc>1?atoi(argv[1]):0); #else Jsi_Interp *interp = Jsi_InterpCreate(NULL, argc, argv, 0); TestSqlite(interp, argc>1?atoi(argv[1]):0); Jsi_Interactive(interp, JSI_OUTPUT_QUOTE|JSI_OUTPUT_NEWLINES); #endif return 0; }
void jsi_ScopeStrsFree(Jsi_ScopeStrs *ss) { if (!ss) return; Jsi_Free(ss->strings); Jsi_Free(ss); }
/* *---------------------------------------------------------------------- * * JsiCreatePipeline -- * * Given an argc/argv array, instantiate a pipeline of processes * as described by the argv. * * Results: * The return value is a count of the number of new processes * created, or -1 if an error occurred while creating the pipeline. * *pidArrayPtr is filled in with the address of a dynamically * allocated array giving the ids of all of the processes. It * is up to the caller to free this array when it isn't needed * anymore. If inPipePtr is non-NULL, *inPipePtr is filled in * with the file id for the input pipe for the pipeline (if any): * the caller must eventually close this file. If outPipePtr * isn't NULL, then *outPipePtr is filled in with the file id * for the output pipe from the pipeline: the caller must close * this file. If errFilePtr isn't NULL, then *errFilePtr is filled * with a file id that may be used to read error output after the * pipeline completes. * * Side effects: * Processes and pipes are created. * *---------------------------------------------------------------------- */ static int JsiCreatePipeline(Jsi_Interp *interp, Jsi_Value* args, int argc, pidtype **pidArrayPtr, fdtype *inPipePtr, fdtype *outPipePtr, fdtype *errFilePtr) { pidtype *pidPtr = NULL; /* Points to malloc-ed array holding all * the pids of child processes. */ int numPids = 0; /* Actual number of processes that exist * at *pidPtr right now. */ int cmdCount; /* Count of number of distinct commands * found in argc/argv. */ const char *input = NULL; /* Describes input for pipeline, depending * on "inputFile". NULL means take input * from stdin/pipe. */ #define FILE_NAME 0 /* input/output: filename */ #define FILE_APPEND 1 /* output only: filename, append */ #define FILE_HANDLE 2 /* input/output: filehandle */ #define FILE_TEXT 3 /* input only: input is actual text */ int inputFile = FILE_NAME; /* 1 means input is name of input file. * 2 means input is filehandle name. * 0 means input holds actual * text to be input to command. */ int outputFile = FILE_NAME; /* 0 means output is the name of output file. * 1 means output is the name of output file, and append. * 2 means output is filehandle name. * All this is ignored if output is NULL */ int errorFile = FILE_NAME; /* 0 means error is the name of error file. * 1 means error is the name of error file, and append. * 2 means error is filehandle name. * All this is ignored if error is NULL */ const char *output = NULL; /* Holds name of output file to pipe to, * or NULL if output goes to stdout/pipe. */ const char *error = NULL; /* Holds name of stderr file to pipe to, * or NULL if stderr goes to stderr/pipe. */ fdtype inputId = JSI_BAD_FD; /* Readable file id input to current command in * pipeline (could be file or pipe). JSI_BAD_FD * means use stdin. */ fdtype outputId = JSI_BAD_FD; /* Writable file id for output from current * command in pipeline (could be file or pipe). * JSI_BAD_FD means use stdout. */ fdtype errorId = JSI_BAD_FD; /* Writable file id for all standard error * output from all commands in pipeline. JSI_BAD_FD * means use stderr. */ fdtype lastOutputId = JSI_BAD_FD; /* Write file id for output from last command * in pipeline (could be file or pipe). * -1 means use stdout. */ fdtype pipeIds[2]; /* File ids for pipe that's being created. */ int firstArg, lastArg; /* Indexes of first and last arguments in * current command. */ int lastBar; int i; pidtype pid; char **save_environ; struct WaitInfoTable *table = jsi_ExecCmdData(interp); /* TODO: mutex??? */ /*int argc = Jsi_ValueGetLength(interp, args);*/ /* Holds the args which will be used to exec */ char **arg_array = Jsi_Calloc((argc + 1), sizeof(*arg_array)); int arg_count = 0; JsiReapDetachedPids(table); if (inPipePtr != NULL) { *inPipePtr = JSI_BAD_FD; } if (outPipePtr != NULL) { *outPipePtr = JSI_BAD_FD; } if (errFilePtr != NULL) { *errFilePtr = JSI_BAD_FD; } pipeIds[0] = pipeIds[1] = JSI_BAD_FD; /* * First, scan through all the arguments to figure out the structure * of the pipeline. Count the number of distinct processes (it's the * number of "|" arguments). If there are "<", "<<", or ">" arguments * then make note of input and output redirection and remove these * arguments and the arguments that follow them. */ cmdCount = 1; lastBar = -1; for (i = 0; i < argc; i++) { if (i == 0) { FileNormalize(interp, Jsi_ValueArrayIndex(interp, args, 0)); } const char *arg = Jsi_ValueArrayIndexToStr(interp, args, i, NULL); if (arg[0] == '<') { inputFile = FILE_NAME; input = arg + 1; if (*input == '<') { inputFile = FILE_TEXT; // TODO: make this or @ from a var input++; } else if (*input == '@') { inputFile = FILE_HANDLE; input++; } if (!*input && ++i < argc) { input = Jsi_ValueArrayIndexToStr(interp, args, i, NULL); } } else if (arg[0] == '>') { int dup_error = 0; outputFile = FILE_NAME; output = arg + 1; if (*output == '>') { outputFile = FILE_APPEND; output++; } if (*output == '&') { /* Redirect stderr too */ output++; dup_error = 1; } if (*output == '@') { outputFile = FILE_HANDLE; output++; } if (!*output && ++i < argc) { output = Jsi_ValueArrayIndexToStr(interp, args, i, NULL); } if (dup_error) { errorFile = outputFile; error = output; } } else if (arg[0] == '2' && arg[1] == '>') { error = arg + 2; errorFile = FILE_NAME; if (*error == '@' || (*error == '&' && error[1] == '1')) { errorFile = FILE_HANDLE; error++; } else if (*error == '>') { errorFile = FILE_APPEND; error++; } if (!*error && ++i < argc) { error = Jsi_ValueArrayIndexToStr(interp, args, i, NULL); } } else { if (Jsi_Strcmp(arg, "|") == 0 || Jsi_Strcmp(arg, "|&") == 0) { if (i == lastBar + 1 || i == argc - 1) { Jsi_LogError("illegal use of | or |& in command"); goto badargs; } lastBar = i; cmdCount++; } /* Either |, |& or a "normal" arg, so store it in the arg array */ arg_array[arg_count++] = (char *)arg; continue; } if (i >= argc) { Jsi_LogError("can't specify \"%s\" as last word in command", arg); goto badargs; } } if (arg_count == 0) { Jsi_LogError("didn't specify command to execute"); badargs: Jsi_Free(arg_array); return -1; } /* Must do this before vfork(), so do it now */ save_environ = JsiSaveEnv(JsiBuildEnv(interp)); /* * Set up the redirected input source for the pipeline, if * so requested. */ if (input != NULL) { if (inputFile == FILE_TEXT) { /* * Immediate data in command. Create temporary file and * put data into file. */ inputId = JsiCreateTemp(interp, input); if (inputId == JSI_BAD_FD) { goto error; } } else if (inputFile == FILE_HANDLE) { /* Should be a file descriptor */ FILE *fh = JsiGetAioFilehandle(interp, input); if (fh == NULL) { goto error; } inputId = JsiDupFd(JsiFileno(fh)); } else { /* * File redirection. Just open the file. */ inputId = JsiOpenForRead(interp, input); if (inputId == JSI_BAD_FD) { Jsi_LogError("couldn't read file \"%s\": %s", input, JsiStrError()); goto error; } } } else if (inPipePtr != NULL) { if (JsiPipe(pipeIds) != 0) { Jsi_LogError("couldn't create input pipe for command"); goto error; } inputId = pipeIds[0]; *inPipePtr = pipeIds[1]; pipeIds[0] = pipeIds[1] = JSI_BAD_FD; } /* * Set up the redirected output sink for the pipeline from one * of two places, if requested. */ if (output != NULL) { if (outputFile == FILE_HANDLE) { FILE *fh = JsiGetAioFilehandle(interp, output); if (fh == NULL) { goto error; } fflush(fh); lastOutputId = JsiDupFd(JsiFileno(fh)); } else { /* * Output is to go to a file. */ lastOutputId = JsiOpenForWrite(interp, output, outputFile == FILE_APPEND); if (lastOutputId == JSI_BAD_FD) { Jsi_LogError("couldn't write file \"%s\": %s", output, JsiStrError()); goto error; } } } else if (outPipePtr != NULL) { /* * Output is to go to a pipe. */ if (JsiPipe(pipeIds) != 0) { Jsi_LogError("couldn't create output pipe"); goto error; } lastOutputId = pipeIds[1]; *outPipePtr = pipeIds[0]; pipeIds[0] = pipeIds[1] = JSI_BAD_FD; } /* If we are redirecting stderr with 2>filename or 2>@fileId, then we ignore errFilePtr */ if (error != NULL) { if (errorFile == FILE_HANDLE) { if (Jsi_Strcmp(error, "1") == 0) { /* Special 2>@1 */ if (lastOutputId != JSI_BAD_FD) { errorId = JsiDupFd(lastOutputId); } else { /* No redirection of stdout, so just use 2>@stdout */ error = "stdout"; } } if (errorId == JSI_BAD_FD) { FILE *fh = JsiGetAioFilehandle(interp, error); if (fh == NULL) { goto error; } fflush(fh); errorId = JsiDupFd(JsiFileno(fh)); } } else { /* * Output is to go to a file. */ errorId = JsiOpenForWrite(interp, error, errorFile == FILE_APPEND); if (errorId == JSI_BAD_FD) { Jsi_LogError("couldn't write file \"%s\": %s", error, JsiStrError()); goto error; } } } else if (errFilePtr != NULL) { /* * Set up the standard error output sink for the pipeline, if * requested. Use a temporary file which is opened, then deleted. * Could potentially just use pipe, but if it filled up it could * cause the pipeline to deadlock: we'd be waiting for processes * to complete before reading stderr, and processes couldn't complete * because stderr was backed up. */ errorId = JsiCreateTemp(interp, NULL); if (errorId == JSI_BAD_FD) { goto error; } *errFilePtr = JsiDupFd(errorId); } /* * Scan through the argc array, forking off a process for each * group of arguments between "|" arguments. */ pidPtr = Jsi_Calloc(cmdCount, sizeof(*pidPtr)); for (i = 0; i < numPids; i++) { pidPtr[i] = JSI_BAD_PID; } for (firstArg = 0; firstArg < arg_count; numPids++, firstArg = lastArg + 1) { int pipe_dup_err = 0; fdtype origErrorId = errorId; for (lastArg = firstArg; lastArg < arg_count; lastArg++) { if (arg_array[lastArg][0] == '|') { if (arg_array[lastArg][1] == '&') { pipe_dup_err = 1; } break; } } /* Replace | with NULL for execv() */ arg_array[lastArg] = NULL; if (lastArg == arg_count) { outputId = lastOutputId; } else { if (JsiPipe(pipeIds) != 0) { Jsi_LogError("couldn't create pipe"); goto error; } outputId = pipeIds[1]; } /* Now fork the child */ #ifdef __MINGW32__ pid = JsiStartWinProcess(interp, &arg_array[firstArg], save_environ ? save_environ[0] : NULL, inputId, outputId, errorId); if (pid == JSI_BAD_PID) { Jsi_LogError("couldn't exec \"%s\"", arg_array[firstArg]); goto error; } #else /* * Disable SIGPIPE signals: if they were allowed, this process * might go away unexpectedly if children misbehave. This code * can potentially interfere with other application code that * expects to handle SIGPIPEs; what's really needed is an * arbiter for signals to allow them to be "shared". */ if (table->info == NULL) { (void)signal(SIGPIPE, SIG_IGN); } /* Need to do this befor vfork() */ if (pipe_dup_err) { errorId = outputId; } /* * Make a new process and enter it into the table if the fork * is successful. */ pid = vfork(); if (pid < 0) { Jsi_LogError("couldn't fork child process"); goto error; } if (pid == 0) { /* Child */ if (inputId != -1) dup2(inputId, 0); if (outputId != -1) dup2(outputId, 1); if (errorId != -1) dup2(errorId, 2); for (i = 3; (i <= outputId) || (i <= inputId) || (i <= errorId); i++) { close(i); } /* TODO: execvpe not using last arg! */ execvpe(arg_array[firstArg], &arg_array[firstArg], Jsi_GetEnviron()); /* Need to prep an error message before vfork(), just in case */ fprintf(stderr, "couldn't exec \"%s\"", arg_array[firstArg]); _exit(127); } #endif /* parent */ /* * Enlarge the wait table if there isn't enough space for a new * entry. */ if (table->used == table->size) { table->size += WAIT_TABLE_GROW_BY; table->info = Jsi_Realloc(table->info, table->size * sizeof(*table->info)); } table->info[table->used].pid = pid; table->info[table->used].flags = 0; table->used++; pidPtr[numPids] = pid; /* Restore in case of pipe_dup_err */ errorId = origErrorId; /* * Close off our copies of file descriptors that were set up for * this child, then set up the input for the next child. */ if (inputId != JSI_BAD_FD) { JsiCloseFd(inputId); } if (outputId != JSI_BAD_FD) { JsiCloseFd(outputId); } inputId = pipeIds[0]; pipeIds[0] = pipeIds[1] = JSI_BAD_FD; } *pidArrayPtr = pidPtr; /* * All done. Cleanup open files lying around and then return. */ cleanup: if (inputId != JSI_BAD_FD) { JsiCloseFd(inputId); } if (lastOutputId != JSI_BAD_FD) { JsiCloseFd(lastOutputId); } if (errorId != JSI_BAD_FD) { JsiCloseFd(errorId); } Jsi_Free(arg_array); if (save_environ) JsiRestoreEnv(save_environ); return numPids; /* * An error occurred. There could have been extra files open, such * as pipes between children. Clean them all up. Detach any child * processes that have been created. */ error: if ((inPipePtr != NULL) && (*inPipePtr != JSI_BAD_FD)) { JsiCloseFd(*inPipePtr); *inPipePtr = JSI_BAD_FD; } if ((outPipePtr != NULL) && (*outPipePtr != JSI_BAD_FD)) { JsiCloseFd(*outPipePtr); *outPipePtr = JSI_BAD_FD; } if ((errFilePtr != NULL) && (*errFilePtr != JSI_BAD_FD)) { JsiCloseFd(*errFilePtr); *errFilePtr = JSI_BAD_FD; } if (pipeIds[0] != JSI_BAD_FD) { JsiCloseFd(pipeIds[0]); } if (pipeIds[1] != JSI_BAD_FD) { JsiCloseFd(pipeIds[1]); } if (pidPtr != NULL) { for (i = 0; i < numPids; i++) { if (pidPtr[i] != JSI_BAD_PID) { JsiDetachPids(interp, 1, &pidPtr[i]); } } Jsi_Free(pidPtr); } numPids = -1; goto cleanup; }
/** * Apply the printf-like format in fmtObjPtr with the given arguments. * * Returns a new object with zero reference count if OK, or NULL on error. */ int Jsi_FormatString(Jsi_Interp *interp, Jsi_Value *args, Jsi_DString *dStr) { const char *span, *format, *formatEnd, *msg; int numBytes = 0, argIndex = 1, gotXpg = 0, gotSequential = 0, argCnt; static const char * const mixedXPG = "cannot mix \"%\" and \"%n$\" conversion specifiers"; static const char * const badIndex[2] = { "not enough arguments for all format specifiers", "\"%n$\" argument index out of range" }; int formatLen; Jsi_Value *v; /* A single buffer is used to store numeric fields (with sprintf()) * This buffer is allocated/reallocated as necessary */ char stat_buf[100], *num_buffer = stat_buf; int num_buffer_size = sizeof(stat_buf); argCnt = Jsi_ValueGetLength(interp, args); if (argCnt<1) { msg = "missing format"; goto errorMsg; } format = Jsi_ValueArrayIndexToStr(interp, args,0, &formatLen); span = format; formatEnd = format + formatLen; Jsi_DSInit(dStr); while (format != formatEnd) { char *end; int gotMinus, sawFlag; int gotPrecision, useShort, useLong; long width, precision; int newXpg; int ch; int step; int doubleType; char pad = ' '; char spec[2*JSI_INTEGER_SPACE + 12]; char *p; int formatted_chars; int formatted_bytes; const char *formatted_buf = NULL; step = jsi_utf8_tounicode(format, &ch); format += step; if (ch != '%') { numBytes += step; continue; } if (numBytes) { Jsi_DSAppendLen(dStr, span, numBytes); numBytes = 0; } /* * Saw a % : process the format specifier. * * Step 0. Handle special case of escaped format marker (i.e., %%). */ step = jsi_utf8_tounicode(format, &ch); if (ch == '%') { span = format; numBytes = step; format += step; continue; } /* * Step 1. XPG3 position specifier */ newXpg = 0; if (isdigit(ch)) { int position = strtoul(format, &end, 10); if (*end == '$') { newXpg = 1; argIndex = position - 1; format = end + 1; step = jsi_utf8_tounicode(format, &ch); } } if (newXpg) { if (gotSequential) { msg = mixedXPG; goto errorMsg; } gotXpg = 1; } else { if (gotXpg) { msg = mixedXPG; goto errorMsg; } gotSequential = 1; } if ((argIndex < 0) || (argIndex >= argCnt)) { msg = badIndex[gotXpg]; goto errorMsg; } /* * Step 2. Set of flags. Also build up the sprintf spec. */ p = spec; *p++ = '%'; gotMinus = 0; sawFlag = 1; do { switch (ch) { case '-': gotMinus = 1; break; case '0': pad = ch; break; case ' ': case '+': case '#': break; default: sawFlag = 0; continue; } *p++ = ch; format += step; step = jsi_utf8_tounicode(format, &ch); } while (sawFlag); /* * Step 3. Minimum field width. */ width = 0; if (isdigit(ch)) { width = strtoul(format, &end, 10); format = end; step = jsi_utf8_tounicode(format, &ch); } else if (ch == '*') { if (argIndex >= argCnt - 1) { msg = badIndex[gotXpg]; goto errorMsg; } v = Jsi_ValueArrayIndex(interp, args, argIndex); if (Jsi_GetLongFromValue(interp, v, &width) != JSI_OK) { goto error; } if (width < 0) { width = -width; if (!gotMinus) { *p++ = '-'; gotMinus = 1; } } argIndex++; format += step; step = jsi_utf8_tounicode(format, &ch); } /* * Step 4. Precision. */ gotPrecision = precision = 0; if (ch == '.') { gotPrecision = 1; format += step; step = jsi_utf8_tounicode(format, &ch); } if (isdigit(ch)) { precision = strtoul(format, &end, 10); format = end; step = jsi_utf8_tounicode(format, &ch); } else if (ch == '*') { if (argIndex >= argCnt - 1) { msg = badIndex[gotXpg]; goto errorMsg; } v = Jsi_ValueArrayIndex(interp, args, argIndex); if (Jsi_GetLongFromValue(interp, v, &precision) != JSI_OK) { goto error; } /* * TODO: Check this truncation logic. */ if (precision < 0) { precision = 0; } argIndex++; format += step; step = jsi_utf8_tounicode(format, &ch); } /* * Step 5. Length modifier. */ useShort = 0; useLong = 0; if (ch == 'h') { useShort = 1; format += step; step = jsi_utf8_tounicode(format, &ch); } else if (ch == 'l') { useLong = 1; format += step; step = jsi_utf8_tounicode(format, &ch); if (ch == 'l') { format += step; step = jsi_utf8_tounicode(format, &ch); } } format += step; span = format; /* * Step 6. The actual conversion character. */ if (ch == 'i') { ch = 'd'; } doubleType = 0; /* Each valid conversion will set: * formatted_buf - the result to be added * formatted_chars - the length of formatted_buf in characters * formatted_bytes - the length of formatted_buf in bytes */ switch (ch) { case '\0': msg = "format string ended in middle of field specifier"; goto errorMsg; case 's': { v = Jsi_ValueArrayIndex(interp, args, argIndex); if (Jsi_GetStringFromValue(interp, v, &formatted_buf) != JSI_OK) goto error; formatted_bytes = formatted_chars = Jsi_Strlen(formatted_buf); if (gotPrecision && (precision < formatted_chars)) { /* Need to build a (null terminated) truncated string */ formatted_chars = precision; formatted_bytes = jsi_utf8_index(formatted_buf, precision); } break; } case 'c': { Jsi_Wide code; v = Jsi_ValueArrayIndex(interp, args, argIndex); if (Jsi_GetWideFromValue(interp, v, &code) != JSI_OK) { goto error; } /* Just store the value in the 'spec' buffer */ formatted_bytes = jsi_utf8_fromunicode(spec, code); formatted_buf = spec; formatted_chars = 1; break; } case 'e': case 'E': case 'f': case 'g': case 'G': doubleType = 1; /* fall through */ case 'd': case 'u': case 'o': case 'x': case 'X': { Jsi_Wide w; Jsi_Number d; int length; /* Fill in the width and precision */ if (width) { p += sprintf(p, "%ld", width); } if (gotPrecision) { p += sprintf(p, ".%ld", precision); } /* Now the modifier, and get the actual value here */ if (doubleType) { v = Jsi_ValueArrayIndex(interp, args, argIndex); if (Jsi_GetDoubleFromValue(interp, v, &d) != JSI_OK) { goto error; } length = MAX_FLOAT_WIDTH; } else { v = Jsi_ValueArrayIndex(interp, args, argIndex); if (Jsi_GetWideFromValue(interp, v, &w) != JSI_OK) { goto error; } length = JSI_INTEGER_SPACE; if (useShort) { *p++ = 'h'; if (ch == 'd') { w = (short)w; } else { w = (unsigned short)w; } } else { *p++ = 'l'; #ifdef HAVE_LONG_LONG if (useLong && sizeof(long long) == sizeof(Jsi_Wide)) { *p++ = 'l'; } #endif } } *p++ = (char) ch; *p = '\0'; /* Adjust length for width and precision */ if (width > length) { length = width; } if (gotPrecision) { length += precision; } /* Increase the size of the buffer if needed */ if (num_buffer_size < length + 1) { num_buffer_size = length + 1; num_buffer = Jsi_Realloc((num_buffer==stat_buf?NULL:num_buffer), num_buffer_size); } if (doubleType) { snprintf(num_buffer, length + 1, spec, d); } else { formatted_bytes = snprintf(num_buffer, length + 1, spec, w); } formatted_chars = formatted_bytes = strlen(num_buffer); formatted_buf = num_buffer; break; } default: { /* Just reuse the 'spec' buffer */ spec[0] = ch; spec[1] = '\0'; Jsi_LogError("bad field specifier \"%s\"", spec); goto error; } } if (!gotMinus) { while (formatted_chars < width) { Jsi_DSAppendLen(dStr, &pad, 1); formatted_chars++; } } Jsi_DSAppendLen(dStr, formatted_buf, formatted_bytes); while (formatted_chars < width) { Jsi_DSAppendLen(dStr, &pad, 1); formatted_chars++; } argIndex += gotSequential; } if (numBytes) { Jsi_DSAppendLen(dStr, span, numBytes); } if (num_buffer!=stat_buf) Jsi_Free(num_buffer); return JSI_OK; errorMsg: Jsi_LogError("%s", msg); error: Jsi_DSFree(dStr); if (num_buffer!=stat_buf) Jsi_Free(num_buffer); return JSI_ERROR; }
/* * The main exec command */ int jsi_execCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_DString *dStr, Jsi_DString *cStr, int *code) { fdtype outputId; /* File id for output pipe. -1 * means command overrode. */ fdtype errorId; /* File id for temporary file * containing error output. */ pidtype *pidPtr; int numPids, result, argc; argc = Jsi_ValueGetLength(interp, args); /* * See if the command is to be run in background; if so, create * the command, detach it, and return. */ if (argc > 1 && Jsi_Strcmp(Jsi_ValueArrayIndexToStr(interp, args, argc-1, NULL), "&") == 0) { int i; argc--; numPids = JsiCreatePipeline(interp, args, argc, &pidPtr, NULL, NULL, NULL); if (numPids < 0) { return JSI_ERROR; } if (cStr) { /* The return value is a list of the pids */ Jsi_DSAppend(cStr,(Jsi_DSLength(cStr)>1?", ":""), "pids : [", NULL); for (i = 0; i < numPids; i++) { char ibuf[100]; sprintf(ibuf, "%s%ld", (i?",":""), (long)pidPtr[i]); Jsi_DSAppend(cStr, ibuf, NULL); } Jsi_DSAppend(cStr, "]", NULL); } JsiDetachPids(interp, numPids, pidPtr); Jsi_Free(pidPtr); return JSI_OK; } /* * Create the command's pipeline. */ numPids = JsiCreatePipeline(interp, args, argc, &pidPtr, NULL, &outputId, &errorId); if (numPids < 0) { return JSI_ERROR; } /* * Read the child's output (if any) and put it into the result. */ result = JSI_OK; if (outputId != JSI_BAD_FD) { result = JsiAppendStreamToString(interp, outputId, dStr); if (result != JSI_OK && cStr) Jsi_DSAppend(cStr, (Jsi_DSLength(cStr)>1?", ":""), "errstr: \"error reading from output pipe\"", NULL); } if (JsiCleanupChildren(interp, numPids, pidPtr, errorId, dStr, cStr, code) != JSI_OK) { result = JSI_ERROR; } return result; }