diff options
Diffstat (limited to 'src/napi-sqlite.c')
-rw-r--r-- | src/napi-sqlite.c | 932 |
1 files changed, 0 insertions, 932 deletions
diff --git a/src/napi-sqlite.c b/src/napi-sqlite.c deleted file mode 100644 index d4352a2..0000000 --- a/src/napi-sqlite.c +++ /dev/null @@ -1,932 +0,0 @@ -#include "i18n.h" -#include "logerr.h" - -#include <assert.h> -#include <errno.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> - -#include <node/node_api.h> -#include <sqlite3.h> - - -static napi_value ffi_open(napi_env env, napi_callback_info info); -static napi_value ffi_exec(napi_env env, napi_callback_info info); -static napi_value ffi_all (napi_env env, napi_callback_info info); -static napi_value ffi_run (napi_env env, napi_callback_info info); - - -struct NAPIContext { - napi_env env; - napi_value value; - uint32_t index; -}; - -struct Fn { - const char *const label; - napi_value(*const handle)(napi_env env, napi_callback_info info); -}; - -static const struct Fn fns[] = { - { .label = "open", .handle = ffi_open, }, - { NULL, NULL }, -}; - -struct Fn methods[] = { - { .label = "exec", .handle = ffi_exec, }, - { .label = "all", .handle = ffi_all, }, - { .label = "run", .handle = ffi_run, }, - { NULL, NULL }, -}; - -static const napi_type_tag SQLITE_DB_TYPE_TAG = { - 0x0e9614d459f746cc, 0x88b814a5dc5c4cf7 -}; - -static const int -SQLITE_OPEN_FLAGS = - /* - From https://www.sqlite.org/c3ref/open.html: - - > The database is opened for reading and writing, and is created if - > it does not already exist. This is the behavior that is always - > used for sqlite3_open() and sqlite3_open16(). - */ - SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | - - /* - From https://www.sqlite.org/c3ref/open.html: - - > The new database connection will use the "serialized" threading - > mode. This means the multiple threads can safely attempt to use - > the same database connection at the same time. (Mutexes will block - > any actual concurrency, but in this mode there is no harm in - > trying.) - */ - SQLITE_OPEN_FULLMUTEX | - - /* - From https://www.sqlite.org/c3ref/open.html: - - > The database connection comes up in "extended result code mode". - > In other words, the database behaves has if - > sqlite3_extended_result_codes(db,1) where called on the database - > connection as soon as the connection is created. In addition to - > setting the extended result code mode, this flag also causes - > sqlite3_open_v2() to return an extended result code. - - From https://www.sqlite.org/c3ref/extended_result_codes.html: - - > The sqlite3_extended_result_codes() routine enables or disables the - > extended result codes feature of SQLite. The extended result codes - > are disabled by default for historical compatibility. - */ - SQLITE_OPEN_EXRESCODE; - - -static const char *const -FIX_SQLITE_PRAGMAS = - /* - From https://research.cs.wisc.edu/adsl/Publications/alice-osdi14.pdf: - - > Similarly, SQLite does not provide durability under the default - > journal-mode (we became aware of this only after interacting with - > developers), but its documentation seems misleading. - > - > (...) - > - > The developers suggest the SQLite vulnerability is actually not a - > behavior guaranteed by SQLite (specifically, that durability cannot - > be achieved under rollback journaling); we believe the - > documentation is misleading. - > - > (...) - > - > Unclear documentation of application guarantees contributes to the - > confusion about crash vulnerabilities. During discussions with - > developers about durability vulnerabilities, we found that SQLite, - > which proclaims itself as fully ACID-complaint, does not provide - > durability (even optionally) with the default storage engine, - > though the documentation suggests it does. - */ - "PRAGMA journal_mode = WAL;\n" - "PRAGMA synchronous = EXTRA;\n" - - /* - From https://www.sqlite.org/foreignkeys.html: - - > Foreign key constraints are disabled by default (for backwards - > compatibility), so must be enabled separately for each database - > connection. - */ - "PRAGMA foreign_keys = ON;\n" - ; - - -// FIXME: make this async -static napi_value -ffi_exec(napi_env env, napi_callback_info info) { - size_t argc = 1; - napi_value argv[1]; - int sqlite_rc; - napi_status napi_rc; - char *sql = NULL; - size_t sql_size1; - size_t sql_size2; - sqlite3 *db = NULL; - char *error_msg = NULL; - - napi_rc = napi_get_cb_info( - env, - info, - &argc, - argv, - NULL, - (void *)&db - ); - if (napi_rc != napi_ok) { - napi_throw_error( - env, - "111TODO ERRCODE", - "Failed to parse arguments TODO i18n" - ); - goto out; - } - - napi_rc = napi_get_value_string_utf8(env, argv[0], NULL, 0, &sql_size1); - if (napi_rc != napi_ok) { - napi_throw_error( - env, - "222TODO ERRCODE", - "Invalid number was passed as argument TODO i18n" - ); - goto out; - } - - if (sql_size1 == SIZE_MAX) { - napi_throw_error( - env, - "SQLITE_EOVERFLOW", - "TODO " - ); - goto out; - } - sql_size1++; // include the NULL-terminator in size measurement - - sql = malloc(sql_size1); - if (!sql) { - napi_throw_error( - env, - "SQLITE_ENOMEM", - "TODO i18n" - ); - goto out; - } - - napi_rc = napi_get_value_string_utf8( - env, - argv[0], - sql, - sql_size1, - &sql_size2 - ); - if (napi_rc != napi_ok) { - napi_throw_error( - env, - "TODO ERRCODE", - "Invalid number was passed as argument TODO i18n" - ); - goto out; - } - assert(sql_size1 == sql_size2 + 1 && - "Unstable behaviour from Node-API"); - assert(sql); - - sqlite_rc = sqlite3_exec( - db, - sql, - NULL, - NULL, - &error_msg - ); - if (sqlite_rc != SQLITE_OK) { - napi_throw_error( - env, - "3iii33TODO ERRCODE", - sqlite3_errstr(sqlite_rc) - ); - goto out; - } - -out: - if (error_msg) { - sqlite3_free(error_msg); - } - if (sql) { - free(sql); - } - return NULL; -} - - -static int -accumulate_all_results( - void *ctxptr, - int argc, - char **argv, - char **column_names -) { - int rc = 0; - - napi_status napi_rc; - napi_value row = NULL; - - struct NAPIContext *ctx = ctxptr; - napi_env env = ctx->env; - napi_value results = ctx->value; - uint32_t index = ctx->index; - - napi_rc = napi_create_object(env, &row); - if (napi_rc != napi_ok) { - napi_throw_error( - env, - "TODO ERRCODE", - "TODO i18n" - ); - rc = -1; - goto out; - } - - for (int i = 0; i < argc; i++) { - const char *const column_name = column_names[i]; - const char *const column_value = argv[i]; - - napi_value str; // FIXME: dispatch on the type - napi_rc = napi_create_string_utf8( - env, - column_value, - NAPI_AUTO_LENGTH, - &str - ); - if (napi_rc != napi_ok) { - napi_throw_error( - env, - "SQLITE_MiiiETHOD_CREATE", - "TODO i18n" - ); - rc = -1; - goto out; - } - - napi_rc = napi_set_named_property( - env, - row, - column_name, - str - ); - if (napi_rc != napi_ok) { - napi_throw_error( - env, - "iSQLITE_MiETHOD_CREATE", - "TODO i18n" - ); - rc = -1; - goto out; - } - } - - napi_rc = napi_set_element( - env, - results, - index, - row - ); - if (napi_rc != napi_ok) { - napi_throw_error( - env, - "TODO ERRCODE", - "TODO i18n" - ); - rc = -1; - goto out; - } - - ctx->index++; - -out: - return rc; -} - -static napi_value -ffi_all(napi_env env, napi_callback_info info) { - napi_value ret = NULL; - - size_t argc = 1; - napi_value argv[1]; - int sqlite_rc; - napi_status napi_rc; - char *sql = NULL; - size_t sql_size1; - size_t sql_size2; - sqlite3 *db = NULL; - char *error_msg = NULL; - napi_value results_array = NULL; - - napi_rc = napi_get_cb_info( - env, - info, - &argc, - argv, - NULL, - (void *)&db - ); - if (napi_rc != napi_ok) { - napi_throw_error( - env, - "111TODO ERRCODE", - "Failed to parse arguments TODO i18n" - ); - goto out; - } - - napi_rc = napi_get_value_string_utf8(env, argv[0], NULL, 0, &sql_size1); - if (napi_rc != napi_ok) { - napi_throw_error( - env, - "222TODO ERRCODE", - "Invalid number was passed as argument TODO i18n" - ); - goto out; - } - - if (sql_size1 == SIZE_MAX) { - napi_throw_error( - env, - "SQLITE_EOVERFLOW", - "TODO " - ); - goto out; - } - sql_size1++; // include the NULL-terminator in size measurement - - sql = malloc(sql_size1); - if (!sql) { - napi_throw_error( - env, - "SQLITE_ENOMEM", - "TODO i18n" - ); - goto out; - } - - napi_rc = napi_get_value_string_utf8( - env, - argv[0], - sql, - sql_size1, - &sql_size2 - ); - if (napi_rc != napi_ok) { - napi_throw_error( - env, - "TODO ERRCODE", - "Invalid number was passed as argument TODO i18n" - ); - goto out; - } - assert(sql_size1 == sql_size2 + 1 && - "Unstable behaviour from Node-API"); - assert(sql); - - napi_rc = napi_create_array(env, &results_array); - if (napi_rc != napi_ok) { - napi_throw_error( - env, - "TODO ERRCODE", - "Invalid number was passed as argument TODO i18n" - ); - goto out; - } - - struct NAPIContext ctx = { - .env = env, - .value = results_array, - .index = 0, - }; - sqlite_rc = sqlite3_exec( - db, - sql, - accumulate_all_results, - &ctx, - &error_msg - ); - if (sqlite_rc != SQLITE_OK) { - napi_throw_error( - env, - "i3iiii33TODO ERRCODE", - sqlite3_errstr(sqlite_rc) - ); - goto out; - } - - ret = results_array; - -out: - if (error_msg) { - sqlite3_free(error_msg); - } - if (sql) { - free(sql); - } - return ret; -} - -static napi_value -ffi_run(napi_env env, napi_callback_info info) { - napi_value ret = NULL; - - size_t argc = 1; - napi_value argv[1]; - int sqlite_rc; - napi_status napi_rc; - char *sql = NULL; - size_t sql_size1; - size_t sql_size2; - sqlite3 *db = NULL; - char *error_msg = NULL; - napi_value results_array = NULL; - - napi_rc = napi_get_cb_info( - env, - info, - &argc, - argv, - NULL, - (void *)&db - ); - if (napi_rc != napi_ok) { - napi_throw_error( - env, - "111TODO ERRCODE", - "Failed to parse arguments TODO i18n" - ); - goto out; - } - - napi_rc = napi_get_value_string_utf8(env, argv[0], NULL, 0, &sql_size1); - if (napi_rc != napi_ok) { - napi_throw_error( - env, - "222TODO ERRCODE", - "Invalid number was passed as argument TODO i18n" - ); - goto out; - } - - if (sql_size1 == SIZE_MAX) { - napi_throw_error( - env, - "SQLITE_EOVERFLOW", - "TODO " - ); - goto out; - } - sql_size1++; // include the NULL-terminator in size measurement - - sql = malloc(sql_size1); - if (!sql) { - napi_throw_error( - env, - "SQLITE_ENOMEM", - "TODO i18n" - ); - goto out; - } - - napi_rc = napi_get_value_string_utf8( - env, - argv[0], - sql, - sql_size1, - &sql_size2 - ); - if (napi_rc != napi_ok) { - napi_throw_error( - env, - "TODO ERRCODE", - "Invalid number was passed as argument TODO i18n" - ); - goto out; - } - assert(sql_size1 == sql_size2 + 1 && - "Unstable behaviour from Node-API"); - assert(sql); - - napi_rc = napi_create_array(env, &results_array); - if (napi_rc != napi_ok) { - napi_throw_error( - env, - "TODO ERRCODE", - "Invalid number was passed as argument TODO i18n" - ); - goto out; - } - - struct NAPIContext ctx = { - .env = env, - .value = results_array, - .index = 0, - }; - sqlite_rc = sqlite3_exec( - db, - sql, - accumulate_all_results, - &ctx, - &error_msg - ); - if (sqlite_rc != SQLITE_OK) { - napi_throw_error( - env, - "i3iiii33TODO ERRCODE", - sqlite3_errstr(sqlite_rc) - ); - goto out; - } - - ret = results_array; - -out: - if (error_msg) { - sqlite3_free(error_msg); - } - if (sql) { - free(sql); - } - return ret; -} - - -static void -sqlite_error_log_callback(void *_data, int rc, const char *const msg) { - (void)_data; - logerr("%s\n%s\n", sqlite3_errstr(rc), msg); -} - -static void -throw_sqlite_error(napi_env env, sqlite3 *const db) { - const char *const errstr = sqlite3_errstr(sqlite3_errcode(db)); - const char *const errmsg = sqlite3_errmsg(db); - logerr("%s\n%s\n", errstr, errmsg); - if (napi_throw_error(env, errstr, errmsg)) { - logerr("napi_throw_err(%s, \"%s\", \"%s\");\n", - "env", - errstr, - errmsg); - goto out; - } - -out: - return; -} - -static void -throw_node_str_error( - napi_env env, - const char *const errstr, - const char *const errmsg -) { - bool is_pending; - - if (napi_is_exception_pending(env, &is_pending)) { - logerr("napi_is_exception_pending(%s, %s);\n", - "env", - "&is_pending"); - goto out; - } - - if (is_pending) { - logerr("exception already pending, not rethrowing"); - goto out; - } - - logerr("%s\n%s\n", errstr, errmsg); - if (napi_throw_error(env, errstr, errmsg)) { - logerr("napi_throw_error(%s, \"%s\", \"%s\");\n" - "env", - errstr, - errmsg); - goto out; - } - -out: - return; -} - -static void -throw_node_env_error(napi_env env) { - const napi_extended_error_info *error_info = NULL; - - if (napi_get_last_error_info(env, &error_info)) { - logerr("napi_get_last_error_info(%s, %s);\n", - "env", - "&error_info"); - goto out; - } - - const char *const errstr = "SQLITE_NAPI_ERR"; - const char *const errmsg = - error_info->error_message ? - error_info->error_message : - _(MSG_ERR_NAPI_MISSING_ERRSTR); - throw_node_str_error(env, errstr, errmsg); - -out: - return; -} - -static void -finalize_db(napi_env env, void *finalize_data, void *_finalize_hint) { - (void)_finalize_hint; - sqlite3 *db = finalize_data; - - if (sqlite3_close(db)) { - logerr("sqlite3_close(%s);\n", - "db"); - throw_sqlite_error(env, db); - goto out; - } - -out: - return; -} - -static napi_value -ffi_open(napi_env env, napi_callback_info info) { - napi_value ret = NULL; - - size_t argc = 1; - napi_value argv[1]; - char *filename = NULL; - size_t filename_size1; - size_t filename_size2; - sqlite3 *db = NULL; - napi_value wrapped_db = NULL; - char *error_msg = NULL; - - if (napi_get_cb_info(env, info, &argc, argv, NULL, NULL)) { - logerr("napi_get_cb_info(%s, %s, %s, %s, %s, %s);\n", - "env", - "info", - "&argc", - "argv", - "NULL", - "NULL"); - throw_node_env_error(env); - goto out; - } - - if (napi_get_value_string_utf8( - env, - argv[0], - NULL, // first we query the size of the string - 0, - &filename_size1 - )) { - logerr("napi_get_value_string_utf8(%s, %s, %s, %s, %s);\n", - "env", - "argv[0]", - "NULL", - "0", - "&filename_size1"); - throw_node_env_error(env); - goto out; - } - - if (filename_size1 == SIZE_MAX) { - logerr("%s: filename_size1 == SIZE_MAX;\n", strerror(EOVERFLOW)); - throw_node_str_error(env, "EOVERFLOW", strerror(EOVERFLOW)); - goto out; - } - filename_size1++; // include the NULL-terminator in size measurement - - filename = malloc(filename_size1); - if (!filename) { - logerr("malloc(filename_size1): %s\n", strerror(errno)); - throw_node_str_error(env, "ENOMEM", strerror(ENOMEM)); - goto out; - } - - if (napi_get_value_string_utf8( - env, - argv[0], - filename, - filename_size1, - &filename_size2 - )) { - logerr("napi_get_value_string_utf8(%s, %s, %s, %s, %s);\n", - "env", - "argv[0]", - "filename", - "filename_size1", - "&filename_size2"); - throw_node_env_error(env); - goto out; - } - assert(filename_size1 == filename_size2 + 1 && - "Unstable behaviour from Node-API"); - assert(filename && "filename should not be NULL at this point"); - - if (sqlite3_open_v2( - filename, - &db, - SQLITE_OPEN_FLAGS, - NULL - )) { - logerr("sqlite3_open_v2(%s, %s, %s, %s);\n", - "filename", - "&db", - "SQLITE_OPEN_FLAGS", - "NULL"); - throw_node_env_error(env); - goto out; - } - - if (sqlite3_config( - SQLITE_CONFIG_LOG, - sqlite_error_log_callback, - NULL - )) { - logerr("sqlite3_config(%s, %s, %s);\n", - "SQLITE_CONFIG_LOG", - "sqlite_error_log_callback", - "NULL"); - throw_sqlite_error(env, db); - goto out; - } - - if (sqlite3_exec( - db, - FIX_SQLITE_PRAGMAS, - NULL, - NULL, - &error_msg - )) { - logerr("sqlite3_exec(%s, %s, %s, %s, %s);\n", - "db", - "FIX_SQLITE_PRAGMAS", - "NULL", - "NULL", - "&error_msg"); - throw_sqlite_error(env, db); - goto out; - } - - if (napi_create_object(env, &wrapped_db)) { - logerr("napi_create_object(%s, %s);\n", - "env", - "&wrapped_db"); - throw_node_env_error(env); - goto out; - } - - if (napi_type_tag_object( - env, - wrapped_db, - &SQLITE_DB_TYPE_TAG - )) { - logerr("napi_type_tag_object(%s, %s, %s);\n", - "env", - "wrapped_db", - "&SQLITE_DB_TYPE_TAG"); - throw_node_env_error(env); - goto out; - } - - if (napi_wrap( - env, - wrapped_db, - db, - finalize_db, - NULL, - NULL - )) { - logerr("napi_wrap(%s, %s, %s, %s, %s, %s);\n", - "env", - "wrapped_db", - "db", - "finalize_db", - "NULL", - "NULL"); - throw_node_env_error(env); - goto out; - } - - for (size_t i = 0; methods[i].label && methods[i].handle; i++) { - napi_value fn; - if (napi_create_function( - env, - methods[i].label, - NAPI_AUTO_LENGTH, - methods[i].handle, - db, - &fn - )) { - logerr("napi_create_function(%s, %s, %s, %s, %s, %s);\n", - "env", - "methods[i].label", - "NAPI_AUTO_LENGTH", - "methods[i].handle", - "db", - "&fn"); - throw_node_env_error(env); - goto out; - } - - if (napi_set_named_property( - env, - wrapped_db, - methods[i].label, - fn - )) { - logerr("napi_set_named_property(%s, %s, %s, %s);\n", - "env," - "wrapped_db", - "methods[i].label", - "fn"); - throw_node_env_error(env); - goto out; - } - } - ret = wrapped_db; - -out: - if (error_msg) { - sqlite3_free(error_msg); - } - if (filename) { - free(filename); - } - if (!ret) { - if (db) { - if (sqlite3_close(db)) { - logerr("sqlite3_close(%s);\n", - "db"); - } - } - } - return ret; -} - - -static napi_value -ffi_init(napi_env env, napi_value exports) { - napi_value ret = exports; - - napi_status status; - - for (size_t i = 0; fns[i].label && fns[i].handle; i++) { - napi_value fn; - status = napi_create_function( - env, - fns[i].label, - NAPI_AUTO_LENGTH, - fns[i].handle, - NULL, - &fn - ); - if (status != napi_ok) { - ret = NULL; - napi_throw_error( - env, - "SQLITE_FN_CREATE", - "Unable to wrap native function TODO i18n" - ); - goto out; - } - - status = napi_set_named_property( - env, - exports, - fns[i].label, - fn - ); - if (status != napi_ok) { - ret = NULL; - napi_throw_error( - env, - "SQLITE_FN_SETNAME", - "Unable to populate exports TODO i18n" - ); - goto out; - } - } - -out: - return ret; -} - - -NAPI_MODULE_INIT() { - return ffi_init(env, exports); -} |