Extend database values with arrays
[src/app-framework-main.git] / src / afm-udb.c
1 /*
2  Copyright 2015, 2016, 2017 IoT.bzh
3
4  author: José Bollo <jose.bollo@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 #include <stdlib.h>
20 #include <assert.h>
21 #include <string.h>
22 #include <errno.h>
23 #include <dirent.h>
24 #include <fcntl.h>
25 #include <unistd.h>
26 #include <sys/types.h>
27
28 #include <json-c/json.h>
29
30 #include "utils-json.h"
31 #include "utils-systemd.h"
32 #include "utils-file.h"
33
34 #include "afm-udb.h"
35
36
37 static const char x_afm_prefix[] = "X-AFM-";
38 static const char service_extension[] = ".service";
39 static const char key_unit_path[] = "-unit-path";
40 static const char key_unit_name[] = "-unit-name";
41 static const char key_unit_scope[] = "-unit-scope";
42 static const char scope_user[] = "user";
43 static const char scope_system[] = "system";
44 static const char key_id[] = "id";
45
46 #define x_afm_prefix_length  (sizeof x_afm_prefix - 1)
47 #define service_extension_length  (sizeof service_extension - 1)
48
49 /*
50  * The structure afm_apps records the data about applications
51  * for several accesses.
52  */
53 struct afm_apps {
54         struct json_object *prvarr; /* array of the private data of apps */
55         struct json_object *pubarr; /* array of the public data of apps */
56         struct json_object *pubobj; /* hash of application's publics */
57         struct json_object *prvobj; /* hash of application's privates */
58 };
59
60 /*
61  * The structure afm_udb records the applications
62  * for a set of directories recorded as a linked list
63  */
64 struct afm_udb {
65         struct afm_apps applications;   /* the data about applications */
66         int refcount;                   /* count of references to the structure */
67         int system;                     /* is managing system units? */
68         int user;                       /* is managing user units? */
69         size_t prefixlen;               /* length of the prefix */
70         char prefix[1];                 /* filtering prefix */
71 };
72
73 /*
74  * The structure afm_updt is internally used for updates
75  */
76 struct afm_updt {
77         struct afm_udb *afudb;
78         struct afm_apps applications;
79 };
80
81 /*
82  * Release the data of the afm_apps object 'apps'.
83  */
84 static void apps_put(struct afm_apps *apps)
85 {
86         json_object_put(apps->prvarr);
87         json_object_put(apps->pubarr);
88         json_object_put(apps->pubobj);
89         json_object_put(apps->prvobj);
90 }
91
92 /*
93  * Append the field 'data' to the field 'name' of the 'object'.
94  * When a second append is done to one field, it is automatically
95  * transformed to an array.
96  * Return 0 in case of success or -1 in case of error.
97  */
98 static int append_field(
99                 struct json_object *object,
100                 const char *name,
101                 struct json_object *data
102 )
103 {
104         struct json_object *item, *array;
105
106         if (!json_object_object_get_ex(object, name, &item))
107                 json_object_object_add(object, name, data);
108         else {
109                 if (json_object_is_type(item, json_type_array))
110                         array = item;
111                 else {
112                         array = json_object_new_array();
113                         if (!array)
114                                 goto error;
115                         json_object_array_add(array, item);
116                         json_object_object_add(object, name, array);
117                 }
118                 json_object_array_add(array, data);
119         }
120         return 0;
121  error:
122         json_object_put(data);
123         errno = ENOMEM;
124         return -1;
125 }
126
127 /*
128  * Adds the field of 'name' and 'value' in 'priv' and also if possible in 'pub'
129  * Returns 0 on success or -1 on error.
130  */
131 static int add_field(
132                 struct json_object *priv,
133                 struct json_object *pub,
134                 const char *name,
135                 const char *value
136 )
137 {
138         long int ival;
139         char *end;
140         struct json_object *v;
141
142         /* try to adapt the value to its type */
143         errno = 0;
144         ival = strtol(value, &end, 10);
145         if (*value && !*end && !errno) {
146                 /* integer value */
147                 v = json_object_new_int64(ival);
148         } else {
149                 /* string value */
150                 v = json_object_new_string(value);
151         }
152         if (!v) {
153                 errno = ENOMEM;
154                 return -1;
155         }
156
157         /* add the value */
158         if (name[0] == '-') {
159                 append_field(priv, &name[1], v);
160         } else {
161                 append_field(priv, name, json_object_get(v));
162                 append_field(pub, name, v);
163         }
164         return 0;
165 }
166
167 /*
168  * Adds the field of 'name' and 'value' in 'priv' and also if possible in 'pub'
169  * Returns 0 on success or -1 on error.
170  */
171 static int add_fields_of_content(
172                 struct json_object *priv,
173                 struct json_object *pub,
174                 char *content,
175                 size_t length
176 )
177 {
178         char *name, *value, *read, *write;
179
180         read = strstr(content, x_afm_prefix);
181         while (read) {
182                 name = read + x_afm_prefix_length;
183                 value = strchr(name, '=');
184                 if (value == NULL)
185                         read = strstr(name, x_afm_prefix);
186                 else {
187                         *value++ = 0;
188                         read = write = value;
189                         while(*read && *read != '\n') {
190                                 if (read[0] != '\\')
191                                         *write++ = *read++;
192                                 else {
193                                         switch(*++read) {
194                                         case 'n': *write++ = '\n'; break;
195                                         case '\n': *write++ = ' '; break;
196                                         default: *write++ = '\\'; *write++ = *read; break;
197                                         }
198                                         read += !!*read;
199                                 }
200                         }
201                         read = strstr(read, x_afm_prefix);
202                         *write = 0;
203                         if (add_field(priv, pub, name, value) < 0)
204                                 return -1;
205                 }
206         }
207         return 0;
208 }
209
210 /*
211  * Adds the application widget 'desc' of the directory 'path' to the
212  * afm_apps object 'apps'.
213  * Returns 0 in case of success.
214  * Returns -1 and set errno in case of error
215  */
216 static int addunit(
217                 struct afm_apps *apps,
218                 int isuser,
219                 const char *unitpath,
220                 const char *unitname,
221                 char *content,
222                 size_t length
223 )
224 {
225         struct json_object *priv, *pub, *id;
226         const char *strid;
227
228         /* create the application structure */
229         priv = json_object_new_object();
230         if (!priv)
231                 return -1;
232
233         pub = json_object_new_object();
234         if (!pub)
235                 goto error;
236
237         /* adds the values */
238         if (add_fields_of_content(priv, pub, content, length)
239          || add_field(priv, pub, key_unit_path, unitpath)
240          || add_field(priv, pub, key_unit_name, unitname)
241          || add_field(priv, pub, key_unit_scope, isuser ? scope_user : scope_system))
242                 goto error;
243
244         /* get the id */
245         if (!json_object_object_get_ex(pub, key_id, &id)) {
246                 errno = EINVAL;
247                 goto error;
248         }
249         strid = json_object_get_string(id);
250
251         /* record the application structure */
252         json_object_get(pub);
253         json_object_array_add(apps->pubarr, pub);
254         json_object_object_add(apps->pubobj, strid, pub);
255         json_object_get(priv);
256         json_object_array_add(apps->prvarr, priv);
257         json_object_object_add(apps->prvobj, strid, priv);
258         return 0;
259
260 error:
261         json_object_put(pub);
262         json_object_put(priv);
263         return -1;
264 }
265
266 /*
267  * called for each unit
268  */
269 static int update_cb(void *closure, const char *name, const char *path, int isuser)
270 {
271         struct afm_updt *updt = closure;
272         char *content;
273         size_t length;
274         int rc;
275
276         /* prefix filtering */
277         length = updt->afudb->prefixlen;
278         if (length && strncmp(updt->afudb->prefix, name, length))
279                 return 0;
280
281         /* only services */
282         length = strlen(name);
283         if (length < service_extension_length || strcmp(service_extension, name + length - service_extension_length))
284                 return 0;
285
286         /* reads the file */
287         rc = getfile(path, &content, &length);
288         if (rc < 0)
289                 return rc;
290
291         /* process the file */
292         rc = addunit(&updt->applications, isuser, path, name, content, length);
293         free(content);
294         return rc;
295 }
296
297 /*
298  * Creates an afm_udb object and returns it with one reference added.
299  * Return NULL with errno = ENOMEM if memory exhausted.
300  */
301 struct afm_udb *afm_udb_create(int sys, int usr, const char *prefix)
302 {
303         size_t length;
304         struct afm_udb *afudb;
305
306         length = prefix ? strlen(prefix) : 0;
307         afudb = malloc(length + sizeof * afudb);
308         if (afudb == NULL)
309                 errno = ENOMEM;
310         else {
311                 afudb->refcount = 1;
312                 afudb->applications.prvarr = NULL;
313                 afudb->applications.pubarr = NULL;
314                 afudb->applications.pubobj = NULL;
315                 afudb->applications.prvobj = NULL;
316                 afudb->system = sys;
317                 afudb->user = usr;
318                 afudb->prefixlen = length;
319                 if (length)
320                         memcpy(afudb->prefix, prefix, length);
321                 afudb->prefix[length] = 0;
322                 if (afm_udb_update(afudb) < 0) {
323                         afm_udb_unref(afudb);
324                         afudb = NULL;
325                 }
326         }
327         return afudb;
328 }
329
330 /*
331  * Adds a reference to an existing afm_udb.
332  */
333 void afm_udb_addref(struct afm_udb *afudb)
334 {
335         assert(afudb);
336         afudb->refcount++;
337 }
338
339 /*
340  * Removes a reference to an existing afm_udb object.
341  * Removes the objet if there no more reference to it.
342  */
343 void afm_udb_unref(struct afm_udb *afudb)
344 {
345         assert(afudb);
346         if (!--afudb->refcount) {
347                 /* no more reference, clean the memory used by the object */
348                 apps_put(&afudb->applications);
349                 free(afudb);
350         }
351 }
352
353 /*
354  * Regenerate the list of applications of the afm_bd object 'afudb'.
355  * Returns 0 in case of success.
356  * Returns -1 and set errno in case of error
357  */
358 int afm_udb_update(struct afm_udb *afudb)
359 {
360         struct afm_updt updt;
361         struct afm_apps tmp;
362
363         /* lock the db */
364         afm_udb_addref(afudb);
365         updt.afudb = afudb;
366
367         /* create the result */
368         updt.applications.prvarr = json_object_new_array();
369         updt.applications.pubarr = json_object_new_array();
370         updt.applications.pubobj = json_object_new_object();
371         updt.applications.prvobj = json_object_new_object();
372         if (updt.applications.pubarr == NULL
373          || updt.applications.prvarr == NULL
374          || updt.applications.pubobj == NULL
375          || updt.applications.prvobj == NULL) {
376                 errno = ENOMEM;
377                 goto error;
378         }
379
380         /* scan the units */
381         if (afudb->user)
382                 if (systemd_unit_list(1, update_cb, &updt) < 0)
383                         goto error;
384         if (afudb->system)
385                 if (systemd_unit_list(0, update_cb, &updt) < 0)
386                         goto error;
387
388         /* commit the result */
389         tmp = afudb->applications;
390         afudb->applications = updt.applications;
391         apps_put(&tmp);
392         afm_udb_addref(afudb);
393         return 0;
394
395 error:
396         apps_put(&updt.applications);
397         afm_udb_addref(afudb);
398         return -1;
399 }
400
401 /*
402  * Get the list of the applications private data of the afm_udb object 'afudb'.
403  * The list is returned as a JSON-array that must be released using
404  * 'json_object_put'.
405  * Returns NULL in case of error.
406  */
407 struct json_object *afm_udb_applications_private(struct afm_udb *afudb)
408 {
409         return json_object_get(afudb->applications.prvarr);
410 }
411
412 /*
413  * Get the list of the applications public data of the afm_udb object 'afudb'.
414  * The list is returned as a JSON-array that must be released using
415  * 'json_object_put'.
416  * Returns NULL in case of error.
417  */
418 struct json_object *afm_udb_applications_public(struct afm_udb *afudb)
419 {
420         return json_object_get(afudb->applications.pubarr);
421 }
422
423 /*
424  * Get the private data of the applications of 'id' in the afm_udb object 'afudb'.
425  * It returns a JSON-object that must be released using 'json_object_put'.
426  * Returns NULL in case of error.
427  */
428 static struct json_object *get_no_case(struct json_object *object, const char *id)
429 {
430         struct json_object *result;
431         struct json_object_iter i;
432
433         /* search case sensitively */
434         if (json_object_object_get_ex(object, id, &result))
435                 return json_object_get(result);
436
437         /* fallback to a case insensitive search */
438         json_object_object_foreachC(object, i) {
439                 if (!strcasecmp(i.key, id))
440                         return json_object_get(i.val);
441         }
442         return NULL;
443 }
444
445 /*
446  * Get the private data of the applications of 'id' in the afm_udb object 'afudb'.
447  * It returns a JSON-object that must be released using 'json_object_put'.
448  * Returns NULL in case of error.
449  */
450 struct json_object *afm_udb_get_application_private(struct afm_udb *afudb, const char *id)
451 {
452         return get_no_case(afudb->applications.prvobj, id);
453 }
454
455 /*
456  * Get the public data of the applications of 'id' in the afm_udb object 'afudb'.
457  * It returns a JSON-object that must be released using 'json_object_put'.
458  * Returns NULL in case of error.
459  */
460 struct json_object *afm_udb_get_application_public(struct afm_udb *afudb,
461                                                         const char *id)
462 {
463         return get_no_case(afudb->applications.pubobj, id);
464 }
465
466
467
468 #if defined(TESTAPPFWK)
469 #include <stdio.h>
470 int main()
471 {
472 struct afm_udb *afudb = afm_udb_create(1, 1, NULL);
473 printf("array = %s\n", json_object_to_json_string_ext(afudb->applications.pubarr, 3));
474 printf("pubobj = %s\n", json_object_to_json_string_ext(afudb->applications.pubobj, 3));
475 printf("prvobj = %s\n", json_object_to_json_string_ext(afudb->applications.prvobj, 3));
476 return 0;
477 }
478 #endif
479