ab0c933b53f9691e65be55ae1298ad3dac62203e
[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];
379         int port, 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->port)
386                                 strcpy(portstr, "0");
387                         else {
388                                 port = conf->port ? conf->port() : 0;
389                                 if (port < 0)
390                                         return port;
391                                 sprintf(portstr, "%d", port);
392                         }
393                         if (!j_add_string_m(targ, "#metatarget.http-port", portstr))
394                                 return -1;
395                 }
396         }
397
398         return  j_add_many_strings_m(jdesc,
399                 "#metadata.install-dir", conf->installdir,
400                 "#metadata.icons-dir", conf->icondir,
401                 NULL) ? 0 : -1;
402 }
403
404 /*
405  * Applies the object 'jdesc' augmented of meta data coming
406  * from 'conf' to the current unit generator.
407  * The current unit generator will be set to the default one if not unit
408  * was previously set using the function 'unit_generator_open_template'.
409  * The callback function 'process' is then called with the
410  * unit descriptors array and the expected closure.
411  * Return what returned process in case of success or a negative
412  * error code.
413  */
414 int unit_generator_process(struct json_object *jdesc, const struct unitconf *conf, int (*process)(void *closure, const struct generatedesc *desc), void *closure)
415 {
416         int rc;
417         size_t size;
418         char *instance;
419
420         rc = add_metadata(jdesc, conf);
421         if (rc)
422                 ERROR("can't set the metadata. %m");
423         else {
424                 rc = template ? 0 : unit_generator_open_template(NULL);
425                 if (!rc) {
426                         instance = NULL;
427                         rc = apply_mustach(template, jdesc, &instance, &size);
428                         if (!rc)
429                                 rc = process_all_units(instance, conf, process, closure, jdesc);
430                         free(instance);
431                 }
432         }
433         return rc;
434 }
435
436 /**************** SPECIALIZED PART *****************************/
437
438 static int check_unit_desc(const struct unitdesc *desc, int tells)
439 {
440         if (desc->scope != unitscope_unknown && desc->type != unittype_unknown && desc->name != NULL)
441                 return 0;
442
443         if (tells) {
444                 if (desc->scope == unitscope_unknown)
445                         ERROR("unit of unknown scope");
446                 if (desc->type == unittype_unknown)
447                         ERROR("unit of unknown type");
448                 if (desc->name == NULL)
449                         ERROR("unit of unknown name");
450         }
451         errno = EINVAL;
452         return -1;
453 }
454
455 static int get_unit_path(char *path, size_t pathlen, const struct unitdesc *desc)
456 {
457         int rc = systemd_get_unit_path(
458                         path, pathlen, desc->scope == unitscope_user,
459                         desc->name, desc->type == unittype_socket ? "socket" : "service");
460
461         if (rc < 0)
462                 ERROR("can't get the unit path for %s", desc->name);
463
464         return rc;
465 }
466
467 static int get_wants_path(char *path, size_t pathlen, const struct unitdesc *desc)
468 {
469         int rc = systemd_get_wants_path(
470                         path, pathlen, desc->scope == unitscope_user, desc->wanted_by,
471                         desc->name, desc->type == unittype_socket ? "socket" : "service");
472
473         if (rc < 0)
474                 ERROR("can't get the wants path for %s and %s", desc->name, desc->wanted_by);
475
476         return rc;
477 }
478
479 static int get_wants_target(char *path, size_t pathlen, const struct unitdesc *desc)
480 {
481         int rc = systemd_get_wants_target(
482                         path, pathlen,
483                         desc->name, desc->type == unittype_socket ? "socket" : "service");
484
485         if (rc < 0)
486                 ERROR("can't get the wants target for %s", desc->name);
487
488         return rc;
489 }
490
491 static int do_uninstall_units(void *closure, const struct generatedesc *desc)
492 {
493         int rc, rc2;
494         int i;
495         char path[PATH_MAX];
496         const struct unitdesc *u;
497
498         rc = 0;
499         for (i = 0 ; i < desc->nunits ; i++) {
500                 u = &desc->units[i];
501                 rc2 = check_unit_desc(u, 0);
502                 if (rc2 == 0) {
503                         rc2 = get_unit_path(path, sizeof path, u);
504                         if (rc2 >= 0) {
505                                 rc2 = unlink(path);
506                         }
507                         if (rc2 < 0 && rc == 0)
508                                 rc = rc2;
509                         if (u->wanted_by != NULL) {
510                                 rc2 = get_wants_path(path, sizeof path, u);
511                                 if (rc2 >= 0)
512                                         rc2 = unlink(path);
513                         }
514                 }
515                 if (rc2 < 0 && rc == 0)
516                         rc = rc2;
517         }
518         return rc;
519 }
520
521 static int do_install_units(void *closure, const struct generatedesc *desc)
522 {
523         int rc;
524         int i;
525         char path[PATH_MAX + 1], target[PATH_MAX + 1];
526         const struct unitdesc *u;
527
528         i = 0;
529         while (i < desc->nunits) {
530                 u = &desc->units[i];
531                 rc = check_unit_desc(u, 1);
532                 if (!rc) {
533                         rc = get_unit_path(path, sizeof path, u);
534                         if (rc >= 0) {
535                                 rc = putfile(path, u->content, u->content_length);
536                                 if (rc >= 0 && u->wanted_by != NULL) {
537                                         rc = get_wants_path(path, sizeof path, u);
538                                         if (rc >= 0) {
539                                                 rc = get_wants_target(target, sizeof target, u);
540                                                 if (rc >= 0) {
541                                                         unlink(path); /* TODO? check? */
542                                                         rc = symlink(target, path);
543                                                 }
544                                         }
545                                 }
546                                 i++;
547                         }
548                 }
549                 if (rc < 0)
550                         goto error;
551         }
552         return 0;
553 error:
554         i = errno;
555         do_uninstall_units(closure, desc);
556         errno = i;
557         return rc;
558 }
559
560 static int do_install_uninstall(
561                 struct wgt_info *ifo,
562                 const struct unitconf *conf,
563                 int (*doer)(void *, const struct generatedesc *)
564 )
565 {
566         int rc;
567         struct json_object *jdesc;
568
569         jdesc = wgt_info_to_json(ifo);
570         if (!jdesc)
571                 rc = -1;
572         else {
573                 rc = unit_generator_process(jdesc, conf, doer, NULL);
574                 json_object_put(jdesc);
575         }
576         return rc;
577 }
578
579 int unit_install(struct wgt_info *ifo, const struct unitconf *conf)
580 {
581         return do_install_uninstall(ifo, conf, do_install_units);
582 }
583
584 int unit_uninstall(struct wgt_info *ifo, const struct unitconf *conf)
585 {
586         return do_install_uninstall(ifo, conf, do_uninstall_units);
587 }
588