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