Skip to content
Snippets Groups Projects
jsexec.c 17.2 KiB
Newer Older
/* Execute a Synchronet JavaScript module from the command-line */

/* $Id$ */

/****************************************************************************
 * @format.tab-size 4		(Plain Text/Source Code File Header)			*
 * @format.use-tabs true	(see http://www.synchro.net/ptsc_hdr.html)		*
 *																			*
 * Copyright 2003 Rob Swindell - http://www.synchro.net/copyright.html		*
 *																			*
 * 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
#ifndef JAVASCRIPT
rswindell's avatar
rswindell committed
#endif

#include "sbbs.h"

JSRuntime*	js_runtime;
JSContext*	js_cx;
JSObject*	js_glob;
scfg_t		scfg;
ulong		js_max_bytes=JAVASCRIPT_MAX_BYTES;
ulong		js_context_stack=JAVASCRIPT_CONTEXT_STACK;
rswindell's avatar
rswindell committed
FILE*		confp;
FILE*		errfp;
FILE*		statfp;
char		revision[16];
BOOL		pause_on_exit=FALSE;
BOOL		pause_on_error=FALSE;

void banner(FILE* fp)
{
	fprintf(fp,"\nJSexec v%s%c-%s (rev %s) - "
		"Execute Synchronet JavaScript Module\n"
		,VERSION,REVISION
		,PLATFORM_DESC
		,revision
		);
}
void usage(FILE* fp)
{
	fprintf(fp,"\nusage: jsexec [-opts] [path]module[.js] [args]\n"
		"\navailable opts:\n\n"
		"\t-c <ctrl_dir>   specify path to Synchronet CTRL directory\n"
		"\t-m <bytes>      set maximum heap size (default=%u bytes)\n"
		"\t-s <bytes>      set context stack size (default=%u bytes)\n"
		"\t-b <limit>      set branch limit (default=%u, 0=unlimited)\n"
		"\t-y <freq>       set yield frequency (default=%u, 0=never)\n"
		"\t-g <freq>       set garbage collection frequency (default=%u, 0=never)\n"
		"\t-t <filename>   send console output to stdout and filename\n"
		"\t-e              send error messages to console instead of stderr\n"
		"\t-n              send status messages to %s instead of stdout\n"
		"\t-q              send console messages to %s instead of stderr\n"
		"\t-p              wait for keypress (pause) on exit\n"
		"\t-!              wait for keypress (pause) on error\n"
		,JAVASCRIPT_MAX_BYTES
		,JAVASCRIPT_CONTEXT_STACK
		,JAVASCRIPT_BRANCH_LIMIT
		,JAVASCRIPT_YIELD_FREQUENCY
		,JAVASCRIPT_GC_FREQUENCY
		,_PATH_DEVNULL
#ifdef _WINSOCKAPI_

WSADATA WSAData;
static BOOL WSAInitialized=FALSE;

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

    if((status = WSAStartup(MAKEWORD(1,1), &WSAData))==0) {
		fprintf(statfp,"%s %s\n",WSAData.szDescription, WSAData.szSystemStatus);
    fprintf(errfp,"!WinSock startup ERROR %d\n", status);
	return(FALSE);
}

#else /* No WINSOCK */

#define winsock_startup()	(TRUE)	

#endif


#ifdef _WINSOCKAPI_
	if(WSAInitialized && WSACleanup()!=0) 
		fprintf(errfp,"!WSACleanup ERROR %d\n",ERROR_VALUE);
#endif

	if(pause_on_exit || (code && pause_on_error)) {
		fprintf(statfp,"\nHit enter to continue...");

	if(code)
		fprintf(statfp,"\nReturning error code: %d\n",code);
static JSBool
js_log(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
{
    uintN		i;
    JSString*	str;

    for (i = 0; i < argc; i++) {
		if((str=JS_ValueToString(cx, argv[i]))==NULL)
		    return(JS_FALSE);
		fprintf(errfp,JS_GetStringBytes(str));
static JSBool
js_read(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
{
	char*	buf;
	int		rd;
	int32	len=128;

	*rval = JSVAL_VOID;

	if(argc)
		JS_ValueToInt32(cx,argv[0],&len);
	if((buf=malloc(len))==NULL)
		return(JS_TRUE);

	rd=fread(buf,sizeof(char),len,stdin);

	if(rd>=0)
		*rval = STRING_TO_JSVAL(JS_NewStringCopyN(cx,buf,rd));

	free(buf);
    return(JS_TRUE);
}

static JSBool
js_readln(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
{
	char*	buf;
	char*	p;
	int32	len=128;

	*rval = JSVAL_VOID;

	if(argc)
		JS_ValueToInt32(cx,argv[0],&len);
	if((buf=malloc(len+1))==NULL)
		return(JS_TRUE);

	p=fgets(buf,len+1,stdin);

	if(p!=NULL)
		*rval = STRING_TO_JSVAL(JS_NewStringCopyZ(cx,p));

	free(buf);
    return(JS_TRUE);
}


js_write(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)

    for (i = 0; i < argc; i++) {
		if((str=JS_ValueToString(cx, argv[i]))==NULL)
		    return(JS_FALSE);
		fprintf(confp,"%s",JS_GetStringBytes(str));
	if(str==NULL)
		*rval = JSVAL_VOID;
	else
		*rval = STRING_TO_JSVAL(str);
    return(JS_TRUE);
}

static JSBool
js_writeln(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
{
	if(!js_write(cx,obj,argc,argv,rval))
		return(JS_FALSE);

	fprintf(confp,"\n");
    return(JS_TRUE);
}

static JSBool
js_printf(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
{
	char*		p;
    uintN		i;
	JSString *	fmt;
    JSString *	str;
	va_list		arglist[64];

	if((fmt = JS_ValueToString(cx, argv[0]))==NULL)
		return(JS_FALSE);

	memset(arglist,0,sizeof(arglist));	// Initialize arglist to NULLs

    for (i = 1; i < argc && i<sizeof(arglist)/sizeof(arglist[0]); i++) {
		if(JSVAL_IS_STRING(argv[i])) {
			if((str=JS_ValueToString(cx, argv[i]))==NULL)
			    return(JS_FALSE);
			arglist[i-1]=JS_GetStringBytes(str);
		} 
		else if(JSVAL_IS_DOUBLE(argv[i]))
			arglist[i-1]=(char*)(unsigned long)*JSVAL_TO_DOUBLE(argv[i]);
		else if(JSVAL_IS_INT(argv[i]) || JSVAL_IS_BOOLEAN(argv[i]))
			arglist[i-1]=(char *)JSVAL_TO_INT(argv[i]);
		else
			arglist[i-1]=NULL;
	}
	
	if((p=JS_vsmprintf(JS_GetStringBytes(fmt),(char*)arglist))==NULL)
		return(JS_FALSE);

	fprintf(confp,"%s",p);

	*rval = STRING_TO_JSVAL(JS_NewStringCopyZ(cx, p));

	JS_smprintf_free(p);

    return(JS_TRUE);
}

static JSBool
js_alert(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
{
    JSString *	str;

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

	fprintf(confp,"!%s\n",JS_GetStringBytes(str));

	*rval = JSVAL_VOID;
    return(JS_TRUE);
}

static JSBool
js_confirm(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
{
    JSString *	str;

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

	*rval = BOOLEAN_TO_JSVAL(FALSE);
	return(JS_TRUE);
}

static JSBool
js_prompt(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
{
	char		instr[81];
    JSString *	prompt;
    JSString *	str;

	if((prompt=JS_ValueToString(cx, argv[0]))==NULL)
	    return(JS_FALSE);

	if(argc>1) {
		if((str=JS_ValueToString(cx, argv[1]))==NULL)
		    return(JS_FALSE);
		SAFECOPY(instr,JS_GetStringBytes(str));
	} else
		instr[0]=0;

	fprintf(confp,"%s: ",JS_GetStringBytes(prompt));
		return(JS_TRUE);
	}

	if((str=JS_NewStringCopyZ(cx, instr))==NULL)
	    return(JS_FALSE);

	*rval = STRING_TO_JSVAL(str);
    return(JS_TRUE);
}

static jsMethodSpec js_global_functions[] = {
	{"log",				js_log,				1},
	{"read",			js_read,            1},
	{"readln",			js_readln,          1},
    {"writeln",         js_writeln,         0},
    {"print",           js_writeln,         0},
    {"printf",          js_printf,          1},	
	{"alert",			js_alert,			1},
	{"prompt",			js_prompt,			1},
	{"confirm",			js_confirm,			1},
    {0}
};

static void
js_ErrorReporter(JSContext *cx, const char *message, JSErrorReport *report)
{
	char	line[64];
	char	file[MAX_PATH+1];
	const char*	warning;

	if(report==NULL) {
		fprintf(errfp,"!JavaScript: %s\n", message);
		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";
	} else
		warning="";

	fprintf(errfp,"!JavaScript %s%s%s: %s\n",warning,file,line,message);
}

static JSBool
js_BranchCallback(JSContext *cx, JSScript *script)
{
	if(branch.limit && branch.counter > branch.limit) {
		JS_ReportError(cx,"Infinite loop (%lu branches) detected",branch.counter);
		branch.counter=0;
		return(JS_FALSE);
	}
	/* Give up timeslices every once in a while */
	if(branch.yield_freq && (branch.counter%branch.yield_freq)==0)
	if(branch.gc_freq && (branch.counter%branch.gc_freq)==0)
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);

	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);
	}

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

	return(TRUE);
}

rswindell's avatar
rswindell committed
static BOOL js_init(char** environ)
	fprintf(statfp,"JavaScript: Creating runtime: %lu bytes\n"
		,js_max_bytes);

	if((js_runtime = JS_NewRuntime(js_max_bytes))==NULL)
		return(FALSE);

	fprintf(statfp,"JavaScript: Initializing context (stack: %lu bytes)\n"
rswindell's avatar
rswindell committed
		,js_context_stack);
    if((js_cx = JS_NewContext(js_runtime, js_context_stack))==NULL)
		return(FALSE);

	JS_SetErrorReporter(js_cx, js_ErrorReporter);

	/* Global Object */
	if((js_glob=js_CreateGlobalObject(js_cx, &scfg, js_global_functions))==NULL)
		return(FALSE);

	/* Branch Object */
	if(js_CreateBranchObject(js_cx, js_glob, &branch)==NULL)
		return(FALSE);

	/* System Object */
	if(js_CreateSystemObject(js_cx, js_glob, &scfg, time(NULL), scfg.sys_inetaddr)==NULL)
		return(FALSE);

	/* Socket Class */
	if(js_CreateSocketClass(js_cx, js_glob)==NULL)
		return(FALSE);

	/* MsgBase Class */
	if(js_CreateMsgBaseClass(js_cx, js_glob, &scfg)==NULL)
		return(FALSE);

	/* File Class */
	if(js_CreateFileClass(js_cx, js_glob)==NULL)
		return(FALSE);

	/* User class */
	if(js_CreateUserClass(js_cx, js_glob, &scfg)==NULL) 
		return(FALSE);

	/* Area Objects */
	if(!js_CreateUserObjects(js_cx, js_glob, &scfg, NULL, NULL, NULL)) 
		return(FALSE);

	/* Environment Object (associative array) */
	if(!js_CreateEnvObject(js_cx, js_glob, environ))
		return(FALSE);

static const char* js_ext(const char* fname)
{
	if(strchr(fname,'.')==NULL)
		return(".js");
	return("");
}

long js_exec(const char *fname, char** args)
{
	int			argc=0;
	char		line[1024];
	char*		js_buf=NULL;
	size_t		js_buflen;
	JSObject*	js_scope=NULL;
	JSScript*	js_script=NULL;
	JSString*	arg;
	JSObject*	argv;
	jsval		rval=JSVAL_VOID;
	int32		result=0;
	if(fname!=NULL) {
		if(strcspn(fname,"/\\")==strlen(fname)) {
			sprintf(path,"%s%s%s",scfg.mods_dir,fname,js_ext(fname));
			if(scfg.mods_dir[0]==0 || !fexistcase(path))
				sprintf(path,"%s%s%s",scfg.exec_dir,fname,js_ext(fname));
		} else

		if(!fexistcase(path)) {
			fprintf(errfp,"!Module file (%s) doesn't exist\n",path);
			return(-1); 
		}

		if((fp=fopen(path,"r"))==NULL) {
			fprintf(errfp,"!Error %d (%s) opening %s\n",errno,STRERROR(errno),path);
			return(-1);
		}
	js_scope=JS_NewObject(js_cx, NULL, NULL, js_glob);

	if(js_scope==NULL) {
		fprintf(errfp,"!Error creating JS scope\n");
		return(-1);
	}

	argv=JS_NewArrayObject(js_cx, 0, NULL);
	JS_DefineProperty(js_cx, js_scope, "argv", OBJECT_TO_JSVAL(argv)
		,NULL,NULL,JSPROP_READONLY|JSPROP_ENUMERATE);

	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;
	}
	JS_DefineProperty(js_cx, js_scope, "argc", INT_TO_JSVAL(argc)
		,NULL,NULL,JSPROP_READONLY|JSPROP_ENUMERATE);

	JS_SetBranchCallback(js_cx, js_BranchCallback);

	if(fp==stdin) 	 /* Using stdin for script source */
	fprintf(statfp,"Reading script from %s\n",path);
	line_no=0;
	js_buflen=0;
	while(!feof(fp)) {
		if(!fgets(line,sizeof(line),fp))
			break;
		line_no++;
#ifdef __unix__	/* Support Unix Shell Scripts that start with #!/path/to/jsexec */
		if(line_no==1 && strncmp(line,"#!",2)==0)
			strcpy(line,"\n");	/* To keep line count correct */
#endif
		if((js_buf=realloc(js_buf,js_buflen+strlen(line)))==NULL) {
			fprintf(errfp,"!Error allocating %u bytes of memory\n"
				,js_buflen+strlen(line));
		strcpy(js_buf+js_buflen,line);
		js_buflen+=strlen(line);
	}
	if((js_script=JS_CompileScript(js_cx, js_scope, js_buf, js_buflen, fname==NULL ? NULL : path, 1))==NULL) {
		fprintf(errfp,"!Error compiling script from %s\n",path);
	JS_ExecuteScript(js_cx, js_scope, js_script, &rval);

	JS_GetProperty(js_cx, js_glob, "exit_code", &rval);

	if(rval!=JSVAL_VOID) {
		fprintf(statfp,"Using JavaScript exit_code: %s\n",JS_GetStringBytes(JS_ValueToString(js_cx,rval)));
		JS_ValueToInt32(js_cx,rval,&result);
	}

	JS_DestroyScript(js_cx, js_script);

	JS_ClearScope(js_cx, js_scope);

	JS_GC(js_cx);

	if(fp!=NULL && fp!=stdin)
		fclose(fp);

	if(js_buf!=NULL)
		free(js_buf);

	return(result);
}

/*********************/
/* Entry point (duh) */
/*********************/
rswindell's avatar
rswindell committed
int main(int argc, char **argv, char** environ)
	char	error[512];
	char*	module=NULL;
	char*	p;
	int		argn;
rswindell's avatar
rswindell committed
	confp=stdout;
	errfp=stderr;
	nulfp=fopen(_PATH_DEVNULL,"w+");
	if(isatty(fileno(stderr)))
		statfp=stderr;
	else	/* if redirected, don't send status messages to stderr */
		statfp=nulfp;
rswindell's avatar
rswindell committed

	branch.limit=JAVASCRIPT_BRANCH_LIMIT;
	branch.yield_freq=JAVASCRIPT_YIELD_FREQUENCY;
	branch.gc_freq=JAVASCRIPT_GC_FREQUENCY;

	sscanf("$Revision$", "%*s %s", revision);

	memset(&scfg,0,sizeof(scfg));
	scfg.size=sizeof(scfg);

	for(argn=1;argn<argc && module==NULL;argn++) {
		if(argv[argn][0]=='-') {
			switch(argv[argn][1]) {
				case 'm':
					js_max_bytes=strtoul(argv[++argn],NULL,0);
					break;
				case 's':
					js_context_stack=strtoul(argv[++argn],NULL,0);
					break;
					branch.limit=strtoul(argv[++argn],NULL,0);
					branch.yield_freq=strtoul(argv[++argn],NULL,0);
					branch.gc_freq=strtoul(argv[++argn],NULL,0);
				case 'e':
					errfp=confp;
					break;
				case 'q':
				case 'n':
					statfp=nulfp;
					break;
				case 'p':
					pause_on_exit=TRUE;
				case '!':
					pause_on_error=TRUE;
					break;
				case 'c':
					SAFECOPY(scfg.ctrl_dir,argv[++argn]);
					break;
				default:
					fprintf(errfp,"\n!Unsupported option: %s\n",argv[argn]);
				case '?':
				case 'h':
					usage(errfp);
			}
			continue;
		}
		module=argv[argn];
	}

	if(scfg.ctrl_dir[0]==0) {
		if((p=getenv("SBBSCTRL"))==NULL) {
			fprintf(errfp,"\nSBBSCTRL environment variable not set and -c option not specified.\n");
			fprintf(errfp,"\nExample: SET SBBSCTRL=/sbbs/ctrl\n");
			fprintf(errfp,"\n     or: %s -c /sbbs/ctrl [module]\n",argv[0]);
			bail(1); 
		}
		SAFECOPY(scfg.ctrl_dir,p);
	}	

	if(module==NULL && isatty(fileno(stdin))) {
		fprintf(errfp,"\n!Module name not specified\n");
		usage(errfp);
	banner(statfp);
		fprintf(errfp,"!ERROR changing directory to: %s", scfg.ctrl_dir);
	fprintf(statfp,"\nLoading configuration files from %s\n",scfg.ctrl_dir);
	if(!load_cfg(&scfg,NULL,TRUE,error)) {
		fprintf(errfp,"!ERROR loading configuration files: %s\n",error);
	}
	prep_dir(scfg.data_dir, scfg.temp_dir, sizeof(scfg.temp_dir));

	if(!(scfg.sys_misc&SM_LOCAL_TZ))
		putenv("TZ=UTC0");

rswindell's avatar
rswindell committed
	if(!js_init(environ)) {
		fprintf(errfp,"!JavaScript initialization failure\n");
	result=js_exec(module,&argv[argn]);

	fprintf(statfp,"\n");
	fprintf(statfp,"JavaScript: Destroying context\n");
	JS_DestroyContext(js_cx);
	fprintf(statfp,"JavaScript: Destroying runtime\n");
	JS_DestroyRuntime(js_runtime);	

	bail(result);

	return(-1);	/* never gets here */