--- /dev/null
+/* $Id: js-server.c,v 1.1 2005/07/15 21:43:45 mmondor Exp $ */
+
+/* Copyright (c) 2004, Matthew Mondor */
+
+
+/*
+ * TODO:
+ *
+ * - Verify with Brendan Eich:
+ * - If reusing the context to execute several other scripts, it is
+ * important that they not be able to add global properties or methods.
+ * This seems to currently work using a custom api_class_property_add().
+ * This however also required standard properties to be shared
+ * (JS_PROP_SHARED), otherwise api_class_property_add() would be called
+ * and even setting values to existing API system properties would fail in
+ * user scripts. Scealing was also too strict.
+ * I assumed that JS_AddNamedRoot() was required for the API class to
+ * never be freed, so that it can be reused after a call to
+ * js_context_reset(). Perhaps this is not necessary.
+ */
+
+
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+
+#include <assert.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <jsapi.h>
+
+#include <js_fd.h>
+#include <js_errno.h>
+
+#include <js_file.h>
+
+
+
+/* Size runtime objects must take to run the GC */
+#define GCBYTES 1048576 /* 1MB */
+
+/* Size of stack to allocate for every context */
+#define STACKBYTES 8192 /* 8KB */
+
+
+
+/* Structure used to link a context with custom objects we need to perform
+ * some cleanup from before destroying the context. Ideally managed via
+ * mmpool(3) in a real world application for slap management and recycling.
+ * We'll have one of these per process in our pool of processes.
+ */
+typedef struct {
+ JSRuntime *rt;
+ JSContext *ctx;
+ JSObject *global, *class_fd, *class_errno;
+} js_context_t;
+
+
+
+/* Defaults for the global class */
+static JSClass global_class = {
+ "global", 0, JS_PropertyStub, JS_PropertyStub,
+ JS_PropertyStub, JS_PropertyStub, JS_EnumerateStub,
+ JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub
+};
+
+
+
+/* Only here for a test */
+int main(int, char **);
+
+static JSBool branch_callback(JSContext *, JSScript *);
+
+/* Exported API */
+js_context_t *js_context_init(size_t, size_t);
+void js_context_destroy(js_context_t *);
+void js_context_reset(js_context_t *);
+
+
+
+int
+main(int argc, char **argv)
+{
+ file_t *file;
+ js_context_t *cctx;
+
+ if (argc != 2) {
+ (void) fprintf(stderr, "Usage: test <scriptfile>\n");
+ exit(EXIT_FAILURE);
+ }
+ if ((file = file_load(argv[1])) == NULL) {
+ (void) fprintf(stderr, "Error loading '%s'\n", argv[1]);
+ exit(EXIT_FAILURE);
+ }
+
+ /*
+ * We always need at least one runtime per process, at least one
+ * context per thread and at least a global object per context
+ * (standard classes, like Date).
+ */
+ if ((cctx = js_context_init(GCBYTES, STACKBYTES)) == NULL) {
+ file_free(file);
+ (void) fprintf(stderr, "js_context_init()\n");
+ exit(EXIT_FAILURE);
+ }
+
+ /*
+ * This is a very useful and important feature, enable our callback
+ * function which will get called whenever the script branches
+ * backwards, returns from a function or exits. It allows us to
+ * even maintain control in cases where the script loops endlessly.
+ */
+ (void) JS_SetBranchCallback(cctx->ctx, branch_callback);
+
+ /*
+ * Now enable addProperty() protection for all classes using our
+ * custom api_class_property_add() function. This will prevent user
+ * code from adding properties or methods to the API class for
+ * instance.
+ * This however requires that properties use the JSPROP_SHARED flag
+ * since addProperty() method would internally get called to create
+ * shadow copies for the runtime otherwise.
+ */
+ /*
+ api_class_protect = JS_TRUE;
+ */
+
+ /*
+ * Now execute script loaded into our file_t.
+ * We simplify this process by calling JS_EvaluateScript() which
+ * will first tokenize/compile the result, and then interpret/run it.
+ * Moreover, it allows the script to optionally return a value
+ * directly like if it was a function.
+ * Alternatively, we could use JS_CompileFile() or JS_CompileScript()
+ * to pre-tokenize the script, and JS_ExecuteScript() to interpret it.
+ */
+ {
+ jsval rval, pval;
+ JSString *str;
+ int i;
+
+ if (JS_EvaluateScript(cctx->ctx, cctx->global, file->data,
+ file->size, argv[1], 1, &rval)) {
+ str = JS_ValueToString(cctx->ctx, rval);
+ (void) printf("Script result: %s\n",
+ JS_GetStringBytes(str));
+ /*
+ * Attempt to call JS function "callMe" if the script
+ * created it.
+ */
+ for (i = 0; i < 10; i++) {
+ pval = INT_TO_JSVAL(i);
+ if (!JS_CallFunctionName(cctx->ctx,
+ cctx->global, "callMe", 1, &pval, &rval))
+ break;
+ }
+ } else {
+ /* XXX how to obtain error and stack backtrace? */
+ }
+ }
+
+ /* Cleanup */
+ file_free(file);
+ js_context_destroy(cctx);
+
+ exit(EXIT_SUCCESS);
+}
+
+/*
+ * This function is called during the execution of the script so that we can
+ * remain in control of the application. If we only allow the scripts to
+ * define functions for callbacks, we can use the first instance if this event
+ * to abort the script if wanted, as well. We can then set an alternative
+ * callback function and execute the script provided functions at specific
+ * events. Of course, it also would be possible to use setitimer(2) to have
+ * a SIGALRM signal trigger a function at regular set intervals.
+ */
+/* ARGSUSED */
+static JSBool
+branch_callback(JSContext *ctx, JSScript *script)
+{
+ /*jsval pval, rval;*/
+ static int count = 0;
+
+ /* Call callMe() script-provided handler if any, as a test */
+ /*
+ pval = INT_TO_JSVAL(count++);
+ (void) JS_CallFunctionName(ctx, JS_GetGlobalObject(ctx), "callMe", 1,
+ &pval, &rval);
+ */
+
+ if (++count > 1000) {
+ count = 0;
+ JS_MaybeGC(ctx);
+ }
+
+ /* Returning JS_FALSE here aborts the script */
+ return JS_TRUE;
+}
+
+
+/* Exported API */
+
+js_context_t *
+js_context_init(size_t gc_size, size_t stack_size)
+{
+ js_context_t *cctx;
+
+ if ((cctx = malloc(sizeof(js_context_t))) == NULL ||
+ (cctx->rt = JS_NewRuntime(gc_size)) == NULL ||
+ (cctx->ctx = JS_NewContext(cctx->rt, stack_size)) == NULL ||
+ (cctx->global = JS_NewObject(cctx->ctx, &global_class, NULL,
+ NULL)) == NULL ||
+ !JS_InitStandardClasses(cctx->ctx, cctx->global) ||
+ (cctx->class_fd = js_InitFDClass(cctx->ctx, cctx->global))
+ == NULL ||
+ (cctx->class_errno = js_InitErrnoClass(cctx->ctx, cctx->global))
+ == NULL) {
+ /* An error, free any partially allocated resources */
+ if (cctx != NULL)
+ js_context_destroy(cctx);
+
+ return NULL;
+ }
+
+ return cctx;
+}
+
+void
+js_context_destroy(js_context_t *cctx)
+{
+
+ assert(cctx != NULL);
+
+ if (cctx->ctx != NULL)
+ JS_DestroyContext(cctx->ctx);
+ if (cctx->rt != NULL)
+ JS_DestroyRuntime(cctx->rt);
+
+ free(cctx);
+}
+
+/*
+ * This function should permit to restore the context to a consistent, known
+ * state before a new script can be executed using the same context instead of
+ * having to destroy and recreate contexts everytime.
+ */
+/* ARGSUSED */
+void
+js_context_reset(js_context_t *cctx)
+{
+
+ /* XXX */
+ /* NOOP */
+}