2 Copyright (C) 2016, 2017, 2018 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);
388 sprintf(portstr, "%d", port);
389 if (!j_add_string_m(targ, "#metatarget.http-port", portstr))
394 return j_add_many_strings_m(jdesc,
395 "#metadata.install-dir", conf->installdir,
396 "#metadata.icons-dir", conf->icondir,
401 * Applies the object 'jdesc' augmented of meta data coming
402 * from 'conf' to the current unit generator.
403 * The current unit generator will be set to the default one if not unit
404 * was previously set using the function 'unit_generator_open_template'.
405 * The callback function 'process' is then called with the
406 * unit descriptors array and the expected closure.
407 * Return what returned process in case of success or a negative
410 int unit_generator_process(struct json_object *jdesc, const struct unitconf *conf, int (*process)(void *closure, const struct generatedesc *desc), void *closure)
416 rc = add_metadata(jdesc, conf);
418 ERROR("can't set the metadata. %m");
420 rc = template ? 0 : unit_generator_open_template(NULL);
423 rc = apply_mustach(template, jdesc, &instance, &size);
425 rc = process_all_units(instance, conf, process, closure, jdesc);
432 /**************** SPECIALIZED PART *****************************/
434 static int check_unit_desc(const struct unitdesc *desc, int tells)
436 if (desc->scope != unitscope_unknown && desc->type != unittype_unknown && desc->name != NULL)
440 if (desc->scope == unitscope_unknown)
441 ERROR("unit of unknown scope");
442 if (desc->type == unittype_unknown)
443 ERROR("unit of unknown type");
444 if (desc->name == NULL)
445 ERROR("unit of unknown name");
451 static int get_unit_path(char *path, size_t pathlen, const struct unitdesc *desc)
453 int rc = systemd_get_unit_path(
454 path, pathlen, desc->scope == unitscope_user,
455 desc->name, desc->type == unittype_socket ? "socket" : "service");
458 ERROR("can't get the unit path for %s", desc->name);
463 static int get_wants_path(char *path, size_t pathlen, const struct unitdesc *desc)
465 int rc = systemd_get_wants_path(
466 path, pathlen, desc->scope == unitscope_user, desc->wanted_by,
467 desc->name, desc->type == unittype_socket ? "socket" : "service");
470 ERROR("can't get the wants path for %s and %s", desc->name, desc->wanted_by);
475 static int get_wants_target(char *path, size_t pathlen, const struct unitdesc *desc)
477 int rc = systemd_get_wants_target(
479 desc->name, desc->type == unittype_socket ? "socket" : "service");
482 ERROR("can't get the wants target for %s", desc->name);
487 static int do_uninstall_units(void *closure, const struct generatedesc *desc)
492 const struct unitdesc *u;
495 for (i = 0 ; i < desc->nunits ; i++) {
497 rc2 = check_unit_desc(u, 0);
499 rc2 = get_unit_path(path, sizeof path, u);
503 if (rc2 < 0 && rc == 0)
505 if (u->wanted_by != NULL) {
506 rc2 = get_wants_path(path, sizeof path, u);
511 if (rc2 < 0 && rc == 0)
517 static int do_install_units(void *closure, const struct generatedesc *desc)
521 char path[PATH_MAX + 1], target[PATH_MAX + 1];
522 const struct unitdesc *u;
525 while (i < desc->nunits) {
527 rc = check_unit_desc(u, 1);
529 rc = get_unit_path(path, sizeof path, u);
531 rc = putfile(path, u->content, u->content_length);
532 if (rc >= 0 && u->wanted_by != NULL) {
533 rc = get_wants_path(path, sizeof path, u);
535 rc = get_wants_target(target, sizeof target, u);
537 unlink(path); /* TODO? check? */
538 rc = symlink(target, path);
551 do_uninstall_units(closure, desc);
556 static int do_install_uninstall(
557 struct wgt_info *ifo,
558 const struct unitconf *conf,
559 int (*doer)(void *, const struct generatedesc *)
563 struct json_object *jdesc;
565 jdesc = wgt_info_to_json(ifo);
569 rc = unit_generator_process(jdesc, conf, doer, NULL);
570 json_object_put(jdesc);
575 int unit_install(struct wgt_info *ifo, const struct unitconf *conf)
577 return do_install_uninstall(ifo, conf, do_install_units);
580 int unit_uninstall(struct wgt_info *ifo, const struct unitconf *conf)
582 return do_install_uninstall(ifo, conf, do_uninstall_units);