e3d5d77a44f6d7dd910ea89f4b7a7c4319d7b165
[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  * read a unit file
268  */
269 static int read_unit_file(const char *path, char **content, size_t *length)
270 {
271         int rc, st;
272         char c, *read, *write;
273
274         /* read the file */
275         rc = getfile(path, content, length);
276         if (rc >= 0) {
277                 /* removes any comment and join lines */
278                 st = 0;
279                 read = write = *content;
280                 for (;;) {
281                         do { c = *read++; } while (c == '\r');
282                         if (!c)
283                                 break;
284                         switch (st) {
285                         case 0:
286                                 if (c == ';' || c == '#') {
287                                         st = 3; /* removes lines starting with ; or # */
288                                         break;
289                                 }
290                                 if (c == '\n')
291                                         break; /* removes empty lines */
292 enter_state_1:
293                                 st = 1;
294                                 /*@fallthrough@*/
295                         case 1:
296                                 if (c == '\\')
297                                         st = 2;
298                                 else {
299                                         *write++ = c;
300                                         if (c == '\n')
301                                                 st = 0;
302                                 }
303                                 break;
304                         case 2:
305                                 if (c == '\n')
306                                         c = ' ';
307                                 else
308                                         *write++ = '\\';
309                                 goto enter_state_1;
310                         case 3:
311                                 if (c == '\n')
312                                         st = 0;
313                                 break;
314                         }
315                 }
316                 if (st == 1)
317                         *write++ = '\n';
318                 *write = 0;
319                 *length = (size_t)(write - *content);
320                 *content = realloc(*content, *length + 1);
321         }
322         return rc;
323 }
324
325 /*
326  * called for each unit
327  */
328 static int update_cb(void *closure, const char *name, const char *path, int isuser)
329 {
330         struct afm_updt *updt = closure;
331         char *content;
332         size_t length;
333         int rc;
334
335         /* prefix filtering */
336         length = updt->afudb->prefixlen;
337         if (length && strncmp(updt->afudb->prefix, name, length))
338                 return 0;
339
340         /* only services */
341         length = strlen(name);
342         if (length < service_extension_length || strcmp(service_extension, name + length - service_extension_length))
343                 return 0;
344
345         /* reads the file */
346         rc = read_unit_file(path, &content, &length);
347         if (rc < 0)
348                 return rc;
349
350         /* process the file */
351         rc = addunit(&updt->applications, isuser, path, name, content, length);
352         free(content);
353         return rc;
354 }
355
356 /*
357  * Creates an afm_udb object and returns it with one reference added.
358  * Return NULL with errno = ENOMEM if memory exhausted.
359  */
360 struct afm_udb *afm_udb_create(int sys, int usr, const char *prefix)
361 {
362         size_t length;
363         struct afm_udb *afudb;
364
365         length = prefix ? strlen(prefix) : 0;
366         afudb = malloc(length + sizeof * afudb);
367         if (afudb == NULL)
368                 errno = ENOMEM;
369         else {
370                 afudb->refcount = 1;
371                 afudb->applications.prvarr = NULL;
372                 afudb->applications.pubarr = NULL;
373                 afudb->applications.pubobj = NULL;
374                 afudb->applications.prvobj = NULL;
375                 afudb->system = sys;
376                 afudb->user = usr;
377                 afudb->prefixlen = length;
378                 if (length)
379                         memcpy(afudb->prefix, prefix, length);
380                 afudb->prefix[length] = 0;
381                 if (afm_udb_update(afudb) < 0) {
382                         afm_udb_unref(afudb);
383                         afudb = NULL;
384                 }
385         }
386         return afudb;
387 }
388
389 /*
390  * Adds a reference to an existing afm_udb.
391  */
392 void afm_udb_addref(struct afm_udb *afudb)
393 {
394         assert(afudb);
395         afudb->refcount++;
396 }
397
398 /*
399  * Removes a reference to an existing afm_udb object.
400  * Removes the objet if there no more reference to it.
401  */
402 void afm_udb_unref(struct afm_udb *afudb)
403 {
404         assert(afudb);
405         if (!--afudb->refcount) {
406                 /* no more reference, clean the memory used by the object */
407                 apps_put(&afudb->applications);
408                 free(afudb);
409         }
410 }
411
412 /*
413  * Regenerate the list of applications of the afm_bd object 'afudb'.
414  * Returns 0 in case of success.
415  * Returns -1 and set errno in case of error
416  */
417 int afm_udb_update(struct afm_udb *afudb)
418 {
419         struct afm_updt updt;
420         struct afm_apps tmp;
421
422         /* lock the db */
423         afm_udb_addref(afudb);
424         updt.afudb = afudb;
425
426         /* create the result */
427         updt.applications.prvarr = json_object_new_array();
428         updt.applications.pubarr = json_object_new_array();
429         updt.applications.pubobj = json_object_new_object();
430         updt.applications.prvobj = json_object_new_object();
431         if (updt.applications.pubarr == NULL
432          || updt.applications.prvarr == NULL
433          || updt.applications.pubobj == NULL
434          || updt.applications.prvobj == NULL) {
435                 errno = ENOMEM;
436                 goto error;
437         }
438
439         /* scan the units */
440         if (afudb->user)
441                 if (systemd_unit_list(1, update_cb, &updt) < 0)
442                         goto error;
443         if (afudb->system)
444                 if (systemd_unit_list(0, update_cb, &updt) < 0)
445                         goto error;
446
447         /* commit the result */
448         tmp = afudb->applications;
449         afudb->applications = updt.applications;
450         apps_put(&tmp);
451         afm_udb_addref(afudb);
452         return 0;
453
454 error:
455         apps_put(&updt.applications);
456         afm_udb_addref(afudb);
457         return -1;
458 }
459
460 /*
461  * Get the list of the applications private data of the afm_udb object 'afudb'.
462  * The list is returned as a JSON-array that must be released using
463  * 'json_object_put'.
464  * Returns NULL in case of error.
465  */
466 struct json_object *afm_udb_applications_private(struct afm_udb *afudb)
467 {
468         return json_object_get(afudb->applications.prvarr);
469 }
470
471 /*
472  * Get the list of the applications public data of the afm_udb object 'afudb'.
473  * The list is returned as a JSON-array that must be released using
474  * 'json_object_put'.
475  * Returns NULL in case of error.
476  */
477 struct json_object *afm_udb_applications_public(struct afm_udb *afudb)
478 {
479         return json_object_get(afudb->applications.pubarr);
480 }
481
482 /*
483  * Get the private data of the applications of 'id' in the afm_udb object 'afudb'.
484  * It returns a JSON-object that must be released using 'json_object_put'.
485  * Returns NULL in case of error.
486  */
487 static struct json_object *get_no_case(struct json_object *object, const char *id)
488 {
489         struct json_object *result;
490         struct json_object_iter i;
491
492         /* search case sensitively */
493         if (json_object_object_get_ex(object, id, &result))
494                 return json_object_get(result);
495
496         /* fallback to a case insensitive search */
497         json_object_object_foreachC(object, i) {
498                 if (!strcasecmp(i.key, id))
499                         return json_object_get(i.val);
500         }
501         return NULL;
502 }
503
504 /*
505  * Get the private data of the applications of 'id' in the afm_udb object 'afudb'.
506  * It returns a JSON-object that must be released using 'json_object_put'.
507  * Returns NULL in case of error.
508  */
509 struct json_object *afm_udb_get_application_private(struct afm_udb *afudb, const char *id)
510 {
511         return get_no_case(afudb->applications.prvobj, id);
512 }
513
514 /*
515  * Get the public data of the applications of 'id' in the afm_udb object 'afudb'.
516  * It returns a JSON-object that must be released using 'json_object_put'.
517  * Returns NULL in case of error.
518  */
519 struct json_object *afm_udb_get_application_public(struct afm_udb *afudb,
520                                                         const char *id)
521 {
522         return get_no_case(afudb->applications.pubobj, id);
523 }
524
525
526
527 #if defined(TESTAPPFWK)
528 #include <stdio.h>
529 int main()
530 {
531 struct afm_udb *afudb = afm_udb_create(1, 1, NULL);
532 printf("array = %s\n", json_object_to_json_string_ext(afudb->applications.pubarr, 3));
533 printf("pubobj = %s\n", json_object_to_json_string_ext(afudb->applications.pubobj, 3));
534 printf("prvobj = %s\n", json_object_to_json_string_ext(afudb->applications.prvobj, 3));
535 return 0;
536 }
537 #endif
538