3f2095c3ede454959819e5bec8c69972e78453db
[src/app-framework-main.git] / src / wgtpkg-unit.c
1 /*
2  Copyright 2016, 2017 IoT.bzh
3
4  author: José Bollo <jose.bollo@iot.bzh>
5
6  Licensed under the Apache License, Version 2.0 (the "License");
7  you may not use this file except in compliance with the License.
8  You may obtain a copy of the License at
9
10      http://www.apache.org/licenses/LICENSE-2.0
11
12  Unless required by applicable law or agreed to in writing, software
13  distributed under the License is distributed on an "AS IS" BASIS,
14  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  See the License for the specific language governing permissions and
16  limitations under the License.
17 */
18
19 #define _GNU_SOURCE
20
21 #include <stdlib.h>
22 #include <stdio.h>
23 #include <sys/types.h>
24 #include <fcntl.h>
25 #include <unistd.h>
26 #include <string.h>
27 #include <errno.h>
28 #include <limits.h>
29
30 #include <json-c/json.h>
31  
32 #include "verbose.h"
33 #include "utils-file.h"
34
35 #include "wgtpkg-mustach.h"
36 #include "utils-json.h"
37 #include "wgt-json.h"
38 #include "utils-systemd.h"
39
40 #include "wgtpkg-unit.h"
41
42 #if 0
43 #include <ctype.h>
44 #else
45 #define isblank(c) ((c)==' '||(c)=='\t')
46 #endif
47
48 /* the template for all units */
49 static char *template;
50
51 /*
52  * Search for the 'pattern' in 'text'.
53  * Returns 1 if 'text' matches the 'pattern' or else returns 0.
54  * When returning 1 and 'after' isn't NULL, the pointer to the
55  * first character after the pettern in 'text' is stored in 'after'.
56  * The characters '\n' and ' ' have a special mening in the search:
57  *  * '\n': matches any space or tabs (including none) followed 
58  *          either by '\n' or '\0' (end of the string)
59  *  * ' ': matches any space or tabs but at least one.
60  */
61 static int matches(const char *pattern, char *text, char **after)
62 {
63         char p, t;
64
65         t = *text;
66         p = *pattern;
67         while(p) {
68                 switch(p) {
69                 case '\n':
70                         while (isblank(t))
71                                 t = *++text;
72                         if (t) {
73                                 if (t != p)
74                                         return 0;
75                                 t = *++text;
76                         }
77                         break;
78                 case ' ':
79                         if (!isblank(t))
80                                 return 0;
81                         do {
82                                 t = *++text;
83                         } while(isblank(t));
84                         break;
85                 default:
86                         if (t != p)
87                                 return 0;
88                         t = *++text;
89                         break;
90                 }
91                 p = *++pattern;
92         }
93         if (after)
94                 *after = text;
95         return 1;
96 }
97
98 /*
99  * Pack a null terminated 'text' by removing empty lines,
100  * lines made of blanks and terminated with \, lines
101  * starting with the 'purge' character (can be null).
102  * Lines made of the 'purge' character followed with
103  * "nl" exactly (without quotes ") are replaced with
104  * an empty line.
105  *
106  * Returns the size after packing (offset of the ending null).
107  */
108 static size_t pack(char *text, char purge)
109 {
110         char *read;    /* read iterator */
111         char *write;   /* write iterator */
112         char *begin;   /* begin the copied text of the line */
113         char *start;   /* first character of the line that isn't blanck */
114         char c;        /* currently scanned character (pointed by read) */
115         char emit;     /* flag telling whether line is to be copied */
116         char cont;     /* flag telling whether the line continues the previous one */
117         char nextcont; /* flag telling whether the line will continues the next one */
118
119         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 = (size_t)(strchrnul(name, '\n') - name);
221                 desc->name = strndup(name, len);
222                 desc->name_length = desc->name ? len : 0;
223         } else {
224                 desc->type = unittype_unknown;
225                 desc->name = NULL;
226                 desc->name_length = 0;
227         }
228
229         if (iswanted) {
230                 len = (size_t)(strchrnul(wanted, '\n') - wanted);
231                 desc->wanted_by = strndup(wanted, len);
232                 desc->wanted_by_length = len;
233         } else {
234                 desc->wanted_by = NULL;
235                 desc->wanted_by_length = 0;
236         }
237
238         desc->content = spec;
239         desc->content_length = pack(spec, '%');
240
241         return 0;
242 }
243
244 /*
245  * Processes all the units of the 'corpus'.
246  * Each unit of the corpus is separated and packed and its
247  * charactistics are stored in a descriptor.
248  * At the end if no error was found, calls the function 'process'
249  * with its given 'closure' and the array descripbing the units.
250  * Return 0 in case of success or a negative value in case of error.
251  */
252 static int process_all_units(char *corpus, const struct unitconf *conf, int (*process)(void *closure, const struct generatedesc *desc), void *closure)
253 {
254         int n, rc, rc2;
255         char *beg, *end;
256         struct unitdesc *units, *u;
257         struct generatedesc gdesc;
258
259         units = NULL;
260         n = 0;
261         rc = rc2 = 0;
262
263         /* while there is a unit in the corpus */
264         while(offset(corpus, "%begin systemd-unit\n", &beg)) {
265
266                 /* get the end of the unit */
267                 end = offset(beg, "%end systemd-unit\n", &corpus);
268                 if (!end) {
269                         /* unterminated unit !! */
270                         ERROR("unterminated unit description!! %s", beg);
271                         corpus = beg;
272                         rc2 = -EINVAL;
273                 } else {
274                         /* separate the unit from the corpus */
275                         *end = 0;
276
277                         /* allocates a descriptor for the unit */
278                         u = realloc(units, ((unsigned)n + 1) * sizeof *units);
279                         if (u == NULL)
280                                 rc2 = -ENOMEM;
281                         else {
282                                 /* creates the unit description */
283                                 units = u;
284                                 u = &u[n];
285                                 memset(u, 0, sizeof *u);
286                                 rc2 = process_one_unit(beg, u);
287                                 if (rc2 >= 0)
288                                         n++;
289                         }
290                 }
291                 /* records the error if there is an error */
292                 if (rc2 < 0) {
293                         rc = rc ? : rc2;
294                         rc2 = 0;
295                 }
296         }
297
298         /* call the function that processes the units */
299         if (rc == 0 && process) {
300                 gdesc.conf = conf;
301                 gdesc.units = units;
302                 gdesc.nunits = n;
303                 rc = process(closure, &gdesc);
304         }
305
306         /* cleanup and frees */
307         while(n) {
308                 free((char *)(units[--n].name));
309                 free((char *)(units[n].wanted_by));
310         }
311         free(units);
312
313         return rc;
314 }
315
316 /*
317  * Clear the unit generator
318  */
319 void unit_generator_off()
320 {
321         free(template);
322         template = NULL;
323 }
324
325 /*
326  * Initialises the unit generator with the content of the file of path 'filename'.
327  * Returns 0 in case of success or a negative number in case of error.
328  */
329 int unit_generator_on(const char *filename)
330 {
331         size_t size;
332         char *tmp;
333         int rc;
334
335         unit_generator_off();
336         rc = getfile(filename ? : FWK_UNIT_CONF, &template, NULL);
337         if (!rc) {
338                 size = pack(template, ';');
339                 tmp = realloc(template, 1 + size);
340                 if (tmp)
341                         template = tmp;
342         }
343         return rc;
344 }
345
346 static int add_metadata(struct json_object *jdesc, const struct unitconf *conf)
347 {
348         char portstr[30];
349
350         sprintf(portstr, "%d", conf->port);
351         return  j_add_many_strings_m(jdesc,
352                 "#metadata.install-dir", conf->installdir,
353                 "#metadata.app-data-dir", "%h/app-data",
354                 "#metadata.icons-dir", conf->icondir,
355                 "#metadata.http-port", portstr,
356                 NULL) ? 0 : -1;
357 }
358
359 /*
360  * Applies the object 'jdesc' augmented of meta data coming
361  * from 'conf' to the current unit generator.
362  * The current unit generator will be set to the default one if not unit
363  * was previously set using the function 'unit_generator_on'.
364  * The callback function 'process' is then called with the
365  * unit descriptors array and the expected closure.
366  * Return what returned process in case of success or a negative
367  * error code.
368  */
369 int unit_generator_process(struct json_object *jdesc, const struct unitconf *conf, int (*process)(void *closure, const struct generatedesc *desc), void *closure)
370 {
371         int rc;
372         size_t size;
373         char *instance;
374
375         rc = add_metadata(jdesc, conf);
376         if (rc)
377                 ERROR("can't set the metadata. %m");
378         else {
379                 rc = template ? 0 : unit_generator_on(NULL);
380                 if (!rc) {
381                         instance = NULL;
382                         rc = apply_mustach(template, jdesc, &instance, &size);
383                         if (!rc)
384                                 rc = process_all_units(instance, conf, process, closure);
385                         free(instance);
386                 }
387         }
388         return rc;
389 }
390
391 /**************** SPECIALIZED PART *****************************/
392
393 static int check_unit_desc(const struct unitdesc *desc, int tells)
394 {
395         if (desc->scope != unitscope_unknown && desc->type != unittype_unknown && desc->name != NULL)
396                 return 0;
397
398         if (tells) {
399                 if (desc->scope == unitscope_unknown)
400                         ERROR("unit of unknown scope");
401                 if (desc->type == unittype_unknown)
402                         ERROR("unit of unknown type");
403                 if (desc->name == NULL)
404                         ERROR("unit of unknown name");
405         }
406         errno = EINVAL;
407         return -1;
408 }
409
410 static int get_unit_path(char *path, size_t pathlen, const struct unitdesc *desc)
411 {
412         int rc = systemd_get_unit_path(
413                         path, pathlen, desc->scope == unitscope_user,
414                         desc->name, desc->type == unittype_socket ? "socket" : "service");
415
416         if (rc < 0)
417                 ERROR("can't get the unit path for %s", desc->name);
418
419         return rc;
420 }
421
422 static int get_wants_path(char *path, size_t pathlen, const struct unitdesc *desc)
423 {
424         int rc = systemd_get_wants_path(
425                         path, pathlen, desc->scope == unitscope_user, desc->wanted_by,
426                         desc->name, desc->type == unittype_socket ? "socket" : "service");
427
428         if (rc < 0)
429                 ERROR("can't get the wants path for %s and %s", desc->name, desc->wanted_by);
430
431         return rc;
432 }
433
434 static int get_wants_target(char *path, size_t pathlen, const struct unitdesc *desc)
435 {
436         int rc = systemd_get_wants_target(
437                         path, pathlen,
438                         desc->name, desc->type == unittype_socket ? "socket" : "service");
439
440         if (rc < 0)
441                 ERROR("can't get the wants target for %s", desc->name);
442
443         return rc;
444 }
445
446 static int do_send_reload(const struct generatedesc *desc)
447 {
448         int i;
449         int reloadsys, reloadusr;
450         const struct unitdesc *u;
451
452         reloadsys = reloadusr = 0;
453         for (i = 0 ; i < desc->nunits ; i++) {
454                 u = &desc->units[i];
455                 if (u->wanted_by != NULL) {
456                         switch (u->scope) {
457                         case unitscope_user:
458                                 reloadusr = 1;
459                                 break;
460                         case unitscope_system:
461                                 reloadsys = 1;
462                                 break;
463                         default:
464                                 break;
465                         }
466                 }
467         }
468
469         if (reloadusr)
470                 reloadusr = systemd_daemon_reload(1);
471         if (reloadsys)
472                 reloadsys = systemd_daemon_reload(0);
473         return reloadsys ? : reloadusr ? : 0;
474 }
475
476 static int do_uninstall_units(void *closure, const struct generatedesc *desc)
477 {
478         int rc, rc2;
479         int i;
480         char path[PATH_MAX];
481         const struct unitdesc *u;
482
483         rc = 0;
484         for (i = 0 ; i < desc->nunits ; i++) {
485                 u = &desc->units[i];
486                 rc2 = check_unit_desc(u, 0);
487                 if (rc2 == 0) {
488                         rc2 = get_unit_path(path, sizeof path, u);
489                         if (rc2 >= 0) {
490                                 rc2 = unlink(path);
491                         }
492                         if (rc2 < 0 && rc == 0)
493                                 rc = rc2;
494                         if (u->wanted_by != NULL) {
495                                 rc2 = get_wants_path(path, sizeof path, u);
496                                 if (rc2 >= 0)
497                                         rc2 = unlink(path);
498                         }
499                 }
500                 if (rc2 < 0 && rc == 0)
501                         rc = rc2;
502         }
503         rc2 = do_send_reload(desc);
504         if (rc2 < 0 && rc == 0)
505                 rc = rc2;
506         return rc;
507 }
508
509 static int do_install_units(void *closure, const struct generatedesc *desc)
510 {
511         int rc;
512         int i;
513         char path[PATH_MAX + 1], target[PATH_MAX + 1];
514         const struct unitdesc *u;
515
516         i = 0;
517         while (i < desc->nunits) {
518                 u = &desc->units[i];
519                 rc = check_unit_desc(u, 1);
520                 if (!rc) {
521                         rc = get_unit_path(path, sizeof path, u);
522                         if (rc >= 0) {
523                                 rc = putfile(path, u->content, u->content_length);
524                                 if (rc >= 0 && u->wanted_by != NULL) {
525                                         rc = get_wants_path(path, sizeof path, u);
526                                         if (rc >= 0) {
527                                                 rc = get_wants_target(target, sizeof target, u);
528                                                 if (rc >= 0) {
529                                                         unlink(path); /* TODO? check? */
530                                                         rc = symlink(target, path);
531                                                 }
532                                         }
533                                 }
534                                 i++;
535                         }
536                 }
537                 if (rc < 0)
538                         goto error;
539         }
540         rc = do_send_reload(desc);
541         if (rc < 0)
542                 goto error;
543         return 0;
544 error:
545         i = errno;
546         do_uninstall_units(closure, desc);
547         errno = i;
548         return rc;
549 }
550
551 static int do_install_uninstall(
552                 struct wgt_info *ifo,
553                 const struct unitconf *conf,
554                 int (*doer)(void *, const struct generatedesc *)
555 )
556 {
557         int rc;
558         struct json_object *jdesc;
559
560         jdesc = wgt_info_to_json(ifo);
561         if (!jdesc)
562                 rc = -1;
563         else {
564                 rc = unit_generator_process(jdesc, conf, doer, NULL);
565                 json_object_put(jdesc);
566         }
567         return rc;
568 }
569
570 int unit_install(struct wgt_info *ifo, const struct unitconf *conf)
571 {
572         return do_install_uninstall(ifo, conf, do_install_units);
573 }
574
575 int unit_uninstall(struct wgt_info *ifo, const struct unitconf *conf)
576 {
577         return do_install_uninstall(ifo, conf, do_uninstall_units);
578 }
579