jsexec.c 30.9 KB
Newer Older
1
2
/* jsexec.c */

rswindell's avatar
rswindell committed
3
/* Execute a Synchronet JavaScript module from the command-line */
4
5
6
7
8
9
10

/* $Id$ */

/****************************************************************************
 * @format.tab-size 4		(Plain Text/Source Code File Header)			*
 * @format.use-tabs true	(see http://www.synchro.net/ptsc_hdr.html)		*
 *																			*
11
 * Copyright 2011 Rob Swindell - http://www.synchro.net/copyright.html		*
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
 *																			*
 * 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									*
 *																			*
 * Note: If this box doesn't appear square, then you need to fix your tabs.	*
 ****************************************************************************/

rswindell's avatar
rswindell committed
38
#ifndef JAVASCRIPT
39
#define JAVASCRIPT
rswindell's avatar
rswindell committed
40
#endif
41

deuce's avatar
deuce committed
42
43
44
45
#ifdef __unix__
#include <signal.h>
#endif

46
#include "sbbs.h"
47
#include "ciolib.h"
48
#include "ini_file.h"
49
#include "js_rtpool.h"
50
#include "js_request.h"
51
#include <jsdbgapi.h>
52

53
#define DEFAULT_LOG_LEVEL	LOG_DEBUG	/* Display all LOG levels */
54
#define DEFAULT_ERR_LOG_LVL	LOG_WARNING
55

56
js_startup_t	startup;
57
58
59
JSRuntime*	js_runtime;
JSContext*	js_cx;
JSObject*	js_glob;
60
js_callback_t	cb;
61
62
scfg_t		scfg;
ulong		js_max_bytes=JAVASCRIPT_MAX_BYTES;
63
ulong		js_cx_stack=JAVASCRIPT_CONTEXT_STACK;
rswindell's avatar
rswindell committed
64
65
FILE*		confp;
FILE*		errfp;
66
FILE*		nulfp;
67
FILE*		statfp;
68
char		revision[16];
69
char		compiler[32];
70
71
char*		host_name=NULL;
char		host_name_buf[128];
72
char*		load_path_list=JAVASCRIPT_LOAD_PATH;
73
BOOL		pause_on_exit=FALSE;
rswindell's avatar
rswindell committed
74
BOOL		pause_on_error=FALSE;
75
BOOL		terminated=FALSE;
76
BOOL		recycled;
77
int			log_level=DEFAULT_LOG_LEVEL;
78
int  		err_level=DEFAULT_ERR_LOG_LVL;
79
pthread_mutex_t output_mutex;
80
81
82
#if defined(__unix__)
BOOL		daemonize=FALSE;
#endif
83
char		orig_cwd[MAX_PATH+1];
deuce's avatar
deuce committed
84
BOOL		debugger=FALSE;
85
86
87
88
89
90
91
enum debug_action {
	DEBUG_CONTINUE,
	DEBUG_EXIT
};

static JSTrapStatus trap_handler(JSContext *cx, JSScript *script, jsbytecode *pc, jsval *rval, jsval closure);
static enum debug_action debug_prompt(JSScript *script);
92

93
94
void banner(FILE* fp)
{
95
	fprintf(fp,"\nJSexec v%s%c-%s (rev %s)%s - "
96
97
98
99
		"Execute Synchronet JavaScript Module\n"
		,VERSION,REVISION
		,PLATFORM_DESC
		,revision
100
101
102
103
104
#ifdef _DEBUG
		," Debug"
#else
		,""
#endif
105
		);
106
107
108

	fprintf(fp, "Compiled %s %s with %s\n"
		,__DATE__, __TIME__, compiler);
109
}
110

rswindell's avatar
rswindell committed
111
112
void usage(FILE* fp)
{
113
114
	banner(fp);

rswindell's avatar
rswindell committed
115
116
	fprintf(fp,"\nusage: jsexec [-opts] [path]module[.js] [args]\n"
		"\navailable opts:\n\n"
117
		"\t-c<ctrl_dir>   specify path to Synchronet CTRL directory\n"
118
119
120
#if defined(__unix__)
		"\t-d             run in background (daemonize)\n"
#endif
121
122
		"\t-m<bytes>      set maximum heap size (default=%u bytes)\n"
		"\t-s<bytes>      set context stack size (default=%u bytes)\n"
123
		"\t-t<limit>      set time limit (default=%u, 0=unlimited)\n"
124
125
126
		"\t-y<interval>   set yield interval (default=%u, 0=never)\n"
		"\t-g<interval>   set garbage collection interval (default=%u, 0=never)\n"
		"\t-h[hostname]   use local or specified host name (instead of SCFG value)\n"
127
		"\t-u<mask>       set file creation permissions mask (in octal)\n"
128
		"\t-L<level>      set log level (default=%u)\n"
129
		"\t-E<level>      set error log level threshold (default=%u)\n"
130
		"\t-i<path_list>  set load() comma-sep search path list (default=\"%s\")\n"
131
		"\t-f             use non-buffered stream for console messages\n"
rswindell's avatar
rswindell committed
132
		"\t-a             append instead of overwriting message output files\n"
133
134
135
136
		"\t-e<filename>   send error messages to file in addition to stderr\n"
		"\t-o<filename>   send console messages to file instead of stdout\n"
		"\t-n             send status messages to %s instead of stderr\n"
		"\t-q             send console messages to %s instead of stdout\n"
137
		"\t-v             display version details and exit\n"
138
		"\t-x             disable auto-termination on local abort signal\n"
139
140
141
		"\t-l             loop until intentionally terminated\n"
		"\t-p             wait for keypress (pause) on exit\n"
		"\t-!             wait for keypress (pause) on error\n"
142
		"\t-D             debugs the script\n"
rswindell's avatar
rswindell committed
143
144
		,JAVASCRIPT_MAX_BYTES
		,JAVASCRIPT_CONTEXT_STACK
145
		,JAVASCRIPT_TIME_LIMIT
146
147
		,JAVASCRIPT_YIELD_INTERVAL
		,JAVASCRIPT_GC_INTERVAL
148
		,DEFAULT_LOG_LEVEL
149
		,DEFAULT_ERR_LOG_LVL
150
		,load_path_list
151
		,_PATH_DEVNULL
152
		,_PATH_DEVNULL
rswindell's avatar
rswindell committed
153
154
		);
}
155

156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
int mfprintf(FILE* fp, char *fmt, ...)
{
	va_list argptr;
	char sbuf[1024];
	int ret=0;

    va_start(argptr,fmt);
    ret=vsnprintf(sbuf,sizeof(sbuf),fmt,argptr);
	sbuf[sizeof(sbuf)-1]=0;
    va_end(argptr);

	/* Mutex-protect stdout/stderr */
	pthread_mutex_lock(&output_mutex);

	ret = fprintf(fp, "%s\n", sbuf);

	pthread_mutex_unlock(&output_mutex);
    return(ret);
}

176
/* Log printf */
177
int lprintf(int level, const char *fmt, ...)
178
179
180
{
	va_list argptr;
	char sbuf[1024];
deuce's avatar
deuce committed
181
	int ret=0;
182

183
	if(level > log_level)
184
185
186
		return(0);

    va_start(argptr,fmt);
187
    ret=vsnprintf(sbuf,sizeof(sbuf),fmt,argptr);
188
189
	sbuf[sizeof(sbuf)-1]=0;
    va_end(argptr);
190
191
#if defined(__unix__)
	if(daemonize) {
192
		syslog(level,"%s",sbuf);
193
194
195
		return(ret);
	}
#endif
196
197
198
199

	/* Mutex-protect stdout/stderr */
	pthread_mutex_lock(&output_mutex);

200
	if(level<=err_level) {
201
		ret=fprintf(errfp,"%s\n",sbuf);
202
203
204
205
		if(errfp!=stderr && confp!=stdout)
			ret=fprintf(statfp,"%s\n",sbuf);
	}
	if(level>err_level || errfp!=stderr)
206
		ret=fprintf(confp,"%s\n",sbuf);
207
208

	pthread_mutex_unlock(&output_mutex);
209
210
211
    return(ret);
}

212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
#if defined(__unix__) && defined(NEEDS_DAEMON)
/****************************************************************************/
/* Daemonizes the process                                                   */
/****************************************************************************/
int
daemon(int nochdir, int noclose)
{
    int fd;

    switch (fork()) {
    case -1:
        return (-1);
    case 0:
        break;
    default:
        _exit(0);
    }

    if (setsid() == -1)
        return (-1);

    if (!nochdir)
        (void)chdir("/");

    if (!noclose && (fd = open(_PATH_DEVNULL, O_RDWR, 0)) != -1) {
        (void)dup2(fd, STDIN_FILENO);
        (void)dup2(fd, STDOUT_FILENO);
        (void)dup2(fd, STDERR_FILENO);
        if (fd > 2)
            (void)close(fd);
    }
    return (0);
}
#endif

247
#if defined(_WINSOCKAPI_)
248
249

WSADATA WSAData;
250
#define SOCKLIB_DESC WSAData.szDescription
251
252
253
254
255
256
257
static BOOL WSAInitialized=FALSE;

static BOOL winsock_startup(void)
{
	int		status;             /* Status Code */

    if((status = WSAStartup(MAKEWORD(1,1), &WSAData))==0) {
258
/*		fprintf(statfp,"%s %s\n",WSAData.szDescription, WSAData.szSystemStatus); */
259
260
261
262
		WSAInitialized=TRUE;
		return(TRUE);
	}

263
    lprintf(LOG_CRIT,"!WinSock startup ERROR %d", status);
264
265
266
267
268
	return(FALSE);
}

#else /* No WINSOCK */

269
270
#define winsock_startup()	(TRUE)
#define SOCKLIB_DESC NULL
271
272
273

#endif

274
static int do_bail(int code)
275
{
276
#if defined(_WINSOCKAPI_)
277
	if(WSAInitialized && WSACleanup()!=0) 
278
		lprintf(LOG_ERR,"!WSACleanup ERROR %d",ERROR_VALUE);
279
280
#endif

rswindell's avatar
rswindell committed
281
	if(pause_on_exit || (code && pause_on_error)) {
282
		fprintf(statfp,"\nHit enter to continue...");
283
284
		getchar();
	}
285
286
287

	if(code)
		fprintf(statfp,"\nReturning error code: %d\n",code);
288
289
290
291
292
293
	return(code);
}

void bail(int code)
{
	exit(do_bail(code));
294
295
}

296
static JSBool
297
js_log(JSContext *cx, uintN argc, jsval *arglist)
298
{
299
	jsval *argv=JS_ARGV(cx, arglist);
300
301
    uintN		i=0;
	int32		level=LOG_INFO;
302
    JSString*	str=NULL;
deuce's avatar
deuce committed
303
	jsrefcount	rc;
deuce's avatar
deuce committed
304
	char		*logstr;
305

306
307
	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

308
309
310
311
	if(argc > 1 && JSVAL_IS_NUMBER(argv[i])) {
		if(!JS_ValueToInt32(cx,argv[i++],&level))
			return JS_FALSE;
	}
312

313
314
315
	for(; i<argc; i++) {
		if((str=JS_ValueToString(cx, argv[i]))==NULL)
			return(JS_FALSE);
316
		JSSTRING_TO_STRING(cx, str, logstr, NULL);
deuce's avatar
deuce committed
317
318
		if(logstr==NULL)
			return(JS_FALSE);
319
		rc=JS_SUSPENDREQUEST(cx);
deuce's avatar
deuce committed
320
		lprintf(level,"%s",logstr);
321
		JS_RESUMEREQUEST(cx, rc);
322
323
	}

deuce's avatar
deuce committed
324
	if(logstr==NULL)
325
		JS_SET_RVAL(cx, arglist, JSVAL_VOID);
326
	else
327
		JS_SET_RVAL(cx, arglist, STRING_TO_JSVAL(str));
328

329
330
331
    return(JS_TRUE);
}

332
static JSBool
333
js_read(JSContext *cx, uintN argc, jsval *arglist)
334
{
335
	jsval *argv=JS_ARGV(cx, arglist);
336
337
338
	char*	buf;
	int		rd;
	int32	len=128;
deuce's avatar
deuce committed
339
	jsrefcount	rc;
340

341
342
	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

343
344
345
346
	if(argc) {
		if(!JS_ValueToInt32(cx,argv[0],&len))
			return JS_FALSE;
	}
347
	if((buf=alloca(len))==NULL)
348
349
		return(JS_TRUE);

350
	rc=JS_SUSPENDREQUEST(cx);
351
	rd=fread(buf,sizeof(char),len,stdin);
352
	JS_RESUMEREQUEST(cx, rc);
353
354

	if(rd>=0)
355
		JS_SET_RVAL(cx, arglist, STRING_TO_JSVAL(JS_NewStringCopyN(cx,buf,rd)));
356
357
358
359
360

    return(JS_TRUE);
}

static JSBool
361
js_readln(JSContext *cx, uintN argc, jsval *arglist)
362
{
363
	jsval *argv=JS_ARGV(cx, arglist);
364
365
366
	char*	buf;
	char*	p;
	int32	len=128;
deuce's avatar
deuce committed
367
	jsrefcount	rc;
368

369
370
	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

371
372
373
374
	if(argc) {
		if(!JS_ValueToInt32(cx,argv[0],&len))
			return JS_FALSE;
	}
375
	if((buf=alloca(len+1))==NULL)
376
377
		return(JS_TRUE);

378
	rc=JS_SUSPENDREQUEST(cx);
379
	p=fgets(buf,len+1,stdin);
380
	JS_RESUMEREQUEST(cx, rc);
381
382

	if(p!=NULL)
383
		JS_SET_RVAL(cx, arglist, STRING_TO_JSVAL(JS_NewStringCopyZ(cx,truncnl(p))));
384
385
386
387
388

    return(JS_TRUE);
}


389
static JSBool
390
js_write(JSContext *cx, uintN argc, jsval *arglist)
391
{
392
	jsval *argv=JS_ARGV(cx, arglist);
393
    uintN		i;
394
    JSString*	str=NULL;
deuce's avatar
deuce committed
395
	jsrefcount	rc;
deuce's avatar
deuce committed
396
	char		*line;
397

398
399
	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

400
    for (i = 0; i < argc; i++) {
deuce's avatar
deuce committed
401
		if((str=JS_ValueToString(cx, argv[0]))==NULL)
402
		    return(JS_FALSE);
403
		JSSTRING_TO_STRING(cx, str, line, NULL);
deuce's avatar
deuce committed
404
405
		if(line==NULL)
			return(JS_FALSE);
406
		rc=JS_SUSPENDREQUEST(cx);
deuce's avatar
deuce committed
407
		fprintf(confp,"%s",line);
408
		JS_RESUMEREQUEST(cx, rc);
409
410
	}

411
	if(str==NULL)
412
		JS_SET_RVAL(cx, arglist, JSVAL_VOID);
413
	else
414
		JS_SET_RVAL(cx, arglist, STRING_TO_JSVAL(str));
415
416
417
418
    return(JS_TRUE);
}

static JSBool
419
js_writeln(JSContext *cx, uintN argc, jsval *arglist)
420
{
deuce's avatar
deuce committed
421
422
	jsrefcount	rc;

423
424
	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

deuce's avatar
deuce committed
425
	if(!js_write(cx,argc,arglist))
426
427
		return(JS_FALSE);

428
	rc=JS_SUSPENDREQUEST(cx);
429
	fprintf(confp,"\n");
430
	JS_RESUMEREQUEST(cx, rc);
431
432
433
434
    return(JS_TRUE);
}

static JSBool
435
jse_printf(JSContext *cx, uintN argc, jsval *arglist)
436
{
437
	jsval *argv=JS_ARGV(cx, arglist);
438
	char* p;
deuce's avatar
deuce committed
439
	jsrefcount	rc;
440

441
442
	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

443
444
	if((p = js_sprintf(cx, 0, argc, argv))==NULL) {
		JS_ReportError(cx,"js_sprintf failed");
445
446
447
		return(JS_FALSE);
	}

448
	rc=JS_SUSPENDREQUEST(cx);
449
	fprintf(confp,"%s",p);
450
	JS_RESUMEREQUEST(cx, rc);
451

452
	JS_SET_RVAL(cx, arglist, STRING_TO_JSVAL(JS_NewStringCopyZ(cx, p)));
453

454
	js_sprintf_free(p);
455
456
457
458
459

    return(JS_TRUE);
}

static JSBool
460
js_alert(JSContext *cx, uintN argc, jsval *arglist)
461
{
462
	jsval *argv=JS_ARGV(cx, arglist);
deuce's avatar
deuce committed
463
	jsrefcount	rc;
deuce's avatar
deuce committed
464
	char		*line;
465

466
467
	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

468
	JSVALUE_TO_STRING(cx, argv[0], line, NULL);
deuce's avatar
deuce committed
469
	if(line==NULL)
470
471
	    return(JS_FALSE);

472
	rc=JS_SUSPENDREQUEST(cx);
deuce's avatar
deuce committed
473
	fprintf(confp,"!%s\n",line);
474
	JS_RESUMEREQUEST(cx, rc);
475

476
	JS_SET_RVAL(cx, arglist, argv[0]);
477

478
479
480
481
    return(JS_TRUE);
}

static JSBool
482
js_confirm(JSContext *cx, uintN argc, jsval *arglist)
483
{
484
	jsval *argv=JS_ARGV(cx, arglist);
485
    JSString *	str;
deuce's avatar
deuce committed
486
	char	 *	cstr;
487
	char     *	p;
deuce's avatar
deuce committed
488
	jsrefcount	rc;
489
	char		instr[81]="y";
490

491
492
	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

493
494
495
	if((str=JS_ValueToString(cx, argv[0]))==NULL)
	    return(JS_FALSE);

496
	JSSTRING_TO_STRING(cx, str, cstr, NULL);
497
	printf("%s (Y/n)? ", cstr);
498
	rc=JS_SUSPENDREQUEST(cx);
499
	fgets(instr,sizeof(instr),stdin);
500
	JS_RESUMEREQUEST(cx, rc);
501

502
503
	p=instr;
	SKIP_WHITESPACE(p);
504
	JS_SET_RVAL(cx, arglist, BOOLEAN_TO_JSVAL(tolower(*p)!='n'));
505
506
507
508
	return(JS_TRUE);
}

static JSBool
509
js_deny(JSContext *cx, uintN argc, jsval *arglist)
510
{
511
	jsval *argv=JS_ARGV(cx, arglist);
512
513
514
515
516
517
    JSString *	str;
	char	 *	cstr;
	char     *	p;
	jsrefcount	rc;
	char		instr[81];

518
519
	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

520
521
522
	if((str=JS_ValueToString(cx, argv[0]))==NULL)
	    return(JS_FALSE);

523
	JSSTRING_TO_STRING(cx, str, cstr, NULL);
524
525
526
527
528
529
530
	printf("%s (N/y)? ", cstr);
	rc=JS_SUSPENDREQUEST(cx);
	fgets(instr,sizeof(instr),stdin);
	JS_RESUMEREQUEST(cx, rc);

	p=instr;
	SKIP_WHITESPACE(p);
531
	JS_SET_RVAL(cx, arglist, BOOLEAN_TO_JSVAL(tolower(*p)!='y'));
532
533
534
535
	return(JS_TRUE);
}

static JSBool
536
js_prompt(JSContext *cx, uintN argc, jsval *arglist)
537
{
538
	jsval *argv=JS_ARGV(cx, arglist);
539
540
	char		instr[81];
    JSString *	str;
deuce's avatar
deuce committed
541
	jsrefcount	rc;
deuce's avatar
deuce committed
542
	char		*prstr;
543

544
545
	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

546
	if(argc>0 && !JSVAL_IS_VOID(argv[0])) {
547
		JSVALUE_TO_STRING(cx, argv[0], prstr, NULL);
548
		if(prstr==NULL)
549
			return(JS_FALSE);
550
		rc=JS_SUSPENDREQUEST(cx);
deuce's avatar
deuce committed
551
		fprintf(confp,"%s: ",prstr);
552
		JS_RESUMEREQUEST(cx, rc);
553
	}
554
555

	if(argc>1) {
556
		JSVALUE_TO_STRING(cx, argv[1], prstr, NULL);
557
		if(prstr==NULL)
558
		    return(JS_FALSE);
deuce's avatar
deuce committed
559
		SAFECOPY(instr,prstr);
560
561
562
	} else
		instr[0]=0;

563
	rc=JS_SUSPENDREQUEST(cx);
564
565
	if(!fgets(instr,sizeof(instr),stdin)) {
		JS_RESUMEREQUEST(cx, rc);
566
		return(JS_TRUE);
567
	}
568
	JS_RESUMEREQUEST(cx, rc);
569

570
	if((str=JS_NewStringCopyZ(cx, truncnl(instr)))==NULL)
571
572
	    return(JS_FALSE);

573
	JS_SET_RVAL(cx, arglist, STRING_TO_JSVAL(str));
574
575
576
    return(JS_TRUE);
}

rswindell's avatar
rswindell committed
577
static JSBool
578
js_chdir(JSContext *cx, uintN argc, jsval *arglist)
rswindell's avatar
rswindell committed
579
{
580
	jsval *argv=JS_ARGV(cx, arglist);
rswindell's avatar
rswindell committed
581
	char*		p;
deuce's avatar
deuce committed
582
	jsrefcount	rc;
rswindell's avatar
rswindell committed
583

584
	JSVALUE_TO_STRING(cx, argv[0], p, NULL);
deuce's avatar
deuce committed
585
	if(p==NULL) {
586
		JS_SET_RVAL(cx, arglist, INT_TO_JSVAL(-1));
rswindell's avatar
rswindell committed
587
588
589
		return(JS_TRUE);
	}

590
	rc=JS_SUSPENDREQUEST(cx);
591
	JS_SET_RVAL(cx, arglist, BOOLEAN_TO_JSVAL(chdir(p)==0));
592
	JS_RESUMEREQUEST(cx, rc);
rswindell's avatar
rswindell committed
593
594
595
	return(JS_TRUE);
}

596
static JSBool
597
js_putenv(JSContext *cx, uintN argc, jsval *arglist)
598
{
599
	jsval *argv=JS_ARGV(cx, arglist);
600
	char*		p=NULL;
601

602
603
	if(argc)
		JSVALUE_TO_STRING(cx, argv[0], p, NULL);
deuce's avatar
deuce committed
604
	if(p==NULL) {
605
		JS_SET_RVAL(cx, arglist, INT_TO_JSVAL(-1));
606
607
608
		return(JS_TRUE);
	}

609
	JS_SET_RVAL(cx, arglist, BOOLEAN_TO_JSVAL(putenv(strdup(p))==0));
610
611
612
	return(JS_TRUE);
}

613
static jsSyncMethodSpec js_global_functions[] = {
614
	{"log",				js_log,				1},
615
	{"read",			js_read,            1},
deuce's avatar
deuce committed
616
617
618
619
	{"readln",			js_readln,			0,	JSTYPE_STRING,	JSDOCSTR("[count]")
	,JSDOCSTR("read a single line, up to count characters, from input stream")
	,311
	},
620
    {"write",           js_write,           0},
621
622
    {"writeln",         js_writeln,         0},
    {"print",           js_writeln,         0},
623
    {"printf",          jse_printf,          1},	
624
625
626
	{"alert",			js_alert,			1},
	{"prompt",			js_prompt,			1},
	{"confirm",			js_confirm,			1},
627
	{"deny",			js_deny,			1},
rswindell's avatar
rswindell committed
628
	{"chdir",			js_chdir,			1},
629
	{"putenv",			js_putenv,			1},
630
631
632
633
634
635
636
637
638
    {0}
};

static void
js_ErrorReporter(JSContext *cx, const char *message, JSErrorReport *report)
{
	char	line[64];
	char	file[MAX_PATH+1];
	const char*	warning;
deuce's avatar
deuce committed
639
	jsrefcount	rc;
640
	int		log_level;
641

642
	rc=JS_SUSPENDREQUEST(cx);
643
	if(report==NULL) {
644
		lprintf(LOG_ERR,"!JavaScript: %s", message);
645
		JS_RESUMEREQUEST(cx, rc);
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
		return;
    }

	if(report->filename)
		sprintf(file," %s",report->filename);
	else
		file[0]=0;

	if(report->lineno)
		sprintf(line," line %d",report->lineno);
	else
		line[0]=0;

	if(JSREPORT_IS_WARNING(report->flags)) {
		if(JSREPORT_IS_STRICT(report->flags))
			warning="strict warning";
		else
			warning="warning";
664
665
666
		log_level=LOG_WARNING;
	} else {
		log_level=LOG_ERR;
667
		warning="";
668
	}
669

670
	lprintf(log_level,"!JavaScript %s%s%s: %s",warning,file,line,message);
671
	JS_RESUMEREQUEST(cx, rc);
672
673
}

674
675
676
677
678
679
static JSBool
js_OperationCallback(JSContext *cx)
{
	JSBool	ret;

	JS_SetOperationCallback(cx, NULL);
680
	ret=js_CommonOperationCallback(cx, &cb);
681
682
683
	JS_SetOperationCallback(cx, js_OperationCallback);
	return ret;
}
684

rswindell's avatar
rswindell committed
685
686
687
688
689
690
691
692
693
694
static BOOL js_CreateEnvObject(JSContext* cx, JSObject* glob, char** env)
{
	char		name[256];
	char*		val;
	uint		i;
	JSObject*	js_env;

	if((js_env=JS_NewObject(js_cx, NULL, NULL, glob))==NULL)
		return(FALSE);

695
696
697
698
	if(!JS_DefineProperty(cx, glob, "env", OBJECT_TO_JSVAL(js_env)
		,NULL,NULL,JSPROP_READONLY|JSPROP_ENUMERATE))
		return(FALSE);

rswindell's avatar
rswindell committed
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
	for(i=0;env[i]!=NULL;i++) {
		SAFECOPY(name,env[i]);
		truncstr(name,"=");
		val=strchr(env[i],'=');
		if(val==NULL)
			val="";
		else
			val++;
		if(!JS_DefineProperty(cx, js_env, name, STRING_TO_JSVAL(JS_NewStringCopyZ(cx,val))
			,NULL,NULL,JSPROP_READONLY|JSPROP_ENUMERATE))
			return(FALSE);
	}

	return(TRUE);
}

rswindell's avatar
rswindell committed
715
static BOOL js_init(char** environ)
716
{
717
	memset(&startup,0,sizeof(startup));
718
	SAFECOPY(startup.load_path, load_path_list);
719

720
721
	fprintf(statfp,"%s\n",(char *)JS_GetImplementationVersion());

722
	fprintf(statfp,"JavaScript: Creating runtime: %lu bytes\n"
723
724
		,js_max_bytes);

725
	if((js_runtime = jsrt_GetNew(js_max_bytes, 5000, __FILE__, __LINE__))==NULL)
726
727
		return(FALSE);

728
	fprintf(statfp,"JavaScript: Initializing context (stack: %lu bytes)\n"
729
		,js_cx_stack);
730

731
    if((js_cx = JS_NewContext(js_runtime, js_cx_stack))==NULL)
732
		return(FALSE);
733
	JS_BEGINREQUEST(js_cx);
734
735
736
737

	JS_SetErrorReporter(js_cx, js_ErrorReporter);

	/* Global Object */
738
	if(!js_CreateCommonObjects(js_cx, &scfg, NULL, js_global_functions
739
		,time(NULL), host_name, SOCKLIB_DESC	/* system */
740
		,&cb,&startup						/* js */
741
		,NULL,INVALID_SOCKET					/* client */
742
		,NULL									/* server */
743
744
		,&js_glob
		)) {
745
		JS_ENDREQUEST(js_cx);
746
		return(FALSE);
747
	}
748

rswindell's avatar
rswindell committed
749
	/* Environment Object (associative array) */
750
	if(!js_CreateEnvObject(js_cx, js_glob, environ)) {
751
		JS_ENDREQUEST(js_cx);
rswindell's avatar
rswindell committed
752
		return(FALSE);
753
	}
rswindell's avatar
rswindell committed
754

755
	if(js_CreateUifcObject(js_cx, js_glob)==NULL) {
756
		JS_ENDREQUEST(js_cx);
757
		return(FALSE);
758
	}
759

760
	if(js_CreateConioObject(js_cx, js_glob)==NULL) {
761
		JS_ENDREQUEST(js_cx);
deuce's avatar
deuce committed
762
		return(FALSE);
763
	}
deuce's avatar
deuce committed
764

765
766
767
	return(TRUE);
}

768
769
770
771
772
773
774
static const char* js_ext(const char* fname)
{
	if(strchr(fname,'.')==NULL)
		return(".js");
	return("");
}

775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
/*
 * This doesn't belong here and is done wrong...
 * mcmlxxix should love it.
 */
static enum debug_action debug_prompt(JSScript *script)
{
	char	line[1024];

	while(1) {
		if(JS_IsExceptionPending(js_cx))
			fputs("!", stdout);
		fputs("JS> ", stdout);

		if(fgets(line, sizeof(line), stdin)==NULL) {
			fputs("Error readin input\n",stderr);
			continue;
		}
		if(strncmp(line, "break ", 6)==0) {
			ulong		linenum=strtoul(line+6, NULL, 10);
			jsbytecode	*pc;
			
			if(linenum==ULONG_MAX) {
				fputs("Unable to parse line number\n",stderr);
				continue;
			}
			pc=JS_LineNumberToPC(js_cx, script, linenum);
			if(pc==NULL) {
				fprintf(stderr, "Unable to locate line %lu\n", linenum);
				break;
			}
			if(!JS_SetTrap(js_cx, script, pc, trap_handler, JSVAL_VOID)) {
				fprintf(stderr, "Unable to set breakpoint at line %lu\n",linenum);
			}
			continue;
		}
		if(strncmp(line, "r", 1)==0) {
			return DEBUG_CONTINUE;
		}
		if(strncmp(line, "eval ", 5)==0 || 
				strncmp(line, "e ", 2)==0
				) {
			jsval			ret;
			JSStackFrame	*fp;
			jsval			oldexcept;
			BOOL			has_old=FALSE;
			int				cmdlen=5;

			if(line[1]==' ')
				cmdlen=2;
			if(JS_IsExceptionPending(js_cx)) {
				if(JS_GetPendingException(js_cx, &oldexcept))
					has_old=TRUE;
				JS_ClearPendingException(js_cx);
			}

			fp=JS_GetScriptedCaller(js_cx, NULL);
			if(!fp) {
				if(has_old)
					JS_SetPendingException(js_cx, oldexcept);
				fputs("Unable to get frame pointer\n", stderr);
				continue;
			}
			if(!JS_EvaluateInStackFrame(js_cx, fp, line+cmdlen, strlen(line)-cmdlen, "eval-d statement", 1, &ret)) {
				if(JS_IsExceptionPending(js_cx)) {
					JS_SetErrorReporter(js_cx, js_ErrorReporter);
					JS_ReportPendingException(js_cx);
					JS_ClearPendingException(js_cx);
				}
			}
			else {
				// TODO: Check ret...
			}
			if(has_old)
				JS_SetPendingException(js_cx, oldexcept);
			continue;
		}
		if(strncmp(line, "clear", 5)==0) {
			JS_ClearPendingException(js_cx);
			continue;
		}
		fputs("Unrecognized command:\n"
			  "break ####       - Sets a breakpoint\n"
			  "r                - Runs the script\n"
			  "eval <statement> - eval() <statement> in the current frame\n"
			  "e <statement>    - eval() <statement> in the current frame\n"
			  "clear            - Clears pending exceptions (doesn't seem to help)\n"
			  "\n",stderr);
	}
	return DEBUG_CONTINUE;
}

static JSTrapStatus trap_handler(JSContext *cx, JSScript *script, jsbytecode *pc, jsval *rval, jsval closure)
{
    JS_GC(cx);

	fputs("Breakpoint reached\n",stdout);

    switch(debug_prompt(script)) {
		case DEBUG_CONTINUE:
			return JSTRAP_CONTINUE;
		case DEBUG_EXIT:
			return JSTRAP_ERROR;
	}
	return JSTRAP_CONTINUE;
}

static JSTrapStatus throw_handler(JSContext *cx, JSScript *script, jsbytecode *pc, jsval *rval, void *closure)
{
    JS_GC(cx);

	fputs("Exception thrown\n",stdout);

    switch(debug_prompt(script)) {
		case DEBUG_CONTINUE:
			return JSTRAP_CONTINUE;
		case DEBUG_EXIT:
			return JSTRAP_ERROR;
	}
	return JSTRAP_CONTINUE;
}

896
897
898
long js_exec(const char *fname, char** args)
{
	int			argc=0;
899
	uint		line_no;
900
	char		path[MAX_PATH+1];
901
	char		line[1024];
902
	char		rev_detail[256];
903
	size_t		len;
904
905
	char*		js_buf=NULL;
	size_t		js_buflen;
deuce's avatar
deuce committed
906
	JSObject*	js_script=NULL;
907
908
	JSString*	arg;
	JSObject*	argv;
909
	FILE*		fp=stdin;
910
	jsval		val;
rswindell's avatar
rswindell committed
911
912
	jsval		rval=JSVAL_VOID;
	int32		result=0;
913
914
	long double	start;
	long double	diff;
deuce's avatar
deuce committed
915

916
	if(fname!=NULL) {
917
918
919
920
		if(isfullpath(fname)) {
			SAFECOPY(path,fname);
		}
		else {
921
922
923
924
925
926
			SAFEPRINTF3(path,"%s%s%s",orig_cwd,fname,js_ext(fname));
			if(!fexistcase(path)) {
				SAFEPRINTF3(path,"%s%s%s",scfg.mods_dir,fname,js_ext(fname));
				if(scfg.mods_dir[0]==0 || !fexistcase(path))
					SAFEPRINTF3(path,"%s%s%s",scfg.exec_dir,fname,js_ext(fname));
			}
927
		}
928
929

		if(!fexistcase(path)) {
930
			lprintf(LOG_ERR,"!Module file (%s) doesn't exist",path);
931
932
			return(-1); 
		}
933
934

		if((fp=fopen(path,"r"))==NULL) {
935
			lprintf(LOG_ERR,"!Error %d (%s) opening %s",errno,STRERROR(errno),path);
936
937
			return(-1);
		}
938
	}
939
940
	JS_ClearPendingException(js_cx);

941
	argv=JS_NewArrayObject(js_cx, 0, NULL);
942
	JS_DefineProperty(js_cx, js_glob, "argv", OBJECT_TO_JSVAL(argv)
943
		,NULL,NULL,JSPROP_READONLY|JSPROP_ENUMERATE);
944
945
946
947
948
949
950
951
952

	for(argc=0;args[argc];argc++) {
		arg = JS_NewStringCopyZ(js_cx, args[argc]);
		if(arg==NULL)
			break;
		val=STRING_TO_JSVAL(arg);
		if(!JS_SetElement(js_cx, argv, argc, &val))
			break;
	}
953
	JS_DefineProperty(js_cx, js_glob, "argc", INT_TO_JSVAL(argc)
954
955
		,NULL,NULL,JSPROP_READONLY|JSPROP_ENUMERATE);

956
957
958
959
	JS_DefineProperty(js_cx, js_glob, "jsexec_revision"
		,STRING_TO_JSVAL(JS_NewStringCopyZ(js_cx,revision))
		,NULL,NULL,JSPROP_READONLY|JSPROP_ENUMERATE);

960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
	sprintf(rev_detail,"JSexec %s%s  "
		"Compiled %s %s with %s"
		,revision
#ifdef _DEBUG
		," Debug"
#else
		,""
#endif
		,__DATE__, __TIME__, compiler
		);

	JS_DefineProperty(js_cx, js_glob, "jsexec_revision_detail"
		,STRING_TO_JSVAL(JS_NewStringCopyZ(js_cx,rev_detail))
		,NULL,NULL,JSPROP_READONLY|JSPROP_ENUMERATE);

975
	cb.terminated=&terminated;
976

977
	JS_SetOperationCallback(js_cx, js_OperationCallback);
978

979
	if(fp==stdin) 	 /* Using stdin for script source */