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