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