Rework on parsing units conf
[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 meaning 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         nextcont = 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                 cont = nextcont;
126                 emit = nextcont = 0;
127                 start = NULL;
128                 begin = read;
129                 while (c && c != '\n') {
130                         if (!isblank(c)) {
131                                 if (c == '\\' && read[1] == '\n')
132                                         nextcont = 1;
133                                 else {
134                                         emit = 1;
135                                         if (!start)
136                                                 start = read;
137                                 }
138                         }
139                         c = *++read;
140                 }
141                 if (c)
142                         c = *++read;
143                 /* emit the line if not empty */
144                 if (emit || (cont && !nextcont)) {
145                         /* removes the blanks on the left of not continuing lines */
146                         if (!cont && start)
147                                 begin = start;
148                         /* check if purge applies */
149                         if (purge && *begin == purge) {
150                                 /* yes, insert new line if requested */
151                                 if (!strncmp(begin+1, "nl\n",3))
152                                         *write++ = '\n';
153                         } else {
154                                 /* copies the line */
155                                 while (begin != read)
156                                         *write++ = *begin++;
157                         }
158                 }
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 = strcspn(name, " \t\n");
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 = strcspn(wanted, " \t\n");
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, const struct unitconf *conf, int (*process)(void *closure, const struct generatedesc *desc), void *closure)
253 {
254         int rc, rc2;
255         char *beg, *end, *befbeg, *aftend;
256         struct unitdesc *u;
257         struct generatedesc gdesc;
258
259         gdesc.conf = conf;
260         gdesc.units = NULL;
261         gdesc.nunits = 0;
262         rc = rc2 = 0;
263
264         /* while there is a unit in the corpus */
265         for(;;) {
266                 befbeg = offset(corpus, "%begin ", &beg);
267                 end = offset(corpus, "%end ", &aftend);
268                 if (!befbeg) {
269                         if (end) {
270                                 /* %end detected without %begin */
271                                 ERROR("unexpected %%end at end");
272                                 rc = rc ? :-EINVAL;
273                         }
274                         break;
275                 }
276                 if (!end) {
277                         /* unterminated unit !! */
278                         ERROR("unterminated unit description!!");
279                         corpus = beg;
280                         rc2 = -EINVAL;
281                 } else if (end < befbeg) {
282                         /* sequence %end ... %begin detected !! */
283                         ERROR("unexpected %%end before %%begin");
284                         corpus = aftend;
285                         rc2 = -EINVAL;
286                 } else {
287                         befbeg =  offset(beg, "%begin ", NULL);
288                         if (befbeg && befbeg < end) {
289                                 /* sequence %begin ... %begin ... %end detected !! */
290                                 ERROR("unexpected %%begin after %%begin");
291                                 corpus = beg;
292                                 rc2 = -EINVAL;
293                         } else {
294                                 *end = 0;
295                                 corpus = aftend;
296                                 if (matches("systemd-unit\n", beg, &beg)) {
297                                         if (!matches("systemd-unit\n", aftend, &corpus)) {
298                                                 /* end doesnt match */
299                                                 ERROR("unmatched %%begin systemd-unit (matching end mismatch)");
300                                                 rc2 = -EINVAL;
301                                         } else {
302                                                 /* allocates a descriptor for the unit */
303                                                 u = realloc((void*)gdesc.units, ((unsigned)gdesc.nunits + 1) * sizeof *gdesc.units);
304                                                 if (u == NULL)
305                                                         rc2 = -ENOMEM;
306                                                 else {
307                                                         /* creates the unit description */
308                                                         gdesc.units = u;
309                                                         u = &u[gdesc.nunits];
310                                                         memset(u, 0, sizeof *u);
311                                                         rc2 = process_one_unit(beg, u);
312                                                         if (rc2 >= 0)
313                                                                 gdesc.nunits++;
314                                                 }
315                                         }
316                                 } else {
317                                         ERROR("unexpected %%begin name");
318                                         rc2 = -EINVAL;
319                                 }
320                         }
321                 }
322                 /* records the error if there is an error */
323                 if (rc2 < 0) {
324                         rc = rc ? : rc2;
325                         rc2 = 0;
326                 }
327         }
328
329         /* call the function that processes the units */
330         if (rc == 0 && process)
331                 rc = process(closure, &gdesc);
332
333         /* cleanup and frees */
334         while(gdesc.nunits) {
335                 free((void*)(gdesc.units[--gdesc.nunits].name));
336                 free((void*)(gdesc.units[gdesc.nunits].wanted_by));
337         }
338         free((void*)gdesc.units);
339
340         return rc;
341 }
342
343 /*
344  * Clear the unit generator
345  */
346 void unit_generator_off()
347 {
348         free(template);
349         template = NULL;
350 }
351
352 /*
353  * Initialises the unit generator with the content of the file of path 'filename'.
354  * Returns 0 in case of success or a negative number in case of error.
355  */
356 int unit_generator_on(const char *filename)
357 {
358         size_t size;
359         char *tmp;
360         int rc;
361
362         unit_generator_off();
363         rc = getfile(filename ? : FWK_UNIT_CONF, &template, NULL);
364         if (!rc) {
365                 size = pack(template, ';');
366                 tmp = realloc(template, 1 + size);
367                 if (tmp)
368                         template = tmp;
369         }
370         return rc;
371 }
372
373 static int add_metadata(struct json_object *jdesc, const struct unitconf *conf)
374 {
375         char portstr[30];
376
377         sprintf(portstr, "%d", conf->port);
378         return  j_add_many_strings_m(jdesc,
379                 "#metadata.install-dir", conf->installdir,
380                 "#metadata.app-data-dir", "%h/app-data",
381                 "#metadata.icons-dir", conf->icondir,
382                 "#metadata.http-port", portstr,
383                 NULL) ? 0 : -1;
384 }
385
386 /*
387  * Applies the object 'jdesc' augmented of meta data coming
388  * from 'conf' to the current unit generator.
389  * The current unit generator will be set to the default one if not unit
390  * was previously set using the function 'unit_generator_on'.
391  * The callback function 'process' is then called with the
392  * unit descriptors array and the expected closure.
393  * Return what returned process in case of success or a negative
394  * error code.
395  */
396 int unit_generator_process(struct json_object *jdesc, const struct unitconf *conf, int (*process)(void *closure, const struct generatedesc *desc), void *closure)
397 {
398         int rc;
399         size_t size;
400         char *instance;
401
402         rc = add_metadata(jdesc, conf);
403         if (rc)
404                 ERROR("can't set the metadata. %m");
405         else {
406                 rc = template ? 0 : unit_generator_on(NULL);
407                 if (!rc) {
408                         instance = NULL;
409                         rc = apply_mustach(template, jdesc, &instance, &size);
410                         if (!rc)
411                                 rc = process_all_units(instance, conf, process, closure);
412                         free(instance);
413                 }
414         }
415         return rc;
416 }
417
418 /**************** SPECIALIZED PART *****************************/
419
420 static int check_unit_desc(const struct unitdesc *desc, int tells)
421 {
422         if (desc->scope != unitscope_unknown && desc->type != unittype_unknown && desc->name != NULL)
423                 return 0;
424
425         if (tells) {
426                 if (desc->scope == unitscope_unknown)
427                         ERROR("unit of unknown scope");
428                 if (desc->type == unittype_unknown)
429                         ERROR("unit of unknown type");
430                 if (desc->name == NULL)
431                         ERROR("unit of unknown name");
432         }
433         errno = EINVAL;
434         return -1;
435 }
436
437 static int get_unit_path(char *path, size_t pathlen, const struct unitdesc *desc)
438 {
439         int rc = systemd_get_unit_path(
440                         path, pathlen, desc->scope == unitscope_user,
441                         desc->name, desc->type == unittype_socket ? "socket" : "service");
442
443         if (rc < 0)
444                 ERROR("can't get the unit path for %s", desc->name);
445
446         return rc;
447 }
448
449 static int get_wants_path(char *path, size_t pathlen, const struct unitdesc *desc)
450 {
451         int rc = systemd_get_wants_path(
452                         path, pathlen, desc->scope == unitscope_user, desc->wanted_by,
453                         desc->name, desc->type == unittype_socket ? "socket" : "service");
454
455         if (rc < 0)
456                 ERROR("can't get the wants path for %s and %s", desc->name, desc->wanted_by);
457
458         return rc;
459 }
460
461 static int get_wants_target(char *path, size_t pathlen, const struct unitdesc *desc)
462 {
463         int rc = systemd_get_wants_target(
464                         path, pathlen,
465                         desc->name, desc->type == unittype_socket ? "socket" : "service");
466
467         if (rc < 0)
468                 ERROR("can't get the wants target for %s", desc->name);
469
470         return rc;
471 }
472
473 static int do_send_reload(const struct generatedesc *desc)
474 {
475         int i;
476         int reloadsys, reloadusr;
477         const struct unitdesc *u;
478
479         reloadsys = reloadusr = 0;
480         for (i = 0 ; i < desc->nunits ; i++) {
481                 u = &desc->units[i];
482                 if (u->wanted_by != NULL) {
483                         switch (u->scope) {
484                         case unitscope_user:
485                                 reloadusr = 1;
486                                 break;
487                         case unitscope_system:
488                                 reloadsys = 1;
489                                 break;
490                         default:
491                                 break;
492                         }
493                 }
494         }
495
496         if (reloadusr)
497                 reloadusr = systemd_daemon_reload(1);
498         if (reloadsys)
499                 reloadsys = systemd_daemon_reload(0);
500         return reloadsys ? : reloadusr ? : 0;
501 }
502
503 static int do_uninstall_units(void *closure, const struct generatedesc *desc)
504 {
505         int rc, rc2;
506         int i;
507         char path[PATH_MAX];
508         const struct unitdesc *u;
509
510         rc = 0;
511         for (i = 0 ; i < desc->nunits ; i++) {
512                 u = &desc->units[i];
513                 rc2 = check_unit_desc(u, 0);
514                 if (rc2 == 0) {
515                         rc2 = get_unit_path(path, sizeof path, u);
516                         if (rc2 >= 0) {
517                                 rc2 = unlink(path);
518                         }
519                         if (rc2 < 0 && rc == 0)
520                                 rc = rc2;
521                         if (u->wanted_by != NULL) {
522                                 rc2 = get_wants_path(path, sizeof path, u);
523                                 if (rc2 >= 0)
524                                         rc2 = unlink(path);
525                         }
526                 }
527                 if (rc2 < 0 && rc == 0)
528                         rc = rc2;
529         }
530         rc2 = do_send_reload(desc);
531         if (rc2 < 0 && rc == 0)
532                 rc = rc2;
533         return rc;
534 }
535
536 static int do_install_units(void *closure, const struct generatedesc *desc)
537 {
538         int rc;
539         int i;
540         char path[PATH_MAX + 1], target[PATH_MAX + 1];
541         const struct unitdesc *u;
542
543         i = 0;
544         while (i < desc->nunits) {
545                 u = &desc->units[i];
546                 rc = check_unit_desc(u, 1);
547                 if (!rc) {
548                         rc = get_unit_path(path, sizeof path, u);
549                         if (rc >= 0) {
550                                 rc = putfile(path, u->content, u->content_length);
551                                 if (rc >= 0 && u->wanted_by != NULL) {
552                                         rc = get_wants_path(path, sizeof path, u);
553                                         if (rc >= 0) {
554                                                 rc = get_wants_target(target, sizeof target, u);
555                                                 if (rc >= 0) {
556                                                         unlink(path); /* TODO? check? */
557                                                         rc = symlink(target, path);
558                                                 }
559                                         }
560                                 }
561                                 i++;
562                         }
563                 }
564                 if (rc < 0)
565                         goto error;
566         }
567         rc = do_send_reload(desc);
568         if (rc < 0)
569                 goto error;
570         return 0;
571 error:
572         i = errno;
573         do_uninstall_units(closure, desc);
574         errno = i;
575         return rc;
576 }
577
578 static int do_install_uninstall(
579                 struct wgt_info *ifo,
580                 const struct unitconf *conf,
581                 int (*doer)(void *, const struct generatedesc *)
582 )
583 {
584         int rc;
585         struct json_object *jdesc;
586
587         jdesc = wgt_info_to_json(ifo);
588         if (!jdesc)
589                 rc = -1;
590         else {
591                 rc = unit_generator_process(jdesc, conf, doer, NULL);
592                 json_object_put(jdesc);
593         }
594         return rc;
595 }
596
597 int unit_install(struct wgt_info *ifo, const struct unitconf *conf)
598 {
599         return do_install_uninstall(ifo, conf, do_install_units);
600 }
601
602 int unit_uninstall(struct wgt_info *ifo, const struct unitconf *conf)
603 {
604         return do_install_uninstall(ifo, conf, do_uninstall_units);
605 }
606