vshost-util-vserver

Build script and sources for util-vserver.
git clone https://ccx.te2000.cz/git/vshost-util-vserver
Log | Files | Refs

systemd-vserver-generator.c (12265B)


      1 /*	--*- c -*--
      2  * Copyright (C) 2015 Enrico Scholz <enrico.scholz@ensc.de>
      3  *
      4  * This program is free software; you can redistribute it and/or modify
      5  * it under the terms of the GNU General Public License as published by
      6  * the Free Software Foundation; version 3 of the License.
      7  *
      8  * This program is distributed in the hope that it will be useful,
      9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
     10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     11  * GNU General Public License for more details.
     12  *
     13  * You should have received a copy of the GNU General Public License
     14  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
     15  */
     16 
     17 #define _BSD_SOURCE	1
     18 #define _DEFAULT_SOURCE	1
     19 #define _ATFILE_SOURCE	1
     20 
     21 #ifdef HAVE_CONFIG_H
     22 #  include <config.h>
     23 #endif
     24 
     25 #include <stdlib.h>
     26 #include <stdio.h>
     27 #include <stdbool.h>
     28 #include <string.h>
     29 #include <errno.h>
     30 #include <unistd.h>
     31 #include <ctype.h>
     32 #include <sysexits.h>
     33 #include <unistd.h>
     34 #include <fcntl.h>
     35 #include <fnmatch.h>
     36 #include <dirent.h>
     37 
     38 #include <sys/stat.h>
     39 #include <sys/mman.h>
     40 
     41 #include "compat.h"
     42 #include "pathconfig.h"
     43 
     44 #include "../lib_internal/util.h"
     45 
     46 #define ENSC_WRAPPERS_STRING 1
     47 #define ENSC_WRAPPERS_STDLIB 1
     48 #include "../ensc_wrappers/wrappers.h"
     49 
     50 #ifndef O_PATH
     51 #  define O_PATH 010000000
     52 #endif
     53 
     54 int wrapper_exit_code = EX_OSERR;
     55 
     56 static char const * const	IGNORE_PATTERN[] = {
     57 	"*.rpmnew", "*.rpmsave", "*.rpmorig", "*.cfsaved*", "*.~*~",
     58 	NULL,
     59 };
     60 
     61 static void show_help(void)
     62 {
     63 	WRITE_MSG(1,
     64 		  "Usage: systemd-vserver-generator <normal> <early> <late>\n"
     65 		  "\n"
     66 		  "see http://www.freedesktop.org/wiki/Software/systemd/Generators/"
     67 		  "\n"
     68 		  "Please report bugs to " PACKAGE_BUGREPORT "\n");
     69 
     70 	exit(0);
     71 }
     72 
     73 static void show_version(void)
     74 {
     75 	WRITE_MSG(1,
     76 		  "systemd-vserver-generator " VERSION " -- generates systemd units"
     77 		  "This program is part of " PACKAGE_STRING "\n\n"
     78 		  "Copyright (C) 2015 Enrico Scholz\n"
     79 		  VERSION_COPYRIGHT_DISCLAIMER);
     80 	exit(0);
     81 }
     82 
     83 /* escape string accordingly 'systemd.unit(5)' which means that '/' gets
     84  * turned into '-' and special chars into '\xXX'; prepend a prefix and reserve
     85  * extra space e.g. for a suffix */
     86 static char *systemd_escape(char const *data, size_t len,
     87 			    char const *prefix,
     88 			    size_t extra_space)
     89 {
     90 	static char const	HEX_DIGIT[] = "0123456789abcdef";
     91 
     92 	/* assume the worst case which means that every char must be
     93 	 * encoded */
     94 	char			*dst =
     95 		Emalloc(len * 4 + strlen(prefix) + extra_space + 1);
     96 	char const		*src = data;
     97 	char			*out;
     98 
     99 	out = stpcpy(dst, prefix);
    100 
    101 	while (src < data + len) {
    102 		unsigned char	c = *src++;
    103 
    104 		if (c == '/') {
    105 			*out++ = '-';
    106 		} else if (c == '-' || c < 32 || !isascii(c)) {
    107 			*out++ = '\\';
    108 			*out++ = 'x';
    109 			*out++ = HEX_DIGIT[(c >> 4) & 0x0f];
    110 			*out++ = HEX_DIGIT[(c >> 0) & 0x0f];
    111 		} else {
    112 			*out++ = c;
    113 		}
    114 	}
    115 
    116 	*out = '\0';
    117 
    118 	return dst;
    119 }
    120 
    121 static int open_or_mkdir(int dirfd, char const *name, int flags)
    122 {
    123 	int		fd;
    124 	bool		first_pass = true;
    125 
    126 	/* try to open the directory in the first pass; when this fails,
    127 	 * mkdir() it and try again */
    128 	for (;;) {
    129 		fd = openat(dirfd, name, O_DIRECTORY | flags);
    130 
    131 		if (fd >= 0)
    132 			break;
    133 		else if (!first_pass) {
    134 			perror("openat()");
    135 			break;
    136 		}
    137 
    138 		first_pass = false;
    139 
    140 		if (mkdirat(dirfd, name, 0755) < 0 && errno != EEXIST) {
    141 			perror("mkdirat()");
    142 			break;
    143 		}
    144 
    145 		/* we created the directory; it can not be a symlink... */
    146 		flags |= O_NOFOLLOW;
    147 	}
    148 
    149 	return fd;
    150 }
    151 
    152 static int open_or_mkdir_rec(char const *name, int flags)
    153 {
    154 	int	cur_fd = AT_FDCWD;
    155 	char	*tmp = Estrdup(name);
    156 
    157 	for (char *ptr = tmp; ptr && *ptr; ) {
    158 		char	*eod;
    159 		int	new_fd;
    160 
    161 		eod = ptr;
    162 		while (*eod == '/')
    163 			++eod;
    164 		eod = strchr(eod, '/');
    165 
    166 		if (eod) {
    167 			*eod = '\0';
    168 			do {
    169 				++eod;
    170 			} while (*eod == '/');
    171 
    172 			if (!*eod)
    173 				eod = NULL;
    174 		}
    175 
    176 		new_fd = open_or_mkdir(cur_fd, ptr,
    177 				       eod ? (O_PATH | O_RDWR) : flags);
    178 
    179 		if (cur_fd != AT_FDCWD)
    180 			close(cur_fd);
    181 
    182 		cur_fd = new_fd;
    183 		if (cur_fd < 0)
    184 			break;
    185 
    186 		ptr = eod;
    187 	}
    188 
    189 	if (cur_fd == AT_FDCWD){
    190 		errno = EINVAL;
    191 		cur_fd = -1;
    192 	}
    193 
    194 	free(tmp);
    195 
    196 	return cur_fd;
    197 }
    198 
    199 static int xsymlinkat(char const *target, int fd, char const *name)
    200 {
    201 	unlinkat(fd, name, 0);		/* ignore errors */
    202 	return symlinkat(target, fd, name);
    203 }
    204 
    205 struct mark_file_data {
    206 	int		unitdir_fd;
    207 	int		targetdir_fd;
    208 	char const	*service_name;
    209 };
    210 
    211 static bool mark_handler(char const *data, size_t len, void *res_)
    212 {
    213 	struct mark_file_data const	*mdata = res_;
    214 	char				*mark;
    215 	int				rc;
    216 	int				markdir_fd;
    217 	bool				res = true;
    218 
    219 	if (len == 0)
    220 		/* ignore empty names */
    221 		return true;
    222 
    223 	/* we create 'vserver-mark@<mark>.target{,.wants}' names */
    224 	mark = systemd_escape(data, len,
    225 			      "vserver-mark@", sizeof ".target.wants");
    226 	strcat(mark, ".target");
    227 
    228 	/* create the 'vserver.target -> vserver-mark@<mark>.target'
    229 	 * dependency */
    230 	rc = xsymlinkat("../vserver-mark@.target", mdata->targetdir_fd, mark);
    231 	if (rc < 0) {
    232 		perror("symlinkat(<mark/target>)");
    233 		res = false;
    234 	}
    235 
    236 	strcat(mark, ".wants");
    237 	markdir_fd = open_or_mkdir(mdata->unitdir_fd, mark, O_RDWR | O_PATH);
    238 	if (markdir_fd < 0) {
    239 		res = false;
    240 	} else {
    241 		/* create the 'vserver-mark@<mark>.target ->
    242 		 * 'vserver@<vserver>.service' dependency */
    243 		rc = xsymlinkat("../vserver@.service", markdir_fd,
    244 				mdata->service_name);
    245 		if (rc < 0) {
    246 			perror("symlinkat(<mark/service>)");
    247 			res = false;
    248 		}
    249 
    250 		close(markdir_fd);
    251 	}
    252 
    253 	free(mark);
    254 
    255 	return res;
    256 }
    257 
    258 struct depends_file_data {
    259 	int		unitdir_fd;
    260 	int		depdir_fd;
    261 	char const	*service_name;
    262 };
    263 
    264 static bool depends_handler(char const *data, size_t len, void *res_)
    265 {
    266 	struct depends_file_data	*ddata = res_;
    267 	char				*dep;
    268 	int				rc;
    269 	bool				res = true;
    270 
    271 	if (len == 0)
    272 		/* ignore empty lines */
    273 		return true;
    274 
    275 	if (ddata->depdir_fd == -1)
    276 		/* do not close it later; it can be reused */
    277 		ddata->depdir_fd = open_or_mkdir(ddata->unitdir_fd,
    278 						 ddata->service_name,
    279 						 O_RDWR | O_PATH);
    280 
    281 	if (ddata->depdir_fd < 0)
    282 		return false;
    283 
    284 	dep = systemd_escape(data, len, "vserver@", sizeof ".service");
    285 	strcat(dep, ".service");
    286 
    287 	rc = xsymlinkat("../vserver@.service", ddata->depdir_fd, dep);
    288 	if (rc < 0) {
    289 		perror("symlinkat(<dep>)");
    290 		res = false;
    291 	}
    292 
    293 	free(dep);
    294 
    295 	return res;
    296 }
    297 
    298 static bool read_file(int dir_fd, char const *name,
    299 		      bool (*line_handler)(char const *line, size_t len,
    300 					   void *data),
    301 		      void *data)
    302 {
    303 	struct stat	st;
    304 	int		fd;
    305 	char const	*mem;
    306 	size_t		len;
    307 	bool		res = true;
    308 
    309 	if (fstatat(dir_fd, name, &st, 0) < 0)
    310 		/* skip non existing files */
    311 		return true;
    312 
    313 	/* ignore special files; mmap() below works only on regular files */
    314 	if (!S_ISREG(st.st_mode)) {
    315 		WRITE_MSG(2, "read_file() called on non regular file\n");
    316 		return false;
    317 	}
    318 
    319 	fd = openat(dir_fd, name, O_RDONLY);
    320 	if (fd < 0) {
    321 		perror("openat()");
    322 		return false;
    323 	}
    324 
    325 	len = st.st_size;
    326 
    327 	/* mmap() the configuration file */
    328 	mem = mmap(NULL, len, PROT_READ, MAP_PRIVATE, fd, 0);
    329 	if (mem == MAP_FAILED) {
    330 		perror("mmap()");
    331 		close(fd);
    332 		return false;
    333 	}
    334 
    335 	close(fd);
    336 
    337 	/* iterate over the lines of the configuration file */
    338 	for (char const *ptr = mem; ptr < mem + len && res;) {
    339 		char const	*eol;
    340 
    341 		/* skip leading whitespaces (and newlines) */
    342 		while (ptr < mem + len && isspace(*ptr))
    343 			++ptr;
    344 
    345 		/* is there a newline? if not, place eol marker to end of
    346 		 * buffer */
    347 		eol = memchr(ptr, '\n', mem + len - ptr);
    348 		if (!eol)
    349 			eol = mem + len;
    350 
    351 		/* skip comment lines */
    352 		if (*ptr != '#') {
    353 			char const	*trim = eol;
    354 
    355 			/* skip trailing whitespace (including the
    356 			 * newlines) */
    357 			while (trim > ptr && isspace(trim[-1]))
    358 				--trim;
    359 
    360 			if (!line_handler(ptr, trim - ptr, data))
    361 				res = false;
    362 		}
    363 
    364 		/* we are now at the newline */
    365 		ptr = eol;
    366 	}
    367 
    368 	munmap((void *)mem, len);
    369 
    370 	return res;
    371 }
    372 
    373 static bool generate_units_for_vserver(int unitdir_fd, int vdir_fd,
    374 				       int targetdir_fd,
    375 				       char const *name)
    376 {
    377 	struct stat			st;
    378 	struct mark_file_data		mark_data = {
    379 		.unitdir_fd	= unitdir_fd,
    380 		.targetdir_fd	= targetdir_fd,
    381 	};
    382 	struct depends_file_data	depends_data = {
    383 		.unitdir_fd	= unitdir_fd,
    384 		.depdir_fd	= -1,
    385 	};
    386 	bool				res = true;
    387 	char				*service_name;
    388 
    389 	/* check whether name matches some common backup file pattern */
    390 	for (char const * const *p = IGNORE_PATTERN; *p; ++p) {
    391 		if (fnmatch(*p, name, 0) == 0)
    392 			return true;
    393 	}
    394 
    395 	if (fstatat(vdir_fd, "disabled", &st, 0) == 0)
    396 		/* vserver is disabled; skip it */
    397 		return true;
    398 
    399 	if (fstatat(vdir_fd, "vdir", &st, AT_SYMLINK_NOFOLLOW) < 0 ||
    400 	    !(S_ISDIR(st.st_mode) || S_ISLNK(st.st_mode)))
    401 		/* 'vdir' does not exist or is invalid; skip dir */
    402 		return true;
    403 
    404 	/* create the basic 'vserver@<name>.service' name */
    405 	service_name = systemd_escape(name, strlen(name),
    406 				      "vserver@", sizeof ".service.requires");
    407 	strcat(service_name, ".service");
    408 
    409 	/* evaluate the 'mark' file and create deps as needed */
    410 	mark_data.service_name = service_name;
    411 	if (!read_file(vdir_fd, "apps/init/mark", mark_handler, &mark_data))
    412 		res = false;
    413 
    414 	/* create the 'vserver@<name>.service.requires' name */
    415 	strcat(service_name, ".requires");
    416 	depends_data.service_name = service_name;
    417 
    418 	/* read the depends file */
    419 	if (!read_file(vdir_fd, "apps/init/depends",
    420 		       depends_handler, &depends_data))
    421 		res = false;
    422 
    423 	/* the depends_handler() can mkdir and open a directory which is
    424 	 * reused for subsequent calls of this handler; close it finally */
    425 	if (depends_data.depdir_fd >= 0)
    426 		close(depends_data.depdir_fd);
    427 
    428 	free(service_name);
    429 
    430 	return res;
    431 }
    432 
    433 static bool generate_units(int unitdir_fd, DIR *vcfg_dir)
    434 {
    435 	int		vcfg_fd = dirfd(vcfg_dir);
    436 	int		targetdir_fd;
    437 	bool		rc = true;
    438 
    439 	/* the 'vserver.target' might be needed by rules evaluating the
    440 	 * 'marks' file; create it here */
    441 	targetdir_fd = open_or_mkdir(unitdir_fd, "vserver.target.wants",
    442 				     O_RDWR | O_PATH);
    443 	if (!targetdir_fd)
    444 		return false;
    445 
    446 	for (;;) {
    447 		struct dirent	*ent;
    448 		int		vdir_fd;
    449 
    450 		errno = 0;
    451 		ent = readdir(vcfg_dir);
    452 
    453 		if (!ent && errno) {
    454 			perror("readdir()");
    455 			return false;
    456 		}
    457 
    458 		if (!ent)
    459 			break;
    460 
    461 		if (ent->d_name[0] == '.')
    462 			/* ignore hidden files (inclusive the special '.' and
    463 			 * '..' directories) */
    464 			continue;
    465 
    466 		/* readdir() does not give information about the file type;
    467 		 * determine it manually */
    468 		if (ent->d_type == DT_UNKNOWN) {
    469 			struct stat	st;
    470 
    471 			if (!fstatat(vcfg_fd, ent->d_name, &st,
    472 				     AT_SYMLINK_NOFOLLOW) < 0) {
    473 				perror("fstatat()");
    474 				continue;
    475 			}
    476 
    477 			/* we are interested in directories only; do not
    478 			 * bother with setting other DT_* values */
    479 			if (S_ISDIR(st.st_mode))
    480 				ent->d_type = DT_DIR;
    481 		}
    482 
    483 		if (ent->d_type != DT_DIR)
    484 			/* we are interested in directories only */
    485 			continue;
    486 
    487 		/* open the '/etc/vservers/<name>' directory */
    488 		vdir_fd = openat(vcfg_fd, ent->d_name,
    489 				 O_RDONLY | O_DIRECTORY | O_PATH | O_NOFOLLOW);
    490 		if (vdir_fd < 0) {
    491 			perror("openat()");
    492 			continue;
    493 		}
    494 
    495 		if (!generate_units_for_vserver(unitdir_fd, vdir_fd,
    496 						targetdir_fd, ent->d_name))
    497 			rc = false;
    498 
    499 		close(vdir_fd);
    500 	}
    501 
    502 	close(targetdir_fd);
    503 
    504 	return rc;
    505 }
    506 
    507 int main(int argc, char *argv[])
    508 {
    509 	int		unitdir_fd = -1;
    510 	DIR		*vcfg_dir = NULL;
    511 	char const	*unitdir;
    512 	int		rc;
    513 
    514 	if (argc < 2) {
    515 		WRITE_MSG(2, "missing arguments\n");
    516 		return EX_USAGE;
    517 	}
    518 	if (strcmp(argv[1], "--help") == 0)
    519 		show_help();
    520 	if (strcmp(argv[1], "--version") == 0)
    521 		show_version();
    522 
    523 	if (argc < 4) {
    524 		WRITE_MSG(2, "bad number of arguments\n");
    525 		return EX_USAGE;
    526 	}
    527 
    528 	/* we use the "normal" startup priority unit directory */
    529 	unitdir = argv[1];
    530 
    531 	/* create and open the generator directory (usually
    532 	 * /run/systemd/generators) */
    533 	unitdir_fd = open_or_mkdir_rec(unitdir, O_RDWR | O_PATH);
    534 	if (unitdir_fd < 0) {
    535 		perror("mkdir(<unitdir>)");
    536 		return EX_OSERR;
    537 	}
    538 
    539 	/* allow to override the CONFDIR (usually /etc/vservers) e.g. for
    540 	 * testing purposes */
    541 	vcfg_dir = opendir(getenv("X_UTIL_VSERVER_CONFDIR") ?
    542 			   getenv("X_UTIL_VSERVER_CONFDIR") : CONFDIR);
    543 	if (!vcfg_dir) {
    544 		perror("opendir(" CONFDIR ")");
    545 		rc = EX_OSERR;
    546 		goto out;
    547 	}
    548 
    549 	/* iterate over the vservers */
    550 	rc = generate_units(unitdir_fd, vcfg_dir) ? 0 : EX_OSERR;
    551 
    552 out:
    553 	if (vcfg_dir)
    554 		closedir(vcfg_dir);
    555 	if (unitdir_fd != -1)
    556 		close(unitdir_fd);
    557 
    558 	return rc;
    559 }