bbbba8087cb607a480937e9bd5835d154d6a44b6
[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  * 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 #include <db.h>
30
31 #define AFB_BINDING_VERSION 2
32 #include <afb/afb-binding.h>
33
34 #define DBFILE          "ll-database-binding.db"
35
36 #if !defined(TO_STRING_FLAGS)
37 # if !defined(JSON_C_TO_STRING_NOSLASHESCAPE)
38 #  define JSON_C_TO_STRING_NOSLASHESCAPE (1<<4)
39 # endif
40 # define TO_STRING_FLAGS (JSON_C_TO_STRING_PLAIN | JSON_C_TO_STRING_NOSLASHESCAPE)
41 #endif
42
43 // ----- Globals -----
44 static DB *database;
45
46 // ----- Binding's declarations -----
47 static int ll_database_binding_init();
48 static void verb_insert(struct afb_req req);
49 static void verb_update(struct afb_req req);
50 static void verb_delete(struct afb_req req);
51 static void verb_read(struct afb_req req);
52
53 // ----- Binding's implementations -----
54
55 /**
56  * @brief Get the path to the database
57  */
58 static int get_database_path(char *buffer, size_t size)
59 {
60         static const char dbfile[] = DBFILE;
61
62         char *home, *config;
63         int rc;
64
65         config = secure_getenv("XDG_CONFIG_HOME");
66         if (config)
67                 rc = snprintf(buffer, size, "%s/%s", config, dbfile);
68         else
69         {
70                 home = secure_getenv("HOME");
71                 if (home)
72                         rc = snprintf(buffer, size, "%s/.config/%s", home, dbfile);
73                 else
74                 {
75                         struct passwd *pwd = getpwuid(getuid());
76                         if (pwd)
77                                 rc = snprintf(buffer, size, "%s/.config/%s", pwd->pw_dir, dbfile);
78                         else
79                                 rc = snprintf(buffer, size, "/home/%d/.config/%s", (int)getuid(), dbfile);
80                 }
81         }
82         return rc;
83 }
84
85 /**
86  * @brief Initialize the binding.
87  * @return Exit code, zero if success.
88  */
89 static int ll_database_binding_init()
90 {
91         char path[PATH_MAX];
92         int ret;
93
94         ret = get_database_path(path, sizeof path);
95         if (ret < 0 || (int)ret >=  (int)(sizeof path))
96         {
97                 AFB_ERROR("Can't compute the database filename");
98                 return -1;
99         }
100
101         AFB_INFO("opening database %s", path);
102
103         ret = db_create(&database, NULL, 0);
104         if (ret != 0)
105         {
106                 AFB_ERROR("Failed to create database: %s.", db_strerror(ret));
107                 return -1;
108         }
109
110         ret = database->open(database, NULL, path, NULL, DB_BTREE, DB_CREATE, 0664);
111         if (ret != 0)
112         {
113                 AFB_ERROR("Failed to open the '%s' database: %s.", path, db_strerror(ret));
114                 database->close(database, 0);
115                 return -1;
116         }
117
118         return 0;
119 }
120
121 /**
122  * Returns the database key for the 'req'
123  */
124 static int get_key(struct afb_req req, DBT *key)
125 {
126         char *appid, *data;
127         const char *jkey;
128
129         size_t ljkey, lappid, size;
130
131         struct json_object* args;
132         struct json_object* item;
133
134         /* get the key */
135         args = afb_req_json(req);
136         if (!json_object_object_get_ex(args, "key", &item))
137         {
138                 afb_req_fail(req, "no-key", NULL);
139                 return -1;
140         }
141         if (!item
142          || !(jkey = json_object_get_string(item))
143          || !(ljkey = strlen(jkey)))
144         {
145                 afb_req_fail(req, "bad-key", NULL);
146                 return -1;
147         }
148
149         /* get the appid */
150         appid = afb_req_get_application_id(req);
151 #if 1
152         if (!appid)
153                 appid = strdup("#UNKNOWN-APP#");
154 #endif
155         if (!appid)
156         {
157                 afb_req_fail(req, "bad-context", NULL);
158                 return -1;
159         }
160
161         /* make the db-key */
162         lappid = strlen(appid);
163         size = lappid + ljkey + 2;
164         data = realloc(appid, size);
165         if (!data)
166         {
167                 free(appid);
168                 afb_req_fail(req, "out-of-memory", NULL);
169                 return -1;
170         }
171         data[lappid] = ':';
172         memcpy(&data[lappid + 1], jkey, ljkey + 1);
173
174         /* return the key */
175         memset(key, 0, sizeof *key);
176         key->data = data;
177         key->size = (uint32_t)size;
178         return 0;
179 }
180
181 static void put(struct afb_req req, int replace)
182 {
183         int ret;
184         DBT key;
185         DBT data;
186
187         const char* value;
188
189         struct json_object* args;
190         struct json_object* item;
191
192         args = afb_req_json(req);
193
194         if (!json_object_object_get_ex(args, "value", &item))
195         {
196                 afb_req_fail(req, "no-value", NULL);
197                 return;
198         }
199
200         value = json_object_to_json_string_ext(item, TO_STRING_FLAGS);
201         if (!value)
202         {
203                 afb_req_fail(req, "out-of-memory", NULL);
204                 return;
205         }
206
207         if (get_key(req, &key))
208                 return;
209
210         AFB_INFO("put: key=%s, value=%s", (char*)key.data, value);
211
212         memset(&data, 0, sizeof data);
213         data.data = (void*)value;
214         data.size = (uint32_t)strlen(value) + 1; /* includes the tailing null */
215
216         ret = database->put(database, NULL, &key, &data, replace ? 0 : DB_NOOVERWRITE);
217         if (ret == 0)
218                 afb_req_success(req, NULL, NULL);
219         else
220         {
221                 AFB_ERROR("can't %s key %s with %s", replace ? "replace" : "insert", (char*)key.data, (char*)data.data);
222                 afb_req_fail_f(req, "failed", "%s", db_strerror(ret));
223         }
224         free(key.data);
225 }
226
227 static void verb_insert(struct afb_req req)
228 {
229         put(req, 0);
230 }
231
232 static void verb_update(struct afb_req req)
233 {
234         put(req, 1);
235 }
236
237 static void verb_delete(struct afb_req req)
238 {
239         DBT key;
240         int ret;
241
242         if (get_key(req, &key))
243                 return;
244
245         AFB_INFO("delete: key=%s", (char*)key.data);
246
247         if ((ret = database->del(database, NULL, &key, 0)) == 0)
248                 afb_req_success_f(req, NULL, "db success: delete %s.", (char *)key.data);
249         else
250                 afb_req_fail_f(req, "Failed to delete datas.", "db fail: delete %s - %s", (char*)key.data, db_strerror(ret));
251
252         free(key.data);
253 }
254
255 static void verb_read(struct afb_req req)
256 {
257         DBT key;
258         DBT data;
259         int ret;
260
261         char value[4096];
262
263         struct json_object* result;
264         struct json_object* val;
265
266
267         if (get_key(req, &key))
268                 return;
269
270         AFB_INFO("read: key=%s", (char*)key.data);
271
272         memset(&data, 0, sizeof data);
273         data.data = value;
274         data.ulen = 4096;
275         data.flags = DB_DBT_USERMEM;
276
277         ret = database->get(database, NULL, &key, &data, 0);
278         if (ret == 0)
279         {
280                 result = json_object_new_object();
281                 val = json_tokener_parse((char*)data.data);
282                 json_object_object_add(result, "value", val ? val : json_object_new_string((char*)data.data));
283
284                 afb_req_success_f(req, result, "db success: read %s=%s.", (char*)key.data, (char*)data.data);
285         }
286         else
287                 afb_req_fail_f(req, "Failed to read datas.", "db fail: read %s - %s", (char*)key.data, db_strerror(ret));
288
289         free(key.data);
290 }
291
292 // ----- Binding's configuration -----
293 /*
294 static const struct afb_auth ll_database_binding_auths[] = {
295 };
296 */
297
298 #define VERB(name_,auth_,info_,sess_) {\
299         .verb = #name_, \
300         .callback = verb_##name_, \
301         .auth = auth_, \
302         .info = info_, \
303         .session = sess_ }
304
305 static const afb_verb_v2 ll_database_binding_verbs[]= {
306         VERB(insert,    NULL, NULL, AFB_SESSION_NONE_V2),
307         VERB(update,    NULL, NULL, AFB_SESSION_NONE_V2),
308         VERB(delete,    NULL, NULL, AFB_SESSION_NONE_V2),
309         VERB(read,      NULL, NULL, AFB_SESSION_NONE_V2),
310         { .verb = NULL}
311 };
312
313 const struct afb_binding_v2 afbBindingV2 = {
314         .api = "ll-database",
315         .specification = NULL,
316         .verbs = ll_database_binding_verbs,
317         .preinit = NULL,
318         .init = ll_database_binding_init,
319         .onevent = NULL,
320         .noconcurrency = 0
321 };