summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/CMakeLists.txt28
-rw-r--r--src/export.map1
-rw-r--r--src/persistence-binding.c469
3 files changed, 498 insertions, 0 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
new file mode 100644
index 0000000..fc65420
--- /dev/null
+++ b/src/CMakeLists.txt
@@ -0,0 +1,28 @@
+PROJECT_TARGET_ADD(persistence-binding)
+
+find_package(GDBM)
+if(DB_FOUND)
+ add_definitions(-DUSE_GDBM)
+else(DB_FOUND)
+ find_package(BerkeleyDB REQUIRED)
+endif(DB_FOUND)
+include_directories(${DB_INCLUDE_DIR})
+
+add_library(persistence-binding MODULE persistence-binding.c)
+target_link_libraries(persistence-binding ${DB_LIBRARY})
+
+set_target_properties(persistence-binding PROPERTIES
+ PREFIX "afb-"
+ LABELS "BINDING"
+ LINK_FLAGS ${BINDINGS_LINK_FLAG}
+ OUTPUT_NAME ${TARGET_NAME})
+
+add_custom_command(TARGET ${TARGET_NAME}
+ PRE_BUILD
+ COMMAND mkdir -p ${CMAKE_CURRENT_BINARY_DIR}/../package/htdocs
+ COMMAND cp -rv ${CMAKE_CURRENT_SOURCE_DIR}/../htdocs ${CMAKE_CURRENT_BINARY_DIR}/../package/)
+
+install(TARGETS persistence-binding
+ RUNTIME DESTINATION bin
+ ARCHIVE DESTINATION lib
+ LIBRARY DESTINATION lib) \ No newline at end of file
diff --git a/src/export.map b/src/export.map
new file mode 100644
index 0000000..ee2f413
--- /dev/null
+++ b/src/export.map
@@ -0,0 +1 @@
+{ global: afbBindingV*; local: *; };
diff --git a/src/persistence-binding.c b/src/persistence-binding.c
new file mode 100644
index 0000000..e16b93b
--- /dev/null
+++ b/src/persistence-binding.c
@@ -0,0 +1,469 @@
+/*
+ * Copyright 2017 IoT.bzh
+ *
+ * author: Loïc Collignon <loic.collignon@iot.bzh>
+ * author: Jose Bollo <jose.bollo@iot.bzh>
+ *
+ * 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 <unistd.h>
+#include <sys/types.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <string.h>
+#include <limits.h>
+
+#include <json-c/json.h>
+
+#define AFB_BINDING_VERSION 2
+#include <afb/afb-binding.h>
+
+#if !defined(TO_STRING_FLAGS)
+# if !defined(JSON_C_TO_STRING_NOSLASHESCAPE)
+# define JSON_C_TO_STRING_NOSLASHESCAPE (1<<4)
+# endif
+# define TO_STRING_FLAGS (JSON_C_TO_STRING_PLAIN | JSON_C_TO_STRING_NOSLASHESCAPE)
+#endif
+
+#if defined(USE_BERKELEY_DB)
+# undef USE_BERKELEY_DB
+#endif
+
+#if defined(USE_GDBM)
+# undef USE_GDBM
+# define USE_GDBM 1
+# define USE_BERKELEY_DB 0
+#else
+# define USE_GDBM 0
+# define USE_BERKELEY_DB 1
+#endif
+
+// ----- Berkeley database -----
+#if USE_BERKELEY_DB
+
+#include <db.h>
+
+#define DBFILE "ll-database-binding.db"
+#define DATA DBT
+#define DATA_SET(k,d,s) do{memset((k),0,sizeof*(k));(k)->data=(void*)d;(k)->size=(uint32_t)s;}while(0)
+#define DATA_PTR(k) ((void*)((k).data))
+#define DATA_STR(k) ((char*)((k).data))
+#define DATA_SZ(k) ((size_t)((k).size))
+
+static DB *database;
+
+static int xdb_open(const char *path)
+{
+ int ret;
+
+ ret = db_create(&database, NULL, 0);
+ if (ret != 0)
+ {
+ AFB_ERROR("Failed to create database: %s.", db_strerror(ret));
+ return -1;
+ }
+
+ ret = database->open(database, NULL, path, NULL, DB_BTREE, DB_CREATE, 0600);
+ if (ret != 0)
+ {
+ AFB_ERROR("Failed to open the '%s' database: %s.", path, db_strerror(ret));
+ database->close(database, 0);
+ return -1;
+ }
+ return 0;
+}
+
+static void xdb_put(struct afb_req req, DBT *key, DBT *data, int replace)
+{
+ int ret;
+
+ ret = database->put(database, NULL, key, data, replace ? 0 : DB_NOOVERWRITE);
+ if (ret == 0)
+ afb_req_success(req, NULL, NULL);
+ else
+ {
+ AFB_ERROR("can't %s key %s with %s", replace ? "replace" : "insert", DATA_STR(*key), DATA_STR(*data));
+ afb_req_fail_f(req, "failed", "%s", db_strerror(ret));
+ }
+}
+
+static void xdb_delete(struct afb_req req, DBT *key)
+{
+ int ret;
+
+ ret = database->del(database, NULL, key, 0);
+ if (ret == 0)
+ afb_req_success_f(req, NULL, NULL);
+ else
+ {
+ AFB_ERROR("can't delete key %s", DATA_STR(*key));
+ afb_req_fail_f(req, "failed", "%s", db_strerror(ret));
+ }
+
+ free(DATA_PTR(key));
+}
+
+static void verb_read(struct afb_req req)
+{
+ DATA key;
+ DATA data;
+ int ret;
+
+ char value[4096];
+
+ struct json_object* result;
+ struct json_object* val;
+
+
+ if (get_key(req, &key))
+ return;
+
+ AFB_INFO("read: key=%s", DATA_STR(key));
+
+ memset(&data, 0, sizeof data);
+ data.data = value;
+ data.ulen = 4096;
+ data.flags = DB_DBT_USERMEM;
+
+ ret = database->get(database, NULL, &key, &data, 0);
+ if (ret == 0)
+ {
+ result = json_object_new_object();
+ val = json_tokener_parse(DATA_STR(data));
+ json_object_object_add(result, "value", val ? val : json_object_new_string(DATA_STR(data)));
+
+ afb_req_success_f(req, result, "db success: read %s=%s.", DATA_STR(key), DATA_STR(data));
+ }
+ else
+ afb_req_fail_f(req, "Failed to read datas.", "db fail: read %s - %s", DATA_STR(key), db_strerror(ret));
+
+ free(DATA_PTR(key));
+}
+
+#endif
+
+// ----- gdbm database -----
+#if USE_GDBM
+
+#include <errno.h>
+#include <gdbm.h>
+
+#define DBFILE "ll-database-binding.dbm"
+#define DATA datum
+#define DATA_SET(k,d,s) do{(k)->dptr=(char*)d;(k)->dsize=(int)s;}while(0)
+#define DATA_PTR(k) ((void*)((k).dptr))
+#define DATA_STR(k) ((char*)((k).dptr))
+#define DATA_SZ(k) ((size_t)((k).dsize))
+
+#if GDBM_VERSION_MAJOR > 1 || (GDBM_VERSION_MAJOR == 1 && GDBM_VERSION_MINOR >= 13)
+# define IFSYS(yes,no) (gdbm_syserr[gdbm_errno] ? (yes) : (no))
+#else
+# define IFSYS(yes,no) (no)
+#endif
+
+static GDBM_FILE database;
+
+static void onfatal(const char *text)
+{
+ AFB_ERROR("fatal gdbm message: %s", text);
+}
+
+static int xdb_open(const char *path)
+{
+ database = gdbm_open(path, 512, GDBM_WRCREAT|GDBM_SYNC, 0600, onfatal);
+ if (!database)
+ {
+ AFB_ERROR("Fail to open/create database: %s%s%s",
+ gdbm_errlist[gdbm_errno],
+ IFSYS(", ", ""),
+ IFSYS(strerror(errno), ""));
+ return -1;
+
+ }
+ return 0;
+}
+
+static void xdb_put(struct afb_req req, datum *key, datum *data, int replace)
+{
+ int ret;
+
+ ret = gdbm_store(database, *key, *data, replace ? GDBM_REPLACE : GDBM_INSERT);
+ if (ret == 0)
+ afb_req_success(req, NULL, NULL);
+ else
+ {
+ AFB_ERROR("can't %s key %s with %s: %s%s%s",
+ replace ? "replace" : "insert",
+ DATA_STR(*key),
+ DATA_STR(*data),
+ gdbm_errlist[gdbm_errno],
+ IFSYS(", ", ""),
+ IFSYS(strerror(errno), ""));
+ afb_req_fail_f(req, "failed", "%s", ret > 0 ? "key already exists" : gdbm_errlist[gdbm_errno]);
+ }
+}
+
+static void xdb_delete(struct afb_req req, datum *key)
+{
+ int ret;
+
+ ret = gdbm_delete(database, *key);
+ if (ret == 0)
+ afb_req_success_f(req, NULL, NULL);
+ else
+ {
+ AFB_ERROR("can't delete key %s: %s%s%s",
+ DATA_STR(*key),
+ gdbm_errlist[gdbm_errno],
+ IFSYS(", ", ""),
+ IFSYS(strerror(errno), ""));
+ afb_req_fail_f(req, "failed", "%s", gdbm_errlist[gdbm_errno]);
+ }
+}
+
+static void xdb_get(struct afb_req req, datum *key)
+{
+ struct json_object* obj;
+ datum result;
+
+ result = gdbm_fetch(database, *key);
+ if (result.dptr)
+ {
+ obj = json_object_new_object();
+ json_object_object_add(obj, "value", json_tokener_parse(result.dptr));
+ afb_req_success(req, obj, NULL);
+ free(result.dptr);
+ }
+ else
+ {
+ AFB_ERROR("can't get key %s: %s%s%s",
+ DATA_STR(*key),
+ gdbm_errlist[gdbm_errno],
+ IFSYS(", ", ""),
+ IFSYS(strerror(errno), ""));
+ afb_req_fail_f(req, "failed", "%s", gdbm_errlist[gdbm_errno]);
+ }
+}
+#endif
+
+// ----- Binding's implementations -----
+
+/**
+ * @brief Get the path to the database
+ */
+static int get_database_path(char *buffer, size_t size)
+{
+ static const char dbfile[] = DBFILE;
+
+ char *home, *config;
+ int rc;
+
+ config = secure_getenv("XDG_CONFIG_HOME");
+ if (config)
+ rc = snprintf(buffer, size, "%s/%s", config, dbfile);
+ else
+ {
+ home = secure_getenv("HOME");
+ if (home)
+ rc = snprintf(buffer, size, "%s/.config/%s", home, dbfile);
+ else
+ {
+ uid_t uid = getuid();
+ struct passwd *pwd = getpwuid(uid);
+ if (pwd)
+ rc = snprintf(buffer, size, "%s/.config/%s", pwd->pw_dir, dbfile);
+ else
+ rc = snprintf(buffer, size, "/home/%d/.config/%s", (int)uid, dbfile);
+ }
+ }
+ return rc;
+}
+
+/**
+ * @brief Initialize the binding.
+ * @return Exit code, zero if success.
+ */
+static int ll_database_binding_init()
+{
+ char path[PATH_MAX];
+ int ret;
+
+ ret = get_database_path(path, sizeof path);
+ if (ret < 0 || (int)ret >= (int)(sizeof path))
+ {
+ AFB_ERROR("Can't compute the database filename");
+ return -1;
+ }
+
+ AFB_INFO("opening database %s", path);
+ return xdb_open(path);
+}
+
+/**
+ * Returns the database key for the 'req'
+ */
+static int get_key(struct afb_req req, DATA *key)
+{
+ char *appid, *data;
+ const char *jkey;
+
+ size_t ljkey, lappid, size;
+
+ struct json_object* args;
+ struct json_object* item;
+
+ /* get the key */
+ args = afb_req_json(req);
+ if (!json_object_object_get_ex(args, "key", &item))
+ {
+ afb_req_fail(req, "no-key", NULL);
+ return -1;
+ }
+ if (!item
+ || !(jkey = json_object_to_json_string_ext(item, JSON_C_TO_STRING_PLAIN))
+ || !(ljkey = strlen(jkey)))
+ {
+ afb_req_fail(req, "bad-key", NULL);
+ return -1;
+ }
+
+ /* get the appid */
+ appid = afb_req_get_application_id(req);
+#if 1
+ if (!appid)
+ appid = strdup("#UNKNOWN-APP#");
+#endif
+ if (!appid)
+ {
+ afb_req_fail(req, "bad-context", NULL);
+ return -1;
+ }
+
+ /* make the db-key */
+ lappid = strlen(appid);
+ size = lappid + ljkey + 2;
+ data = realloc(appid, size);
+ if (!data)
+ {
+ free(appid);
+ afb_req_fail(req, "out-of-memory", NULL);
+ return -1;
+ }
+ data[lappid] = ':';
+ memcpy(&data[lappid + 1], jkey, ljkey + 1);
+
+ /* return the key */
+ DATA_SET(key, data, size);
+ return 0;
+}
+
+static void put(struct afb_req req, int replace)
+{
+ DATA key;
+ DATA data;
+
+ const char* value;
+
+ struct json_object* args;
+ struct json_object* item;
+
+ /* get the value */
+ args = afb_req_json(req);
+ if (!json_object_object_get_ex(args, "value", &item))
+ {
+ afb_req_fail(req, "no-value", NULL);
+ return;
+ }
+ value = json_object_to_json_string_ext(item, TO_STRING_FLAGS);
+ if (!value)
+ {
+ afb_req_fail(req, "out-of-memory", NULL);
+ return;
+ }
+ DATA_SET(&data, value, strlen(value) + 1); /* includes the tailing null */
+
+ /* get the key */
+ if (get_key(req, &key))
+ return;
+
+ AFB_INFO("put: key=%s, value=%s", DATA_STR(key), DATA_STR(data));
+ xdb_put(req, &key, &data, replace);
+ free(DATA_PTR(key));
+}
+
+static void verb_insert(struct afb_req req)
+{
+ put(req, 0);
+}
+
+static void verb_update(struct afb_req req)
+{
+ put(req, 1);
+}
+
+static void verb_delete(struct afb_req req)
+{
+ DATA key;
+
+ if (get_key(req, &key))
+ return;
+
+ AFB_INFO("delete: key=%s", DATA_STR(key));
+ xdb_delete(req, &key);
+ free(DATA_PTR(key));
+}
+
+static void verb_read(struct afb_req req)
+{
+ DATA key;
+
+ if (get_key(req, &key))
+ return;
+
+ AFB_INFO("read: key=%s", DATA_STR(key));
+ xdb_get(req, &key);
+ free(DATA_PTR(key));
+}
+
+// ----- Binding's configuration -----
+/*
+static const struct afb_auth ll_database_binding_auths[] = {
+};
+*/
+
+#define VERB(name_,auth_,info_,sess_) {\
+ .verb = #name_, \
+ .callback = verb_##name_, \
+ .auth = auth_, \
+ .info = info_, \
+ .session = sess_ }
+
+static const afb_verb_v2 ll_database_binding_verbs[]= {
+ VERB(insert, NULL, NULL, AFB_SESSION_NONE_V2),
+ VERB(update, NULL, NULL, AFB_SESSION_NONE_V2),
+ VERB(delete, NULL, NULL, AFB_SESSION_NONE_V2),
+ VERB(read, NULL, NULL, AFB_SESSION_NONE_V2),
+ { .verb = NULL}
+};
+
+const struct afb_binding_v2 afbBindingV2 = {
+ .api = "persistence",
+ .specification = NULL,
+ .verbs = ll_database_binding_verbs,
+ .preinit = NULL,
+ .init = ll_database_binding_init,
+ .onevent = NULL,
+ .noconcurrency = 0
+};