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"
39 #include "wgtpkg-unit.h"
41 #if !defined(SYSTEMD_UNITS_ROOT)
42 # define SYSTEMD_UNITS_ROOT "/usr/local/lib/systemd"
48 #define isblank(c) ((c)==' '||(c)=='\t')
51 /* the template for all units */
52 static char *template;
55 * Search for the 'pattern' in 'text'.
56 * Returns 1 if 'text' matches the 'pattern' or else returns 0.
57 * When returning 1 and 'after' isn't NULL, the pointer to the
58 * first character after the pettern in 'text' is stored in 'after'.
59 * The characters '\n' and ' ' have a special mening in the search:
60 * * '\n': matches any space or tabs (including none) followed
61 * either by '\n' or '\0' (end of the string)
62 * * ' ': matches any space or tabs but at least one.
64 static int matches(const char *pattern, char *text, char **after)
102 * Pack a null terminated 'text' by removing empty lines,
103 * lines made of blanks and terminated with \, lines
104 * starting with the 'purge' character (can be null).
105 * Lines made of the 'purge' character followed with
106 * "nl" exactly (without quotes ") are replaced with
109 * Returns the size after packing (offset of the ending null).
111 static size_t pack(char *text, char purge)
113 char *read; /* read iterator */
114 char *write; /* write iterator */
115 char *begin; /* begin the copied text of the line */
116 char *start; /* first character of the line that isn't blanck */
117 char c; /* currently scanned character (pointed by read) */
118 char emit; /* flag telling whether line is to be copied */
119 char cont; /* flag telling whether the line continues the previous one */
120 char nextcont; /* flag telling whether the line will continues the next one */
123 c = *(write = read = text);
125 /* iteration over lines */
127 /* computes emit, nextcont, emit and start for the current line */
131 while (c && c != '\n') {
133 if (c == '\\' && read[1] == '\n')
145 /* emit the line if not empty */
147 /* removes the blanks on the left of not continuing lines */
150 /* check if purge applies */
151 if (purge && *begin == purge) {
152 /* yes, insert new line if requested */
153 if (!strncmp(begin+1, "nl\n",3))
156 /* copies the line */
157 while (begin != read)
164 return (size_t)(write - text);
168 * Searchs the first character of the next line
169 * of the 'text' and returns its address
170 * Returns NULL if there is no next line.
172 static inline char *nextline(char *text)
174 char *result = strchr(text, '\n');
175 return result + !!result;
179 * Search in 'text' the offset of a line beginning with the 'pattern'
180 * Returns NULL if not found or the address of the line contning the pattern
181 * If args isn't NULL and the pattern is found, the pointed pattern is
182 * updated with the address of the character following the found pattern.
184 static char *offset(char *text, const char *pattern, char **args)
186 while (text && !matches(pattern, text, args))
187 text = nextline(text);
194 static int process_one_unit(char *spec, struct unitdesc *desc)
196 char *nsoc, *nsrv, *name, *wanted;
197 int isuser, issystem, issock, isserv, iswanted;
200 /* finds the configuration directive of the unit */
201 isuser = !!offset(spec, "%systemd-unit user\n", NULL);
202 issystem = !!offset(spec, "%systemd-unit system\n", NULL);
203 issock = !!offset(spec, "%systemd-unit socket ", &nsoc);
204 isserv = !!offset(spec, "%systemd-unit service ", &nsrv);
205 iswanted = !!offset(spec, "%systemd-unit wanted-by ", &wanted);
207 /* check the unit scope */
208 if ((isuser + issystem) == 1) {
209 desc->scope = isuser ? unitscope_user : unitscope_system;
211 desc->scope = unitscope_unknown;
214 /* check the unit type */
215 if ((issock + isserv) == 1) {
217 desc->type = unittype_socket;
220 desc->type = unittype_service;
223 len = (size_t)(strchrnul(name, '\n') - name);
224 desc->name = strndup(name, len);
225 desc->name_length = desc->name ? len : 0;
227 desc->type = unittype_unknown;
229 desc->name_length = 0;
233 len = (size_t)(strchrnul(wanted, '\n') - wanted);
234 desc->wanted_by = strndup(wanted, len);
235 desc->wanted_by_length = len;
237 desc->wanted_by = NULL;
238 desc->wanted_by_length = 0;
241 desc->content = spec;
242 desc->content_length = pack(spec, '%');
248 * Processes all the units of the 'corpus'.
249 * Each unit of the corpus is separated and packed and its
250 * charactistics are stored in a descriptor.
251 * At the end if no error was found, calls the function 'process'
252 * with its given 'closure' and the array descripbing the units.
253 * Return 0 in case of success or a negative value in case of error.
255 static int process_all_units(char *corpus, int (*process)(void *closure, const struct unitdesc descs[], unsigned count), void *closure)
260 struct unitdesc *descs, *d;
266 /* while there is a unit in the corpus */
267 while(offset(corpus, "%begin systemd-unit\n", &beg)) {
269 /* get the end of the unit */
270 end = offset(beg, "%end systemd-unit\n", &corpus);
272 /* unterminated unit !! */
273 ERROR("unterminated unit description!! %s", beg);
277 /* separate the unit from the corpus */
280 /* allocates a descriptor for the unit */
281 d = realloc(descs, (n + 1) * sizeof *descs);
285 /* creates the unit description */
286 memset(&d[n], 0, sizeof *d);
288 rc2 = process_one_unit(beg, &descs[n]);
293 /* records the error if there is an error */
300 /* call the function that processes the units */
301 if (rc == 0 && process)
302 rc = process(closure, descs, n);
304 /* cleanup and frees */
306 free((char *)(descs[--n].name));
307 free((char *)(descs[n].wanted_by));
315 * Clear the unit generator
317 void unit_generator_off()
324 * Initialises the unit generator with the content of the file of path 'filename'.
325 * Returns 0 in case of success or a negative number in case of error.
327 int unit_generator_on(const char *filename)
333 unit_generator_off();
334 rc = getfile(filename ? : FWK_UNIT_CONF, &template, NULL);
336 size = pack(template, ';');
337 tmp = realloc(template, 1 + size);
345 * Applies the object 'jdesc' to the current unit generator.
346 * The current unit generator will be set to the default one if not unit
347 * was previously set using the function 'unit_generator_on'.
348 * The callback function 'process' is then called with the
349 * unit descriptors array and the expected closure.
350 * Return what returned process in case of success or a negative
353 int unit_generator_process(struct json_object *jdesc, int (*process)(void *closure, const struct unitdesc descs[], unsigned count), void *closure)
359 rc = template ? 0 : unit_generator_on(NULL);
362 rc = apply_mustach(template, jdesc, &instance, &size);
364 rc = process_all_units(instance, process, closure);
370 /**************** SPECIALIZED PART *****************************/
372 static int check_unit_desc(const struct unitdesc *desc, int tells)
374 if (desc->scope != unitscope_unknown && desc->type != unittype_unknown && desc->name != NULL)
378 if (desc->scope == unitscope_unknown)
379 ERROR("unit of unknown scope");
380 if (desc->type == unittype_unknown)
381 ERROR("unit of unknown type");
382 if (desc->name == NULL)
383 ERROR("unit of unknown name");
389 static int get_unit_path(char *path, size_t pathlen, const struct unitdesc *desc)
393 rc = snprintf(path, pathlen, "%s/%s/%s.%s",
395 desc->scope == unitscope_system ? "system" : "user",
397 desc->type == unittype_socket ? "socket" : "service");
399 if (rc >= 0 && (size_t)rc >= pathlen) {
400 ERROR("can't set the unit path");
407 static int get_wants_path(char *path, size_t pathlen, const struct unitdesc *desc)
411 rc = snprintf(path, pathlen, "%s/%s/%s.wants/%s.%s",
413 desc->scope == unitscope_system ? "system" : "user",
416 desc->type == unittype_socket ? "socket" : "service");
418 if (rc >= 0 && (size_t)rc >= pathlen) {
419 ERROR("can't set the wants path");
426 static int get_wants_target(char *path, size_t pathlen, const struct unitdesc *desc)
430 rc = snprintf(path, pathlen, "../%s.%s",
432 desc->type == unittype_socket ? "socket" : "service");
434 if (rc >= 0 && (size_t)rc >= pathlen) {
435 ERROR("can't set the wants target");
442 static int do_uninstall_units(void *closure, const struct unitdesc descs[], unsigned count)
448 for (i = 0 ; i < count ; i++) {
449 rc = check_unit_desc(&descs[i], 0);
451 rc = get_unit_path(path, sizeof path, &descs[i]);
455 if (descs[i].wanted_by != NULL) {
456 rc2 = get_wants_path(path, sizeof path, &descs[i]);
459 rc = rc < 0 ? rc : rc2;
466 static int do_install_units(void *closure, const struct unitdesc descs[], unsigned count)
470 char path[PATH_MAX + 1], target[PATH_MAX + 1];
474 rc = check_unit_desc(&descs[i], 1);
476 rc = get_unit_path(path, sizeof path, &descs[i]);
478 rc = putfile(path, descs[i].content, descs[i].content_length);
479 if (descs[i].wanted_by != NULL) {
480 rc = get_wants_path(path, sizeof path, &descs[i]);
482 rc = get_wants_target(target, sizeof target, &descs[i]);
484 unlink(path); /* TODO? check? */
485 rc = symlink(target, path);
493 do_uninstall_units(closure, descs, i);
500 static int add_metadata(struct json_object *jdesc, const char *installdir, const char *icondir, int port)
504 sprintf(portstr, "%d", port);
505 return j_add_many_strings_m(jdesc,
506 "#metadata.install-dir", installdir,
507 "#metadata.app-data-dir", "%h/app-data",
508 "#metadata.icons-dir", icondir,
509 "#metadata.http-port", portstr,
513 static int do_install_uninstall(
514 struct wgt_info *ifo,
515 const char *installdir,
518 int (*doer)(void *, const struct unitdesc[], unsigned)
522 struct json_object *jdesc;
524 jdesc = wgt_info_to_json(ifo);
528 rc = add_metadata(jdesc, installdir, icondir, port);
530 ERROR("can't set the metadata. %m");
532 rc = unit_generator_process(jdesc, doer, NULL);
534 ERROR("can't install units, error %d", rc);
536 json_object_put(jdesc);
541 int unit_install(struct wgt_info *ifo, const char *installdir, const char *icondir, int port)
543 return do_install_uninstall(ifo, installdir, icondir, port, do_install_units);
546 int unit_uninstall(struct wgt_info *ifo)
548 return do_install_uninstall(ifo, "", "", 0, do_uninstall_units);