Add gitlab issue/merge request templates
[apps/agl-service-data-persistence.git] / src / persistence-binding.c
1 /*
2  * Copyright 2017 IoT.bzh
3  *
4  * author: Loïc Collignon <loic.collignon@iot.bzh>
5  * author: Jose Bollo <jose.bollo@iot.bzh>
6  *
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
10  *
11  *     http://www.apache.org/licenses/LICENSE-2.0
12  *
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.
18  */
19
20 #define _GNU_SOURCE
21 #include <unistd.h>
22 #include <sys/types.h>
23 #include <pwd.h>
24 #include <stdio.h>
25 #include <string.h>
26 #include <limits.h>
27
28 #include <json-c/json.h>
29
30 #define AFB_BINDING_VERSION 3
31 #include <afb/afb-binding.h>
32
33 #if !defined(TO_STRING_FLAGS)
34 # if !defined(JSON_C_TO_STRING_NOSLASHESCAPE)
35 #  define JSON_C_TO_STRING_NOSLASHESCAPE (1<<4)
36 # endif
37 # define TO_STRING_FLAGS (JSON_C_TO_STRING_PLAIN | JSON_C_TO_STRING_NOSLASHESCAPE)
38 #endif
39
40 #if defined(USE_BERKELEY_DB)
41 #  undef USE_BERKELEY_DB
42 #endif
43
44 #if defined(USE_GDBM)
45 #  undef USE_GDBM
46 #  define USE_GDBM        1
47 #  define USE_BERKELEY_DB 0
48 #else
49 #  define USE_GDBM        0
50 #  define USE_BERKELEY_DB 1
51 #endif
52
53 // ----- Berkeley database -----
54 #if USE_BERKELEY_DB
55
56 #include <db.h>
57
58 #define DBFILE  "ll-database-binding.db"
59 #define DATA    DBT
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))
64
65 static DB *database;
66
67 static int xdb_open(const char *path)
68 {
69         int ret;
70
71         ret = db_create(&database, NULL, 0);
72         if (ret != 0)
73         {
74                 AFB_API_ERROR(afbBindingRoot, "Failed to create database: %s.", db_strerror(ret));
75                 return -1;
76         }
77
78         ret = database->open(database, NULL, path, NULL, DB_BTREE, DB_CREATE, 0600);
79         if (ret != 0)
80         {
81                 AFB_API_ERROR(afbBindingRoot, "Failed to open the '%s' database: %s.", path, db_strerror(ret));
82                 database->close(database, 0);
83                 return -1;
84         }
85         return 0;
86 }
87
88 static void xdb_put(afb_req_t req, DBT *key, DBT *data, int replace)
89 {
90         int ret;
91
92         ret = database->put(database, NULL, key, data, replace ? 0 : DB_NOOVERWRITE);
93         if (ret == 0)
94                 afb_req_reply(req, NULL, NULL, NULL);
95         else
96         {
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));
99         }
100 }
101
102 static void xdb_delete(afb_req_t req, DBT *key)
103 {
104         int ret;
105
106         ret = database->del(database, NULL, key, 0);
107         if (ret == 0)
108                 afb_req_reply_f(req, NULL, NULL, NULL);
109         else
110         {
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));
113         }
114
115         free(DATA_PTR(key));
116 }
117
118 static void verb_read(afb_req_t req)
119 {
120         DATA key;
121         DATA data;
122         int ret;
123
124         char value[4096];
125
126         struct json_object* result;
127         struct json_object* val;
128
129
130         if (get_key(req, &key))
131                 return;
132
133         AFB_API_DEBUG(afbBindingRoot, "read: key=%s", DATA_STR(key));
134
135         memset(&data, 0, sizeof data);
136         data.data = value;
137         data.ulen = 4096;
138         data.flags = DB_DBT_USERMEM;
139
140         ret = database->get(database, NULL, &key, &data, 0);
141         if (ret == 0)
142         {
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)));
146
147                 afb_req_reply_f(req, result, NULL, "db success: read %s=%s.", DATA_STR(key), DATA_STR(data));
148         }
149         else
150                 afb_req_reply_f(req, NULL, "Failed to read datas.", "db fail: read %s - %s", DATA_STR(key), db_strerror(ret));
151
152         free(DATA_PTR(key));
153 }
154
155 #endif
156
157 // ----- gdbm database -----
158 #if USE_GDBM
159
160 #include <errno.h>
161 #include <gdbm.h>
162
163 #define DBFILE  "ll-database-binding.dbm"
164 #define DATA    datum
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))
169
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))
172 #else
173 # define IFSYS(yes,no)   (no)
174 #endif
175
176 static GDBM_FILE database;
177
178 static void onfatal(const char *text)
179 {
180         AFB_API_ERROR(afbBindingRoot, "fatal gdbm message: %s", text);
181 }
182
183 static int xdb_open(const char *path)
184 {
185         database = gdbm_open(path, 512, GDBM_WRCREAT|GDBM_SYNC, 0600, onfatal);
186         if (!database)
187         {
188                 AFB_API_ERROR(afbBindingRoot, "Fail to open/create database: %s%s%s",
189                         gdbm_strerror(gdbm_errno),
190                         IFSYS(", ", ""),
191                         IFSYS(strerror(errno), ""));
192                 return -1;
193                 
194         }
195         return 0;
196 }
197
198 static void xdb_put(afb_req_t req, datum *key, datum *data, int replace)
199 {
200         int ret;
201
202         ret = gdbm_store(database, *key, *data, replace ? GDBM_REPLACE : GDBM_INSERT);
203         if (ret == 0)
204                 afb_req_reply(req, NULL, NULL, NULL);
205         else
206         {
207                 AFB_API_ERROR(afbBindingRoot, "can't %s key %s with %s: %s%s%s",
208                         replace ? "replace" : "insert",
209                         DATA_STR(*key),
210                         DATA_STR(*data),
211                         gdbm_strerror(gdbm_errno),
212                         IFSYS(", ", ""),
213                         IFSYS(strerror(errno), ""));
214                 afb_req_reply_f(req, NULL, "failed", "%s", ret > 0 ? "key already exists" : gdbm_strerror(gdbm_errno));
215         }
216 }
217
218 static void xdb_delete(afb_req_t req, datum *key)
219 {
220         int ret;
221
222         ret = gdbm_delete(database, *key);
223         if (ret == 0)
224                 afb_req_reply_f(req, NULL, NULL, NULL);
225         else
226         {
227                 AFB_API_ERROR(afbBindingRoot, "can't delete key %s: %s%s%s",
228                         DATA_STR(*key),
229                         gdbm_strerror(gdbm_errno),
230                         IFSYS(", ", ""),
231                         IFSYS(strerror(errno), ""));
232                 afb_req_reply_f(req, NULL, "failed", "%s", gdbm_strerror(gdbm_errno));
233         }
234 }
235
236 static void xdb_get(afb_req_t req, datum *key)
237 {
238         struct json_object* obj;
239         struct json_object* args;
240         struct json_object* item = NULL;
241         datum result;
242
243         /* get the key */
244         args = afb_req_json(req);
245
246         obj = json_object_new_object();
247
248         if (json_object_object_get_ex(args, "key", &item))
249         {
250                 json_object_object_add(obj, "key", json_object_get(item));
251         }
252
253         result = gdbm_fetch(database, *key);
254         if (result.dptr)
255         {
256
257                 json_object_object_add(obj, "value", json_tokener_parse(result.dptr));
258
259                 afb_req_reply(req, obj, NULL, NULL);
260
261                 free(result.dptr);
262
263         }
264         else
265         {
266                 json_object_object_add(obj, "value", NULL);
267                 AFB_API_ERROR(afbBindingRoot, "can't get key %s: %s%s%s",
268                         DATA_STR(*key),
269                         gdbm_strerror(gdbm_errno),
270                         IFSYS(", ", ""),
271                         IFSYS(strerror(errno), ""));
272                 afb_req_reply_f(req, obj, "failed", "%s", gdbm_strerror(gdbm_errno));
273         }
274 }
275 #endif
276
277 // ----- Binding's implementations -----
278
279 /**
280  * @brief Get the path to the database
281  */
282 static int get_database_path(char *buffer, size_t size)
283 {
284         static const char dbfile[] = DBFILE;
285
286         char *home, *config;
287         int rc;
288
289         config = secure_getenv("XDG_CONFIG_HOME");
290         if (config)
291                 rc = snprintf(buffer, size, "%s/%s", config, dbfile);
292         else
293         {
294                 home = secure_getenv("HOME");
295                 if (home)
296                         rc = snprintf(buffer, size, "%s/.config/%s", home, dbfile);
297                 else
298                 {
299                         uid_t uid = getuid();
300                         struct passwd *pwd = getpwuid(uid);
301                         if (pwd)
302                                 rc = snprintf(buffer, size, "%s/.config/%s", pwd->pw_dir, dbfile);
303                         else
304                                 rc = snprintf(buffer, size, "/home/%d/.config/%s", (int)uid, dbfile);
305                 }
306         }
307         return rc;
308 }
309
310 /**
311  * @brief Initialize the binding.
312  * @return Exit code, zero if success.
313  */
314 static int ll_database_binding_init(afb_api_t api)
315 {
316         char path[PATH_MAX];
317         int ret;
318
319         ret = get_database_path(path, sizeof path);
320         if (ret < 0 || (int)ret >=  (int)(sizeof path))
321         {
322                 AFB_API_ERROR(afbBindingRoot, "Can't compute the database filename");
323                 return -1;
324         }
325
326         AFB_API_INFO(afbBindingRoot, "opening database %s", path);
327         return xdb_open(path);
328 }
329
330 /**
331  * Returns the database key for the 'req'
332  */
333 static int get_key(afb_req_t req, DATA *key)
334 {
335         char *appid, *data;
336         const char *jkey;
337
338         size_t ljkey, lappid, size;
339
340         struct json_object* args;
341         struct json_object* item;
342
343         /* get the key */
344         args = afb_req_json(req);
345         if (!json_object_object_get_ex(args, "key", &item))
346         {
347                 afb_req_reply(req, NULL, "no-key", NULL);
348                 return -1;
349         }
350         if (!item
351      || !(jkey = json_object_to_json_string_ext(item, JSON_C_TO_STRING_PLAIN))
352          || !(ljkey = strlen(jkey)))
353         {
354                 afb_req_reply(req, NULL, "bad-key", NULL);
355                 return -1;
356         }
357
358         /* get the appid */
359         appid = afb_req_get_application_id(req);
360 #if 1
361         if (!appid)
362                 appid = strdup("#UNKNOWN-APP#");
363 #endif
364         if (!appid)
365         {
366                 afb_req_reply(req, NULL, "bad-context", NULL);
367                 return -1;
368         }
369
370         /* make the db-key */
371         lappid = strlen(appid);
372         size = lappid + ljkey + 2;
373         data = realloc(appid, size);
374         if (!data)
375         {
376                 free(appid);
377                 afb_req_reply(req, NULL, "out-of-memory", NULL);
378                 return -1;
379         }
380         data[lappid] = ':';
381         memcpy(&data[lappid + 1], jkey, ljkey + 1);
382
383         /* return the key */
384         DATA_SET(key, data, size);
385         return 0;
386 }
387
388 static void put(afb_req_t req, int replace)
389 {
390         DATA key;
391         DATA data;
392
393         const char* value;
394
395         struct json_object* args;
396         struct json_object* item;
397
398         /* get the value */
399         args = afb_req_json(req);
400         if (!json_object_object_get_ex(args, "value", &item))
401         {
402                 afb_req_reply(req, NULL, "no-value", NULL);
403                 return;
404         }
405         value = json_object_to_json_string_ext(item, TO_STRING_FLAGS);
406         if (!value)
407         {
408                 afb_req_reply(req, NULL, "out-of-memory", NULL);
409                 return;
410         }
411         DATA_SET(&data, value, strlen(value) + 1); /* includes the tailing null */
412
413         /* get the key */
414         if (get_key(req, &key))
415                 return;
416
417         AFB_API_DEBUG(afbBindingRoot, "put: key=%s, value=%s", DATA_STR(key), DATA_STR(data));
418         xdb_put(req, &key, &data, replace);
419         free(DATA_PTR(key));
420 }
421
422 static void verb_insert(afb_req_t req)
423 {
424         put(req, 0);
425 }
426
427 static void verb_update(afb_req_t req)
428 {
429         put(req, 1);
430 }
431
432 static void verb_delete(afb_req_t req)
433 {
434         DATA key;
435
436         if (get_key(req, &key))
437                 return;
438
439         AFB_API_DEBUG(afbBindingRoot, "delete: key=%s", DATA_STR(key));
440         xdb_delete(req, &key);
441         free(DATA_PTR(key));
442 }
443
444 static void verb_read(afb_req_t req)
445 {
446         DATA key;
447
448         if (get_key(req, &key))
449                 return;
450
451         AFB_API_DEBUG(afbBindingRoot, "read: key=%s", DATA_STR(key));
452         xdb_get(req, &key);
453         free(DATA_PTR(key));
454 }
455
456 // ----- Binding's configuration -----
457 /*
458 static const struct afb_auth ll_database_binding_auths[] = {
459 };
460 */
461
462 #define VERB(name_,auth_,info_,sess_) {\
463         .verb = #name_, \
464         .callback = verb_##name_, \
465         .auth = auth_, \
466         .info = info_, \
467         .session = sess_ }
468
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),
474         { .verb = NULL}
475 };
476
477 const afb_binding_t afbBindingExport = {
478         .api = "persistence",
479         .specification = NULL,
480         .verbs = ll_database_binding_verbs,
481         .preinit = NULL,
482         .init = ll_database_binding_init,
483         .onevent = NULL,
484         .noconcurrency = 0
485 };