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