2 Copyright 2016, 2017 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"
45 #define isblank(c) ((c)==' '||(c)=='\t')
48 /* the template for all units */
49 static char *template;
52 * Search for the 'pattern' in 'text'.
53 * Returns 1 if 'text' matches the 'pattern' or else returns 0.
54 * When returning 1 and 'after' isn't NULL, the pointer to the
55 * first character after the pettern in 'text' is stored in 'after'.
56 * The characters '\n' and ' ' have a special meaning in the search:
57 * * '\n': matches any space or tabs (including none) followed
58 * either by '\n' or '\0' (end of the string)
59 * * ' ': matches any space or tabs but at least one.
61 static int matches(const char *pattern, char *text, char **after)
99 * Pack a null terminated 'text' by removing empty lines,
100 * lines made of blanks and terminated with \, lines
101 * starting with the 'purge' character (can be null).
102 * Lines made of the 'purge' character followed with
103 * "nl" exactly (without quotes ") are replaced with
106 * Returns the size after packing (offset of the ending null).
108 static size_t pack(char *text, char purge)
110 char *read; /* read iterator */
111 char *write; /* write iterator */
112 char *begin; /* begin the copied text of the line */
113 char *start; /* first character of the line that isn't blanck */
114 char c; /* currently scanned character (pointed by read) */
115 char emit; /* flag telling whether line is to be copied */
116 char cont; /* flag telling whether the line continues the previous one */
117 char nextcont; /* flag telling whether the line will continues the next one */
120 c = *(write = read = text);
122 /* iteration over lines */
124 /* computes emit, nextcont, emit and start for the current line */
129 while (c && c != '\n') {
131 if (c == '\\' && read[1] == '\n')
143 /* emit the line if not empty */
144 if (emit || (cont && !nextcont)) {
145 /* removes the blanks on the left of not continuing lines */
148 /* check if purge applies */
149 if (purge && *begin == purge) {
150 /* yes, insert new line if requested */
151 if (!strncmp(begin+1, "nl\n",3))
154 /* copies the line */
155 while (begin != read)
161 return (size_t)(write - text);
165 * Searchs the first character of the next line
166 * of the 'text' and returns its address
167 * Returns NULL if there is no next line.
169 static inline char *nextline(char *text)
171 char *result = strchr(text, '\n');
172 return result + !!result;
176 * Search in 'text' the offset of a line beginning with the 'pattern'
177 * Returns NULL if not found or the address of the line contning the pattern
178 * If args isn't NULL and the pattern is found, the pointed pattern is
179 * updated with the address of the character following the found pattern.
181 static char *offset(char *text, const char *pattern, char **args)
183 while (text && !matches(pattern, text, args))
184 text = nextline(text);
191 static int process_one_unit(char *spec, struct unitdesc *desc)
193 char *nsoc, *nsrv, *name, *wanted;
194 int isuser, issystem, issock, isserv, iswanted;
197 /* finds the configuration directive of the unit */
198 isuser = !!offset(spec, "%systemd-unit user\n", NULL);
199 issystem = !!offset(spec, "%systemd-unit system\n", NULL);
200 issock = !!offset(spec, "%systemd-unit socket ", &nsoc);
201 isserv = !!offset(spec, "%systemd-unit service ", &nsrv);
202 iswanted = !!offset(spec, "%systemd-unit wanted-by ", &wanted);
204 /* check the unit scope */
205 if ((isuser + issystem) == 1) {
206 desc->scope = isuser ? unitscope_user : unitscope_system;
208 desc->scope = unitscope_unknown;
211 /* check the unit type */
212 if ((issock + isserv) == 1) {
214 desc->type = unittype_socket;
217 desc->type = unittype_service;
220 len = strcspn(name, " \t\n");
221 desc->name = strndup(name, len);
222 desc->name_length = desc->name ? len : 0;
224 desc->type = unittype_unknown;
226 desc->name_length = 0;
230 len = strcspn(wanted, " \t\n");
231 desc->wanted_by = strndup(wanted, len);
232 desc->wanted_by_length = len;
234 desc->wanted_by = NULL;
235 desc->wanted_by_length = 0;
238 desc->content = spec;
239 desc->content_length = pack(spec, '%');
245 * Processes all the units of the 'corpus'.
246 * Each unit of the corpus is separated and packed and its
247 * charactistics are stored in a descriptor.
248 * At the end if no error was found, calls the function 'process'
249 * with its given 'closure' and the array descripbing the units.
250 * Return 0 in case of success or a negative value in case of error.
252 static int process_all_units(char *corpus, const struct unitconf *conf, int (*process)(void *closure, const struct generatedesc *desc), void *closure)
255 char *beg, *end, *befbeg, *aftend;
257 struct generatedesc gdesc;
264 /* while there is a unit in the corpus */
266 befbeg = offset(corpus, "%begin ", &beg);
267 end = offset(corpus, "%end ", &aftend);
270 /* %end detected without %begin */
271 ERROR("unexpected %%end at end");
277 /* unterminated unit !! */
278 ERROR("unterminated unit description!!");
281 } else if (end < befbeg) {
282 /* sequence %end ... %begin detected !! */
283 ERROR("unexpected %%end before %%begin");
287 befbeg = offset(beg, "%begin ", NULL);
288 if (befbeg && befbeg < end) {
289 /* sequence %begin ... %begin ... %end detected !! */
290 ERROR("unexpected %%begin after %%begin");
296 if (matches("systemd-unit\n", beg, &beg)) {
297 if (!matches("systemd-unit\n", aftend, &corpus)) {
298 /* end doesnt match */
299 ERROR("unmatched %%begin systemd-unit (matching end mismatch)");
302 /* allocates a descriptor for the unit */
303 u = realloc((void*)gdesc.units, ((unsigned)gdesc.nunits + 1) * sizeof *gdesc.units);
307 /* creates the unit description */
309 u = &u[gdesc.nunits];
310 memset(u, 0, sizeof *u);
311 rc2 = process_one_unit(beg, u);
317 ERROR("unexpected %%begin name");
322 /* records the error if there is an error */
329 /* call the function that processes the units */
330 if (rc == 0 && process)
331 rc = process(closure, &gdesc);
333 /* cleanup and frees */
334 while(gdesc.nunits) {
335 free((void*)(gdesc.units[--gdesc.nunits].name));
336 free((void*)(gdesc.units[gdesc.nunits].wanted_by));
338 free((void*)gdesc.units);
344 * Clear the unit generator
346 void unit_generator_off()
353 * Initialises the unit generator with the content of the file of path 'filename'.
354 * Returns 0 in case of success or a negative number in case of error.
356 int unit_generator_on(const char *filename)
362 unit_generator_off();
363 rc = getfile(filename ? : FWK_UNIT_CONF, &template, NULL);
365 size = pack(template, ';');
366 tmp = realloc(template, 1 + size);
373 static int add_metadata(struct json_object *jdesc, const struct unitconf *conf)
377 sprintf(portstr, "%d", conf->port);
378 return j_add_many_strings_m(jdesc,
379 "#metadata.install-dir", conf->installdir,
380 "#metadata.app-data-dir", "%h/app-data",
381 "#metadata.icons-dir", conf->icondir,
382 "#metadata.http-port", portstr,
387 * Applies the object 'jdesc' augmented of meta data coming
388 * from 'conf' to the current unit generator.
389 * The current unit generator will be set to the default one if not unit
390 * was previously set using the function 'unit_generator_on'.
391 * The callback function 'process' is then called with the
392 * unit descriptors array and the expected closure.
393 * Return what returned process in case of success or a negative
396 int unit_generator_process(struct json_object *jdesc, const struct unitconf *conf, int (*process)(void *closure, const struct generatedesc *desc), void *closure)
402 rc = add_metadata(jdesc, conf);
404 ERROR("can't set the metadata. %m");
406 rc = template ? 0 : unit_generator_on(NULL);
409 rc = apply_mustach(template, jdesc, &instance, &size);
411 rc = process_all_units(instance, conf, process, closure);
418 /**************** SPECIALIZED PART *****************************/
420 static int check_unit_desc(const struct unitdesc *desc, int tells)
422 if (desc->scope != unitscope_unknown && desc->type != unittype_unknown && desc->name != NULL)
426 if (desc->scope == unitscope_unknown)
427 ERROR("unit of unknown scope");
428 if (desc->type == unittype_unknown)
429 ERROR("unit of unknown type");
430 if (desc->name == NULL)
431 ERROR("unit of unknown name");
437 static int get_unit_path(char *path, size_t pathlen, const struct unitdesc *desc)
439 int rc = systemd_get_unit_path(
440 path, pathlen, desc->scope == unitscope_user,
441 desc->name, desc->type == unittype_socket ? "socket" : "service");
444 ERROR("can't get the unit path for %s", desc->name);
449 static int get_wants_path(char *path, size_t pathlen, const struct unitdesc *desc)
451 int rc = systemd_get_wants_path(
452 path, pathlen, desc->scope == unitscope_user, desc->wanted_by,
453 desc->name, desc->type == unittype_socket ? "socket" : "service");
456 ERROR("can't get the wants path for %s and %s", desc->name, desc->wanted_by);
461 static int get_wants_target(char *path, size_t pathlen, const struct unitdesc *desc)
463 int rc = systemd_get_wants_target(
465 desc->name, desc->type == unittype_socket ? "socket" : "service");
468 ERROR("can't get the wants target for %s", desc->name);
473 static int do_send_reload(const struct generatedesc *desc)
476 int reloadsys, reloadusr;
477 const struct unitdesc *u;
479 reloadsys = reloadusr = 0;
480 for (i = 0 ; i < desc->nunits ; i++) {
482 if (u->wanted_by != NULL) {
487 case unitscope_system:
497 reloadusr = systemd_daemon_reload(1);
499 reloadsys = systemd_daemon_reload(0);
500 return reloadsys ? : reloadusr ? : 0;
503 static int do_uninstall_units(void *closure, const struct generatedesc *desc)
508 const struct unitdesc *u;
511 for (i = 0 ; i < desc->nunits ; i++) {
513 rc2 = check_unit_desc(u, 0);
515 rc2 = get_unit_path(path, sizeof path, u);
519 if (rc2 < 0 && rc == 0)
521 if (u->wanted_by != NULL) {
522 rc2 = get_wants_path(path, sizeof path, u);
527 if (rc2 < 0 && rc == 0)
530 rc2 = do_send_reload(desc);
531 if (rc2 < 0 && rc == 0)
536 static int do_install_units(void *closure, const struct generatedesc *desc)
540 char path[PATH_MAX + 1], target[PATH_MAX + 1];
541 const struct unitdesc *u;
544 while (i < desc->nunits) {
546 rc = check_unit_desc(u, 1);
548 rc = get_unit_path(path, sizeof path, u);
550 rc = putfile(path, u->content, u->content_length);
551 if (rc >= 0 && u->wanted_by != NULL) {
552 rc = get_wants_path(path, sizeof path, u);
554 rc = get_wants_target(target, sizeof target, u);
556 unlink(path); /* TODO? check? */
557 rc = symlink(target, path);
567 rc = do_send_reload(desc);
573 do_uninstall_units(closure, desc);
578 static int do_install_uninstall(
579 struct wgt_info *ifo,
580 const struct unitconf *conf,
581 int (*doer)(void *, const struct generatedesc *)
585 struct json_object *jdesc;
587 jdesc = wgt_info_to_json(ifo);
591 rc = unit_generator_process(jdesc, conf, doer, NULL);
592 json_object_put(jdesc);
597 int unit_install(struct wgt_info *ifo, const struct unitconf *conf)
599 return do_install_uninstall(ifo, conf, do_install_units);
602 int unit_uninstall(struct wgt_info *ifo, const struct unitconf *conf)
604 return do_install_uninstall(ifo, conf, do_uninstall_units);