2 Copyright (C) 2016-2019 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;
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);
386 strcpy(portstr, "0");
388 port = conf->port ? conf->port() : 0;
391 sprintf(portstr, "%d", port);
393 if (!j_add_string_m(targ, "#metatarget.http-port", portstr))
398 return j_add_many_strings_m(jdesc,
399 "#metadata.install-dir", conf->installdir,
400 "#metadata.icons-dir", conf->icondir,
405 * Applies the object 'jdesc' augmented of meta data coming
406 * from 'conf' to the current unit generator.
407 * The current unit generator will be set to the default one if not unit
408 * was previously set using the function 'unit_generator_open_template'.
409 * The callback function 'process' is then called with the
410 * unit descriptors array and the expected closure.
411 * Return what returned process in case of success or a negative
414 int unit_generator_process(struct json_object *jdesc, const struct unitconf *conf, int (*process)(void *closure, const struct generatedesc *desc), void *closure)
420 rc = add_metadata(jdesc, conf);
422 ERROR("can't set the metadata. %m");
424 rc = template ? 0 : unit_generator_open_template(NULL);
427 rc = apply_mustach(template, jdesc, &instance, &size);
429 rc = process_all_units(instance, conf, process, closure, jdesc);
436 /**************** SPECIALIZED PART *****************************/
438 static int check_unit_desc(const struct unitdesc *desc, int tells)
440 if (desc->scope != unitscope_unknown && desc->type != unittype_unknown && desc->name != NULL)
444 if (desc->scope == unitscope_unknown)
445 ERROR("unit of unknown scope");
446 if (desc->type == unittype_unknown)
447 ERROR("unit of unknown type");
448 if (desc->name == NULL)
449 ERROR("unit of unknown name");
455 static int get_unit_path(char *path, size_t pathlen, const struct unitdesc *desc)
457 int rc = systemd_get_unit_path(
458 path, pathlen, desc->scope == unitscope_user,
459 desc->name, desc->type == unittype_socket ? "socket" : "service");
462 ERROR("can't get the unit path for %s", desc->name);
467 static int get_wants_path(char *path, size_t pathlen, const struct unitdesc *desc)
469 int rc = systemd_get_wants_path(
470 path, pathlen, desc->scope == unitscope_user, desc->wanted_by,
471 desc->name, desc->type == unittype_socket ? "socket" : "service");
474 ERROR("can't get the wants path for %s and %s", desc->name, desc->wanted_by);
479 static int get_wants_target(char *path, size_t pathlen, const struct unitdesc *desc)
481 int rc = systemd_get_wants_target(
483 desc->name, desc->type == unittype_socket ? "socket" : "service");
486 ERROR("can't get the wants target for %s", desc->name);
491 static int do_uninstall_units(void *closure, const struct generatedesc *desc)
496 const struct unitdesc *u;
499 for (i = 0 ; i < desc->nunits ; i++) {
501 rc2 = check_unit_desc(u, 0);
503 rc2 = get_unit_path(path, sizeof path, u);
507 if (rc2 < 0 && rc == 0)
509 if (u->wanted_by != NULL) {
510 rc2 = get_wants_path(path, sizeof path, u);
515 if (rc2 < 0 && rc == 0)
521 static int do_install_units(void *closure, const struct generatedesc *desc)
525 char path[PATH_MAX + 1], target[PATH_MAX + 1];
526 const struct unitdesc *u;
529 while (i < desc->nunits) {
531 rc = check_unit_desc(u, 1);
533 rc = get_unit_path(path, sizeof path, u);
535 rc = putfile(path, u->content, u->content_length);
536 if (rc >= 0 && u->wanted_by != NULL) {
537 rc = get_wants_path(path, sizeof path, u);
539 rc = get_wants_target(target, sizeof target, u);
541 unlink(path); /* TODO? check? */
542 rc = symlink(target, path);
555 do_uninstall_units(closure, desc);
560 static int do_install_uninstall(
561 struct wgt_info *ifo,
562 const struct unitconf *conf,
563 int (*doer)(void *, const struct generatedesc *)
567 struct json_object *jdesc;
569 jdesc = wgt_info_to_json(ifo);
573 rc = unit_generator_process(jdesc, conf, doer, NULL);
574 json_object_put(jdesc);
579 int unit_install(struct wgt_info *ifo, const struct unitconf *conf)
581 return do_install_uninstall(ifo, conf, do_install_units);
584 int unit_uninstall(struct wgt_info *ifo, const struct unitconf *conf)
586 return do_install_uninstall(ifo, conf, do_uninstall_units);