utils-systemd: introduction of systemd interface
[src/app-framework-main.git] / src / wgtpkg-unit.c
1 /*
2  Copyright 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 #define _GNU_SOURCE
20
21 #include <stdlib.h>
22 #include <stdio.h>
23 #include <sys/types.h>
24 #include <fcntl.h>
25 #include <unistd.h>
26 #include <string.h>
27 #include <errno.h>
28 #include <limits.h>
29
30 #include <json-c/json.h>
31  
32 #include "verbose.h"
33 #include "utils-file.h"
34
35 #include "wgtpkg-mustach.h"
36 #include "utils-json.h"
37 #include "wgt-json.h"
38 #include "utils-systemd.h"
39
40 #include "wgtpkg-unit.h"
41
42 #if 0
43 #include <ctype.h>
44 #else
45 #define isblank(c) ((c)==' '||(c)=='\t')
46 #endif
47
48 /* the template for all units */
49 static char *template;
50
51 /*
52  * Search for the 'pattern' in 'text'.
53  * Returns 1 if 'text' matches the 'pattern' or else returns 0.
54  * When returning 1 and 'after' isn't NULL, the pointer to the
55  * first character after the pettern in 'text' is stored in 'after'.
56  * The characters '\n' and ' ' have a special mening in the search:
57  *  * '\n': matches any space or tabs (including none) followed 
58  *          either by '\n' or '\0' (end of the string)
59  *  * ' ': matches any space or tabs but at least one.
60  */
61 static int matches(const char *pattern, char *text, char **after)
62 {
63         char p, t;
64
65         t = *text;
66         p = *pattern;
67         while(p) {
68                 switch(p) {
69                 case '\n':
70                         while (isblank(t))
71                                 t = *++text;
72                         if (t) {
73                                 if (t != p)
74                                         return 0;
75                                 t = *++text;
76                         }
77                         break;
78                 case ' ':
79                         if (!isblank(t))
80                                 return 0;
81                         do {
82                                 t = *++text;
83                         } while(isblank(t));
84                         break;
85                 default:
86                         if (t != p)
87                                 return 0;
88                         t = *++text;
89                         break;
90                 }
91                 p = *++pattern;
92         }
93         if (after)
94                 *after = text;
95         return 1;
96 }
97
98 /*
99  * Pack a null terminated 'text' by removing empty lines,
100  * lines made of blanks and terminated with \, lines
101  * starting with the 'purge' character (can be null).
102  * Lines made of the 'purge' character followed with
103  * "nl" exactly (without quotes ") are replaced with
104  * an empty line.
105  *
106  * Returns the size after packing (offset of the ending null).
107  */
108 static size_t pack(char *text, char purge)
109 {
110         char *read;    /* read iterator */
111         char *write;   /* write iterator */
112         char *begin;   /* begin the copied text of the line */
113         char *start;   /* first character of the line that isn't blanck */
114         char c;        /* currently scanned character (pointed by read) */
115         char emit;     /* flag telling whether line is to be copied */
116         char cont;     /* flag telling whether the line continues the previous one */
117         char nextcont; /* flag telling whether the line will continues the next one */
118
119         cont = 0;
120         c = *(write = read = text);
121
122         /* iteration over lines */
123         while (c) {
124                 /* computes emit, nextcont, emit and start for the current line */
125                 emit = nextcont = 0;
126                 start = NULL;
127                 begin = read;
128                 while (c && c != '\n') {
129                         if (!isblank(c)) {
130                                 if (c == '\\' && read[1] == '\n')
131                                         nextcont = 1;
132                                 else {
133                                         emit = 1;
134                                         if (!start)
135                                                 start = read;
136                                 }
137                         }
138                         c = *++read;
139                 }
140                 if (c)
141                         c = *++read;
142                 /* emit the line if not empty */
143                 if (emit) {
144                         /* removes the blanks on the left of not continuing lines */
145                         if (!cont && start)
146                                 begin = start;
147                         /* check if purge applies */
148                         if (purge && *begin == purge) {
149                                 /* yes, insert new line if requested */
150                                 if (!strncmp(begin+1, "nl\n",3))
151                                         *write++ = '\n';
152                         } else {
153                                 /* copies the line */
154                                 while (begin != read)
155                                         *write++ = *begin++;
156                         }
157                 }
158                 cont = nextcont;
159         }
160         *write = 0;
161         return (size_t)(write - text);
162 }
163
164 /*
165  * Searchs the first character of the next line
166  * of the 'text' and returns its address
167  * Returns NULL if there is no next line.
168  */
169 static inline char *nextline(char *text)
170 {
171         char *result = strchr(text, '\n');
172         return result + !!result;
173 }
174
175 /*
176  * Search in 'text' the offset of a line beginning with the 'pattern'
177  * Returns NULL if not found or the address of the line contning the pattern
178  * If args isn't NULL and the pattern is found, the pointed pattern is
179  * updated with the address of the character following the found pattern.
180  */
181 static char *offset(char *text, const char *pattern, char **args)
182 {
183         while (text && !matches(pattern, text, args))
184                 text = nextline(text);
185         return text;
186 }
187
188 /*
189  * process one unit
190  */
191 static int process_one_unit(char *spec, struct unitdesc *desc)
192 {
193         char *nsoc, *nsrv, *name, *wanted;
194         int isuser, issystem, issock, isserv, iswanted;
195         size_t len;
196
197         /* finds the configuration directive of the unit */
198         isuser = !!offset(spec, "%systemd-unit user\n", NULL);
199         issystem = !!offset(spec, "%systemd-unit system\n", NULL);
200         issock  = !!offset(spec, "%systemd-unit socket ", &nsoc);
201         isserv  = !!offset(spec, "%systemd-unit service ", &nsrv);
202         iswanted = !!offset(spec, "%systemd-unit wanted-by ", &wanted);
203
204         /* check the unit scope */
205         if ((isuser + issystem) == 1) {
206                 desc->scope = isuser ? unitscope_user : unitscope_system;
207         } else {
208                 desc->scope = unitscope_unknown;
209         }
210
211         /* check the unit type */
212         if ((issock + isserv) == 1) {
213                 if (issock) {
214                         desc->type = unittype_socket;
215                         name = nsoc;
216                 } else {
217                         desc->type = unittype_service;
218                         name = nsrv;
219                 }
220                 len = (size_t)(strchrnul(name, '\n') - name);
221                 desc->name = strndup(name, len);
222                 desc->name_length = desc->name ? len : 0;
223         } else {
224                 desc->type = unittype_unknown;
225                 desc->name = NULL;
226                 desc->name_length = 0;
227         }
228
229         if (iswanted) {
230                 len = (size_t)(strchrnul(wanted, '\n') - wanted);
231                 desc->wanted_by = strndup(wanted, len);
232                 desc->wanted_by_length = len;
233         } else {
234                 desc->wanted_by = NULL;
235                 desc->wanted_by_length = 0;
236         }
237
238         desc->content = spec;
239         desc->content_length = pack(spec, '%');
240
241         return 0;
242 }
243
244 /*
245  * Processes all the units of the 'corpus'.
246  * Each unit of the corpus is separated and packed and its
247  * charactistics are stored in a descriptor.
248  * At the end if no error was found, calls the function 'process'
249  * with its given 'closure' and the array descripbing the units.
250  * Return 0 in case of success or a negative value in case of error.
251  */
252 static int process_all_units(char *corpus, int (*process)(void *closure, const struct unitdesc descs[], unsigned count), void *closure)
253 {
254         int rc, rc2;
255         unsigned n;
256         char *beg, *end;
257         struct unitdesc *descs, *d;
258
259         descs = NULL;
260         n = 0;
261         rc = rc2 = 0;
262
263         /* while there is a unit in the corpus */
264         while(offset(corpus, "%begin systemd-unit\n", &beg)) {
265
266                 /* get the end of the unit */
267                 end = offset(beg, "%end systemd-unit\n", &corpus);
268                 if (!end) {
269                         /* unterminated unit !! */
270                         ERROR("unterminated unit description!! %s", beg);
271                         corpus = beg;
272                         rc2 = -EINVAL;
273                 } else {
274                         /* separate the unit from the corpus */
275                         *end = 0;
276
277                         /* allocates a descriptor for the unit */
278                         d = realloc(descs, (n + 1) * sizeof *descs);
279                         if (d == NULL)
280                                 rc2 = -ENOMEM;
281                         else {
282                                 /* creates the unit description */
283                                 memset(&d[n], 0, sizeof *d);
284                                 descs = d;
285                                 rc2 = process_one_unit(beg, &descs[n]);
286                                 if (rc2 >= 0)
287                                         n++;
288                         }
289                 }
290                 /* records the error if there is an error */
291                 if (rc2 < 0) {
292                         rc = rc ? : rc2;
293                         rc2 = 0;
294                 }
295         }
296
297         /* call the function that processes the units */
298         if (rc == 0 && process)
299                 rc = process(closure, descs, n);
300
301         /* cleanup and frees */
302         while(n) {
303                 free((char *)(descs[--n].name));
304                 free((char *)(descs[n].wanted_by));
305         }
306         free(descs);
307
308         return rc;
309 }
310
311 /*
312  * Clear the unit generator
313  */
314 void unit_generator_off()
315 {
316         free(template);
317         template = NULL;
318 }
319
320 /*
321  * Initialises the unit generator with the content of the file of path 'filename'.
322  * Returns 0 in case of success or a negative number in case of error.
323  */
324 int unit_generator_on(const char *filename)
325 {
326         size_t size;
327         char *tmp;
328         int rc;
329
330         unit_generator_off();
331         rc = getfile(filename ? : FWK_UNIT_CONF, &template, NULL);
332         if (!rc) {
333                 size = pack(template, ';');
334                 tmp = realloc(template, 1 + size);
335                 if (tmp)
336                         template = tmp;
337         }
338         return rc;
339 }
340
341 /*
342  * Applies the object 'jdesc' to the current unit generator.
343  * The current unit generator will be set to the default one if not unit
344  * was previously set using the function 'unit_generator_on'.
345  * The callback function 'process' is then called with the
346  * unit descriptors array and the expected closure.
347  * Return what returned process in case of success or a negative
348  * error code.
349  */
350 int unit_generator_process(struct json_object *jdesc, int (*process)(void *closure, const struct unitdesc descs[], unsigned count), void *closure)
351 {
352         int rc;
353         size_t size;
354         char *instance;
355
356         rc = template ? 0 : unit_generator_on(NULL);
357         if (!rc) {
358                 instance = NULL;
359                 rc = apply_mustach(template, jdesc, &instance, &size);
360                 if (!rc)
361                         rc = process_all_units(instance, process, closure);
362                 free(instance);
363         }
364         return rc;
365 }
366
367 /**************** SPECIALIZED PART *****************************/
368
369 static int check_unit_desc(const struct unitdesc *desc, int tells)
370 {
371         if (desc->scope != unitscope_unknown && desc->type != unittype_unknown && desc->name != NULL)
372                 return 0;
373
374         if (tells) {
375                 if (desc->scope == unitscope_unknown)
376                         ERROR("unit of unknown scope");
377                 if (desc->type == unittype_unknown)
378                         ERROR("unit of unknown type");
379                 if (desc->name == NULL)
380                         ERROR("unit of unknown name");
381         }
382         errno = EINVAL;
383         return -1;
384 }
385
386 static int get_unit_path(char *path, size_t pathlen, const struct unitdesc *desc)
387 {
388         int rc = systemd_get_unit_path(
389                         path, pathlen, desc->scope == unitscope_user,
390                         desc->name, desc->type == unittype_socket ? "socket" : "service");
391
392         if (rc < 0)
393                 ERROR("can't get the unit path for %s", desc->name);
394
395         return rc;
396 }
397
398 static int get_wants_path(char *path, size_t pathlen, const struct unitdesc *desc)
399 {
400         int rc = systemd_get_wants_path(
401                         path, pathlen, desc->scope == unitscope_user, desc->wanted_by,
402                         desc->name, desc->type == unittype_socket ? "socket" : "service");
403
404         if (rc < 0)
405                 ERROR("can't get the wants path for %s and %s", desc->name, desc->wanted_by);
406
407         return rc;
408 }
409
410 static int get_wants_target(char *path, size_t pathlen, const struct unitdesc *desc)
411 {
412         int rc = systemd_get_wants_target(
413                         path, pathlen,
414                         desc->name, desc->type == unittype_socket ? "socket" : "service");
415
416         if (rc < 0)
417                 ERROR("can't get the wants target for %s", desc->name);
418
419         return rc;
420 }
421
422 static int do_uninstall_units(void *closure, const struct unitdesc descs[], unsigned count)
423 {
424         int rc, rc2;
425         unsigned i;
426         char path[PATH_MAX];
427
428         for (i = 0 ; i < count ; i++) {
429                 rc = check_unit_desc(&descs[i], 0);
430                 if (rc == 0) {
431                         rc = get_unit_path(path, sizeof path, &descs[i]);
432                         if (rc >= 0) {
433                                 rc = unlink(path);
434                         }
435                         if (descs[i].wanted_by != NULL) {
436                                 rc2 = get_wants_path(path, sizeof path, &descs[i]);
437                                 if (rc2 >= 0)
438                                         rc2 = unlink(path);
439                                 rc = rc < 0 ? rc : rc2;
440                         }
441                 }
442         }
443         return 0;
444 }
445
446 static int do_install_units(void *closure, const struct unitdesc descs[], unsigned count)
447 {
448         int rc;
449         unsigned i;
450         char path[PATH_MAX + 1], target[PATH_MAX + 1];
451
452         i = 0;
453         while (i < count) {
454                 rc = check_unit_desc(&descs[i], 1);
455                 if (!rc) {
456                         rc = get_unit_path(path, sizeof path, &descs[i]);
457                         if (rc >= 0) {
458                                 rc = putfile(path, descs[i].content, descs[i].content_length);
459                                 if (descs[i].wanted_by != NULL) {
460                                         rc = get_wants_path(path, sizeof path, &descs[i]);
461                                         if (rc >= 0) {
462                                                 rc = get_wants_target(target, sizeof target, &descs[i]);
463                                                 if (rc >= 0) {
464                                                         unlink(path); /* TODO? check? */
465                                                         rc = symlink(target, path);
466                                                 }
467                                         }
468                                 }
469                                 i++;
470                         }
471                 }
472                 if (rc < 0) {
473                         do_uninstall_units(closure, descs, i);
474                         return rc;
475                 }
476         }
477         return 0;
478 }
479
480 static int add_metadata(struct json_object *jdesc, const char *installdir, const char *icondir, int port)
481 {
482         char portstr[30];
483
484         sprintf(portstr, "%d", port);
485         return  j_add_many_strings_m(jdesc,
486                 "#metadata.install-dir", installdir,
487                 "#metadata.app-data-dir", "%h/app-data",
488                 "#metadata.icons-dir", icondir,
489                 "#metadata.http-port", portstr,
490                 NULL) ? 0 : -1;
491 }
492
493 static int do_install_uninstall(
494                 struct wgt_info *ifo,
495                 const char *installdir,
496                 const char *icondir,
497                 int port,
498                 int (*doer)(void *, const struct unitdesc[], unsigned)
499 )
500 {
501         int rc;
502         struct json_object *jdesc;
503
504         jdesc = wgt_info_to_json(ifo);
505         if (!jdesc)
506                 rc = -1;
507         else {
508                 rc = add_metadata(jdesc, installdir, icondir, port);
509                 if (rc)
510                         ERROR("can't set the metadata. %m");
511                 else {
512                         rc = unit_generator_process(jdesc, doer, NULL);
513                         if (rc)
514                                 ERROR("can't install units, error %d", rc);
515                 }
516                 json_object_put(jdesc);
517         }
518         return rc;
519 }
520
521 int unit_install(struct wgt_info *ifo, const char *installdir, const char *icondir, int port)
522 {
523         return do_install_uninstall(ifo, installdir, icondir, port, do_install_units);
524 }
525
526 int unit_uninstall(struct wgt_info *ifo)
527 {
528         return do_install_uninstall(ifo, "", "", 0, do_uninstall_units);
529 }
530