wgtpkg-unit: install/uninstall functions added
[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;
197         int isuser, issystem, issock, isserv;
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
206         /* check the unit scope */
207         if ((isuser + issystem) == 1) {
208                 desc->scope = isuser ? unitscope_user : unitscope_system;
209         } else {
210                 desc->scope = unitscope_unknown;
211         }
212
213         /* check the unit type */
214         if ((issock + isserv) == 1) {
215                 if (issock) {
216                         desc->type = unittype_socket;
217                         name = nsoc;
218                 } else {
219                         desc->type = unittype_service;
220                         name = nsrv;
221                 }
222                 len = (size_t)(strchrnul(name, '\n') - name);
223                 desc->name = strndup(name, len);
224                 desc->name_length = desc->name ? len : 0;
225         } else {
226                 desc->type = unittype_unknown;
227                 desc->name = NULL;
228                 desc->name_length = 0;
229         }
230
231         desc->content = spec;
232         desc->content_length = pack(spec, '%');
233
234         return 0;
235 }
236
237 /*
238  * Processes all the units of the 'corpus'.
239  * Each unit of the corpus is separated and packed and its
240  * charactistics are stored in a descriptor.
241  * At the end if no error was found, calls the function 'process'
242  * with its given 'closure' and the array descripbing the units.
243  * Return 0 in case of success or a negative value in case of error.
244  */
245 static int process_all_units(char *corpus, int (*process)(void *closure, const struct unitdesc descs[], unsigned count), void *closure)
246 {
247         int rc, rc2;
248         unsigned n;
249         char *beg, *end;
250         struct unitdesc *descs, *d;
251
252         descs = NULL;
253         n = 0;
254         rc = rc2 = 0;
255
256         /* while there is a unit in the corpus */
257         while(offset(corpus, "%begin systemd-unit\n", &beg)) {
258
259                 /* get the end of the unit */
260                 end = offset(beg, "%end systemd-unit\n", &corpus);
261                 if (!end) {
262                         /* unterminated unit !! */
263                         ERROR("unterminated unit description!! %s", beg);
264                         corpus = beg;
265                         rc2 = -EINVAL;
266                 } else {
267                         /* separate the unit from the corpus */
268                         *end = 0;
269
270                         /* allocates a descriptor for the unit */
271                         d = realloc(descs, (n + 1) * sizeof *descs);
272                         if (d == NULL)
273                                 rc2 = -ENOMEM;
274                         else {
275                                 /* creates the unit description */
276                                 memset(&d[n], 0, sizeof *d);
277                                 descs = d;
278                                 rc2 = process_one_unit(beg, &descs[n]);
279                                 if (rc2 >= 0)
280                                         n++;
281                         }
282                 }
283                 /* records the error if there is an error */
284                 if (rc2 < 0) {
285                         rc = rc ? : rc2;
286                         rc2 = 0;
287                 }
288         }
289
290         /* call the function that processes the units */
291         if (rc == 0 && process)
292                 rc = process(closure, descs, n);
293
294         /* cleanup and frees */
295         while(n)
296                 free((char *)(descs[--n].name));
297         free(descs);
298
299         return rc;
300 }
301
302 /*
303  * Clear the unit generator
304  */
305 void unit_generator_off()
306 {
307         free(template);
308         template = NULL;
309 }
310
311 /*
312  * Initialises the unit generator with the content of the file of path 'filename'.
313  * Returns 0 in case of success or a negative number in case of error.
314  */
315 int unit_generator_on(const char *filename)
316 {
317         size_t size;
318         char *tmp;
319         int rc;
320
321         unit_generator_off();
322         rc = getfile(filename ? : FWK_UNIT_CONF, &template, NULL);
323         if (!rc) {
324                 size = pack(template, ';');
325                 tmp = realloc(template, 1 + size);
326                 if (tmp)
327                         template = tmp;
328         }
329         return rc;
330 }
331
332 /*
333  * Applies the object 'jdesc' to the current unit generator.
334  * The current unit generator will be set to the default one if not unit
335  * was previously set using the function 'unit_generator_on'.
336  * The callback function 'process' is then called with the
337  * unit descriptors array and the expected closure.
338  * Return what returned process in case of success or a negative
339  * error code.
340  */
341 int unit_generator_process(struct json_object *jdesc, int (*process)(void *closure, const struct unitdesc descs[], unsigned count), void *closure)
342 {
343         int rc;
344         size_t size;
345         char *instance;
346
347         rc = template ? 0 : unit_generator_on(NULL);
348         if (!rc) {
349                 instance = NULL;
350                 rc = apply_mustach(template, jdesc, &instance, &size);
351                 if (!rc)
352                         rc = process_all_units(instance, process, closure);
353                 free(instance);
354         }
355         return rc;
356 }
357
358 /**************** SPECIALIZED PART *****************************/
359
360
361 static int get_unit_path(char *path, size_t pathlen, const struct unitdesc *desc)
362 {
363         int rc;
364
365         if (desc->scope == unitscope_unknown || desc->type == unittype_unknown || desc->name == NULL) {
366                 if (desc->scope == unitscope_unknown)
367                         ERROR("unit of unknown scope");
368                 if (desc->type == unittype_unknown)
369                         ERROR("unit of unknown type");
370                 if (desc->name == NULL)
371                         ERROR("unit of unknown name");
372                 errno = EINVAL;
373                 rc = -1;
374         }
375         else {
376                 rc = snprintf(path, pathlen, "%s/%s/%s.%s", 
377                                 SYSTEMD_UNITS_ROOT,
378                                 desc->scope == unitscope_system ? "system" : "user",
379                                 desc->name,
380                                 desc->type == unittype_socket ? "socket" : "service");
381
382                 if (rc >= 0 && (size_t)rc >= pathlen) {
383                         ERROR("can't set the unit name");
384                         errno = EINVAL;
385                         rc = -1;
386                 }
387         }
388         return rc;
389 }
390
391 static int do_install_units(void *closure, const struct unitdesc descs[], unsigned count)
392 {
393         int rc;
394         unsigned i;
395         char path[PATH_MAX + 1];
396
397         for (i = 0 ; i < count ; i++) {
398                 rc = get_unit_path(path, sizeof path, &descs[i]);
399                 if (rc >= 0) {
400                         rc = putfile(path, descs[i].content, descs[i].content_length);
401                 }
402         }
403         return 0;
404 }
405
406 static int do_uninstall_units(void *closure, const struct unitdesc descs[], unsigned count)
407 {
408         int rc;
409         unsigned i;
410         char path[PATH_MAX];
411
412         for (i = 0 ; i < count ; i++) {
413                 rc = get_unit_path(path, sizeof path, &descs[i]);
414                 if (rc >= 0) {
415                         rc = unlink(path);
416                 }
417         }
418         return 0;
419 }
420
421 static int add_metadata(struct json_object *jdesc, const char *installdir, const char *icondir, int port)
422 {
423         char portstr[30];
424
425         sprintf(portstr, "%d", port);
426         return  j_add_many_strings_m(jdesc,
427                 "#metadata.install-dir", installdir,
428                 "#metadata.app-data-dir", "%h/app-data",
429                 "#metadata.icons-dir", icondir,
430                 "#metadata.http-port", portstr,
431                 NULL) ? 0 : -1;
432 }
433
434 static int do_install_uninstall(
435                 struct wgt_info *ifo,
436                 const char *installdir,
437                 const char *icondir,
438                 int port,
439                 int (*doer)(void *, const struct unitdesc[], unsigned)
440 )
441 {
442         int rc;
443         struct json_object *jdesc;
444
445         jdesc = wgt_info_to_json(ifo);
446         if (!jdesc)
447                 rc = -1;
448         else {
449                 rc = add_metadata(jdesc, installdir, icondir, port);
450                 if (rc)
451                         ERROR("can't set the metadata. %m");
452                 else {
453                         rc = unit_generator_process(jdesc, doer, NULL);
454                         if (rc)
455                                 ERROR("can't install units, error %d", rc);
456                 }
457                 json_object_put(jdesc);
458         }
459         return rc;
460 }
461
462 int unit_install(struct wgt_info *ifo, const char *installdir, const char *icondir, int port)
463 {
464         return do_install_uninstall(ifo, installdir, icondir, port, do_install_units);
465 }
466
467 int unit_uninstall(struct wgt_info *ifo)
468 {
469         return do_install_uninstall(ifo, "", "", 0, do_uninstall_units);
470 }
471