Synchronet now requires the libarchive development package (e.g. libarchive-dev on Debian-based Linux distros, libarchive.org for more info) to build successfully.

js_global.c 116 KB
Newer Older
1 2 3
/* Synchronet JavaScript "global" object properties/methods for all servers */

/* $Id$ */
4
// vi: tabstop=4
5 6 7 8 9

/****************************************************************************
 * @format.tab-size 4		(Plain Text/Source Code File Header)			*
 * @format.use-tabs true	(see http://www.synchro.net/ptsc_hdr.html)		*
 *																			*
10
 * Copyright Rob Swindell - http://www.synchro.net/copyright.html			*
11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
 *																			*
 * This program is free software; you can redistribute it and/or			*
 * modify it under the terms of the GNU General Public License				*
 * as published by the Free Software Foundation; either version 2			*
 * of the License, or (at your option) any later version.					*
 * See the GNU General Public License for more details: gpl.txt or			*
 * http://www.fsf.org/copyleft/gpl.html										*
 *																			*
 * Anonymous FTP access to the most recent released source is available at	*
 * ftp://vert.synchro.net, ftp://cvs.synchro.net and ftp://ftp.synchro.net	*
 *																			*
 * Anonymous CVS access to the development source and modification history	*
 * is available at cvs.synchro.net:/cvsroot/sbbs, example:					*
 * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs login			*
 *     (just hit return, no password is necessary)							*
 * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs checkout src		*
 *																			*
 * For Synchronet coding style and modification guidelines, see				*
 * http://www.synchro.net/source.html										*
 *																			*
 * You are encouraged to submit any modifications (preferably in Unix diff	*
 * format) via e-mail to mods@synchro.net									*
33
 *												
34 35 36 37
 * Note: If this box doesn't appear square, then you need to fix your tabs.	*
 ****************************************************************************/

#include "sbbs.h"
38
#include "md5.h"
39
#include "base64.h"
40
#include "htmlansi.h"
41
#include "ini_file.h"
42
#include "js_rtpool.h"
43
#include "js_request.h"
44
#include "wordwrap.h"
45

46
/* SpiderMonkey: */
47
#include <jsapi.h>
48
#include <jsdbgapi.h>
49

50 51
#define MAX_ANSI_SEQ	16
#define MAX_ANSI_PARAMS	8
52 53 54

#ifdef JAVASCRIPT

55 56 57
extern JSClass js_global_class;

/* Global Object Properties */
rswindell's avatar
rswindell committed
58 59
enum {
	 GLOB_PROP_ERRNO
60
	,GLOB_PROP_ERRNO_STR
61
	,GLOB_PROP_SOCKET_ERRNO
rswindell's avatar
rswindell committed
62 63
};

64 65 66 67 68 69 70 71 72
BOOL DLLCALL js_argc(JSContext *cx, uintN argc, uintN min)
{
	if(argc < min) {
		JS_ReportError(cx, "Insufficient Arguments");
		return FALSE;
	}
	return TRUE;
}

73
static JSBool js_system_get(JSContext *cx, JSObject *obj, jsid id, jsval *vp)
rswindell's avatar
rswindell committed
74
{
75
	jsval idval;
rswindell's avatar
rswindell committed
76
    jsint       tiny;
77
	JSString*	js_str;
rswindell's avatar
rswindell committed
78

79 80
    JS_IdToValue(cx, id, &idval);
    tiny = JSVAL_TO_INT(idval);
rswindell's avatar
rswindell committed
81 82

	switch(tiny) {
83
		case GLOB_PROP_SOCKET_ERRNO:
84
			*vp=DOUBLE_TO_JSVAL(ERROR_VALUE);
85
			break;
rswindell's avatar
rswindell committed
86
		case GLOB_PROP_ERRNO:
87
			*vp=INT_TO_JSVAL(errno);
rswindell's avatar
rswindell committed
88
			break;
89
		case GLOB_PROP_ERRNO_STR:
90 91 92
			if((js_str=JS_NewStringCopyZ(cx, strerror(errno)))==NULL)
				return(JS_FALSE);
	        *vp = STRING_TO_JSVAL(js_str);
93
			break;
rswindell's avatar
rswindell committed
94
	}
95
	return(JS_TRUE);
rswindell's avatar
rswindell committed
96 97
}

98
#define GLOBOBJ_FLAGS JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_SHARED
rswindell's avatar
rswindell committed
99

100 101
static jsSyncPropertySpec js_global_properties[] = {
/*		 name,			tinyid,					flags,			ver */
rswindell's avatar
rswindell committed
102

103 104 105
	{	"errno"			,GLOB_PROP_ERRNO		,GLOBOBJ_FLAGS, 310 },
	{	"errno_str"		,GLOB_PROP_ERRNO_STR	,GLOBOBJ_FLAGS, 310 },
	{	"socket_errno"	,GLOB_PROP_SOCKET_ERRNO	,GLOBOBJ_FLAGS, 310 },
rswindell's avatar
rswindell committed
106 107 108
	{0}
};

109
typedef struct {
110 111 112 113
	JSRuntime*		runtime;
	JSContext*		cx;
	JSContext*		parent_cx;
	JSObject*		obj;
114
	JSObject*		script;
115
	msg_queue_t*	msg_queue;
116
	js_callback_t	cb;
117
	JSErrorReporter error_reporter;
118
	JSObject*		logobj;
119
	sem_t			*sem;
120 121 122
} background_data_t;

static void background_thread(void* arg)
123
{
124
	background_data_t* bg = (background_data_t*)arg;
125
	jsval result=JSVAL_VOID;
126
	jsval exit_code;
127

128
	SetThreadName("sbbs/jsBackgrnd");
129
	msgQueueAttach(bg->msg_queue);
130
	JS_SetContextThread(bg->cx);
131
	JS_BEGINREQUEST(bg->cx);
132 133 134
	if(!JS_ExecuteScript(bg->cx, bg->obj, bg->script, &result)
		&& JS_GetProperty(bg->cx, bg->obj, "exit_code", &exit_code))
		result=exit_code;
135
	js_EvalOnExit(bg->cx, bg->obj, &bg->cb);
136
	js_enqueue_value(bg->cx, bg->msg_queue, result, NULL);
137
	JS_RemoveObjectRoot(bg->cx, &bg->logobj);
138
	JS_RemoveObjectRoot(bg->cx, &bg->obj);
139
	JS_ENDREQUEST(bg->cx);
140 141 142 143 144 145 146 147 148 149 150
	JS_DestroyContext(bg->cx);	/* exception here (Feb-5-2012):
 	mozjs185-1.0.dll!66f3cede() 	
 	[Frames below may be incorrect and/or missing, no symbols loaded for mozjs185-1.0.dll]	
 	mozjs185-1.0.dll!66f3d49d() 	
 	mozjs185-1.0.dll!66ee010f() 	
 	mozjs185-1.0.dll!66ebb0c9() 	
>	sbbs.dll!background_thread(void * arg=0x0167f050)  Line 146 + 0xf bytes	C
 	sbbs.dll!_callthreadstart()  Line 259 + 0xf bytes	C
 	sbbs.dll!_threadstart(void * ptd=0x016a0590)  Line 243	C
*/

151
	jsrt_Release(bg->runtime);
152
	sem_post(bg->sem);
153 154 155
	free(bg);
}

156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173
static void
js_ErrorReporter(JSContext *cx, const char *message, JSErrorReport *report)
{
	background_data_t* bg;

	if((bg=(background_data_t*)JS_GetContextPrivate(cx))==NULL)
		return;

	/* Use parent's context private data */
	JS_SetContextPrivate(cx, JS_GetContextPrivate(bg->parent_cx));

	/* Call parent's error reporter */
	bg->error_reporter(cx, message, report);

	/* Restore our context private data */
	JS_SetContextPrivate(cx, bg);
}

174 175
static JSBool
js_OperationCallback(JSContext *cx)
176 177
{
	background_data_t* bg;
178
	JSBool	ret;
179

180 181 182 183
	JS_SetOperationCallback(cx, NULL);

	if((bg=(background_data_t*)JS_GetContextPrivate(cx))==NULL) {
		JS_SetOperationCallback(cx, js_OperationCallback);
184
		return(JS_FALSE);
185
	}
186

187 188
	if(bg->parent_cx!=NULL && !JS_IsRunning(bg->parent_cx)) { 	/* die when parent dies */
		JS_SetOperationCallback(cx, js_OperationCallback);
189
		return(JS_FALSE);
190
	}
191

192
	ret=js_CommonOperationCallback(cx,&bg->cb);
193 194 195 196
	JS_SetOperationCallback(cx, js_OperationCallback);
	return ret;
}

197 198 199 200 201 202 203 204 205
static JSBool
js_log(JSContext *cx, uintN argc, jsval *arglist)
{
	jsval *argv=JS_ARGV(cx, arglist);
	JSBool retval;
	background_data_t* bg;
	jsval	rval;

	rval=JSVAL_VOID;
deuce's avatar
deuce committed
206
	JS_SET_RVAL(cx, arglist, rval);
207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222

	if((bg=(background_data_t*)JS_GetContextPrivate(cx))==NULL)
		return JS_FALSE;

	/* Use parent's context private data */
	JS_SetContextPrivate(cx, JS_GetContextPrivate(bg->parent_cx));

	/* Call parent's log() function */
	retval = JS_CallFunctionName(cx, bg->logobj, "log", argc, argv, &rval);

	/* Restore our context private data */
	JS_SetContextPrivate(cx, bg);

	return retval;
}

223
/* Create a new value in the new context with a value from the original context */
224 225
/* BOTH CONTEXTX NEED TO BE SUSPENDED! */
static jsval* js_CopyValue(JSContext* cx, jsrefcount *cx_rc, jsval val, JSContext* new_cx, jsrefcount *new_rc, jsval* rval)
226
{
227
	size_t	size;
deuce's avatar
deuce committed
228
	uint64	*nval;
229

deuce's avatar
deuce committed
230
	*rval = JSVAL_VOID;
231
	JS_RESUMEREQUEST(cx, *cx_rc);
232
	if(JS_WriteStructuredClone(cx, val, &nval, &size, NULL, NULL)) {
233 234
		*cx_rc=JS_SUSPENDREQUEST(cx);
		JS_RESUMEREQUEST(new_cx, *new_rc);
235
		JS_ReadStructuredClone(new_cx, nval, size, JS_STRUCTURED_CLONE_VERSION, rval, NULL, NULL);
236 237
		*new_rc=JS_SUSPENDREQUEST(new_cx);
		JS_RESUMEREQUEST(cx, *cx_rc);
238
		JS_free(cx, nval);
239
	}
240
	*cx_rc=JS_SUSPENDREQUEST(cx);
241 242 243 244

	return rval;
}

245
static JSBool
246
js_load(JSContext *cx, uintN argc, jsval *arglist)
247
{
248 249
	JSObject *obj=JS_THIS_OBJECT(cx, arglist);
	jsval *argv=JS_ARGV(cx, arglist);
250 251
	char		path[MAX_PATH+1];
    uintN		i;
252
	uintN		argn=0;
deuce's avatar
deuce committed
253
    char*		filename;
254
    JSObject*	script;
255
	global_private_t*	p;
256
	jsval		val;
257
	JSObject*	js_argv;
258 259 260
	jsval		old_js_argv = JSVAL_VOID;
	jsval		old_js_argc = JSVAL_VOID;
	BOOL		restore_args = FALSE;
261
	JSObject*	exec_obj;
262
	JSObject*	js_internal;
263
	JSContext*	exec_cx=cx;
264
	JSBool		success;
265
	JSBool		background=JS_FALSE;
266
	background_data_t* bg;
deuce's avatar
deuce committed
267
	jsrefcount	rc;
268
	jsrefcount	brc;
269
	size_t		len;
270

271
	JS_SET_RVAL(cx, arglist,JSVAL_VOID);
272

273
	if((p=(global_private_t*)js_GetClassPrivate(cx, obj, &js_global_class))==NULL)
274
		return(JS_FALSE);
275

276 277 278 279 280 281
	exec_obj=JS_GetScopeChain(cx);

	if(JSVAL_IS_BOOLEAN(argv[argn]))
		background=JSVAL_TO_BOOLEAN(argv[argn++]);

	if(background) {
282
		rc=JS_SUSPENDREQUEST(cx);
283

284 285
		if((bg=(background_data_t*)malloc(sizeof(background_data_t)))==NULL) {
			JS_RESUMEREQUEST(cx, rc);
286
			return(JS_FALSE);
287
		}
288
		memset(bg,0,sizeof(background_data_t));
289

290
		bg->parent_cx = cx;
291

292 293 294 295 296 297 298
		/* Setup default values for callback settings */
		bg->cb.limit=p->startup->time_limit;
		bg->cb.gc_interval=p->startup->gc_interval;
		bg->cb.yield_interval=p->startup->yield_interval;
		bg->cb.terminated=NULL;	/* could be bad pointer at any time */
		bg->cb.counter=0;
		bg->cb.gc_attempts=0;
299 300 301 302 303 304 305 306 307 308 309
		bg->cb.bg = TRUE;

		// Get the js.internal private data since it's the parents js_callback_t...
		if(JS_GetProperty(cx, JS_GetGlobalObject(cx), "js", &val) && !JSVAL_NULL_OR_VOID(val)) {
			js_internal = JSVAL_TO_OBJECT(val);
			bg->cb.parent_cb = (js_callback_t*)JS_GetPrivate(cx,js_internal);
			if (bg->cb.parent_cb == NULL) {
				lprintf(LOG_ERR, "!ERROR parent CB is NULL");
			}
		}
		else {
310
			lprintf(LOG_ERR, "!ERROR unable to locate global js object");
311
		}
312

313 314
		if((bg->runtime = jsrt_GetNew(JAVASCRIPT_MAX_BYTES, 1000, __FILE__, __LINE__))==NULL) {
			free(bg);
315
			JS_RESUMEREQUEST(cx, rc);
316
			return(JS_FALSE);
317
		}
318

319 320 321
	    if((bg->cx = JS_NewContext(bg->runtime, JAVASCRIPT_CONTEXT_STACK))==NULL) {
			jsrt_Release(bg->runtime);
			free(bg);
322
			JS_RESUMEREQUEST(cx, rc);
323
			return(JS_FALSE);
324
		}
325
		JS_BEGINREQUEST(bg->cx);
326

327
		if(!js_CreateCommonObjects(bg->cx
328
				,p->cfg			/* common config */
329
				,NULL			/* node-specific config */
330
				,p->methods		/* additional global methods */
331 332 333
				,0				/* uptime */
				,""				/* hostname */
				,""				/* socklib_desc */
334
				,&bg->cb		/* js */
335
				,p->startup		/* js */
336 337
				,NULL			/* client */
				,INVALID_SOCKET	/* client_socket */
338
				,-1				/* client TLS session */
339
				,NULL			/* server props */
340 341 342 343 344 345
				,&bg->obj
				)) {
			JS_ENDREQUEST(bg->cx);
			JS_DestroyContext(bg->cx);
			jsrt_Release(bg->runtime);
			free(bg);
346
			JS_RESUMEREQUEST(cx, rc);
347
			return(JS_FALSE);
348
		}
349

350
		if((bg->logobj=JS_NewObjectWithGivenProto(bg->cx, NULL, NULL, bg->obj))==NULL) {
351
			JS_RemoveObjectRoot(bg->cx, &bg->obj);
352 353 354 355
			JS_ENDREQUEST(bg->cx);
			JS_DestroyContext(bg->cx);
			jsrt_Release(bg->runtime);
			free(bg);
356
			JS_RESUMEREQUEST(cx, rc);
357 358
			return(JS_FALSE);
		}
359
		JS_AddObjectRoot(bg->cx, &bg->logobj);
360

361 362 363 364
		bg->msg_queue = msgQueueInit(NULL,MSG_QUEUE_BIDIR);

		js_CreateQueueObject(bg->cx, bg->obj, "parent_queue", bg->msg_queue);

365
		/* Save parent's error reporter (for later use by our error reporter) */
366 367
		brc=JS_SUSPENDREQUEST(bg->cx);
		JS_RESUMEREQUEST(cx, rc);
368 369
		bg->error_reporter=JS_SetErrorReporter(cx,NULL);
		JS_SetErrorReporter(cx,bg->error_reporter);
370 371
		rc=JS_SUSPENDREQUEST(cx);
		JS_RESUMEREQUEST(bg->cx, brc);
372
		JS_SetErrorReporter(bg->cx,js_ErrorReporter);
373

374
		/* Set our Operation callback (which calls the generic branch callback) */
375
		JS_SetContextPrivate(bg->cx, bg);
376
		JS_SetOperationCallback(bg->cx, js_OperationCallback);
377

378
		/* Save parent's 'log' function (for later use by our log function) */
379 380
		brc=JS_SUSPENDREQUEST(bg->cx);
		JS_RESUMEREQUEST(cx, rc);
381 382
		if(JS_GetProperty(cx, obj, "log", &val)) {
			JSFunction* func;
383 384 385
			if((func=JS_ValueToFunction(cx, val))!=NULL) {
				JSObject *obj;

386 387
				rc=JS_SUSPENDREQUEST(cx);
				JS_RESUMEREQUEST(bg->cx, brc);
388 389 390
				obj=JS_CloneFunctionObject(bg->cx, JS_GetFunctionObject(func), bg->logobj);
				JS_DefineProperty(bg->cx, bg->logobj, "log", OBJECT_TO_JSVAL(obj), NULL, NULL, JSPROP_ENUMERATE|JSPROP_PERMANENT);
				JS_DefineFunction(bg->cx, bg->obj, "log", js_log, JS_GetFunctionArity(func), JS_GetFunctionFlags(func));
391 392
				brc=JS_SUSPENDREQUEST(bg->cx);
				JS_RESUMEREQUEST(cx, rc);
393 394 395
			}
		}

396 397 398 399 400 401
		// These js_Create*Object() functions use GetContextPrivate() for the sbbs_t.
		JS_SetContextPrivate(bg->cx, JS_GetContextPrivate(bg->parent_cx));
		if (JS_HasProperty(cx, obj, "bbs", &success) && success)
			js_CreateBbsObject(bg->cx, bg->obj);
		if (JS_HasProperty(cx, obj, "console", &success) && success)
			js_CreateConsoleObject(bg->cx, bg->obj);
402
		if (JS_HasProperty(cx, obj, "stdin", &success) && success)
403
			js_CreateFileObject(bg->cx, bg->obj, "stdin", STDIN_FILENO, "r");
404
		if (JS_HasProperty(cx, obj, "stdout", &success) && success)
405
			js_CreateFileObject(bg->cx, bg->obj, "stdout", STDOUT_FILENO, "w");
406
		if (JS_HasProperty(cx, obj, "stderr", &success) && success)
407
			js_CreateFileObject(bg->cx, bg->obj, "stderr", STDERR_FILENO, "w");
408 409
		JS_SetContextPrivate(bg->cx, bg);

410 411
		exec_cx = bg->cx;
		exec_obj = bg->obj;
412
		
413 414 415 416 417
	} else if(JSVAL_IS_OBJECT(argv[argn])) {
		JSObject* tmp_obj=JSVAL_TO_OBJECT(argv[argn++]);
		if(!JS_ObjectIsFunction(cx,tmp_obj))	/* Scope specified */
			exec_obj=tmp_obj;
	}
418

419 420
	if(argn==argc) {
		JS_ReportError(cx,"no filename specified");
421
		if(background) {
422 423
			rc=JS_SUSPENDREQUEST(cx);
			JS_RESUMEREQUEST(bg->cx, brc);
424 425 426 427
			JS_RemoveObjectRoot(bg->cx, &bg->obj);
			JS_ENDREQUEST(bg->cx);
			JS_DestroyContext(bg->cx);
			jsrt_Release(bg->runtime);
428
			JS_RESUMEREQUEST(cx, rc);
429 430
			free(bg);
		}
431 432
		return(JS_FALSE);
	}
433 434
	JSVALUE_TO_MSTRING(cx, argv[argn], filename, NULL);
	argn++;
435
	if(filename==NULL) {	// This handles the pending error as well as a null JS string.
436
		if(background) {
437 438
			rc=JS_SUSPENDREQUEST(cx);
			JS_RESUMEREQUEST(bg->cx, brc);
439 440 441 442
			JS_RemoveObjectRoot(bg->cx, &bg->obj);
			JS_ENDREQUEST(bg->cx);
			JS_DestroyContext(bg->cx);
			jsrt_Release(bg->runtime);
443
			JS_RESUMEREQUEST(cx, rc);
444 445
			free(bg);
		}
446
		return(JS_FALSE);
447
	}
448

449
	if(argc>argn || background) {
450

451 452 453 454
		if(background) {
			rc=JS_SUSPENDREQUEST(cx);
			JS_RESUMEREQUEST(bg->cx, brc);
		}
455 456 457 458 459 460 461
		else {
			JS_GetProperty(exec_cx, exec_obj, "argv", &old_js_argv);
			JS_AddValueRoot(exec_cx, &old_js_argv);
			JS_GetProperty(exec_cx, exec_obj, "argc", &old_js_argc);
			JS_AddValueRoot(exec_cx, &old_js_argc);
			restore_args = TRUE;
		}
462

463 464 465 466 467 468 469 470
		if((js_argv=JS_NewArrayObject(exec_cx, 0, NULL)) == NULL) {
			if(background) {
				JS_RemoveObjectRoot(bg->cx, &bg->obj);
				JS_ENDREQUEST(bg->cx);
				JS_DestroyContext(bg->cx);
				jsrt_Release(bg->runtime);
				free(bg);
			}
471
			free(filename);
472
			return(JS_FALSE);
473
		}
474

475
		JS_DefineProperty(exec_cx, exec_obj, "argv", OBJECT_TO_JSVAL(js_argv)
476 477
			,NULL,NULL,JSPROP_ENUMERATE|JSPROP_READONLY);

478 479
		for(i=argn; i<argc; i++) {
			jsval *copy;
480 481 482 483 484 485 486 487 488 489
			if(background) {
				brc=JS_SUSPENDREQUEST(bg->cx);
				copy=js_CopyValue(cx,&rc,argv[i],exec_cx,&brc,&val);
				JS_RESUMEREQUEST(bg->cx, brc);
			}
			else {
				rc=JS_SUSPENDREQUEST(cx);
				copy=js_CopyValue(cx,&rc,argv[i],exec_cx,&rc,&val);
				JS_RESUMEREQUEST(cx, rc);
			}
490 491
			JS_SetElement(exec_cx, js_argv, i-argn, copy);
		}
492 493 494

		JS_DefineProperty(exec_cx, exec_obj, "argc", INT_TO_JSVAL(argc-argn)
			,NULL,NULL,JSPROP_ENUMERATE|JSPROP_READONLY);
495 496 497 498 499

		if(background) {
			brc=JS_SUSPENDREQUEST(bg->cx);
			JS_RESUMEREQUEST(cx, rc);
		}
500 501
	}

502
	rc=JS_SUSPENDREQUEST(cx);
503
	errno = 0;
504
	if(isfullpath(filename))
505
		SAFECOPY(path,filename);
506
	else {
507 508 509 510
		JSObject* js_load_list = NULL;

		path[0]=0;	/* Empty path, indicates load file not found (yet) */

511
		JS_RESUMEREQUEST(cx, rc);
rswindell's avatar
rswindell committed
512
		if(JS_GetProperty(cx, obj, "js", &val) && val!=JSVAL_VOID && JSVAL_IS_OBJECT(val)) {
513 514
			JSObject* js_obj = JSVAL_TO_OBJECT(val);
			
515
			/* if js.exec_dir is defined (location of executed script), search there first */
516
			if(JS_GetProperty(cx, js_obj, "exec_dir", &val) && val!=JSVAL_VOID && JSVAL_IS_STRING(val)) {
517 518 519
				JSVALUE_TO_STRBUF(cx, val, path, sizeof(path), &len);
				strncat(path, filename, sizeof(path)-len);
				path[sizeof(path)-1]=0;
520
				rc=JS_SUSPENDREQUEST(cx);
521 522
				if(!fexistcase(path))
					path[0]=0;
523
				JS_RESUMEREQUEST(cx, rc);
524
			}
525 526 527 528 529
			if(JS_GetProperty(cx, js_obj, JAVASCRIPT_LOAD_PATH_LIST, &val) && val!=JSVAL_VOID && JSVAL_IS_OBJECT(val))
				js_load_list = JSVAL_TO_OBJECT(val);

			/* if mods_dir is defined, search mods/js.load_path_list[n] next */
			if(path[0]==0 && p->cfg->mods_dir[0]!=0 && js_load_list!=NULL) {
530 531
				jsuint		i;
				char		prefix[MAX_PATH+1];
532

533
				for(i=0;path[0]==0;i++) {
534
					if(!JS_GetElement(cx, js_load_list, i, &val) || val==JSVAL_VOID)
535
						break;
536
					JSVALUE_TO_STRBUF(cx, val, prefix, sizeof(prefix), NULL);
537 538 539
					if(prefix[0]==0)
						continue;
					backslash(prefix);
540
					rc=JS_SUSPENDREQUEST(cx);
541 542 543 544 545 546 547
					if(isfullpath(prefix)) {
						SAFEPRINTF2(path,"%s%s",prefix,filename);
						if(!fexistcase(path))
							path[0]=0;
					} else {
						/* relative path */
						SAFEPRINTF3(path,"%s%s%s",p->cfg->mods_dir,prefix,filename);
548 549
						if(!fexistcase(path))
							path[0]=0;
550
					}
551
					JS_RESUMEREQUEST(cx, rc);
552 553 554
				}
			}
		}
555
		rc=JS_SUSPENDREQUEST(cx);
rswindell's avatar
rswindell committed
556
		/* if mods_dir is defined, search there next */
557
		if(path[0]==0 && p->cfg->mods_dir[0]!=0) {
558
			SAFEPRINTF2(path,"%s%s",p->cfg->mods_dir,filename);
559 560 561 562 563 564 565 566 567
			if(!fexistcase(path))
				path[0]=0;
		}
		/* if js.load_path_list is defined, search exec/load_path_list[n] next */
		if(path[0]==0 && js_load_list!=NULL) {
			jsuint		i;
			char		prefix[MAX_PATH+1];

			for(i=0;path[0]==0;i++) {
568 569 570
				JS_RESUMEREQUEST(cx, rc);
				if(!JS_GetElement(cx, js_load_list, i, &val) || val==JSVAL_VOID) {
					rc=JS_SUSPENDREQUEST(cx);
571
					break;
572
				}
573
				JSVALUE_TO_STRBUF(cx, val, prefix, sizeof(prefix), NULL);
574
				rc=JS_SUSPENDREQUEST(cx);
575 576 577 578 579 580 581 582 583 584 585 586 587 588
				if(prefix[0]==0)
					continue;
				backslash(prefix);
				if(isfullpath(prefix)) {
					SAFEPRINTF2(path,"%s%s",prefix,filename);
					if(!fexistcase(path))
						path[0]=0;
				} else {
					/* relative path */
					SAFEPRINTF3(path,"%s%s%s",p->cfg->exec_dir,prefix,filename);
					if(!fexistcase(path))
						path[0]=0;
				}
			}
589
		}
590 591 592
		/* lastly, search exec dir */
		if(path[0]==0)
			SAFEPRINTF2(path,"%s%s",p->cfg->exec_dir,filename);
593
	}
594
	free(filename);
595 596 597 598 599

	if(background)
		JS_RESUMEREQUEST(bg->cx, brc);
	else
		JS_RESUMEREQUEST(cx, rc);
600

601
	JS_ClearPendingException(exec_cx);
602

603 604
	if((script=JS_CompileFile(exec_cx, exec_obj, path))==NULL) {
		if(background) {
605
			JS_ENDREQUEST(bg->cx);
606 607 608 609
			JS_RemoveObjectRoot(bg->cx, &bg->obj);
			JS_DestroyContext(bg->cx);
			jsrt_Release(bg->runtime);
			free(bg);
610
			JS_RESUMEREQUEST(cx, rc);
611
		}
612 613 614 615 616 617 618 619 620 621 622 623 624
		// Restore args
		if (restore_args) {
			if (old_js_argv == JSVAL_VOID) {
				JS_DeleteProperty(exec_cx, exec_obj, "argv");
				JS_DeleteProperty(exec_cx, exec_obj, "argc");
			}
			else {
				JS_DefineProperty(exec_cx, exec_obj, "argv", old_js_argv
					,NULL,NULL,JSPROP_ENUMERATE|JSPROP_READONLY);
				JS_DefineProperty(exec_cx, exec_obj, "argc", old_js_argc
					,NULL,NULL,JSPROP_ENUMERATE|JSPROP_READONLY);
			}
		}
625 626
		JS_RemoveValueRoot(exec_cx, &old_js_argv);
		JS_RemoveValueRoot(exec_cx, &old_js_argc);
627
		return(JS_FALSE);
628
	}
629

630
	if(background) {
631

632
		bg->script = script;
633 634
		brc=JS_SUSPENDREQUEST(bg->cx);
		JS_RESUMEREQUEST(cx, rc);
635
		JS_SET_RVAL(cx, arglist, OBJECT_TO_JSVAL(js_CreateQueueObject(cx, obj, NULL, bg->msg_queue)));
636 637
		rc=JS_SUSPENDREQUEST(cx);
		JS_RESUMEREQUEST(bg->cx, brc);
638
		js_PrepareToExecute(bg->cx, bg->obj, path, NULL, bg->obj);
639
		JS_ENDREQUEST(bg->cx);
640
		JS_ClearContextThread(bg->cx);
641
		bg->sem=&p->bg_sem;
642
		lprintf(LOG_DEBUG, "JavaScript Background Load: %s", path);
643
		success = _beginthread(background_thread,0,bg)!=-1;
644
		JS_RESUMEREQUEST(cx, rc);
645 646 647
		if(success) {
			p->bg_count++;
		}
648

649
	} else {
deuce's avatar
deuce committed
650
		jsval	rval;
651

deuce's avatar
deuce committed
652 653
		success = JS_ExecuteScript(exec_cx, exec_obj, script, &rval);
		JS_SET_RVAL(cx, arglist, rval);
654 655 656 657 658 659 660 661 662 663 664
		if (restore_args) {
			if (old_js_argv == JSVAL_VOID) {
				JS_DeleteProperty(exec_cx, exec_obj, "argv");
				JS_DeleteProperty(exec_cx, exec_obj, "argc");
			}
			else {
				JS_DefineProperty(exec_cx, exec_obj, "argv", old_js_argv
					,NULL,NULL,JSPROP_ENUMERATE|JSPROP_READONLY);
				JS_DefineProperty(exec_cx, exec_obj, "argc", old_js_argc
					,NULL,NULL,JSPROP_ENUMERATE|JSPROP_READONLY);
			}
665 666
			JS_RemoveValueRoot(exec_cx, &old_js_argv);
			JS_RemoveValueRoot(exec_cx, &old_js_argc);
667
		}
668
	}
669

670
    return(success);
671 672
}

673 674 675 676 677
/*
 * This is hacky, but a but less hacky than using a magic '2'
 * It does assume the args are always last though (which seems reasonable
 * since it's variable length)
 */
678
#define JS_ARGS_OFFSET	((unsigned long)(JS_ARGV(0, (jsval *)NULL))/sizeof(jsval *))
679 680 681 682 683

static JSBool
js_require(JSContext *cx, uintN argc, jsval *arglist)
{
	uintN argn = 0;
684
	uintN fnarg;
685 686 687
	JSObject*	exec_obj;
	JSObject*	tmp_obj;
    char*		property;
688
    char*		filename;
689
    JSBool		found = JS_FALSE;
690
    JSBool		ret;
691 692 693 694 695 696 697 698 699 700 701 702 703 704 705
	jsval *argv=JS_ARGV(cx, arglist);

	exec_obj=JS_GetScopeChain(cx);
	if(JSVAL_IS_BOOLEAN(argv[argn])) {
		JS_ReportError(cx,"cannot require() background processes");
		return(JS_FALSE);
	}

	if(JSVAL_IS_OBJECT(argv[argn])) {
		tmp_obj=JSVAL_TO_OBJECT(argv[argn++]);
		if(!JS_ObjectIsFunction(cx,tmp_obj))	/* Scope specified */
			exec_obj=tmp_obj;
	}

	// Skip filename
706
	fnarg = argn++;
707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724

	if(argn==argc) {
		JS_ReportError(cx,"no symbol name specified");
		return(JS_FALSE);
	}
	JSVALUE_TO_MSTRING(cx, argv[argn], property, NULL);

	// TODO: Does this support sub-objects?
	if (JS_HasProperty(cx, exec_obj, property, &found) && found) {
		JS_SET_RVAL(cx, arglist,JSVAL_VOID);
		free(property);
		return JS_TRUE;
	}

	// Remove symbol name from args
	if (argc > argn+1)
		memmove(&arglist[argn+JS_ARGS_OFFSET], &arglist[argn+JS_ARGS_OFFSET+1], sizeof(arglist[0]) * (argc - argn - 1));

725 726
	ret = js_load(cx, argc-1, arglist);

727 728 729 730 731 732 733
	if (!JS_IsExceptionPending(cx)) {
		if (!JS_HasProperty(cx, exec_obj, property, &found) || !found) {
			JSVALUE_TO_MSTRING(cx, argv[fnarg], filename, NULL);
			JS_ReportError(cx,"symbol '%s' not defined by script '%s'", property, filename);
			free(filename);
			return(JS_FALSE);
		}
734 735 736
	}
	free(property);
	return ret;
737 738
}

739
static JSBool
740
js_format(JSContext *cx, uintN argc, jsval *arglist)
741
{
742
	jsval *argv=JS_ARGV(cx, arglist);
743
	char*		p;
744
    JSString*	str;
745

746 747
	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

748 749
	if((p=js_sprintf(cx, 0, argc, argv))==NULL) {
		JS_ReportError(cx,"js_sprintf failed");
750
		return(JS_FALSE);
751 752 753
	}

	str = JS_NewStringCopyZ(cx, p);
754
	js_sprintf_free(p);
755

756 757 758
	if(str==NULL)
		return(JS_FALSE);

759
	JS_SET_RVAL(cx, arglist, STRING_TO_JSVAL(str));
760
    return(JS_TRUE);
761 762
}

763
static JSBool
764
js_yield(JSContext *cx, uintN argc, jsval *arglist)
765
{
766
	jsval *argv=JS_ARGV(cx, arglist);
767
	BOOL forced=TRUE;
deuce's avatar
deuce committed
768
	jsrefcount	rc;
769

770 771
	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

772 773
	if(argc)
		JS_ValueToBoolean(cx, argv[0], &forced);
774
	rc=JS_SUSPENDREQUEST(cx);
775 776 777 778 779
	if(forced) {
		YIELD();
	} else {
		MAYBE_YIELD();
	}
780
	JS_RESUMEREQUEST(cx, rc);
781 782 783 784

	return(JS_TRUE);
}

785
static JSBool
786
js_mswait(JSContext *cx, uintN argc, jsval *arglist)
787
{
788
	jsval *argv=JS_ARGV(cx, arglist);
789
	int32 val=1;
790
	clock_t start=msclock();
deuce's avatar
deuce committed
791
	jsrefcount	rc;
792

793 794 795 796
	if(argc) {
		if(!JS_ValueToInt32(cx,argv[0],&val))
			return JS_FALSE;
	}
797
	rc=JS_SUSPENDREQUEST(cx);
798
	mswait(val);
799
	JS_RESUMEREQUEST(cx, rc);
800

801
	JS_SET_RVAL(cx, arglist,UINT_TO_JSVAL(msclock()-start));
802

803 804 805
	return(JS_TRUE);
}

806
static JSBool
807
js_random(JSContext *cx, uintN argc, jsval *arglist)
808
{