database: fix regression bug
[apps/agl-service-data-persistence.git] / ll-database-binding / src / ll-database-binding.c
1 /*
2  * Copyright 2017 IoT.bzh
3  *
4  * author: Loïc Collignon <loic.collignon@iot.bzh>
5  *
6  * Licensed under the Apache License, Version 2.0 (the "License");
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  *     http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  */
18
19 #define _GNU_SOURCE
20 #include <unistd.h>
21 #include <sys/types.h>
22 #include <pwd.h>
23 #include <stdio.h>
24 #include <string.h>
25 #include <linux/limits.h>
26 #include <json-c/json.h>
27 #include <db.h>
28
29 #define AFB_BINDING_VERSION 2
30 #include <afb/afb-binding.h>
31
32 #include "utils.h"
33
34 #ifndef MAX_PATH
35 #define MAX_PATH 1024
36 #endif
37
38 #define DBFILE          "ll-database-binding.db"
39
40 #if !defined(TO_STRING_FLAGS)
41 # if !defined(JSON_C_TO_STRING_NOSLASHESCAPE)
42 #  define JSON_C_TO_STRING_NOSLASHESCAPE (1<<4)
43 # endif
44 # define TO_STRING_FLAGS (JSON_C_TO_STRING_PLAIN | JSON_C_TO_STRING_NOSLASHESCAPE)
45 #endif
46
47 // ----- Globals -----
48 static DB *database;
49
50 // ----- Binding's declarations -----
51 static int ll_database_binding_init();
52 static void verb_insert(struct afb_req req);
53 static void verb_update(struct afb_req req);
54 static void verb_delete(struct afb_req req);
55 static void verb_read(struct afb_req req);
56
57 // ----- Binding's implementations -----
58
59 /**
60  * @brief Get the path to the database
61  */
62 static int get_database_path(char *buffer, size_t size)
63 {
64         static const char dbfile[] = DBFILE;
65
66         char *home, *config;
67         int rc;
68
69         config = secure_getenv("XDG_CONFIG_HOME");
70         if (config)
71                 rc = snprintf(buffer, size, "%s/%s", config, dbfile);
72         else
73         {
74                 home = secure_getenv("HOME");
75                 if (home)
76                         rc = snprintf(buffer, size, "%s/.config/%s", home, dbfile);
77                 else
78                 {
79                         struct passwd *pwd = getpwuid(getuid());
80                         if (pwd)
81                                 rc = snprintf(buffer, size, "%s/.config/%s", pwd->pw_dir, dbfile);
82                         else
83                                 rc = snprintf(buffer, size, "/home/%d/.config/%s", (int)getuid(), dbfile);
84                 }
85         }
86         return rc;
87 }
88
89 /**
90  * @brief Initialize the binding.
91  * @return Exit code, zero if success.
92  */
93 static int ll_database_binding_init()
94 {
95         char path[MAX_PATH];
96         int ret;
97
98         ret = get_database_path(path, sizeof path);
99         if (ret < 0 || (int)ret >=  (int)(sizeof path))
100         {
101                 AFB_ERROR("Can't compute the database filename");
102                 return -1;
103         }
104
105         AFB_INFO("opening database %s", path);
106
107         ret = db_create(&database, NULL, 0);
108         if (ret != 0)
109         {
110                 AFB_ERROR("Failed to create database: %s.", db_strerror(ret));
111                 return -1;
112         }
113
114         ret = database->open(database, NULL, path, NULL, DB_BTREE, DB_CREATE, 0664);
115         if (ret != 0)
116         {
117                 AFB_ERROR("Failed to open the '%s' database: %s.", path, db_strerror(ret));
118                 database->close(database, 0);
119                 return -1;
120         }
121
122         return 0;
123 }
124
125 /**
126  * Returns the database key for the 'req'
127  */
128 static int get_key(struct afb_req req, DBT *key)
129 {
130         char *appid, *data;
131         const char *jkey;
132
133         size_t ljkey, lappid, size;
134
135         struct json_object* args;
136         struct json_object* item;
137
138         /* get the key */
139         args = afb_req_json(req);
140         if (!json_object_object_get_ex(args, "key", &item))
141         {
142                 afb_req_fail(req, "no-key", NULL);
143                 return -1;
144         }
145         if (!item
146          || !(jkey = json_object_get_string(item))
147          || !(ljkey = strlen(jkey)))
148         {
149                 afb_req_fail(req, "bad-key", NULL);
150                 return -1;
151         }
152
153         /* get the appid */
154         appid = afb_req_get_application_id(req);
155 #if 1
156         if (!appid)
157                 appid = strdup("#UNKNOWN-APP#");
158 #endif
159         if (!appid)
160         {
161                 afb_req_fail(req, "bad-context", NULL);
162                 return -1;
163         }
164
165         /* make the db-key */
166         lappid = strlen(appid);
167         size = lappid + ljkey + 2;
168         data = realloc(appid, size);
169         if (!data)
170         {
171                 free(appid);
172                 afb_req_fail(req, "out-of-memory", NULL);
173                 return -1;
174         }
175         data[lappid] = ':';
176         memcpy(&data[lappid + 1], jkey, ljkey + 1);
177
178         /* return the key */
179         memset(key, 0, sizeof *key);
180         key->data = data;
181         key->size = (uint32_t)size;
182         return 0;
183 }
184
185 static void put(struct afb_req req, int replace)
186 {
187         int ret;
188         DBT key;
189         DBT data;
190
191         const char* value;
192
193         struct json_object* args;
194         struct json_object* item;
195
196         args = afb_req_json(req);
197
198         if (!json_object_object_get_ex(args, "value", &item))
199         {
200                 afb_req_fail(req, "no-value", NULL);
201                 return;
202         }
203
204         value = json_object_to_json_string_ext(item, TO_STRING_FLAGS);
205         if (!value)
206         {
207                 afb_req_fail(req, "out-of-memory", NULL);
208                 return;
209         }
210
211         if (get_key(req, &key))
212                 return;
213
214         AFB_INFO("put: key=%s, value=%s", (char*)key.data, value);
215
216         memset(&data, 0, sizeof data);
217         data.data = (void*)value;
218         data.size = (uint32_t)strlen(value) + 1; /* includes the tailing null */
219
220         ret = database->put(database, NULL, &key, &data, replace ? 0 : DB_NOOVERWRITE);
221         if (ret == 0)
222                 afb_req_success(req, NULL, NULL);
223         else
224         {
225                 AFB_ERROR("can't %s key %s with %s", replace ? "replace" : "insert", (char*)key.data, (char*)data.data);
226                 afb_req_fail_f(req, "failed", "%s", db_strerror(ret));
227         }
228         free(key.data);
229 }
230
231 static void verb_insert(struct afb_req req)
232 {
233         put(req, 0);
234 }
235
236 static void verb_update(struct afb_req req)
237 {
238         put(req, 1);
239 }
240
241 static void verb_delete(struct afb_req req)
242 {
243         DBT key;
244         int ret;
245
246         if (get_key(req, &key))
247                 return;
248
249         AFB_INFO("delete: key=%s", (char*)key.data);
250
251         if ((ret = database->del(database, NULL, &key, 0)) == 0)
252                 afb_req_success_f(req, NULL, "db success: delete %s.", (char *)key.data);
253         else
254                 afb_req_fail_f(req, "Failed to delete datas.", "db fail: delete %s - %s", (char*)key.data, db_strerror(ret));
255
256         free(key.data);
257 }
258
259 static void verb_read(struct afb_req req)
260 {
261         DBT key;
262         DBT data;
263         int ret;
264
265         char value[4096];
266
267         struct json_object* result;
268         struct json_object* val;
269
270
271         if (get_key(req, &key))
272                 return;
273
274         AFB_INFO("read: key=%s", (char*)key.data);
275
276         memset(&data, 0, sizeof data);
277         data.data = value;
278         data.ulen = 4096;
279         data.flags = DB_DBT_USERMEM;
280
281         ret = database->get(database, NULL, &key, &data, 0);
282         if (ret == 0)
283         {
284                 result = json_object_new_object();
285                 val = json_tokener_parse((char*)data.data);
286                 json_object_object_add(result, "value", val ? val : json_object_new_string((char*)data.data));
287
288                 afb_req_success_f(req, result, "db success: read %s=%s.", (char*)key.data, (char*)data.data);
289         }
290         else
291                 afb_req_fail_f(req, "Failed to read datas.", "db fail: read %s - %s", (char*)key.data, db_strerror(ret));
292
293         free(key.data);
294 }
295
296 // ----- Binding's configuration -----
297 /*
298 static const struct afb_auth ll_database_binding_auths[] = {
299 };
300 */
301
302 static const afb_verb_v2 ll_database_binding_verbs[]= {
303                 REGISTER_VERB(insert,   NULL, NULL, AFB_SESSION_NONE_V2),
304                 REGISTER_VERB(update,   NULL, NULL, AFB_SESSION_NONE_V2),
305                 REGISTER_VERB(delete,   NULL, NULL, AFB_SESSION_NONE_V2),
306                 REGISTER_VERB(read,             NULL, NULL, AFB_SESSION_NONE_V2),
307         { .verb = NULL}
308 };
309
310 const struct afb_binding_v2 afbBindingV2 = {
311                 .api = "ll-database",
312                 .specification = NULL,
313                 .verbs = ll_database_binding_verbs,
314                 .preinit = NULL,
315                 .init = ll_database_binding_init,
316                 .onevent = NULL,
317                 .noconcurrency = 0
318 };