2 Copyright (C) 2015-2020 IoT.bzh
4 author: José Bollo <jose.bollo@iot.bzh>
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
10 http://www.apache.org/licenses/LICENSE-2.0
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.
23 #include <sys/types.h>
30 #include <json-c/json.h>
33 #include "utils-file.h"
35 #include "wgtpkg-mustach.h"
36 #include "utils-json.h"
38 #include "utils-systemd.h"
40 #include "wgtpkg-unit.h"
41 #include "wgt-strings.h"
46 #define isblank(c) ((c)==' '||(c)=='\t')
49 /* the template for all units */
50 static char *template;
53 * Search for the 'pattern' in 'text'.
54 * Returns 1 if 'text' matches the 'pattern' or else returns 0.
55 * When returning 1 and 'after' isn't NULL, the pointer to the
56 * first character after the pettern in 'text' is stored in 'after'.
57 * The characters '\n' and ' ' have a special meaning in the search:
58 * * '\n': matches any space or tabs (including none) followed
59 * either by '\n' or '\0' (end of the string)
60 * * ' ': matches any space or tabs but at least one.
62 static int matches(const char *pattern, char *text, char **after)
100 * Pack a null terminated 'text' by removing empty lines,
101 * lines made of blanks and terminated with \, lines
102 * starting with the 'purge' character (can be null).
103 * Lines made of the 'purge' character followed with
104 * "nl" exactly (without quotes ") are replaced with
107 * Returns the size after packing (offset of the ending null).
109 static size_t pack(char *text, char purge)
111 char *read; /* read iterator */
112 char *write; /* write iterator */
113 char *begin; /* begin the copied text of the line */
114 char *start; /* first character of the line that isn't blanck */
115 char c; /* currently scanned character (pointed by read) */
116 char emit; /* flag telling whether line is to be copied */
117 char cont; /* flag telling whether the line continues the previous one */
118 char nextcont; /* flag telling whether the line will continues the next one */
121 c = *(write = read = text);
123 /* iteration over lines */
125 /* computes emit, nextcont, emit and start for the current line */
130 while (c && c != '\n') {
132 if (c == '\\' && read[1] == '\n')
144 /* emit the line if not empty */
145 if (emit || (cont && !nextcont)) {
146 /* removes the blanks on the left of not continuing lines */
149 /* check if purge applies */
150 if (purge && *begin == purge) {
151 /* yes, insert new line if requested */
152 if (!strncmp(begin+1, "nl\n",3))
155 /* copies the line */
156 while (begin != read)
162 return (size_t)(write - text);
166 * Searchs the first character of the next line
167 * of the 'text' and returns its address
168 * Returns NULL if there is no next line.
170 static inline char *nextline(char *text)
172 char *result = strchr(text, '\n');
173 return result + !!result;
177 * Search in 'text' the offset of a line beginning with the 'pattern'
178 * Returns NULL if not found or the address of the line contning the pattern
179 * If args isn't NULL and the pattern is found, the pointed pattern is
180 * updated with the address of the character following the found pattern.
182 static char *offset(char *text, const char *pattern, char **args)
184 while (text && !matches(pattern, text, args))
185 text = nextline(text);
192 static int process_one_unit(char *spec, struct unitdesc *desc)
194 char *nsoc, *nsrv, *name, *wanted;
195 int isuser, issystem, issock, isserv, iswanted;
198 /* finds the configuration directive of the unit */
199 isuser = !!offset(spec, "%systemd-unit user\n", NULL);
200 issystem = !!offset(spec, "%systemd-unit system\n", NULL);
201 issock = !!offset(spec, "%systemd-unit socket ", &nsoc);
202 isserv = !!offset(spec, "%systemd-unit service ", &nsrv);
203 iswanted = !!offset(spec, "%systemd-unit wanted-by ", &wanted);
205 /* check the unit scope */
206 if ((isuser + issystem) == 1) {
207 desc->scope = isuser ? unitscope_user : unitscope_system;
209 desc->scope = unitscope_unknown;
212 /* check the unit type */
213 if ((issock + isserv) == 1) {
215 desc->type = unittype_socket;
218 desc->type = unittype_service;
221 len = strcspn(name, " \t\n");
222 desc->name = strndup(name, len);
223 desc->name_length = desc->name ? len : 0;
225 desc->type = unittype_unknown;
227 desc->name_length = 0;
231 len = strcspn(wanted, " \t\n");
232 desc->wanted_by = strndup(wanted, len);
233 desc->wanted_by_length = len;
235 desc->wanted_by = NULL;
236 desc->wanted_by_length = 0;
239 desc->content = spec;
240 desc->content_length = pack(spec, '%');
246 * Processes all the units of the 'corpus'.
247 * Each unit of the corpus is separated and packed and its
248 * charactistics are stored in a descriptor.
249 * At the end if no error was found, calls the function 'process'
250 * with its given 'closure' and the array descripbing the units.
251 * Return 0 in case of success or a negative value in case of error.
253 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)
256 char *beg, *end, *befbeg, *aftend;
258 struct generatedesc gdesc;
266 /* while there is a unit in the corpus */
268 befbeg = offset(corpus, "%begin ", &beg);
269 end = offset(corpus, "%end ", &aftend);
272 /* %end detected without %begin */
273 ERROR("unexpected %%end at end");
279 /* unterminated unit !! */
280 ERROR("unterminated unit description!!");
283 } else if (end < befbeg) {
284 /* sequence %end ... %begin detected !! */
285 ERROR("unexpected %%end before %%begin");
289 befbeg = offset(beg, "%begin ", NULL);
290 if (befbeg && befbeg < end) {
291 /* sequence %begin ... %begin ... %end detected !! */
292 ERROR("unexpected %%begin after %%begin");
298 if (matches("systemd-unit\n", beg, &beg)) {
299 if (!matches("systemd-unit\n", aftend, &corpus)) {
300 /* end doesnt match */
301 ERROR("unmatched %%begin systemd-unit (matching end mismatch)");
304 /* allocates a descriptor for the unit */
305 u = realloc((void*)gdesc.units, ((unsigned)gdesc.nunits + 1) * sizeof *gdesc.units);
309 /* creates the unit description */
311 u = &u[gdesc.nunits];
312 memset(u, 0, sizeof *u);
313 rc2 = process_one_unit(beg, u);
319 ERROR("unexpected %%begin name");
324 /* records the error if there is an error */
331 /* call the function that processes the units */
332 if (rc == 0 && process)
333 rc = process(closure, &gdesc);
335 /* cleanup and frees */
336 while(gdesc.nunits) {
337 free((void*)(gdesc.units[--gdesc.nunits].name));
338 free((void*)(gdesc.units[gdesc.nunits].wanted_by));
340 free((void*)gdesc.units);
346 * Clear the unit generator
348 void unit_generator_close_template()
355 * Initialises the unit generator with the content of the file of path 'filename'.
356 * Returns 0 in case of success or a negative number in case of error.
358 int unit_generator_open_template(const char *filename)
364 unit_generator_close_template();
365 rc = getfile(filename ? : FWK_UNIT_CONF, &template, NULL);
367 size = pack(template, ';');
368 tmp = realloc(template, 1 + size);
375 static int add_metadata(struct json_object *jdesc, const struct unitconf *conf)
377 struct json_object *targets, *targ;
378 char portstr[30], afidstr[30];
379 int port, afid, i, n;
381 if (json_object_object_get_ex(jdesc, string_targets, &targets)) {
382 n = json_object_array_length(targets);
383 for (i = 0 ; i < n ; i++) {
384 targ = json_object_array_get_idx(targets, i);
385 if (!conf->new_afid) {
389 afid = conf->new_afid();
392 port = conf->base_http_ports + afid;
394 sprintf(afidstr, "%d", afid);
395 sprintf(portstr, "%d", port);
396 if (!j_add_many_strings_m(targ,
397 "#metatarget.http-port", portstr,
398 "#metatarget.afid", afidstr,
404 return j_add_many_strings_m(jdesc,
405 "#metadata.install-dir", conf->installdir,
406 "#metadata.icons-dir", conf->icondir,
411 * Applies the object 'jdesc' augmented of meta data coming
412 * from 'conf' to the current unit generator.
413 * The current unit generator will be set to the default one if not unit
414 * was previously set using the function 'unit_generator_open_template'.
415 * The callback function 'process' is then called with the
416 * unit descriptors array and the expected closure.
417 * Return what returned process in case of success or a negative
420 int unit_generator_process(struct json_object *jdesc, const struct unitconf *conf, int (*process)(void *closure, const struct generatedesc *desc), void *closure)
426 rc = add_metadata(jdesc, conf);
428 ERROR("can't set the metadata. %m");
430 rc = template ? 0 : unit_generator_open_template(NULL);
433 rc = apply_mustach(template, jdesc, &instance, &size);
435 rc = process_all_units(instance, conf, process, closure, jdesc);
442 /**************** SPECIALIZED PART *****************************/
444 static int check_unit_desc(const struct unitdesc *desc, int tells)
446 if (desc->scope != unitscope_unknown && desc->type != unittype_unknown && desc->name != NULL)
450 if (desc->scope == unitscope_unknown)
451 ERROR("unit of unknown scope");
452 if (desc->type == unittype_unknown)
453 ERROR("unit of unknown type");
454 if (desc->name == NULL)
455 ERROR("unit of unknown name");
461 static int get_unit_path(char *path, size_t pathlen, const struct unitdesc *desc)
463 int rc = systemd_get_unit_path(
464 path, pathlen, desc->scope == unitscope_user,
465 desc->name, desc->type == unittype_socket ? "socket" : "service");
468 ERROR("can't get the unit path for %s", desc->name);
473 static int get_wants_path(char *path, size_t pathlen, const struct unitdesc *desc)
475 int rc = systemd_get_wants_path(
476 path, pathlen, desc->scope == unitscope_user, desc->wanted_by,
477 desc->name, desc->type == unittype_socket ? "socket" : "service");
480 ERROR("can't get the wants path for %s and %s", desc->name, desc->wanted_by);
485 static int get_wants_target(char *path, size_t pathlen, const struct unitdesc *desc)
487 int rc = systemd_get_wants_target(
489 desc->name, desc->type == unittype_socket ? "socket" : "service");
492 ERROR("can't get the wants target for %s", desc->name);
497 static int do_uninstall_units(void *closure, const struct generatedesc *desc)
502 const struct unitdesc *u;
505 for (i = 0 ; i < desc->nunits ; i++) {
507 rc2 = check_unit_desc(u, 0);
509 rc2 = get_unit_path(path, sizeof path, u);
513 if (rc2 < 0 && rc == 0)
515 if (u->wanted_by != NULL) {
516 rc2 = get_wants_path(path, sizeof path, u);
521 if (rc2 < 0 && rc == 0)
527 static int do_install_units(void *closure, const struct generatedesc *desc)
531 char path[PATH_MAX + 1], target[PATH_MAX + 1];
532 const struct unitdesc *u;
535 while (i < desc->nunits) {
537 rc = check_unit_desc(u, 1);
539 rc = get_unit_path(path, sizeof path, u);
541 rc = putfile(path, u->content, u->content_length);
542 if (rc >= 0 && u->wanted_by != NULL) {
543 rc = get_wants_path(path, sizeof path, u);
545 rc = get_wants_target(target, sizeof target, u);
547 unlink(path); /* TODO? check? */
548 rc = symlink(target, path);
561 do_uninstall_units(closure, desc);
566 static int do_install_uninstall(
567 struct wgt_info *ifo,
568 const struct unitconf *conf,
569 int (*doer)(void *, const struct generatedesc *)
573 struct json_object *jdesc;
575 jdesc = wgt_info_to_json(ifo);
579 rc = unit_generator_process(jdesc, conf, doer, NULL);
580 json_object_put(jdesc);
585 int unit_install(struct wgt_info *ifo, const struct unitconf *conf)
587 return do_install_uninstall(ifo, conf, do_install_units);
590 int unit_uninstall(struct wgt_info *ifo, const struct unitconf *conf)
592 return do_install_uninstall(ifo, conf, do_uninstall_units);