Switch to use systemd database
[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 <assert.h>
21 #include <string.h>
22 #include <errno.h>
23 #include <dirent.h>
24 #include <fcntl.h>
25 #include <unistd.h>
26 #include <sys/types.h>
27
28 #include <json-c/json.h>
29
30 #include "utils-json.h"
31 #include "utils-systemd.h"
32 #include "utils-file.h"
33
34 #include "afm-udb.h"
35
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  * Adds the field of 'name' and 'value' in 'priv' and also if possible in 'pub'
94  * Returns 0 on success or -1 on error.
95  */
96 static int add_field(
97                 struct json_object *priv,
98                 struct json_object *pub,
99                 const char *name,
100                 const char *value
101 )
102 {
103         long int ival;
104         char *end;
105         struct json_object *v;
106
107         /* try to adapt the value to its type */
108         errno = 0;
109         ival = strtol(value, &end, 10);
110         if (*value && !*end && !errno) {
111                 /* integer value */
112                 v = json_object_new_int64(ival);
113         } else {
114                 /* string value */
115                 v = json_object_new_string(value);
116         }
117         if (!v) {
118                 errno = ENOMEM;
119                 return -1;
120         }
121
122         /* add the value */
123         if (name[0] == '-') {
124                 json_object_object_add(priv, &name[1], v);
125         } else {
126                 json_object_object_add(priv, name, json_object_get(v));
127                 json_object_object_add(pub, name, v);
128         }
129         return 0;
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_fields_of_content(
137                 struct json_object *priv,
138                 struct json_object *pub,
139                 char *content,
140                 size_t length
141 )
142 {
143         char *name, *value, *read, *write;
144
145         read = strstr(content, x_afm_prefix);
146         while (read) {
147                 name = read + x_afm_prefix_length;
148                 value = strchr(name, '=');
149                 if (value == NULL)
150                         read = strstr(name, x_afm_prefix);
151                 else {
152                         *value++ = 0;
153                         read = write = value;
154                         while(*read && *read != '\n') {
155                                 if (read[0] != '\\')
156                                         *write++ = *read++;
157                                 else {
158                                         switch(*++read) {
159                                         case 'n': *write++ = '\n'; break;
160                                         case '\n': *write++ = ' '; break;
161                                         default: *write++ = '\\'; *write++ = *read; break;
162                                         }
163                                         read += !!*read;
164                                 }
165                         }
166                         read = strstr(read, x_afm_prefix);
167                         *write = 0;
168                         if (add_field(priv, pub, name, value) < 0)
169                                 return -1;
170                 }
171         }
172         return 0;
173 }
174
175 /*
176  * Adds the application widget 'desc' of the directory 'path' to the
177  * afm_apps object 'apps'.
178  * Returns 0 in case of success.
179  * Returns -1 and set errno in case of error
180  */
181 static int addunit(
182                 struct afm_apps *apps,
183                 int isuser,
184                 const char *unitpath,
185                 const char *unitname,
186                 char *content,
187                 size_t length
188 )
189 {
190         struct json_object *priv, *pub, *id;
191         const char *strid;
192
193         /* create the application structure */
194         priv = json_object_new_object();
195         if (!priv)
196                 return -1;
197
198         pub = json_object_new_object();
199         if (!pub)
200                 goto error;
201
202         /* adds the values */
203         if (add_fields_of_content(priv, pub, content, length)
204          || add_field(priv, pub, key_unit_path, unitpath)
205          || add_field(priv, pub, key_unit_name, unitname)
206          || add_field(priv, pub, key_unit_scope, isuser ? scope_user : scope_system))
207                 goto error;
208
209         /* get the id */
210         if (!json_object_object_get_ex(pub, key_id, &id)) {
211                 errno = EINVAL;
212                 goto error;
213         }
214         strid = json_object_get_string(id);
215
216         /* record the application structure */
217         json_object_get(pub);
218         json_object_array_add(apps->pubarr, pub);
219         json_object_object_add(apps->pubobj, strid, pub);
220         json_object_get(priv);
221         json_object_array_add(apps->prvarr, priv);
222         json_object_object_add(apps->prvobj, strid, priv);
223         return 0;
224
225 error:
226         json_object_put(pub);
227         json_object_put(priv);
228         return -1;
229 }
230
231 /*
232  * called for each unit
233  */
234 static int update_cb(void *closure, const char *name, const char *path, int isuser)
235 {
236         struct afm_updt *updt = closure;
237         char *content;
238         size_t length;
239         int rc;
240
241         /* prefix filtering */
242         length = updt->afudb->prefixlen;
243         if (length && strncmp(updt->afudb->prefix, name, length))
244                 return 0;
245
246         /* only services */
247         length = strlen(name);
248         if (length < service_extension_length || strcmp(service_extension, name + length - service_extension_length))
249                 return 0;
250
251         /* reads the file */
252         rc = getfile(path, &content, &length);
253         if (rc < 0)
254                 return rc;
255
256         /* process the file */
257         rc = addunit(&updt->applications, isuser, path, name, content, length);
258         free(content);
259         return rc;
260 }
261
262 /*
263  * Creates an afm_udb object and returns it with one reference added.
264  * Return NULL with errno = ENOMEM if memory exhausted.
265  */
266 struct afm_udb *afm_udb_create(int sys, int usr, const char *prefix)
267 {
268         size_t length;
269         struct afm_udb *afudb;
270
271         length = prefix ? strlen(prefix) : 0;
272         afudb = malloc(length + sizeof * afudb);
273         if (afudb == NULL)
274                 errno = ENOMEM;
275         else {
276                 afudb->refcount = 1;
277                 afudb->applications.prvarr = NULL;
278                 afudb->applications.pubarr = NULL;
279                 afudb->applications.pubobj = NULL;
280                 afudb->applications.prvobj = NULL;
281                 afudb->system = sys;
282                 afudb->user = usr;
283                 afudb->prefixlen = length;
284                 if (length)
285                         memcpy(afudb->prefix, prefix, length);
286                 afudb->prefix[length] = 0;
287                 if (afm_udb_update(afudb) < 0) {
288                         afm_udb_unref(afudb);
289                         afudb = NULL;
290                 }
291         }
292         return afudb;
293 }
294
295 /*
296  * Adds a reference to an existing afm_udb.
297  */
298 void afm_udb_addref(struct afm_udb *afudb)
299 {
300         assert(afudb);
301         afudb->refcount++;
302 }
303
304 /*
305  * Removes a reference to an existing afm_udb object.
306  * Removes the objet if there no more reference to it.
307  */
308 void afm_udb_unref(struct afm_udb *afudb)
309 {
310         assert(afudb);
311         if (!--afudb->refcount) {
312                 /* no more reference, clean the memory used by the object */
313                 apps_put(&afudb->applications);
314                 free(afudb);
315         }
316 }
317
318 /*
319  * Regenerate the list of applications of the afm_bd object 'afudb'.
320  * Returns 0 in case of success.
321  * Returns -1 and set errno in case of error
322  */
323 int afm_udb_update(struct afm_udb *afudb)
324 {
325         struct afm_updt updt;
326         struct afm_apps tmp;
327
328         /* lock the db */
329         afm_udb_addref(afudb);
330         updt.afudb = afudb;
331
332         /* create the result */
333         updt.applications.prvarr = json_object_new_array();
334         updt.applications.pubarr = json_object_new_array();
335         updt.applications.pubobj = json_object_new_object();
336         updt.applications.prvobj = json_object_new_object();
337         if (updt.applications.pubarr == NULL
338          || updt.applications.prvarr == NULL
339          || updt.applications.pubobj == NULL
340          || updt.applications.prvobj == NULL) {
341                 errno = ENOMEM;
342                 goto error;
343         }
344
345         /* scan the units */
346         if (afudb->user)
347                 if (systemd_unit_list(1, update_cb, &updt) < 0)
348                         goto error;
349         if (afudb->system)
350                 if (systemd_unit_list(0, update_cb, &updt) < 0)
351                         goto error;
352
353         /* commit the result */
354         tmp = afudb->applications;
355         afudb->applications = updt.applications;
356         apps_put(&tmp);
357         afm_udb_addref(afudb);
358         return 0;
359
360 error:
361         apps_put(&updt.applications);
362         afm_udb_addref(afudb);
363         return -1;
364 }
365
366 /*
367  * Get the list of the applications private data of the afm_udb object 'afudb'.
368  * The list is returned as a JSON-array that must be released using
369  * 'json_object_put'.
370  * Returns NULL in case of error.
371  */
372 struct json_object *afm_udb_applications_private(struct afm_udb *afudb)
373 {
374         return json_object_get(afudb->applications.prvarr);
375 }
376
377 /*
378  * Get the list of the applications public data of the afm_udb object 'afudb'.
379  * The list is returned as a JSON-array that must be released using
380  * 'json_object_put'.
381  * Returns NULL in case of error.
382  */
383 struct json_object *afm_udb_applications_public(struct afm_udb *afudb)
384 {
385         return json_object_get(afudb->applications.pubarr);
386 }
387
388 /*
389  * Get the private data of the applications of 'id' in the afm_udb object 'afudb'.
390  * It returns a JSON-object that must be released using 'json_object_put'.
391  * Returns NULL in case of error.
392  */
393 static struct json_object *get_no_case(struct json_object *object, const char *id)
394 {
395         struct json_object *result;
396         struct json_object_iter i;
397
398         /* search case sensitively */
399         if (json_object_object_get_ex(object, id, &result))
400                 return json_object_get(result);
401
402         /* fallback to a case insensitive search */
403         json_object_object_foreachC(object, i) {
404                 if (!strcasecmp(i.key, id))
405                         return json_object_get(i.val);
406         }
407         return NULL;
408 }
409
410 /*
411  * Get the private data of the applications of 'id' in the afm_udb object 'afudb'.
412  * It returns a JSON-object that must be released using 'json_object_put'.
413  * Returns NULL in case of error.
414  */
415 struct json_object *afm_udb_get_application_private(struct afm_udb *afudb, const char *id)
416 {
417         return get_no_case(afudb->applications.prvobj, id);
418 }
419
420 /*
421  * Get the public data of the applications of 'id' in the afm_udb object 'afudb'.
422  * It returns a JSON-object that must be released using 'json_object_put'.
423  * Returns NULL in case of error.
424  */
425 struct json_object *afm_udb_get_application_public(struct afm_udb *afudb,
426                                                         const char *id)
427 {
428         return get_no_case(afudb->applications.pubobj, id);
429 }
430
431
432
433 #if defined(TESTAPPFWK)
434 #include <stdio.h>
435 int main()
436 {
437 struct afm_udb *afudb = afm_udb_create(1, 1, NULL);
438 printf("array = %s\n", json_object_to_json_string_ext(afudb->applications.pubarr, 3));
439 printf("pubobj = %s\n", json_object_to_json_string_ext(afudb->applications.pubobj, 3));
440 printf("prvobj = %s\n", json_object_to_json_string_ext(afudb->applications.prvobj, 3));
441 return 0;
442 }
443 #endif
444