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