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