Skip to content

Commit

Permalink
Merge pull request #75 from krallin/expect-status-code
Browse files Browse the repository at this point in the history
Add -e flag to a expect a given exit code
  • Loading branch information
krallin authored Feb 18, 2018
2 parents 3da8adc + 4466cec commit bd22496
Show file tree
Hide file tree
Showing 5 changed files with 108 additions and 4 deletions.
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,22 @@ as PID 1.
and isn't registered as a subreaper. If you don't see a warning, you're fine.*


### Remapping exit codes ###

Tini will reuse the child's exit code when exiting, but occasionally, this may
not be exactly what you want (e.g. if your child exits with 143 after receiving
SIGTERM). Notably, this can be an issue with Java apps.

In this case, you can use the `-e` flag to remap an arbitrary exit code to 0.
You can pass the flag multiple times if needed.

For example:

```
tini -e 143 -- ...
```


### Process group killing ###

By default, Tini only kills its immediate child process. This can be
Expand Down
2 changes: 1 addition & 1 deletion ci/run_build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ fi

echo "CC=${CC}"
echo "CFLAGS=${CFLAGS}"
echo "MINIMAL=${MINIMAL-}"
echo "ARCH_SUFFIX=${ARCH_SUFFIX-}"
echo "ARCH_NATIVE=${ARCH_NATIVE-}"

Expand Down Expand Up @@ -64,7 +65,6 @@ popd

pkg_version="$(cat "${BUILD_DIR}/VERSION")"


if [[ -n "${ARCH_NATIVE-}" ]]; then
echo "Built native package (ARCH_NATIVE=${ARCH_NATIVE-})"
echo "Running smoke and internal tests"
Expand Down
53 changes: 50 additions & 3 deletions src/tini.c
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include <sys/wait.h>
#include <sys/prctl.h>

#include <assert.h>
#include <errno.h>
#include <signal.h>
#include <string.h>
Expand Down Expand Up @@ -33,7 +34,15 @@
#define DEFAULT_VERBOSITY 1
#endif

#define ARRAY_LEN(x) (sizeof(x) / sizeof(x[0]))
#define ARRAY_LEN(x) (sizeof(x) / sizeof((x)[0]))

#define INT32_BITFIELD_SET(F, i) ( F[(i / 32)] |= (1 << (i % 32)) )
#define INT32_BITFIELD_CLEAR(F, i) ( F[(i / 32)] &= ~(1 << (i % 32)) )
#define INT32_BITFIELD_TEST(F, i) ( F[(i / 32)] & (1 << (i % 32)) )
#define INT32_BITFIELD_CHECK_BOUNDS(F, i) do { assert(i >= 0); assert(ARRAY_LEN(F) > (uint) (i / 32)); } while(0)

#define STATUS_MAX 255
#define STATUS_MIN 0

typedef struct {
sigset_t* const sigmask_ptr;
Expand All @@ -43,13 +52,15 @@ typedef struct {

static unsigned int verbosity = DEFAULT_VERBOSITY;

static int32_t expect_status[(STATUS_MAX - STATUS_MIN + 1) / 32];

#ifdef PR_SET_CHILD_SUBREAPER
#define HAS_SUBREAPER 1
#define OPT_STRING "hsvwgl"
#define OPT_STRING "hvwgle:s"
#define SUBREAPER_ENV_VAR "TINI_SUBREAPER"
#else
#define HAS_SUBREAPER 0
#define OPT_STRING "hvwgl"
#define OPT_STRING "hvwgle:"
#endif

#define VERBOSITY_ENV_VAR "TINI_VERBOSITY"
Expand Down Expand Up @@ -199,6 +210,7 @@ void print_usage(char* const name, FILE* const file) {
fprintf(file, " -v: Generate more verbose output. Repeat up to 3 times.\n");
fprintf(file, " -w: Print a warning when processes are getting reaped.\n");
fprintf(file, " -g: Send signals to the child's process group.\n");
fprintf(file, " -e EXIT_CODE: Remap EXIT_CODE (from 0 to 255) to 0.\n");
fprintf(file, " -l: Show license and exit.\n");
#endif

Expand All @@ -223,6 +235,24 @@ void print_license(FILE* const file) {
}
}

int add_expect_status(char* arg) {
long status = 0;
char* endptr = NULL;
status = strtol(arg, &endptr, 10);

if ((endptr == NULL) || (*endptr != 0)) {
return 1;
}

if ((status < STATUS_MIN) || (status > STATUS_MAX)) {
return 1;
}

INT32_BITFIELD_CHECK_BOUNDS(expect_status, status);
INT32_BITFIELD_SET(expect_status, status);
return 0;
}

int parse_args(const int argc, char* const argv[], char* (**child_args_ptr_ptr)[], int* const parse_fail_exitcode_ptr) {
char* name = argv[0];

Expand Down Expand Up @@ -258,6 +288,14 @@ int parse_args(const int argc, char* const argv[], char* (**child_args_ptr_ptr)[
kill_process_group++;
break;

case 'e':
if (add_expect_status(optarg)) {
PRINT_FATAL("Not a valid option for -e: %s", optarg);
*parse_fail_exitcode_ptr = 1;
return 1;
}
break;

case 'l':
print_license(stdout);
*parse_fail_exitcode_ptr = 0;
Expand Down Expand Up @@ -477,6 +515,15 @@ int reap_zombies(const pid_t child_pid, int* const child_exitcode_ptr) {
PRINT_FATAL("Main child exited for unknown reason");
return 1;
}

// Be safe, ensure the status code is indeed between 0 and 255.
*child_exitcode_ptr = *child_exitcode_ptr % (STATUS_MAX - STATUS_MIN + 1);

// If this exitcode was remapped, then set it to 0.
INT32_BITFIELD_CHECK_BOUNDS(expect_status, *child_exitcode_ptr);
if (INT32_BITFIELD_TEST(expect_status, *child_exitcode_ptr)) {
*child_exitcode_ptr = 0;
}
} else if (warn_on_reap > 0) {
PRINT_WARNING("Reaped zombie process with pid=%i", current_pid);
}
Expand Down
25 changes: 25 additions & 0 deletions test/run_inner_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@
import psutil
import bitmap
import re
import itertools

DEVNULL = open(os.devnull, 'wb')

SIGNUM_TO_SIGNAME = dict((v, k) for k,v in signal.__dict__.items() if re.match("^SIG[A-Z]+$", k))

Expand Down Expand Up @@ -36,6 +38,29 @@ def main():

subreaper_support = bool(int(os.environ["FORCE_SUBREAPER"]))

# Run the exit code test. We use POSIXLY_CORRECT here to not need --
# until that's the default in Tini anyways.
if not args_disabled:
print "Running exit code test for {0}".format(tini)
for code in range(0, 256):
p = subprocess.Popen(
[tini, '-e', str(code), '--', 'sh', '-c', "exit {0}".format(code)],
stdout=DEVNULL, stderr=DEVNULL
)
ret = p.wait()
assert ret == 0, "Inclusive exit code test failed for %s, exit: %s" % (code, ret)

other_codes = [x for x in range(0, 256) if x != code]
args = list(itertools.chain(*[['-e', str(x)] for x in other_codes]))

p = subprocess.Popen(
[tini] + args + ['sh', '-c', "exit {0}".format(code)],
env=dict(os.environ, POSIXLY_CORRECT="1"),
stdout=DEVNULL, stderr=DEVNULL
)
ret = p.wait()
assert ret == code, "Exclusive exit code test failed for %s, exit: %s" % (code, ret)

tests = [([proxy, tini], {}),]

if subreaper_support:
Expand Down
16 changes: 16 additions & 0 deletions tpl/README.md.in
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,22 @@ as PID 1.
and isn't registered as a subreaper. If you don't see a warning, you're fine.*


### Remapping exit codes ###

Tini will reuse the child's exit code when exiting, but occasionally, this may
not be exactly what you want (e.g. if your child exits with 143 after receiving
SIGTERM). Notably, this can be an issue with Java apps.

In this case, you can use the `-e` flag to remap an arbitrary exit code to 0.
You can pass the flag multiple times if needed.

For example:

```
tini -e 143 -- ...
```


### Process group killing ###

By default, Tini only kills its immediate child process. This can be
Expand Down

0 comments on commit bd22496

Please sign in to comment.