database: factorize insert and replace
[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 #define USERNAME        "agl"
47 #define APPNAME         "firefox"
48
49 // ----- Globals -----
50 static DB*              database;
51 static char     database_file[MAX_PATH];
52
53 // ----- Binding's declarations -----
54 static int ll_database_binding_init();
55 static void verb_insert(struct afb_req req);
56 static void verb_update(struct afb_req req);
57 static void verb_delete(struct afb_req req);
58 static void verb_read(struct afb_req req);
59
60 // ----- Binding's implementations -----
61
62 /**
63  * @brief Initialize the binding.
64  * @return Exit code, zero if success.
65  */
66 static int ll_database_binding_init()
67 {
68         struct passwd pwd;
69         struct passwd* result;
70         char buf[MAX_PATH];
71         size_t bufsize;
72         int ret;
73
74         bufsize = sysconf(_SC_GETPW_R_SIZE_MAX);
75         if (bufsize == -1 || bufsize > MAX_PATH) bufsize = MAX_PATH;
76
77         ret = getpwuid_r(getuid(), &pwd, buf, bufsize, &result);
78         if (result == NULL)
79         {
80                 if (ret == 0) AFB_ERROR("User not found");
81                 else AFB_ERROR("getpwuid_r failed with %d code", ret);
82                 return -1;
83         }
84
85         memset(database_file, 0, MAX_PATH);
86         strcat(database_file, result->pw_dir);
87         strcat(database_file, DBFILE);
88
89         AFB_INFO("The database file is '%s'", database_file);
90
91         if ((ret = db_create(&database, NULL, 0)) != 0)
92         {
93                 AFB_ERROR("Failed to create database: %s.", db_strerror(ret));
94                 return -1;
95         }
96
97         if ((ret = database->open(database, NULL, database_file, NULL, DB_BTREE, DB_CREATE, 0664)) != 0)
98         {
99                 AFB_ERROR("Failed to open the '%s' database: %s.", database_file, db_strerror(ret));
100                 database->close(database, 0);
101                 return -1;
102         }
103
104         return 0;
105 }
106
107 /**
108  * Returns the database key for the 'req'
109  */
110 static int get_key(struct afb_req req, DBT *key)
111 {
112         char *appid, *data;
113         const char *jkey;
114
115         size_t ljkey, lappid, size;
116
117         struct json_object* args;
118         struct json_object* item;
119
120         /* get the key */
121         args = afb_req_json(req);
122         if (!json_object_object_get_ex(args, "key", &item))
123         {
124                 afb_req_fail(req, "no-key", NULL);
125                 return -1;
126         }
127         if (!item
128          || !(jkey = json_object_get_string(item))
129          || !(ljkey = strlen(jkey)))
130         {
131                 afb_req_fail(req, "bad-key", NULL);
132                 return -1;
133         }
134
135         /* get the appid */
136         appid = afb_req_get_application_id(req);
137         if (!appid)
138         {
139                 afb_req_fail(req, "bad-context", NULL);
140                 return -1;
141         }
142
143         /* make the db-key */
144         lappid = strlen(appid);
145         size = lappid + ljkey + 2;
146         data = realloc(appid, size);
147         if (!data)
148         {
149                 free(appid);
150                 afb_req_fail(req, "out-of-memory", NULL);
151                 return -1;
152         }
153         data[lappid] = ':';
154         memcpy(&data[lappid + 1], jkey, ljkey + 1);
155
156         /* return the key */
157         key->data = data;
158         key->size = (uint32_t)size;
159         return 0;
160 }
161
162 static void put(struct afb_req req, int replace)
163 {
164         int ret;
165         DBT key;
166         DBT data;
167
168         const char* value;
169
170         struct json_object* args;
171         struct json_object* item;
172
173         args = afb_req_json(req);
174
175         if (!json_object_object_get_ex(args, "value", &item))
176         {
177                 afb_req_fail(req, "no-value", NULL);
178                 return;
179         }
180
181         value = json_object_to_json_string_ext(item, TO_STRING_FLAGS);
182         if (!value)
183         {
184                 afb_req_fail(req, "out-of-memory", NULL);
185                 return;
186         }
187
188         if (get_key(req, &key))
189                 return;
190
191         AFB_INFO("put: key=%s, value=%s", (char*)key.data, value);
192
193         data.data = (void*)value;
194         data.size = (uint32_t)strlen(value);
195
196         ret = database->put(database, NULL, &key, &data, replace ? 0 : DB_NOOVERWRITE);
197         if (ret == 0)
198                 afb_req_success(req, NULL, NULL);
199         else
200         {
201                 AFB_ERROR("can't %s key %s with %s", replace ? "replace" : "insert", (char*)key.data, (char*)data.data);
202                 afb_req_fail_f(req, "failed", "%s", db_strerror(ret));
203         }
204         free(key.data);
205 }
206
207 static void verb_insert(struct afb_req req)
208 {
209         put(req, 0);
210 }
211
212 static void verb_update(struct afb_req req)
213 {
214         put(req, 1);
215 }
216
217 static void verb_delete(struct afb_req req)
218 {
219         DBT key;
220         int ret;
221
222         if (get_key(req, &key))
223                 return;
224
225         AFB_INFO("delete: key=%s", (char*)key.data);
226
227         if ((ret = database->del(database, NULL, &key, 0)) == 0)
228                 afb_req_success_f(req, NULL, "db success: delete %s.", (char *)key.data);
229         else
230                 afb_req_fail_f(req, "Failed to delete datas.", "db fail: delete %s - %s", (char*)key.data, db_strerror(ret));
231
232         free(key.data);
233 }
234
235 static void verb_read(struct afb_req req)
236 {
237         DBT key;
238         DBT data;
239         int ret;
240
241         char value[4096];
242
243         struct json_object* result;
244         struct json_object* val;
245
246
247         if (get_key(req, &key))
248                 return;
249
250         AFB_INFO("read: key=%s", (char*)key.data);
251
252         data.data = value;
253         data.ulen = 4096;
254         data.flags = DB_DBT_USERMEM;
255
256         if ((ret = database->get(database, NULL, &key, &data, 0)) == 0)
257         {
258                 result = json_object_new_object();
259                 val = json_tokener_parse((char*)data.data);
260                 json_object_object_add(result, "value", val ? val : json_object_new_string((char*)data.data));
261
262                 afb_req_success_f(req, result, "db success: read %s=%s.", (char*)key.data, (char*)data.data);
263         }
264         else
265                 afb_req_fail_f(req, "Failed to read datas.", "db fail: read %s - %s", (char*)key.data, db_strerror(ret));
266
267         free(key.data);
268 }
269
270 // ----- Binding's configuration -----
271 /*
272 static const struct afb_auth ll_database_binding_auths[] = {
273 };
274 */
275
276 static const afb_verb_v2 ll_database_binding_verbs[]= {
277                 REGISTER_VERB(insert,   NULL, NULL, AFB_SESSION_NONE_V2),
278                 REGISTER_VERB(update,   NULL, NULL, AFB_SESSION_NONE_V2),
279                 REGISTER_VERB(delete,   NULL, NULL, AFB_SESSION_NONE_V2),
280                 REGISTER_VERB(read,             NULL, NULL, AFB_SESSION_NONE_V2),
281         { .verb = NULL}
282 };
283
284 const struct afb_binding_v2 afbBindingV2 = {
285                 .api = "ll-database",
286                 .specification = NULL,
287                 .verbs = ll_database_binding_verbs,
288                 .preinit = NULL,
289                 .init = ll_database_binding_init,
290                 .onevent = NULL,
291                 .noconcurrency = 0
292 };