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 }