github.com/zhuohuang-hust/src-cbuild@v0.0.0-20230105071821-c7aab3e7c840/mergeCode/tini/src/tini.c (about) 1 /* See LICENSE file for copyright and license details. */ 2 #define _GNU_SOURCE 3 4 #include <sys/types.h> 5 #include <sys/wait.h> 6 #include <sys/prctl.h> 7 8 #include <errno.h> 9 #include <signal.h> 10 #include <string.h> 11 #include <time.h> 12 #include <stdio.h> 13 #include <stdlib.h> 14 #include <unistd.h> 15 #include <stdbool.h> 16 17 #include "tiniConfig.h" 18 #include "tiniLicense.h" 19 20 #if TINI_MINIMAL 21 #define PRINT_FATAL(...) fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n"); 22 #define PRINT_WARNING(...) if (verbosity > 0) { fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n"); } 23 #define PRINT_INFO(...) if (verbosity > 1) { fprintf(stdout, __VA_ARGS__); fprintf(stdout, "\n"); } 24 #define PRINT_DEBUG(...) if (verbosity > 2) { fprintf(stdout, __VA_ARGS__); fprintf(stdout, "\n"); } 25 #define PRINT_TRACE(...) if (verbosity > 3) { fprintf(stdout, __VA_ARGS__); fprintf(stdout, "\n"); } 26 #define DEFAULT_VERBOSITY 0 27 #else 28 #define PRINT_FATAL(...) fprintf(stderr, "[FATAL tini (%i)] ", getpid()); fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n"); 29 #define PRINT_WARNING(...) if (verbosity > 0) { fprintf(stderr, "[WARN tini (%i)] ", getpid()); fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n"); } 30 #define PRINT_INFO(...) if (verbosity > 1) { fprintf(stdout, "[INFO tini (%i)] ", getpid()); fprintf(stdout, __VA_ARGS__); fprintf(stdout, "\n"); } 31 #define PRINT_DEBUG(...) if (verbosity > 2) { fprintf(stdout, "[DEBUG tini (%i)] ", getpid()); fprintf(stdout, __VA_ARGS__); fprintf(stdout, "\n"); } 32 #define PRINT_TRACE(...) if (verbosity > 3) { fprintf(stdout, "[TRACE tini (%i)] ", getpid()); fprintf(stdout, __VA_ARGS__); fprintf(stdout, "\n"); } 33 #define DEFAULT_VERBOSITY 1 34 #endif 35 36 #define ARRAY_LEN(x) (sizeof(x) / sizeof(x[0])) 37 38 typedef struct { 39 sigset_t* const sigmask_ptr; 40 struct sigaction* const sigttin_action_ptr; 41 struct sigaction* const sigttou_action_ptr; 42 } signal_configuration_t; 43 44 static unsigned int verbosity = DEFAULT_VERBOSITY; 45 46 #ifdef PR_SET_CHILD_SUBREAPER 47 #define HAS_SUBREAPER 1 48 #define OPT_STRING "hsvgl" 49 #define SUBREAPER_ENV_VAR "TINI_SUBREAPER" 50 #else 51 #define HAS_SUBREAPER 0 52 #define OPT_STRING "hvgl" 53 #endif 54 55 #define VERBOSITY_ENV_VAR "TINI_VERBOSITY" 56 57 #define TINI_VERSION_STRING "tini version " TINI_VERSION TINI_GIT 58 59 60 #if HAS_SUBREAPER 61 static unsigned int subreaper = 0; 62 #endif 63 static unsigned int kill_process_group = 0; 64 65 static struct timespec ts = { .tv_sec = 1, .tv_nsec = 0 }; 66 67 static const char reaper_warning[] = "Tini is not running as PID 1 " 68 #if HAS_SUBREAPER 69 "and isn't registered as a child subreaper" 70 #endif 71 ".\n\ 72 Zombie processes will not be re-parented to Tini, so zombie reaping won't work.\n\ 73 To fix the problem, " 74 #if HAS_SUBREAPER 75 #ifndef TINI_MINIMAL 76 "use the -s option " 77 #endif 78 "or set the environment variable " SUBREAPER_ENV_VAR " to register Tini as a child subreaper, or " 79 #endif 80 "run Tini as PID 1."; 81 82 int restore_signals(const signal_configuration_t* const sigconf_ptr) { 83 if (sigprocmask(SIG_SETMASK, sigconf_ptr->sigmask_ptr, NULL)) { 84 PRINT_FATAL("Restoring child signal mask failed: '%s'", strerror(errno)); 85 return 1; 86 } 87 88 if (sigaction(SIGTTIN, sigconf_ptr->sigttin_action_ptr, NULL)) { 89 PRINT_FATAL("Restoring SIGTTIN handler failed: '%s'", strerror((errno))); 90 return 1; 91 } 92 93 if (sigaction(SIGTTOU, sigconf_ptr->sigttou_action_ptr, NULL)) { 94 PRINT_FATAL("Restoring SIGTTOU handler failed: '%s'", strerror((errno))); 95 return 1; 96 } 97 98 return 0; 99 } 100 101 int isolate_child() { 102 // Put the child into a new process group. 103 if (setpgid(0, 0) < 0) { 104 PRINT_FATAL("setpgid failed: %s", strerror(errno)); 105 return 1; 106 } 107 108 // If there is a tty, allocate it to this new process group. We 109 // can do this in the child process because we're blocking 110 // SIGTTIN / SIGTTOU. 111 112 // Doing it in the child process avoids a race condition scenario 113 // if Tini is calling Tini (in which case the grandparent may make the 114 // parent the foreground process group, and the actual child ends up... 115 // in the background!) 116 if (tcsetpgrp(STDIN_FILENO, getpgrp())) { 117 if (errno == ENOTTY) { 118 PRINT_DEBUG("tcsetpgrp failed: no tty (ok to proceed)") 119 } else { 120 PRINT_FATAL("tcsetpgrp failed: %s", strerror(errno)); 121 return 1; 122 } 123 } 124 125 return 0; 126 } 127 128 129 int spawn(const signal_configuration_t* const sigconf_ptr, char* const argv[], int* const child_pid_ptr) { 130 pid_t pid; 131 132 // TODO: check if tini was a foreground process to begin with (it's not OK to "steal" the foreground!") 133 134 pid = fork(); 135 if (pid < 0) { 136 PRINT_FATAL("fork failed: %s", strerror(errno)); 137 return 1; 138 } else if (pid == 0) { 139 140 // Put the child in a process group and make it the foreground process if there is a tty. 141 if (isolate_child()) { 142 return 1; 143 } 144 145 // Restore all signal handlers to the way they were before we touched them. 146 if (restore_signals(sigconf_ptr)) { 147 return 1; 148 } 149 150 execvp(argv[0], argv); 151 152 // execvp will only return on an error so make sure that we check the errno 153 // and exit with the correct return status for the error that we encountered 154 // See: http://www.tldp.org/LDP/abs/html/exitcodes.html#EXITCODESREF 155 int status = 1; 156 switch errno { 157 case ENOENT: 158 status = 127; 159 break; 160 case EACCES: 161 status = 126; 162 break; 163 } 164 PRINT_FATAL("exec %s failed: %s", argv[0], strerror(errno)); 165 return status; 166 } else { 167 // Parent 168 PRINT_INFO("Spawned child process '%s' with pid '%i'", argv[0], pid); 169 *child_pid_ptr = pid; 170 return 0; 171 } 172 } 173 174 void print_usage(char* const name, FILE* const file) { 175 fprintf(file, "%s (%s)\n", basename(name), TINI_VERSION_STRING); 176 177 #if TINI_MINIMAL 178 fprintf(file, "Usage: %s PROGRAM [ARGS] | --version\n\n", basename(name)); 179 #else 180 fprintf(file, "Usage: %s [OPTIONS] PROGRAM -- [ARGS] | --version\n\n", basename(name)); 181 #endif 182 fprintf(file, "Execute a program under the supervision of a valid init process (%s)\n\n", basename(name)); 183 184 fprintf(file, "Command line options:\n\n"); 185 186 fprintf(file, " --version: Show version and exit.\n"); 187 188 #if TINI_MINIMAL 189 #else 190 fprintf(file, " -h: Show this help message and exit.\n"); 191 #if HAS_SUBREAPER 192 fprintf(file, " -s: Register as a process subreaper (requires Linux >= 3.4).\n"); 193 #endif 194 fprintf(file, " -v: Generate more verbose output. Repeat up to 3 times.\n"); 195 fprintf(file, " -g: Send signals to the child's process group.\n"); 196 fprintf(file, " -l: Show license and exit.\n"); 197 #endif 198 199 fprintf(file, "\n"); 200 201 fprintf(file, "Environment variables:\n\n"); 202 #if HAS_SUBREAPER 203 fprintf(file, " %s: Register as a process subreaper (requires Linux >= 3.4)\n", SUBREAPER_ENV_VAR); 204 #endif 205 fprintf(file, " %s: Set the verbosity level (default: %d)\n", VERBOSITY_ENV_VAR, DEFAULT_VERBOSITY); 206 207 fprintf(file, "\n"); 208 } 209 210 void print_license(FILE* const file) { 211 if(LICENSE_len > fwrite(LICENSE, sizeof(char), LICENSE_len, file)) { 212 // Don't handle this error for now, since parse_args won't care 213 // about the return value. We do need to check it to compile with 214 // older glibc, though. 215 // See: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=25509 216 // See: http://sourceware.org/bugzilla/show_bug.cgi?id=11959 217 } 218 } 219 220 int parse_args(const int argc, char* const argv[], char* (**child_args_ptr_ptr)[], int* const parse_fail_exitcode_ptr) { 221 char* name = argv[0]; 222 223 // We handle --version if it's the *only* argument provided. 224 if (argc == 2 && strcmp("--version", argv[1]) == 0) { 225 *parse_fail_exitcode_ptr = 0; 226 fprintf(stdout, "%s\n", TINI_VERSION_STRING); 227 return 1; 228 } 229 230 #ifndef TINI_MINIMAL 231 int c; 232 while ((c = getopt(argc, argv, OPT_STRING)) != -1) { 233 switch (c) { 234 case 'h': 235 print_usage(name, stdout); 236 *parse_fail_exitcode_ptr = 0; 237 return 1; 238 #if HAS_SUBREAPER 239 case 's': 240 subreaper++; 241 break; 242 #endif 243 case 'v': 244 verbosity++; 245 break; 246 247 case 'g': 248 kill_process_group++; 249 break; 250 251 case 'l': 252 print_license(stdout); 253 *parse_fail_exitcode_ptr = 0; 254 return 1; 255 256 case '?': 257 print_usage(name, stderr); 258 return 1; 259 default: 260 /* Should never happen */ 261 return 1; 262 } 263 } 264 #endif 265 266 *child_args_ptr_ptr = calloc(argc-optind+1, sizeof(char*)); 267 if (*child_args_ptr_ptr == NULL) { 268 PRINT_FATAL("Failed to allocate memory for child args: '%s'", strerror(errno)); 269 return 1; 270 } 271 272 int i; 273 for (i = 0; i < argc - optind; i++) { 274 (**child_args_ptr_ptr)[i] = argv[optind+i]; 275 } 276 (**child_args_ptr_ptr)[i] = NULL; 277 278 if (i == 0) { 279 /* User forgot to provide args! */ 280 print_usage(name, stderr); 281 return 1; 282 } 283 284 return 0; 285 } 286 287 int parse_env() { 288 #if HAS_SUBREAPER 289 if (getenv(SUBREAPER_ENV_VAR) != NULL) { 290 subreaper++; 291 } 292 #endif 293 294 char* env_verbosity = getenv(VERBOSITY_ENV_VAR); 295 if (env_verbosity != NULL) { 296 verbosity = atoi(env_verbosity); 297 } 298 299 return 0; 300 } 301 302 303 #if HAS_SUBREAPER 304 int register_subreaper () { 305 if (subreaper > 0) { 306 if (prctl(PR_SET_CHILD_SUBREAPER)) { 307 if (errno == EINVAL) { 308 PRINT_FATAL("PR_SET_CHILD_SUBREAPER is unavailable on this platform. Are you using Linux >= 3.4?") 309 } else { 310 PRINT_FATAL("Failed to register as child subreaper: %s", strerror(errno)) 311 } 312 return 1; 313 } else { 314 PRINT_TRACE("Registered as child subreaper"); 315 } 316 } 317 return 0; 318 } 319 #endif 320 321 322 void reaper_check () { 323 /* Check that we can properly reap zombies */ 324 #if HAS_SUBREAPER 325 int bit = 0; 326 #endif 327 328 if (getpid() == 1) { 329 return; 330 } 331 332 #if HAS_SUBREAPER 333 if (prctl(PR_GET_CHILD_SUBREAPER, &bit)) { 334 PRINT_DEBUG("Failed to read child subreaper attribute: %s", strerror(errno)); 335 } else if (bit == 1) { 336 return; 337 } 338 #endif 339 340 PRINT_WARNING(reaper_warning); 341 } 342 343 344 int configure_signals(sigset_t* const parent_sigset_ptr, const signal_configuration_t* const sigconf_ptr) { 345 /* Block all signals that are meant to be collected by the main loop */ 346 if (sigfillset(parent_sigset_ptr)) { 347 PRINT_FATAL("sigfillset failed: '%s'", strerror(errno)); 348 return 1; 349 } 350 351 // These ones shouldn't be collected by the main loop 352 uint i; 353 int signals_for_tini[] = {SIGFPE, SIGILL, SIGSEGV, SIGBUS, SIGABRT, SIGTRAP, SIGSYS, SIGTTIN, SIGTTOU}; 354 for (i = 0; i < ARRAY_LEN(signals_for_tini); i++) { 355 if (sigdelset(parent_sigset_ptr, signals_for_tini[i])) { 356 PRINT_FATAL("sigdelset failed: '%i'", signals_for_tini[i]); 357 return 1; 358 } 359 } 360 361 if (sigprocmask(SIG_SETMASK, parent_sigset_ptr, sigconf_ptr->sigmask_ptr)) { 362 PRINT_FATAL("sigprocmask failed: '%s'", strerror(errno)); 363 return 1; 364 } 365 366 // Handle SIGTTIN and SIGTTOU separately. Since Tini makes the child process group 367 // the foreground process group, there's a chance Tini can end up not controlling the tty. 368 // If TOSTOP is set on the tty, this could block Tini on writing debug messages. We don't 369 // want that. Ignore those signals. 370 struct sigaction ign_action; 371 memset(&ign_action, 0, sizeof ign_action); 372 373 ign_action.sa_handler = SIG_IGN; 374 sigemptyset(&ign_action.sa_mask); 375 376 if (sigaction(SIGTTIN, &ign_action, sigconf_ptr->sigttin_action_ptr)) { 377 PRINT_FATAL("Failed to ignore SIGTTIN"); 378 return 1; 379 } 380 381 if (sigaction(SIGTTOU, &ign_action, sigconf_ptr->sigttou_action_ptr)) { 382 PRINT_FATAL("Failed to ignore SIGTTOU"); 383 return 1; 384 } 385 386 return 0; 387 } 388 389 int wait_and_forward_signal(sigset_t const* const parent_sigset_ptr, pid_t const child_pid) { 390 siginfo_t sig; 391 392 if (sigtimedwait(parent_sigset_ptr, &sig, &ts) == -1) { 393 switch (errno) { 394 case EAGAIN: 395 break; 396 case EINTR: 397 break; 398 default: 399 PRINT_FATAL("Unexpected error in sigtimedwait: '%s'", strerror(errno)); 400 return 1; 401 } 402 } else { 403 /* There is a signal to handle here */ 404 switch (sig.si_signo) { 405 case SIGCHLD: 406 /* Special-cased, as we don't forward SIGCHLD. Instead, we'll 407 * fallthrough to reaping processes. 408 */ 409 PRINT_DEBUG("Received SIGCHLD"); 410 break; 411 default: 412 PRINT_DEBUG("Passing signal: '%s'", strsignal(sig.si_signo)); 413 /* Forward anything else */ 414 if (kill(kill_process_group ? -child_pid : child_pid, sig.si_signo)) { 415 if (errno == ESRCH) { 416 PRINT_WARNING("Child was dead when forwarding signal"); 417 } else { 418 PRINT_FATAL("Unexpected error when forwarding signal: '%s'", strerror(errno)); 419 return 1; 420 } 421 } 422 break; 423 } 424 } 425 426 return 0; 427 } 428 429 int reap_zombies(const pid_t child_pid, int* const child_exitcode_ptr) { 430 pid_t current_pid; 431 int current_status; 432 433 while (1) { 434 current_pid = waitpid(-1, ¤t_status, WNOHANG); 435 436 switch (current_pid) { 437 438 case -1: 439 if (errno == ECHILD) { 440 PRINT_TRACE("No child to wait"); 441 break; 442 } 443 PRINT_FATAL("Error while waiting for pids: '%s'", strerror(errno)); 444 return 1; 445 446 case 0: 447 PRINT_TRACE("No child to reap"); 448 break; 449 450 default: 451 /* A child was reaped. Check whether it's the main one. If it is, then 452 * set the exit_code, which will cause us to exit once we've reaped everyone else. 453 */ 454 PRINT_DEBUG("Reaped child with pid: '%i'", current_pid); 455 if (current_pid == child_pid) { 456 if (WIFEXITED(current_status)) { 457 /* Our process exited normally. */ 458 PRINT_INFO("Main child exited normally (with status '%i')", WEXITSTATUS(current_status)); 459 *child_exitcode_ptr = WEXITSTATUS(current_status); 460 } else if (WIFSIGNALED(current_status)) { 461 /* Our process was terminated. Emulate what sh / bash 462 * would do, which is to return 128 + signal number. 463 */ 464 PRINT_INFO("Main child exited with signal (with signal '%s')", strsignal(WTERMSIG(current_status))); 465 *child_exitcode_ptr = 128 + WTERMSIG(current_status); 466 } else { 467 PRINT_FATAL("Main child exited for unknown reason"); 468 return 1; 469 } 470 } 471 472 // Check if other childs have been reaped. 473 continue; 474 } 475 476 /* If we make it here, that's because we did not continue in the switch case. */ 477 break; 478 } 479 480 return 0; 481 } 482 483 484 int main(int argc, char *argv[]) { 485 pid_t child_pid; 486 487 // Those are passed to functions to get an exitcode back. 488 int child_exitcode = -1; // This isn't a valid exitcode, and lets us tell whether the child has exited. 489 int parse_exitcode = 1; // By default, we exit with 1 if parsing fails. 490 491 /* Parse command line arguments */ 492 char* (*child_args_ptr)[]; 493 int parse_args_ret = parse_args(argc, argv, &child_args_ptr, &parse_exitcode); 494 if (parse_args_ret) { 495 return parse_exitcode; 496 } 497 498 /* Parse environment */ 499 if (parse_env()) { 500 return 1; 501 } 502 503 /* Configure signals */ 504 sigset_t parent_sigset, child_sigset; 505 struct sigaction sigttin_action, sigttou_action; 506 memset(&sigttin_action, 0, sizeof sigttin_action); 507 memset(&sigttou_action, 0, sizeof sigttou_action); 508 509 signal_configuration_t child_sigconf = { 510 .sigmask_ptr = &child_sigset, 511 .sigttin_action_ptr = &sigttin_action, 512 .sigttou_action_ptr = &sigttou_action, 513 }; 514 515 if (configure_signals(&parent_sigset, &child_sigconf)) { 516 return 1; 517 } 518 519 #if HAS_SUBREAPER 520 /* If available and requested, register as a subreaper */ 521 if (register_subreaper()) { 522 return 1; 523 }; 524 #endif 525 526 /* Are we going to reap zombies properly? If not, warn. */ 527 reaper_check(); 528 529 /* Go on */ 530 int spawn_ret = spawn(&child_sigconf, *child_args_ptr, &child_pid); 531 if (spawn_ret) { 532 return spawn_ret; 533 } 534 free(child_args_ptr); 535 536 while (1) { 537 /* Wait for one signal, and forward it */ 538 if (wait_and_forward_signal(&parent_sigset, child_pid)) { 539 return 1; 540 } 541 542 /* Now, reap zombies */ 543 if (reap_zombies(child_pid, &child_exitcode)) { 544 return 1; 545 } 546 547 if (child_exitcode != -1) { 548 PRINT_TRACE("Exiting: child has exited"); 549 return child_exitcode; 550 } 551 } 552 }