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