Skip to content

Commit

Permalink
Apply krallin#82
Browse files Browse the repository at this point in the history
  • Loading branch information
karteek-hpe committed Feb 7, 2021
1 parent b9f42a0 commit ec95160
Show file tree
Hide file tree
Showing 4 changed files with 151 additions and 5 deletions.
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM ubuntu:xenial
FROM ubuntu:bionic

ARG ARCH_SUFFIX

Expand Down
2 changes: 1 addition & 1 deletion ci/install_deps.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ set -o xtrace
DEPS=(
build-essential git gdb valgrind cmake rpm file
libcap-dev python3-dev python3-pip python3-setuptools
hardening-includes gnupg
devscripts gnupg
)

case "${ARCH_SUFFIX-}" in
Expand Down
121 changes: 118 additions & 3 deletions src/tini.c
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/prctl.h>
#include <sys/stat.h>

#include <assert.h>
#include <errno.h>
Expand All @@ -18,6 +19,8 @@
#include "tiniConfig.h"
#include "tiniLicense.h"

extern char *optarg;

#if TINI_MINIMAL
#define PRINT_FATAL(...) fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n");
#define PRINT_WARNING(...) if (verbosity > 0) { fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n"); }
Expand Down Expand Up @@ -50,6 +53,17 @@ typedef struct {
struct sigaction* const sigttou_action_ptr;
} signal_configuration_t;

struct file_change_t {
const char * filename;
dev_t last_dev;
ino_t last_ino;
time_t last_mtime;
time_t last_ctime;
struct file_change_t *next;
};

typedef struct file_change_t file_change_t;

static const struct {
char *const name;
int number;
Expand Down Expand Up @@ -90,11 +104,11 @@ static int32_t expect_status[(STATUS_MAX - STATUS_MIN + 1) / 32];

#ifdef PR_SET_CHILD_SUBREAPER
#define HAS_SUBREAPER 1
#define OPT_STRING "p:hvwgle:s"
#define OPT_STRING "p:hvwgle:sS:F:"
#define SUBREAPER_ENV_VAR "TINI_SUBREAPER"
#else
#define HAS_SUBREAPER 0
#define OPT_STRING "p:hvwgle:"
#define OPT_STRING "p:hvwgle:sS:F:"
#endif

#define VERBOSITY_ENV_VAR "TINI_VERBOSITY"
Expand All @@ -109,6 +123,9 @@ static unsigned int subreaper = 0;
static unsigned int parent_death_signal = 0;
static unsigned int kill_process_group = 0;

static unsigned int file_change_signal = 1; /* HUP */
static file_change_t *file_change_files = NULL;

static unsigned int warn_on_reap = 0;

static struct timespec ts = { .tv_sec = 1, .tv_nsec = 0 };
Expand Down Expand Up @@ -247,6 +264,10 @@ 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, " -S signal: numeric signal to send to child if '-F' files\n"
" change. (default: 1 => HUP)\n");
fprintf(file, " -F path: file(s) to check for changes for reload signal\n"
" (may be specified more than once).\n");
fprintf(file, " -e EXIT_CODE: Remap EXIT_CODE (from 0 to 255) to 0 (can be repeated).\n");
fprintf(file, " -l: Show license and exit.\n");
#endif
Expand Down Expand Up @@ -305,6 +326,44 @@ int add_expect_status(char* arg) {
return 0;
}

void add_file_change(char* const filename) {
file_change_t *new = NULL, *curr;
struct stat file_st;

// Here we add to a linked list of file_change_t structures
// we layout each "structure" as <struct><filename><nul> in
// the RAM. We expect only a very few - so a linked list here
// is fine.
new = malloc(sizeof(file_change_t) + 1 + strlen(filename));
new->next = NULL;
new->filename = (const char *)(new + 1);
// we break the const once...
strcpy((char *)(new->filename), filename);

if(stat(new->filename, &file_st) == 0) {
new->last_dev = file_st.st_dev;
new->last_ino = file_st.st_ino;
new->last_mtime = file_st.st_mtime;
new->last_ctime = file_st.st_ctime;
} else {
// we produce blank records for files that don't
// exist or are otherwise not accessible to us,
// this way, if they become accessible, we also
// signal the reload.
new->last_dev = 0;
new->last_ino = 0;
new->last_mtime = 0;
new->last_ctime = 0;
}

if(!file_change_files) {
file_change_files = new;
} else {
for (curr = file_change_files; curr->next != NULL; curr = curr->next);
curr->next = new;
}
}

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 All @@ -316,7 +375,7 @@ int parse_args(const int argc, char* const argv[], char* (**child_args_ptr_ptr)[
}

#ifndef TINI_MINIMAL
int c;
int c, tmpi;
while ((c = getopt(argc, argv, OPT_STRING)) != -1) {
switch (c) {
case 'h':
Expand Down Expand Up @@ -356,6 +415,19 @@ int parse_args(const int argc, char* const argv[], char* (**child_args_ptr_ptr)[
}
break;

case 'S':
tmpi = atoi(optarg);
if(tmpi == 0) {
print_usage(name, stderr);
return 1;
}
file_change_signal = tmpi;
break;

case 'F':
add_file_change(optarg);
break;

case 'l':
print_license(stdout);
*parse_fail_exitcode_ptr = 0;
Expand Down Expand Up @@ -603,6 +675,44 @@ int reap_zombies(const pid_t child_pid, int* const child_exitcode_ptr) {
return 0;
}

int kill_on_change_files(pid_t child_pid) {
file_change_t *curr;
struct stat file_st;
char changed = 0;

// We go through our linked list and figure out what changed
for(curr = file_change_files; curr != NULL; curr = curr->next) {
if(stat(curr->filename, &file_st) == 0) {
if( curr->last_dev != file_st.st_dev ||
curr->last_ino != file_st.st_ino ||
curr->last_mtime != file_st.st_mtime ||
curr->last_ctime != file_st.st_ctime ||
0) {
PRINT_DEBUG("Found new/changed file: %s", curr->filename);
changed = 1;
curr->last_dev = file_st.st_dev;
curr->last_ino = file_st.st_ino;
curr->last_mtime = file_st.st_mtime;
curr->last_ctime = file_st.st_ctime;
}
} else if(curr->last_ctime != 0) {
PRINT_DEBUG("Found deleted file: %s", curr->filename);
changed = 1;
curr->last_dev = 0;
curr->last_ino = 0;
curr->last_mtime = 0;
curr->last_ctime = 0;
}
}

if(changed) {
PRINT_INFO("files changed, killing %d with %d", child_pid, file_change_signal);
kill(kill_process_group ? -child_pid : child_pid, file_change_signal);
}

return 0;
}


int main(int argc, char *argv[]) {
pid_t child_pid;
Expand Down Expand Up @@ -668,6 +778,11 @@ int main(int argc, char *argv[]) {
return 1;
}

/* check the files for changes */
if (file_change_files && kill_on_change_files(child_pid)) {
return 1;
}

/* Now, reap zombies */
if (reap_zombies(child_pid, &child_exitcode)) {
return 1;
Expand Down
31 changes: 31 additions & 0 deletions test/run_inner_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,37 @@ def main():
p.send_signal(signal.SIGUSR1)
busy_wait(lambda: p.poll() is not None, 10)

# Run a file-change check test
# This test has Tini spawn a long sleep, similar to above, at which point, we briefly
# sleep ourselves and touch a file underneath
if not args_disabled:
print("Running file-change tests")
t_file = os.path.join(src, "test", ".file_test")
try:
os.unlink(t_file)
except:
pass

p = subprocess.Popen([tini, "-S", "{0}".format(signal.SIGUSR1), "-F", t_file, "sleep", "1000"],
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
busy_wait(lambda: len(psutil.Process(p.pid).children(recursive=True)) == 1, 10)
with open(t_file, 'w') as f:
f.write('{}'.format(time.time()))
busy_wait(lambda: p.poll() is not None, 10)

p = subprocess.Popen([tini, "-S", "{0}".format(signal.SIGUSR1), "-F", t_file, "sleep", "1000"],
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
busy_wait(lambda: len(psutil.Process(p.pid).children(recursive=True)) == 1, 10)
with open(t_file, 'w') as f:
f.write('{}'.format(time.time()))
busy_wait(lambda: p.poll() is not None, 10)

p = subprocess.Popen([tini, "-S", "{0}".format(signal.SIGUSR1), "-F", t_file, "sleep", "1000"],
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
busy_wait(lambda: len(psutil.Process(p.pid).children(recursive=True)) == 1, 10)
os.unlink(t_file)
busy_wait(lambda: p.poll() is not None, 10)

# Run failing test. Force verbosity to 1 so we see the subreaper warning
# regardless of whether MINIMAL is set.
print("Running zombie reaping failure test (Tini should warn)")
Expand Down

0 comments on commit ec95160

Please sign in to comment.