2 * Copyright 2017 IoT.bzh
4 * author: Loïc Collignon <loic.collignon@iot.bzh>
5 * author: Jose Bollo <jose.bollo@iot.bzh>
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
11 * http://www.apache.org/licenses/LICENSE-2.0
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
22 #include <sys/types.h>
28 #include <json-c/json.h>
30 #define AFB_BINDING_VERSION 3
31 #include <afb/afb-binding.h>
33 #if !defined(TO_STRING_FLAGS)
34 # if !defined(JSON_C_TO_STRING_NOSLASHESCAPE)
35 # define JSON_C_TO_STRING_NOSLASHESCAPE (1<<4)
37 # define TO_STRING_FLAGS (JSON_C_TO_STRING_PLAIN | JSON_C_TO_STRING_NOSLASHESCAPE)
40 #if defined(USE_BERKELEY_DB)
41 # undef USE_BERKELEY_DB
47 # define USE_BERKELEY_DB 0
50 # define USE_BERKELEY_DB 1
53 // ----- Berkeley database -----
58 #define DBFILE "ll-database-binding.db"
60 #define DATA_SET(k,d,s) do{memset((k),0,sizeof*(k));(k)->data=(void*)d;(k)->size=(uint32_t)s;}while(0)
61 #define DATA_PTR(k) ((void*)((k).data))
62 #define DATA_STR(k) ((char*)((k).data))
63 #define DATA_SZ(k) ((size_t)((k).size))
67 static int xdb_open(const char *path)
71 ret = db_create(&database, NULL, 0);
74 AFB_API_ERROR(afbBindingRoot, "Failed to create database: %s.", db_strerror(ret));
78 ret = database->open(database, NULL, path, NULL, DB_BTREE, DB_CREATE, 0600);
81 AFB_API_ERROR(afbBindingRoot, "Failed to open the '%s' database: %s.", path, db_strerror(ret));
82 database->close(database, 0);
88 static void xdb_put(afb_req_t req, DBT *key, DBT *data, int replace)
92 ret = database->put(database, NULL, key, data, replace ? 0 : DB_NOOVERWRITE);
94 afb_req_reply(req, NULL, NULL, NULL);
97 AFB_API_ERROR(afbBindingRoot, "can't %s key %s with %s", replace ? "replace" : "insert", DATA_STR(*key), DATA_STR(*data));
98 afb_req_reply_f(req, NULL, "failed", "%s", db_strerror(ret));
102 static void xdb_delete(afb_req_t req, DBT *key)
106 ret = database->del(database, NULL, key, 0);
108 afb_req_reply_f(req, NULL, NULL, NULL);
111 AFB_API_ERROR(afbBindingRoot, "can't delete key %s", DATA_STR(*key));
112 afb_req_reply_f(req, NULL, "failed", "%s", db_strerror(ret));
118 static void verb_read(afb_req_t req)
126 struct json_object* result;
127 struct json_object* val;
130 if (get_key(req, &key))
133 AFB_API_DEBUG(afbBindingRoot, "read: key=%s", DATA_STR(key));
135 memset(&data, 0, sizeof data);
138 data.flags = DB_DBT_USERMEM;
140 ret = database->get(database, NULL, &key, &data, 0);
143 result = json_object_new_object();
144 val = json_tokener_parse(DATA_STR(data));
145 json_object_object_add(result, "value", val ? val : json_object_new_string(DATA_STR(data)));
147 afb_req_reply_f(req, result, NULL, "db success: read %s=%s.", DATA_STR(key), DATA_STR(data));
150 afb_req_reply_f(req, NULL, "Failed to read datas.", "db fail: read %s - %s", DATA_STR(key), db_strerror(ret));
157 // ----- gdbm database -----
163 #define DBFILE "ll-database-binding.dbm"
165 #define DATA_SET(k,d,s) do{(k)->dptr=(char*)d;(k)->dsize=(int)s;}while(0)
166 #define DATA_PTR(k) ((void*)((k).dptr))
167 #define DATA_STR(k) ((char*)((k).dptr))
168 #define DATA_SZ(k) ((size_t)((k).dsize))
170 #if GDBM_VERSION_MAJOR > 1 || (GDBM_VERSION_MAJOR == 1 && GDBM_VERSION_MINOR >= 13)
171 # define IFSYS(yes,no) (gdbm_syserr[gdbm_errno] ? (yes) : (no))
173 # define IFSYS(yes,no) (no)
176 static GDBM_FILE database;
178 static void onfatal(const char *text)
180 AFB_API_ERROR(afbBindingRoot, "fatal gdbm message: %s", text);
183 static int xdb_open(const char *path)
185 database = gdbm_open(path, 512, GDBM_WRCREAT|GDBM_SYNC, 0600, onfatal);
188 AFB_API_ERROR(afbBindingRoot, "Fail to open/create database: %s%s%s",
189 gdbm_strerror(gdbm_errno),
191 IFSYS(strerror(errno), ""));
198 static void xdb_put(afb_req_t req, datum *key, datum *data, int replace)
202 ret = gdbm_store(database, *key, *data, replace ? GDBM_REPLACE : GDBM_INSERT);
204 afb_req_reply(req, NULL, NULL, NULL);
207 AFB_API_ERROR(afbBindingRoot, "can't %s key %s with %s: %s%s%s",
208 replace ? "replace" : "insert",
211 gdbm_strerror(gdbm_errno),
213 IFSYS(strerror(errno), ""));
214 afb_req_reply_f(req, NULL, "failed", "%s", ret > 0 ? "key already exists" : gdbm_strerror(gdbm_errno));
218 static void xdb_delete(afb_req_t req, datum *key)
222 ret = gdbm_delete(database, *key);
224 afb_req_reply_f(req, NULL, NULL, NULL);
227 AFB_API_ERROR(afbBindingRoot, "can't delete key %s: %s%s%s",
229 gdbm_strerror(gdbm_errno),
231 IFSYS(strerror(errno), ""));
232 afb_req_reply_f(req, NULL, "failed", "%s", gdbm_strerror(gdbm_errno));
236 static void xdb_get(afb_req_t req, datum *key)
238 struct json_object* obj;
239 struct json_object* args;
240 struct json_object* item = NULL;
244 args = afb_req_json(req);
246 obj = json_object_new_object();
248 if (json_object_object_get_ex(args, "key", &item))
250 json_object_object_add(obj, "key", json_object_get(item));
253 result = gdbm_fetch(database, *key);
257 json_object_object_add(obj, "value", json_tokener_parse(result.dptr));
259 afb_req_reply(req, obj, NULL, NULL);
266 json_object_object_add(obj, "value", NULL);
267 AFB_API_ERROR(afbBindingRoot, "can't get key %s: %s%s%s",
269 gdbm_strerror(gdbm_errno),
271 IFSYS(strerror(errno), ""));
272 afb_req_reply_f(req, obj, "failed", "%s", gdbm_strerror(gdbm_errno));
277 // ----- Binding's implementations -----
280 * @brief Get the path to the database
282 static int get_database_path(char *buffer, size_t size)
284 static const char dbfile[] = DBFILE;
289 config = secure_getenv("XDG_CONFIG_HOME");
291 rc = snprintf(buffer, size, "%s/%s", config, dbfile);
294 home = secure_getenv("HOME");
296 rc = snprintf(buffer, size, "%s/.config/%s", home, dbfile);
299 uid_t uid = getuid();
300 struct passwd *pwd = getpwuid(uid);
302 rc = snprintf(buffer, size, "%s/.config/%s", pwd->pw_dir, dbfile);
304 rc = snprintf(buffer, size, "/home/%d/.config/%s", (int)uid, dbfile);
311 * @brief Initialize the binding.
312 * @return Exit code, zero if success.
314 static int ll_database_binding_init(afb_api_t api)
319 ret = get_database_path(path, sizeof path);
320 if (ret < 0 || (int)ret >= (int)(sizeof path))
322 AFB_API_ERROR(afbBindingRoot, "Can't compute the database filename");
326 AFB_API_INFO(afbBindingRoot, "opening database %s", path);
327 return xdb_open(path);
331 * Returns the database key for the 'req'
333 static int get_key(afb_req_t req, DATA *key)
338 size_t ljkey, lappid, size;
340 struct json_object* args;
341 struct json_object* item;
344 args = afb_req_json(req);
345 if (!json_object_object_get_ex(args, "key", &item))
347 afb_req_reply(req, NULL, "no-key", NULL);
351 || !(jkey = json_object_to_json_string_ext(item, JSON_C_TO_STRING_PLAIN))
352 || !(ljkey = strlen(jkey)))
354 afb_req_reply(req, NULL, "bad-key", NULL);
359 appid = afb_req_get_application_id(req);
362 appid = strdup("#UNKNOWN-APP#");
366 afb_req_reply(req, NULL, "bad-context", NULL);
370 /* make the db-key */
371 lappid = strlen(appid);
372 size = lappid + ljkey + 2;
373 data = realloc(appid, size);
377 afb_req_reply(req, NULL, "out-of-memory", NULL);
381 memcpy(&data[lappid + 1], jkey, ljkey + 1);
384 DATA_SET(key, data, size);
388 static void put(afb_req_t req, int replace)
395 struct json_object* args;
396 struct json_object* item;
399 args = afb_req_json(req);
400 if (!json_object_object_get_ex(args, "value", &item))
402 afb_req_reply(req, NULL, "no-value", NULL);
405 value = json_object_to_json_string_ext(item, TO_STRING_FLAGS);
408 afb_req_reply(req, NULL, "out-of-memory", NULL);
411 DATA_SET(&data, value, strlen(value) + 1); /* includes the tailing null */
414 if (get_key(req, &key))
417 AFB_API_DEBUG(afbBindingRoot, "put: key=%s, value=%s", DATA_STR(key), DATA_STR(data));
418 xdb_put(req, &key, &data, replace);
422 static void verb_insert(afb_req_t req)
427 static void verb_update(afb_req_t req)
432 static void verb_delete(afb_req_t req)
436 if (get_key(req, &key))
439 AFB_API_DEBUG(afbBindingRoot, "delete: key=%s", DATA_STR(key));
440 xdb_delete(req, &key);
444 static void verb_read(afb_req_t req)
448 if (get_key(req, &key))
451 AFB_API_DEBUG(afbBindingRoot, "read: key=%s", DATA_STR(key));
456 // ----- Binding's configuration -----
458 static const struct afb_auth ll_database_binding_auths[] = {
462 #define VERB(name_,auth_,info_,sess_) {\
464 .callback = verb_##name_, \
469 static const afb_verb_t ll_database_binding_verbs[]= {
470 VERB(insert, NULL, NULL, AFB_SESSION_NONE),
471 VERB(update, NULL, NULL, AFB_SESSION_NONE),
472 VERB(delete, NULL, NULL, AFB_SESSION_NONE),
473 VERB(read, NULL, NULL, AFB_SESSION_NONE),
477 const afb_binding_t afbBindingExport = {
478 .api = "persistence",
479 .specification = NULL,
480 .verbs = ll_database_binding_verbs,
482 .init = ll_database_binding_init,