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