26cf324571c19db9bbacfde054bed517402a85dd
[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  
29 #include "verbose.h"
30 #include "utils-file.h"
31
32 #include "wgtpkg-mustach.h"
33 #include "wgtpkg-unit.h"
34
35 #if 0
36 #include <ctype.h>
37 #else
38 #define isblank(c) ((c)==' '||(c)=='\t')
39 #endif
40
41 /* the template for all units */
42 static char *template;
43
44 /*
45  * Search for the 'pattern' in 'text'.
46  * Returns 1 if 'text' matches the 'pattern' or else returns 0.
47  * When returning 1 and 'after' isn't NULL, the pointer to the
48  * first character after the pettern in 'text' is stored in 'after'.
49  * The characters '\n' and ' ' have a special mening in the search:
50  *  * '\n': matches any space or tabs (including none) followed 
51  *          either by '\n' or '\0' (end of the string)
52  *  * ' ': matches any space or tabs but at least one.
53  */
54 static int matches(const char *pattern, char *text, char **after)
55 {
56         char p, t;
57
58         t = *text;
59         p = *pattern;
60         while(p) {
61                 switch(p) {
62                 case '\n':
63                         while (isblank(t))
64                                 t = *++text;
65                         if (t) {
66                                 if (t != p)
67                                         return 0;
68                                 t = *++text;
69                         }
70                         break;
71                 case ' ':
72                         if (!isblank(t))
73                                 return 0;
74                         do {
75                                 t = *++text;
76                         } while(isblank(t));
77                         break;
78                 default:
79                         if (t != p)
80                                 return 0;
81                         t = *++text;
82                         break;
83                 }
84                 p = *++pattern;
85         }
86         if (after)
87                 *after = text;
88         return 1;
89 }
90
91 /*
92  * Pack a null terminated 'text' by removing empty lines,
93  * lines made of blanks and terminated with \, lines
94  * starting with the 'purge' character (can be null).
95  * Lines made of the 'purge' character followed with
96  * "nl" exactly (without quotes ") are replaced with
97  * an empty line.
98  *
99  * Returns the size after packing (offset of the ending null).
100  */
101 static size_t pack(char *text, char purge)
102 {
103         char *read;    /* read iterator */
104         char *write;   /* write iterator */
105         char *begin;   /* begin the copied text of the line */
106         char *start;   /* first character of the line that isn't blanck */
107         char c;        /* currently scanned character (pointed by read) */
108         char emit;     /* flag telling whether line is to be copied */
109         char cont;     /* flag telling whether the line continues the previous one */
110         char nextcont; /* flag telling whether the line will continues the next one */
111
112         cont = 0;
113         c = *(write = read = text);
114
115         /* iteration over lines */
116         while (c) {
117                 /* computes emit, nextcont, emit and start for the current line */
118                 emit = nextcont = 0;
119                 start = NULL;
120                 begin = read;
121                 while (c && c != '\n') {
122                         if (!isblank(c)) {
123                                 if (c == '\\' && read[1] == '\n')
124                                         nextcont = 1;
125                                 else {
126                                         emit = 1;
127                                         if (!start)
128                                                 start = read;
129                                 }
130                         }
131                         c = *++read;
132                 }
133                 if (c)
134                         c = *++read;
135                 /* emit the line if not empty */
136                 if (emit) {
137                         /* removes the blanks on the left of not continuing lines */
138                         if (!cont && start)
139                                 begin = start;
140                         /* check if purge applies */
141                         if (purge && *begin == purge) {
142                                 /* yes, insert new line if requested */
143                                 if (!strncmp(begin+1, "nl\n",3))
144                                         *write++ = '\n';
145                         } else {
146                                 /* copies the line */
147                                 while (begin != read)
148                                         *write++ = *begin++;
149                         }
150                 }
151                 cont = nextcont;
152         }
153         *write = 0;
154         return (size_t)(write - text);
155 }
156
157 /*
158  * Searchs the first character of the next line
159  * of the 'text' and returns its address
160  * Returns NULL if there is no next line.
161  */
162 static inline char *nextline(char *text)
163 {
164         char *result = strchr(text, '\n');
165         return result + !!result;
166 }
167
168 /*
169  * Search in 'text' the offset of a line beginning with the 'pattern'
170  * Returns NULL if not found or the address of the line contning the pattern
171  * If args isn't NULL and the pattern is found, the pointed pattern is
172  * updated with the address of the character following the found pattern.
173  */
174 static char *offset(char *text, const char *pattern, char **args)
175 {
176         while (text && !matches(pattern, text, args))
177                 text = nextline(text);
178         return text;
179 }
180
181 /*
182  * process one unit
183  */
184 static int process_one_unit(char *spec, struct unitdesc *desc)
185 {
186         char *nsoc, *nsrv, *name;
187         int isuser, issystem, issock, isserv;
188         size_t len;
189
190         /* finds the configuration directive of the unit */
191         isuser = !!offset(spec, "%systemd-unit user\n", NULL);
192         issystem = !!offset(spec, "%systemd-unit system\n", NULL);
193         issock  = !!offset(spec, "%systemd-unit socket ", &nsoc);
194         isserv  = !!offset(spec, "%systemd-unit service ", &nsrv);
195
196         /* check the unit scope */
197         if ((isuser + issystem) == 1) {
198                 desc->scope = isuser ? unitscope_user : unitscope_system;
199         } else {
200                 desc->scope = unitscope_unknown;
201         }
202
203         /* check the unit type */
204         if ((issock + isserv) == 1) {
205                 if (issock) {
206                         desc->type = unittype_socket;
207                         name = nsoc;
208                 } else {
209                         desc->type = unittype_service;
210                         name = nsrv;
211                 }
212                 len = (size_t)(strchrnul(name, '\n') - name);
213                 desc->name = strndup(name, len);
214                 desc->name_length = desc->name ? len : 0;
215         } else {
216                 desc->type = unittype_unknown;
217                 desc->name = NULL;
218                 desc->name_length = 0;
219         }
220
221         desc->content = spec;
222         desc->content_length = pack(spec, '%');
223
224         return 0;
225 }
226
227 /*
228  * Processes all the units of the 'corpus'.
229  * Each unit of the corpus is separated and packed and its
230  * charactistics are stored in a descriptor.
231  * At the end if no error was found, calls the function 'process'
232  * with its given 'closure' and the array descripbing the units.
233  * Return 0 in case of success or a negative value in case of error.
234  */
235 static int process_all_units(char *corpus, int (*process)(void *closure, const struct unitdesc descs[], unsigned count), void *closure)
236 {
237         int rc, rc2;
238         unsigned n;
239         char *beg, *end;
240         struct unitdesc *descs, *d;
241
242         descs = NULL;
243         n = 0;
244         rc = rc2 = 0;
245
246         /* while there is a unit in the corpus */
247         while(offset(corpus, "%begin systemd-unit\n", &beg)) {
248
249                 /* get the end of the unit */
250                 end = offset(beg, "%end systemd-unit\n", &corpus);
251                 if (!end) {
252                         /* unterminated unit !! */
253                         ERROR("unterminated unit description!! %s", beg);
254                         corpus = beg;
255                         rc2 = -EINVAL;
256                 } else {
257                         /* separate the unit from the corpus */
258                         *end = 0;
259
260                         /* allocates a descriptor for the unit */
261                         d = realloc(descs, (n + 1) * sizeof *descs);
262                         if (d == NULL)
263                                 rc2 = -ENOMEM;
264                         else {
265                                 /* creates the unit description */
266                                 memset(&d[n], 0, sizeof *d);
267                                 descs = d;
268                                 rc2 = process_one_unit(beg, &descs[n]);
269                                 if (rc2 >= 0)
270                                         n++;
271                         }
272                 }
273                 /* records the error if there is an error */
274                 if (rc2 < 0) {
275                         rc = rc ? : rc2;
276                         rc2 = 0;
277                 }
278         }
279
280         /* call the function that processes the units */
281         if (rc == 0 && process)
282                 rc = process(closure, descs, n);
283
284         /* cleanup and frees */
285         while(n)
286                 free((char *)(descs[--n].name));
287         free(descs);
288
289         return rc;
290 }
291
292 /*
293  * Clear the unit generator
294  */
295 void unit_generator_off()
296 {
297         free(template);
298         template = NULL;
299 }
300
301 /*
302  * Initialises the unit generator with 'filename'.
303  * Returns 0 in case of success or a negative number in case of error.
304  */
305 int unit_generator_on(const char *filename)
306 {
307         size_t size;
308         char *tmp;
309         int rc;
310
311         unit_generator_off();
312         rc = getfile(filename ? : FWK_UNIT_CONF, &template, NULL);
313         if (!rc) {
314                 size = pack(template, ';');
315                 tmp = realloc(template, 1 + size);
316                 if (tmp)
317                         template = tmp;
318         }
319         return rc;
320 }
321
322 /*
323  * Applies the object 'jdesc' to the current unit generator.
324  * The current unit generator will be set to the default one if not unit
325  * was previously set using the function 'unit_generator_on'.
326  * The callback function 'process' is then called with the
327  * unit descriptors array and the expected closure.
328  * Return what returned process in case of success or a negative
329  * error code.
330  */
331 int unit_generator_process(struct json_object *jdesc, int (*process)(void *closure, const struct unitdesc descs[], unsigned count), void *closure)
332 {
333         int rc;
334         size_t size;
335         char *instance;
336
337         rc = template ? 0 : unit_generator_on(NULL);
338         if (!rc) {
339                 instance = NULL;
340                 rc = apply_mustach(template, jdesc, &instance, &size);
341                 if (!rc)
342                         rc = process_all_units(instance, process, closure);
343                 free(instance);
344         }
345         return rc;
346 }
347