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