mirror of https://github.com/krallin/tini.git
Refactor further around functions
This commit is contained in:
parent
191dd0a97f
commit
fa8200cc1c
|
@ -1,6 +1,8 @@
|
|||
FROM ubuntu
|
||||
|
||||
RUN apt-get update && apt-get install -y build-essential && rm -rf /var/lib/apt/lists/*
|
||||
RUN apt-get update && apt-get install --no-install-recommends -y build-essential gdb && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
ADD . /tini
|
||||
RUN cd /tini && make
|
||||
|
||||
ENTRYPOINT ["/tini/tini"]
|
||||
|
|
1
Makefile
1
Makefile
|
@ -11,6 +11,7 @@ $(BIN): $(OBJ)
|
|||
$(OBJ):
|
||||
|
||||
check:
|
||||
docker build -t tini .
|
||||
python test/test.py
|
||||
|
||||
install: all
|
||||
|
|
|
@ -41,8 +41,9 @@ Debugging
|
|||
---------
|
||||
|
||||
If something isn't working just like you expect, consider increasing the
|
||||
verbosity level (up to 3):
|
||||
verbosity level (up to 4):
|
||||
|
||||
tini -v -- bash -c 'exit 1'
|
||||
tini -vv -- true
|
||||
tini -vvv -- pwd
|
||||
tini -v -- bash -c 'exit 1'
|
||||
tini -vv -- true
|
||||
tini -vvv -- pwd
|
||||
tini -vvvv -- ls
|
||||
|
|
69
test/test.py
69
test/test.py
|
@ -1,33 +1,57 @@
|
|||
#coding:utf-8
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import subprocess
|
||||
import threading
|
||||
|
||||
|
||||
class Command(object):
|
||||
def __init__(self, cmd, post_cmd=None, post_delay=None):
|
||||
def __init__(self, cmd, fail_cmd, post_cmd=None, post_delay=0):
|
||||
self.cmd = cmd
|
||||
self.fail_cmd = fail_cmd
|
||||
self.post_cmd = post_cmd
|
||||
self.post_delay = post_delay
|
||||
self._process = None
|
||||
self.proc = None
|
||||
|
||||
def run(self, timeout=None, retcode=None):
|
||||
print "Testing...", self.cmd,
|
||||
sys.stdout.flush()
|
||||
|
||||
err = None
|
||||
pipe_kwargs = {"stdout": subprocess.PIPE, "stderr": subprocess.PIPE, "stdin": subprocess.PIPE}
|
||||
|
||||
def run(self, timeout):
|
||||
def target():
|
||||
self._process = subprocess.Popen(self.cmd)
|
||||
self._process.communicate()
|
||||
self.proc = subprocess.Popen(self.cmd, **pipe_kwargs)
|
||||
self.proc.communicate()
|
||||
|
||||
thread = threading.Thread(target=target)
|
||||
thread.start()
|
||||
|
||||
if self.post_cmd is not None:
|
||||
if self.post_delay is not None:
|
||||
time.sleep(self.post_delay)
|
||||
subprocess.check_call(self.post_cmd)
|
||||
time.sleep(self.post_delay)
|
||||
subprocess.check_call(self.post_cmd, **pipe_kwargs)
|
||||
|
||||
thread.join(timeout)
|
||||
thread.join(timeout - self.post_delay if timeout is not None else timeout)
|
||||
|
||||
# Checks
|
||||
if thread.is_alive():
|
||||
raise Exception("Test failed!")
|
||||
subprocess.check_call(self.fail_cmd, **pipe_kwargs)
|
||||
err = Exception("Test failed with timeout!")
|
||||
|
||||
elif retcode is not None and self.proc.returncode != retcode:
|
||||
err = Exception("Test failed with unexpected returncode (expected {0}, got {1})".format(retcode, self.proc.returncode))
|
||||
|
||||
if err is not None:
|
||||
print "FAIL"
|
||||
print "--- STDOUT ---"
|
||||
print out
|
||||
print "--- STDERR ---"
|
||||
print err
|
||||
print "--- ... ---"
|
||||
raise err
|
||||
else:
|
||||
print "OK"
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
@ -37,24 +61,27 @@ if __name__ == "__main__":
|
|||
base_cmd = [
|
||||
"docker",
|
||||
"run",
|
||||
"-it",
|
||||
"--rm",
|
||||
"--name=tini-test",
|
||||
"-v",
|
||||
"{0}:{0}".format(root),
|
||||
"ubuntu",
|
||||
"{0}/tini".format(root),
|
||||
"-vvv",
|
||||
"--",
|
||||
"tini",
|
||||
"-vvvv",
|
||||
]
|
||||
|
||||
fail_cmd = ["docker", "kill", "tini-test"]
|
||||
|
||||
# Reaping test
|
||||
Command(base_cmd + ["/Users/thomas/dev/tini/test/reaping/stage_1.py"]).run(timeout=10)
|
||||
Command(base_cmd + ["/tini/test/reaping/stage_1.py"], fail_cmd).run(timeout=10)
|
||||
|
||||
# Signals test
|
||||
for sig in ["INT", "TERM"]:
|
||||
for sig, retcode in [("INT", 1), ("TERM", 143)]:
|
||||
Command(
|
||||
base_cmd + ["/Users/thomas/dev/tini/test/signals/test.py"],
|
||||
base_cmd + ["--", "/tini/test/signals/test.py"],
|
||||
fail_cmd,
|
||||
["docker", "kill", "-s", sig, "tini-test"],
|
||||
2
|
||||
).run(timeout=10)
|
||||
).run(timeout=10, retcode=retcode)
|
||||
|
||||
# Exit code test
|
||||
c = Command(base_cmd + ["-z"], fail_cmd).run(retcode=1)
|
||||
c = Command(base_cmd + ["zzzz"], fail_cmd).run(retcode=1)
|
||||
c = Command(base_cmd + ["-h"], fail_cmd).run(retcode=0)
|
||||
|
|
229
tini.c
229
tini.c
|
@ -9,19 +9,23 @@
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
|
||||
#define PRINT_FATAL(...) fprintf(stderr, "[FATAL] "); fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n");
|
||||
#define PRINT_WARNING(...) if (verbosity > 0) { fprintf(stderr, "[WARN ] "); fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n"); }
|
||||
#define PRINT_INFO(...) if (verbosity > 1) { fprintf(stderr, "[INFO ] "); fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n"); }
|
||||
#define PRINT_DEBUG(...) if (verbosity > 2) { fprintf(stderr, "[DEBUG] "); fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n"); }
|
||||
#define PRINT_TRACE(...) if (verbosity > 3) { fprintf(stderr, "[TRACE] "); fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n"); }
|
||||
|
||||
#define ARRAY_LEN(x) (sizeof(x) / sizeof(x[0]))
|
||||
|
||||
|
||||
static int verbosity = 0;
|
||||
static struct timespec ts = { .tv_sec = 1, .tv_nsec = 0 };
|
||||
|
||||
|
||||
pid_t spawn(sigset_t *child_sigset_ptr, char (*argv[])) {
|
||||
pid_t spawn(const sigset_t* const child_sigset_ptr, char (*argv[])) {
|
||||
/* TODO - Don't exit here! */
|
||||
pid_t pid;
|
||||
|
||||
|
@ -36,35 +40,41 @@ pid_t spawn(sigset_t *child_sigset_ptr, char (*argv[])) {
|
|||
_exit(1);
|
||||
}
|
||||
execvp(argv[0], argv);
|
||||
PRINT_FATAL("Executing child process failed: '%s'", strerror(errno));
|
||||
PRINT_FATAL("Executing child process '%s' failed: '%s'", argv[0], strerror(errno));
|
||||
_exit(1);
|
||||
} else {
|
||||
// Parent
|
||||
PRINT_INFO("Spawned child process '%s' with pid '%d'", argv[0], pid);
|
||||
PRINT_INFO("Spawned child process '%s' with pid '%i'", argv[0], pid);
|
||||
return pid;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void print_usage(const char *name, FILE *file, const int status) {
|
||||
void print_usage(char* const name, FILE* const file) {
|
||||
fprintf(file, "Usage: %s [-h | program arg1 arg2]\n", name);
|
||||
}
|
||||
|
||||
|
||||
int parse_args(int argc, char *argv[], char* (**child_args_ptr_ptr)[]) {
|
||||
int parse_args(const int argc, char* const argv[], char* (**child_args_ptr_ptr)[], int* const parse_fail_exitcode_ptr) {
|
||||
/*
|
||||
* Returns with 0 to indicate success, a positive value to indicate the process
|
||||
* should exit with success, and -1 to indicate it should exit with a failure.
|
||||
*/
|
||||
char* name = argv[0];
|
||||
|
||||
int c;
|
||||
while ((c = getopt (argc, argv, "hv")) != -1) {
|
||||
switch (c) {
|
||||
case 'h':
|
||||
print_usage(name, stdout, 0);
|
||||
/* TODO - Shouldn't cause exit with -1 ..*/
|
||||
print_usage(name, stdout);
|
||||
*parse_fail_exitcode_ptr = 0;
|
||||
return 1;
|
||||
case 'v':
|
||||
verbosity++;
|
||||
return 1;
|
||||
break;
|
||||
case '?':
|
||||
print_usage(name, stderr, 1);
|
||||
print_usage(name, stderr);
|
||||
return 1;
|
||||
default:
|
||||
/* Should never happen */
|
||||
|
@ -86,25 +96,25 @@ int parse_args(int argc, char *argv[], char* (**child_args_ptr_ptr)[]) {
|
|||
|
||||
if (i == 0) {
|
||||
/* User forgot to provide args! */
|
||||
print_usage(name, stdout, 1);
|
||||
print_usage(name, stderr);
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int prepare_sigmask(sigset_t *parent_sigset_ptr, sigset_t *child_sigset_ptr) {
|
||||
int prepare_sigmask(sigset_t* const parent_sigset_ptr, sigset_t* const child_sigset_ptr) {
|
||||
/* Prepare signals to block; make sure we don't block program error signals. */
|
||||
if (sigfillset(parent_sigset_ptr)) {
|
||||
PRINT_FATAL("sigfillset failed: '%s'", strerror(errno));
|
||||
return 1;
|
||||
}
|
||||
|
||||
int i;
|
||||
int ignore_signals[] = {SIGFPE, SIGILL, SIGSEGV, SIGBUS, SIGABRT, SIGIOT, SIGTRAP, SIGEMT, SIGSYS} ;
|
||||
uint i;
|
||||
int ignore_signals[] = {SIGFPE, SIGILL, SIGSEGV, SIGBUS, SIGABRT, SIGTRAP, SIGSYS} ;
|
||||
for (i = 0; i < ARRAY_LEN(ignore_signals); i++) {
|
||||
if (sigdelset(parent_sigset_ptr, ignore_signals[i])) {
|
||||
PRINT_FATAL("sigdelset failed: '%d'", ignore_signals[i]);
|
||||
PRINT_FATAL("sigdelset failed: '%i'", ignore_signals[i]);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
@ -117,20 +127,104 @@ int prepare_sigmask(sigset_t *parent_sigset_ptr, sigset_t *child_sigset_ptr) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
int wait_and_forward_signal(sigset_t const* const parent_sigset_ptr, pid_t const child_pid) {
|
||||
siginfo_t sig;
|
||||
|
||||
pid_t child_pid;
|
||||
if (sigtimedwait(parent_sigset_ptr, &sig, &ts) == -1) {
|
||||
switch (errno) {
|
||||
case EAGAIN:
|
||||
break;
|
||||
case EINTR:
|
||||
break;
|
||||
case EINVAL:
|
||||
PRINT_FATAL("EINVAL on sigtimedwait!");
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
/* There is a signal to handle here */
|
||||
switch (sig.si_signo) {
|
||||
case SIGCHLD:
|
||||
/* Special-cased, as we don't forward SIGCHLD. Instead, we'll
|
||||
* fallthrough to reaping processes.
|
||||
*/
|
||||
PRINT_DEBUG("Received SIGCHLD");
|
||||
break;
|
||||
default:
|
||||
PRINT_DEBUG("Passing signal: '%s'", strsignal(sig.si_signo));
|
||||
/* Forward anything else */
|
||||
kill(child_pid, sig.si_signo); // TODO - Check retcode!
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int reap_zombies(const pid_t child_pid, int* const child_exitcode_ptr) {
|
||||
/*
|
||||
* Returns:
|
||||
* + = 0: The iteration completed successfully, but the child is still alive.
|
||||
* + > 0: The iteration completed successfully, and the child was reaped.
|
||||
* + < 0: An error occured
|
||||
*/
|
||||
pid_t current_pid;
|
||||
int current_status;
|
||||
|
||||
int exit_code = -1;
|
||||
while (1) {
|
||||
current_pid = waitpid(-1, ¤t_status, WNOHANG);
|
||||
|
||||
struct timespec ts;
|
||||
ts.tv_sec = 1;
|
||||
ts.tv_nsec = 0;
|
||||
switch (current_pid) {
|
||||
|
||||
case -1:
|
||||
if (errno == ECHILD) {
|
||||
PRINT_TRACE("No child to wait.");
|
||||
break;
|
||||
}
|
||||
PRINT_FATAL("Error while waiting for pids: '%s'", strerror(errno));
|
||||
return -1;
|
||||
|
||||
case 0:
|
||||
PRINT_TRACE("No child to reap.");
|
||||
break;
|
||||
|
||||
default:
|
||||
/* A child was reaped. Check whether it's the main one. If it is, then
|
||||
* set the exit_code, which will cause us to exit once we've reaped everyone else.
|
||||
*/
|
||||
PRINT_DEBUG("Reaped child with pid: '%i'", current_pid);
|
||||
if (current_pid == child_pid) {
|
||||
if (WIFEXITED(current_status)) {
|
||||
/* Our process exited normally. */
|
||||
PRINT_INFO("Main child exited normally (with status '%i')", WEXITSTATUS(current_status));
|
||||
*child_exitcode_ptr = WEXITSTATUS(current_status);
|
||||
} else if (WIFSIGNALED(current_status)) {
|
||||
/* Our process was terminated. Emulate what sh / bash
|
||||
* would do, which is to return 128 + signal number.
|
||||
*/
|
||||
PRINT_INFO("Main child exited with signal (with signal '%s')", strsignal(WTERMSIG(current_status)));
|
||||
*child_exitcode_ptr = 128 + WTERMSIG(current_status);
|
||||
} else {
|
||||
PRINT_FATAL("Main child exited for unknown reason!");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if other childs have been reaped.
|
||||
continue;
|
||||
}
|
||||
|
||||
/* If we make it here, that's because we did not continue in the switch case. */
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
pid_t child_pid;
|
||||
int child_exitcode = -1;
|
||||
int parse_exitcode = 1; // By default, we exit with 1 if parsing fails
|
||||
|
||||
/* Prepare sigmask */
|
||||
sigset_t parent_sigset;
|
||||
|
@ -139,96 +233,31 @@ int main(int argc, char *argv[]) {
|
|||
return 1;
|
||||
}
|
||||
|
||||
/* Spawn the main command */
|
||||
/* Parse command line arguments */
|
||||
char* (*child_args_ptr)[];
|
||||
if (parse_args(argc, argv, &child_args_ptr)) {
|
||||
return 1;
|
||||
int parse_args_ret = parse_args(argc, argv, &child_args_ptr, &parse_exitcode);
|
||||
if (parse_args_ret) {
|
||||
return parse_exitcode;
|
||||
}
|
||||
|
||||
child_pid = spawn(&child_sigset, *child_args_ptr);
|
||||
free(child_args_ptr);
|
||||
|
||||
/* Loop forever:
|
||||
* - Reap zombies
|
||||
* - Forward signals
|
||||
*/
|
||||
while (1) {
|
||||
if (sigtimedwait(&parent_sigset, &sig, &ts) == -1) {
|
||||
switch (errno) {
|
||||
case EAGAIN:
|
||||
break;
|
||||
case EINTR:
|
||||
break;
|
||||
case EINVAL:
|
||||
PRINT_FATAL("EINVAL on sigtimedwait!");
|
||||
return 2;
|
||||
}
|
||||
} else {
|
||||
/* There is a signal to handle here */
|
||||
switch (sig.si_signo) {
|
||||
case SIGCHLD:
|
||||
/* Special-cased, as we don't forward SIGCHLD. Instead, we'll
|
||||
* fallthrough to reaping processes.
|
||||
*/
|
||||
PRINT_DEBUG("Received SIGCHLD");
|
||||
break;
|
||||
default:
|
||||
PRINT_DEBUG("Passing signal: '%s'", strsignal(sig.si_signo));
|
||||
/* Forward anything else */
|
||||
kill(child_pid, sig.si_signo);
|
||||
break;
|
||||
}
|
||||
/* Wait for one signal, and forward it */
|
||||
if (wait_and_forward_signal(&parent_sigset, child_pid)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Now, reap zombies */
|
||||
while (1) {
|
||||
current_pid = waitpid(-1, ¤t_status, WNOHANG);
|
||||
switch (current_pid) {
|
||||
case -1:
|
||||
if (errno == ECHILD) {
|
||||
// No childs to wait. Let's break out of the loop.
|
||||
break;
|
||||
}
|
||||
/* An unknown error occured. Print it and exit. */
|
||||
PRINT_FATAL("Error while waiting for pids: '%s'", strerror(errno));
|
||||
return 1;
|
||||
case 0:
|
||||
/* No child to reap. We'll break out of the loop here. */
|
||||
break;
|
||||
default:
|
||||
/* A child was reaped. Check whether it's the main one. If it is, then
|
||||
* set the exit_code, which will cause us to exit once we've reaped everyone else.
|
||||
*/
|
||||
PRINT_DEBUG("Reaped child with pid: '%i'", current_pid);
|
||||
if (current_pid == child_pid) {
|
||||
if (WIFEXITED(current_status)) {
|
||||
/* Our process exited normally. */
|
||||
PRINT_INFO("Main child exited normally (with status '%i')", WEXITSTATUS(current_status));
|
||||
exit_code = WEXITSTATUS(current_status);
|
||||
} else if (WIFSIGNALED(current_status)) {
|
||||
/* Our process was terminated. Emulate what sh / bash
|
||||
* would do, which is to return 128 + signal number.
|
||||
*/
|
||||
PRINT_INFO("Main child exited with signal (with signal '%s')", strsignal(WTERMSIG(current_status)));
|
||||
exit_code = 128 + WTERMSIG(current_status);
|
||||
} else {
|
||||
PRINT_FATAL("Main child exited for unknown reason!");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
/* If exit_code is not equal to -1, then we're exiting because our main child has exited */
|
||||
if (exit_code != -1 ) {
|
||||
return exit_code;
|
||||
}
|
||||
|
||||
/* If we make it here, that's because we did not continue in the switch case. */
|
||||
break;
|
||||
if (reap_zombies(child_pid, &child_exitcode)) {
|
||||
// Oops!
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (child_exitcode != -1) {
|
||||
PRINT_TRACE("Child has exited. Exiting");
|
||||
return child_exitcode;
|
||||
}
|
||||
}
|
||||
/* not reachable */
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue