/* * Copyright (C) 2016, 2017 "IoT.bzh" * Author José Bollo * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include "duktape.h" #include "afb-common.h" #include "afb-api.h" #include "afb-apiset.h" #include "afb-api-js.h" #include "afb-xreq.h" #include "jobs.h" #include "verbose.h" /********************************************************************/ struct jsapi { int logmask; duk_context *context; char api[1]; }; /********************************************************************/ static void jsapi_call(void *closure, struct afb_xreq *xreq); static int jsapi_service_start(void *closure); static int jsapi_get_logmask(void *closure); static void jsapi_set_logmask(void *closure, int level); static struct json_object *jsapi_describe(void *closure); static struct afb_api_itf jsapi_itf = { .call = jsapi_call, .service_start = jsapi_service_start, .set_logmask = jsapi_set_logmask, .get_logmask = jsapi_get_logmask, .describe = jsapi_describe }; /********************************************************************/ static duk_ret_t do_success(duk_context *ctx); static duk_ret_t do_fail(duk_context *ctx); static duk_ret_t do_subcall_sync(duk_context *ctx); static duk_ret_t do_error(duk_context *ctx); static duk_ret_t do_require(duk_context *ctx); static const duk_function_list_entry funcs[] = { { "afb_req_success", do_success, 3 }, { "afb_req_fail", do_fail, 3 }, { "afb_req_subcall_sync", do_subcall_sync, 4 }, { "afb_error", do_error, 1 }, { "require", do_require, 1 }, { NULL, NULL, 0 } }; /********************************************************************/ static void on_heap_fatal(void *udata, const char *msg) { ERROR("Got fatal from duktape: %s", msg); abort(); } static int jsapi_load(duk_context *ctx, const char *path) { static const char prefix[] = "function(exports){"; static const char suffix[] = "}"; int fd, rc; struct stat st; char *buffer; ssize_t s; fd = afb_common_rootdir_open_locale(path, O_RDONLY, NULL); if (fd < 0) { fd = open(path, O_RDONLY); if (fd < 0) { ERROR("Can't open %s: %m", path); duk_push_error_object(ctx, DUK_ERR_ERROR, "Can't open file %s: %m", path); goto error; } } rc = fstat(fd, &st); if (rc < 0) { goto error2; } buffer = alloca(st.st_size + sizeof prefix + sizeof suffix); s = read(fd, &buffer[sizeof prefix - 1], st.st_size); if (s < 0) goto error2; if (s != st.st_size) goto error2; memcpy(buffer, prefix, sizeof prefix - 1); memcpy(&buffer[sizeof prefix - 1 + st.st_size], suffix, sizeof suffix); close(fd); duk_push_object(ctx); /* exports */ duk_push_string(ctx, path); /* exports path */ rc = duk_pcompile_string_filename(ctx, DUK_COMPILE_FUNCTION|DUK_COMPILE_STRICT, buffer); /* exports func */ if (rc) { duk_dup_top(ctx); /* exports error error */ ERROR("compiling of %s failed: %s", path, duk_safe_to_string(ctx, -1)); /* exports error error */ duk_pop(ctx); /* exports error */ duk_replace(ctx, -2); /* error */ goto error; } duk_dup(ctx, -2); /* exports func exports */ rc = duk_pcall(ctx, 1); /* exports ret */ if (rc) { duk_dup_top(ctx); /* exports error error */ if (!duk_is_error(ctx, -1)) { duk_push_error_object(ctx, DUK_ERR_ERROR, "%s", duk_safe_to_string(ctx, -1)); /* exports error error error */ duk_replace(ctx, -3); /* exports error error */ } ERROR("initialisation of %s failed: %s", path, duk_safe_to_string(ctx, -1)); /* exports error */ duk_pop(ctx); /* exports error */ duk_replace(ctx, -2); /* error */ goto error; } duk_pop(ctx); /* exports */ return 1; error2: ERROR("can't process file %s: %m", path); duk_push_error_object(ctx, DUK_ERR_ERROR, "Can't process file %s: %m", path); close(fd); error: return duk_throw(ctx); } static duk_ret_t do_require(duk_context *ctx) { int rc; const char *path; path = duk_require_string(ctx, -1); /* path */ duk_push_global_stash(ctx); /* path gstash */ duk_dup(ctx, -2); /* path gstash path */ rc = duk_get_prop(ctx, -2); /* path gstash ? */ if (!rc) { /* path gstash error (ELSE: path gstash exports) */ duk_pop(ctx); /* path gstash */ rc = jsapi_load(ctx, path); /* path gstash ? */ if (rc > 0) { /* path gstash exports (ELSE: path gstash error) */ duk_dup_top(ctx); /* path gstash exports exports */ duk_swap(ctx, -2, -4); /* exports gstash path exports */ duk_put_prop(ctx, -3); /* exports gstash */ duk_pop(ctx); /* exports */ } } return 1; } static void jsapi_destroy(struct jsapi *jsapi) { duk_destroy_heap(jsapi->context); free(jsapi); } static struct jsapi *jsapi_create(const char *path) { struct jsapi *jsapi; duk_context *ctx; const char *api, *ext; /* allocate and initialise names */ api = strrchr(path, '/'); api = api ? api + 1 : path; ext = strrchr(api, '.') ?: &api[strlen(api)]; jsapi = malloc(sizeof *jsapi + 1 + (ext - api)); if (!jsapi) goto error; memcpy(jsapi->api, api, ext - api); jsapi->api[ext - api] = 0; jsapi->logmask = logmask; /* create the duktape context */ ctx = duk_create_heap(NULL, NULL, NULL, NULL, on_heap_fatal); if (!ctx) goto error; jsapi->context = ctx; /* populate global with functions */ duk_push_global_object(ctx); duk_put_function_list(ctx, -1, funcs); duk_pop(ctx); /* call the require path */ ctx = jsapi->context; duk_get_global_string(ctx, "require"); duk_push_string(ctx, path); duk_pcall(ctx, 1); if (duk_is_error(ctx, -1)) { const char *message, *file, *stack; int line; duk_get_prop_string(ctx, -1, "message"); message = duk_get_string(ctx, -1); duk_get_prop_string(ctx, -2, "fileName"); file = duk_get_string(ctx, -1); duk_get_prop_string(ctx, -3, "lineNumber"); line = (int)duk_get_int(ctx, -1); duk_get_prop_string(ctx, -4, "stack"); stack = duk_get_string(ctx, -1); ERROR("Initialisation of API %s from jsapi %s failed file %s (file %s, line %d) stack:\n%s\n", jsapi->api, path, message, file, line, stack); jsapi_destroy(jsapi); return NULL; } duk_put_global_string(ctx, "exports"); return jsapi; error: ERROR("out of memory"); free(jsapi); errno = ENOMEM; return NULL; } int afb_api_js_add(const char *path, struct afb_apiset *declare_set, struct afb_apiset* call_set) { int rc; struct jsapi *jsapi; struct afb_api_item api; jsapi = jsapi_create(path); if (!jsapi) goto error; api.closure = jsapi; api.itf = &jsapi_itf; api.group = jsapi; rc = afb_apiset_add(declare_set, jsapi->api, api); if (!rc) return 0; duk_destroy_heap(jsapi->context); free(jsapi); error: return -1; } /********************************************************************/ static duk_ret_t do_success(duk_context *ctx) { struct afb_xreq *xreq; const char *json, *info; xreq = duk_get_pointer(ctx, -3); duk_json_encode(ctx, -2); json = duk_get_string(ctx, -2); info = duk_get_string(ctx, -1); afb_xreq_reply(xreq, json ? json_tokener_parse(json) : NULL, NULL, info); return 0; } static duk_ret_t do_fail(duk_context *ctx) { struct afb_xreq *xreq; const char *status, *info; xreq = duk_get_pointer(ctx, -3); status = duk_get_string(ctx, -2); info = duk_get_string(ctx, -1); afb_xreq_reply(xreq, NULL, status ?: "error", info); return 0; } static duk_ret_t do_subcall_sync(duk_context *ctx) { int rc; struct afb_xreq *xreq; const char *api, *verb, *json; struct json_object *resu; xreq = duk_get_pointer(ctx, -4); api = duk_get_string(ctx, -3); verb = duk_get_string(ctx, -2); duk_json_decode(ctx, -1); json = duk_get_string(ctx, -1); resu = NULL; rc = afb_xreq_legacy_subcall_sync(xreq, api, verb, json ? json_tokener_parse(json) : NULL, &resu); if (rc) duk_push_null(ctx); else { duk_push_string(ctx, json_object_to_json_string(resu)); duk_json_decode(ctx, -1); } json_object_put(resu); return 1; } static duk_ret_t do_error(duk_context *ctx) { const char *message; message = duk_get_string(ctx, -1); ERROR("%s", message ? : "null"); return 0; } /********************************************************************/ static void jsapi_call(void *closure, struct afb_xreq *xreq) { duk_idx_t top; duk_context *ctx; json_object *args; const char *json; struct jsapi *jsapi = closure; ctx = jsapi->context; top = duk_get_top(ctx); duk_get_global_string(ctx, "exports"); if (!duk_is_object(ctx, -1)) { afb_xreq_reply(xreq, NULL, "internal-error", "no exports!?"); goto end; } duk_get_prop_string(ctx, -1, xreq->request.called_verb); if (!duk_is_function(ctx, -1)) { afb_xreq_reply_unknown_verb(xreq); goto end; } duk_push_pointer(ctx, xreq); args = afb_xreq_json(xreq); json = json_object_to_json_string(args); duk_push_string(ctx, json); duk_json_decode(ctx, -1); duk_pcall(ctx, 2); end: duk_pop_n(ctx, duk_get_top(ctx) - top); } static int jsapi_service_start(void *closure) { struct jsapi *jsapi = closure; return 0; } static int jsapi_get_logmask(void *closure) { struct jsapi *jsapi = closure; return jsapi->logmask; } static void jsapi_set_logmask(void *closure, int level) { struct jsapi *jsapi = closure; jsapi->logmask = level; } static struct json_object *jsapi_describe(void *closure) { struct jsapi *jsapi = closure; return NULL; }