Add ability to manage all widget/applications
[src/app-framework-main.git] / src / afm-udb.c
1 /*
2  Copyright (C) 2015-2019 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 <stdio.h>
21 #include <assert.h>
22 #include <string.h>
23 #include <errno.h>
24 #include <dirent.h>
25 #include <fcntl.h>
26 #include <unistd.h>
27 #include <sys/types.h>
28
29 #include <json-c/json.h>
30
31 #include "utils-json.h"
32 #include "utils-systemd.h"
33 #include "utils-file.h"
34
35 #include "afm-udb.h"
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 static const char key_visibility[] = "visibility";
46 static const char value_visible[] = "visible";
47
48 #define x_afm_prefix_length  (sizeof x_afm_prefix - 1)
49 #define service_extension_length  (sizeof service_extension - 1)
50
51 /*
52  * The structure afm_apps records the data about applications
53  * for several accesses.
54  */
55 struct afm_apps {
56         struct {
57                 struct json_object *visibles; /* array of the private data of visible apps */
58                 struct json_object *all; /* array of the private data of all apps */
59                 struct json_object *byname; /* hash of application's privates */
60         } privates, publics;
61 };
62
63 /*
64  * The structure afm_udb records the applications
65  * for a set of directories recorded as a linked list
66  */
67 struct afm_udb {
68         struct afm_apps applications;   /* the data about applications */
69         int refcount;                   /* count of references to the structure */
70         int system;                     /* is managing system units? */
71         int user;                       /* is managing user units? */
72         size_t prefixlen;               /* length of the prefix */
73         char prefix[1];                 /* filtering prefix */
74 };
75
76 /*
77  * The structure afm_updt is internally used for updates
78  */
79 struct afm_updt {
80         struct afm_udb *afudb;
81         struct afm_apps applications;
82 };
83
84 /*
85  * The default language
86  */
87 static char *default_lang;
88
89 /*
90  * initilize object 'apps'.
91  * returns 1 if okay or 0 on case of memory depletion
92  */
93 static int apps_init(struct afm_apps *apps)
94 {
95         apps->publics.all = json_object_new_array();
96         apps->publics.visibles = json_object_new_array();
97         apps->publics.byname = json_object_new_object();
98
99         apps->privates.all = json_object_new_array();
100         apps->privates.visibles = json_object_new_array();
101         apps->privates.byname = json_object_new_object();
102
103         return apps->publics.all
104            && apps->publics.visibles
105            && apps->publics.byname
106            && apps->privates.all
107            && apps->privates.visibles
108            && apps->privates.byname;
109 }
110
111 /*
112  * Release the data of the afm_apps object 'apps'.
113  */
114 static void apps_put(struct afm_apps *apps)
115 {
116         json_object_put(apps->publics.all);
117         json_object_put(apps->publics.visibles);
118         json_object_put(apps->publics.byname);
119         json_object_put(apps->privates.all);
120         json_object_put(apps->privates.visibles);
121         json_object_put(apps->privates.byname);
122 }
123
124 /*
125  * Append the field 'data' to the field 'name' of the 'object'.
126  * When a second append is done to one field, it is automatically
127  * transformed to an array.
128  * Return 0 in case of success or -1 in case of error.
129  */
130 static int append_field(
131                 struct json_object *object,
132                 const char *name,
133                 struct json_object *data
134 )
135 {
136         struct json_object *item, *array;
137
138         if (!json_object_object_get_ex(object, name, &item))
139                 json_object_object_add(object, name, data);
140         else {
141                 if (json_object_is_type(item, json_type_array))
142                         array = item;
143                 else {
144                         array = json_object_new_array();
145                         if (!array)
146                                 goto error;
147                         json_object_array_add(array, json_object_get(item));
148                         json_object_object_add(object, name, array);
149                 }
150                 json_object_array_add(array, data);
151         }
152         return 0;
153  error:
154         json_object_put(data);
155         errno = ENOMEM;
156         return -1;
157 }
158
159 /*
160  * Adds the field of 'name' and 'value' in 'priv' and also if possible in 'pub'
161  * Returns 0 on success or -1 on error.
162  */
163 static int add_field(
164                 struct json_object *priv,
165                 struct json_object *pub,
166                 const char *name,
167                 const char *value
168 )
169 {
170         long int ival;
171         char *end;
172         struct json_object *v;
173
174         /* try to adapt the value to its type */
175         errno = 0;
176         ival = strtol(value, &end, 10);
177         if (*value && !*end && !errno) {
178                 /* integer value */
179                 v = json_object_new_int64(ival);
180         } else {
181                 /* string value */
182                 v = json_object_new_string(value);
183         }
184         if (!v) {
185                 errno = ENOMEM;
186                 return -1;
187         }
188
189         /* add the value */
190         if (name[0] == '-') {
191                 /* private value */
192                 append_field(priv, &name[1], v);
193         } else {
194                 /* public value */
195                 append_field(priv, name, json_object_get(v));
196                 append_field(pub, name, v);
197         }
198         return 0;
199 }
200
201 /*
202  * Adds the field of 'name' and 'value' in 'priv' and also if possible in 'pub'
203  * Returns 0 on success or -1 on error.
204  */
205 static int add_fields_of_content(
206                 struct json_object *priv,
207                 struct json_object *pub,
208                 char *content,
209                 size_t length
210 )
211 {
212         char *name, *value, *read, *write;
213
214         /* start at the beginning */
215         read = content;
216         for (;;) {
217                 /* search the next key */
218                 read = strstr(read, x_afm_prefix);
219                 if (!read)
220                         return 0;
221
222                 /* search to equal */
223                 name = read + x_afm_prefix_length;
224                 value = strchr(name, '=');
225                 if (value == NULL)
226                         read = name; /* not found */
227                 else {
228                         /* get the value (translate it) */
229                         *value++ = 0;
230                         read = write = value;
231                         while(*read && *read != '\n') {
232                                 if (*read != '\\')
233                                         *write++ = *read++;
234                                 else {
235                                         switch(*++read) {
236                                         case 'n': *write++ = '\n'; break;
237                                         case '\n': *write++ = ' '; break;
238                                         default: *write++ = '\\'; *write++ = *read; break;
239                                         }
240                                         read += !!*read;
241                                 }
242                         }
243                         read += !!*read;
244                         *write = 0;
245
246                         /* add the found field now */
247                         if (add_field(priv, pub, name, value) < 0)
248                                 return -1;
249                 }
250         }
251 }
252
253 /*
254  * Adds the application widget 'desc' of the directory 'path' to the
255  * afm_apps object 'apps'.
256  * Returns 0 in case of success.
257  * Returns -1 and set errno in case of error
258  */
259 static int addunit(
260                 struct afm_apps *apps,
261                 int isuser,
262                 const char *unitpath,
263                 const char *unitname,
264                 char *content,
265                 size_t length
266 )
267 {
268         struct json_object *priv, *pub, *id, *visi;
269         const char *strid;
270         size_t len;
271
272         /* create the application structure */
273         priv = json_object_new_object();
274         if (!priv)
275                 return -1;
276
277         pub = json_object_new_object();
278         if (!pub)
279                 goto error;
280
281         /* make the unit name */
282         len = strlen(unitname);
283         assert(len >= (sizeof service_extension - 1));
284         assert(!memcmp(&unitname[len - (sizeof service_extension - 1)], service_extension, sizeof service_extension));
285
286         /* adds the values */
287         if (add_fields_of_content(priv, pub, content, length)
288          || add_field(priv, pub, key_unit_path, unitpath)
289          || add_field(priv, pub, key_unit_name, unitname)
290          || add_field(priv, pub, key_unit_scope, isuser ? scope_user : scope_system))
291                 goto error;
292
293         /* get the id */
294         if (!json_object_object_get_ex(pub, key_id, &id)) {
295                 errno = EINVAL;
296                 goto error;
297         }
298         strid = json_object_get_string(id);
299
300         /* record the application structure */
301         json_object_get(pub);
302         json_object_array_add(apps->publics.all, pub);
303         json_object_object_add(apps->publics.byname, strid, pub);
304         json_object_get(priv);
305         json_object_array_add(apps->privates.all, priv);
306         json_object_object_add(apps->privates.byname, strid, priv);
307
308         /* handle visibility */
309         if (json_object_object_get_ex(priv, key_visibility, &visi)
310                 && !strcasecmp(json_object_get_string(visi), value_visible)) {
311                 json_object_array_add(apps->publics.visibles, json_object_get(pub));
312                 json_object_array_add(apps->privates.visibles, json_object_get(priv));
313         }
314
315         return 0;
316
317 error:
318         json_object_put(pub);
319         json_object_put(priv);
320         return -1;
321 }
322
323 /*
324  * Crop and trim unit 'content' of 'length'. Return the new length.
325  */
326 static size_t crop_and_trim_unit_content(char *content, size_t length)
327 {
328         int st;
329         char c, *read, *write;
330
331         /* removes any comment and join continued lines */
332         st = 0;
333         read = write = content;
334         for (;;) {
335                 do { c = *read++; } while (c == '\r');
336                 if (!c)
337                         break;
338                 switch (st) {
339                 case 0:
340                         /* state 0: begin of a line */
341                         if (c == ';' || c == '#') {
342                                 st = 3; /* removes lines starting with ; or # */
343                                 break;
344                         }
345                         if (c == '\n')
346                                 break; /* removes empty lines */
347 enter_state_1:
348                         st = 1;
349                         /*@fallthrough@*/
350                 case 1:
351                         /* state 1: emitting a normal line */
352                         if (c == '\\')
353                                 st = 2;
354                         else {
355                                 *write++ = c;
356                                 if (c == '\n')
357                                         st = 0;
358                         }
359                         break;
360                 case 2:
361                         /* state 2: character after '\' */
362                         if (c == '\n')
363                                 c = ' ';
364                         else
365                                 *write++ = '\\';
366                         goto enter_state_1;
367                 case 3:
368                         /* state 3: inside a comment, wait its end */
369                         if (c == '\n')
370                                 st = 0;
371                         break;
372                 }
373         }
374         if (st == 1)
375                 *write++ = '\n';
376         *write = 0;
377         return (size_t)(write - content);
378 }
379
380 /*
381  * read a unit file
382  */
383 static int read_unit_file(const char *path, char **content, size_t *length)
384 {
385         int rc;
386         size_t nl;
387
388         /* read the file */
389         rc = getfile(path, content, length);
390         if (rc >= 0) {
391                 /* crop and trim it */
392                 *length = nl = crop_and_trim_unit_content(*content, *length);
393                 *content = realloc(*content, nl + 1);
394         }
395         return rc;
396 }
397
398 /*
399  * called for each unit
400  */
401 static int update_cb(void *closure, const char *name, const char *path, int isuser)
402 {
403         struct afm_updt *updt = closure;
404         char *content;
405         size_t length;
406         int rc;
407
408         /* prefix filtering */
409         length = updt->afudb->prefixlen;
410         if (length && strncmp(updt->afudb->prefix, name, length))
411                 return 0;
412
413         /* only services */
414         length = strlen(name);
415         if (length < service_extension_length || strcmp(service_extension, name + length - service_extension_length))
416                 return 0;
417
418         /* reads the file */
419         rc = read_unit_file(path, &content, &length);
420         if (rc < 0)
421                 return 0;
422
423         /* process the file */
424         rc = addunit(&updt->applications, isuser, path, name, content, length);
425         /* TODO: if (rc < 0)
426                 ERROR("Ignored boggus unit %s (error: %m)", path); */
427         free(content);
428         return 0;
429 }
430
431 /*
432  * Creates an afm_udb object and returns it with one reference added.
433  * Return NULL with errno = ENOMEM if memory exhausted.
434  */
435 struct afm_udb *afm_udb_create(int sys, int usr, const char *prefix)
436 {
437         size_t length;
438         struct afm_udb *afudb;
439
440         length = prefix ? strlen(prefix) : 0;
441         afudb = malloc(length + sizeof * afudb);
442         if (afudb == NULL)
443                 errno = ENOMEM;
444         else {
445                 afudb->refcount = 1;
446                 memset(&afudb->applications, 0, sizeof afudb->applications);
447                 afudb->system = sys;
448                 afudb->user = usr;
449                 afudb->prefixlen = length;
450                 if (length)
451                         memcpy(afudb->prefix, prefix, length);
452                 afudb->prefix[length] = 0;
453                 if (afm_udb_update(afudb) < 0) {
454                         afm_udb_unref(afudb);
455                         afudb = NULL;
456                 }
457         }
458         return afudb;
459 }
460
461 /*
462  * Adds a reference to an existing afm_udb.
463  */
464 void afm_udb_addref(struct afm_udb *afudb)
465 {
466         assert(afudb);
467         afudb->refcount++;
468 }
469
470 /*
471  * Removes a reference to an existing afm_udb object.
472  * Removes the objet if there no more reference to it.
473  */
474 void afm_udb_unref(struct afm_udb *afudb)
475 {
476         assert(afudb);
477         if (!--afudb->refcount) {
478                 /* no more reference, clean the memory used by the object */
479                 apps_put(&afudb->applications);
480                 free(afudb);
481         }
482 }
483
484 /*
485  * Regenerate the list of applications of the afm_bd object 'afudb'.
486  * Returns 0 in case of success.
487  * Returns -1 and set errno in case of error
488  */
489 int afm_udb_update(struct afm_udb *afudb)
490 {
491         struct afm_updt updt;
492         struct afm_apps tmp;
493         int result;
494
495         /* lock the db */
496         afm_udb_addref(afudb);
497         updt.afudb = afudb;
498
499         /* create the apps */
500         if (!apps_init(&updt.applications))
501                 result = -1;
502         else {
503                 /* scan the units */
504                 if (afudb->user && systemd_unit_list(1, update_cb, &updt) < 0)
505                         result = -1;
506                 else if (afudb->system && systemd_unit_list(0, update_cb, &updt) < 0)
507                         result = -1;
508                 else {
509                         /* commit the result */
510                         tmp = afudb->applications;
511                         afudb->applications = updt.applications;
512                         updt.applications = tmp;
513                         result = 0;
514                 }
515                 apps_put(&updt.applications);
516         }
517         /* unlock the db and return status */
518         afm_udb_unref(afudb);
519         return result;
520 }
521
522 /*
523  * set the default language to 'lang'
524  */
525 void afm_udb_set_default_lang(const char *lang)
526 {
527         char *oldval = default_lang;
528         default_lang = lang ? strdup(lang) : NULL;
529         free(oldval);
530 }
531
532 /*
533  * Get the list of the applications private data of the afm_udb object 'afudb'.
534  * The list is returned as a JSON-array that must be released using
535  * 'json_object_put'.
536  * Returns NULL in case of error.
537  */
538 struct json_object *afm_udb_applications_private(struct afm_udb *afudb, int all, int uid)
539 {
540         return json_object_get(all ? afudb->applications.privates.all : afudb->applications.privates.visibles);
541 }
542
543 /*
544  * Get the list of the applications public data of the afm_udb object 'afudb'.
545  * The list is returned as a JSON-array that must be released using
546  * 'json_object_put'.
547  * Returns NULL in case of error.
548  */
549 struct json_object *afm_udb_applications_public(struct afm_udb *afudb, int all, int uid, const char *lang)
550 {
551         return json_object_get(all ? afudb->applications.publics.all : afudb->applications.publics.visibles);
552 }
553
554 /*
555  * Get the private data of the applications of 'id' in the afm_udb object 'afudb'.
556  * It returns a JSON-object that must be released using 'json_object_put'.
557  * Returns NULL in case of error.
558  */
559 static struct json_object *get_no_case(struct json_object *object, const char *id, int uid, const char *lang)
560 {
561         struct json_object *result;
562         struct json_object_iter i;
563
564         /* search case sensitively */
565         if (json_object_object_get_ex(object, id, &result))
566                 return json_object_get(result);
567
568         /* fallback to a case insensitive search */
569         json_object_object_foreachC(object, i) {
570                 if (!strcasecmp(i.key, id))
571                         return json_object_get(i.val);
572         }
573         return NULL;
574 }
575
576 /*
577  * Get the private data of the applications of 'id' in the afm_udb object 'afudb'.
578  * It returns a JSON-object that must be released using 'json_object_put'.
579  * Returns NULL in case of error.
580  */
581 struct json_object *afm_udb_get_application_private(struct afm_udb *afudb, const char *id, int uid)
582 {
583         return get_no_case(afudb->applications.privates.byname, id, uid, NULL);
584 }
585
586 /*
587  * Get the public data of the applications of 'id' in the afm_udb object 'afudb'.
588  * It returns a JSON-object that must be released using 'json_object_put'.
589  * Returns NULL in case of error.
590  */
591 struct json_object *afm_udb_get_application_public(struct afm_udb *afudb,
592                                                         const char *id, int uid, const char *lang)
593 {
594         return get_no_case(afudb->applications.publics.byname, id, uid, lang);
595 }
596
597
598
599 #if defined(TESTAPPFWK)
600 #include <stdio.h>
601 int main()
602 {
603 struct afm_udb *afudb = afm_udb_create(1, 1, NULL);
604 printf("publics.all = %s\n", json_object_to_json_string_ext(afudb->applications.publics.all, 3));
605 printf("publics.byname = %s\n", json_object_to_json_string_ext(afudb->applications.publics.byname, 3));
606 printf("privates.byname = %s\n", json_object_to_json_string_ext(afudb->applications.privates.byname, 3));
607 return 0;
608 }
609 #endif
610