diff --git a/src/cli/proot.c b/src/cli/proot.c index e46d87e5..90bd03b8 100644 --- a/src/cli/proot.c +++ b/src/cli/proot.c @@ -174,6 +174,12 @@ static int handle_option_0(Tracee *tracee, const Cli *cli, const char *value UNU return handle_option_i(tracee, cli, "0:0"); } +static int handle_option_kill_on_exit(Tracee *tracee, const Cli *cli UNUSED, const char *value UNUSED) +{ + tracee->killall_on_exit = true; + return 0; +} + static int handle_option_v(Tracee *tracee, const Cli *cli UNUSED, const char *value) { int status; diff --git a/src/cli/proot.h b/src/cli/proot.h index a06abd71..021eb299 100644 --- a/src/cli/proot.h +++ b/src/cli/proot.h @@ -60,6 +60,7 @@ static int handle_option_0(Tracee *tracee, const Cli *cli, const char *value); static int handle_option_i(Tracee *tracee, const Cli *cli, const char *value); static int handle_option_R(Tracee *tracee, const Cli *cli, const char *value); static int handle_option_S(Tracee *tracee, const Cli *cli, const char *value); +static int handle_option_kill_on_exit(Tracee *tracee, const Cli *cli, const char *value); static int pre_initialize_bindings(Tracee *, const Cli *, size_t, char *const *, size_t); static int post_initialize_exe(Tracee *, const Cli *, size_t, char *const *, size_t); @@ -142,6 +143,16 @@ Copyright (C) 2014 STMicroelectronics, licensed under GPL v2 or later.", .detail = "\tSome programs expect to be launched from a given directory but do\n\ \tnot perform any chdir by themselves. This option avoids the\n\ \tneed for running a shell and then entering the directory manually.", + }, + { .class = "Regular options", + .arguments = { + { .name = "--kill-on-exit", .separator = '\0', .value = NULL }, + { .name = NULL, .separator = '\0', .value = NULL } }, + .handler = handle_option_kill_on_exit, + .description = "Kill all processes on command exit.", + .detail = "\tWhen the executed command leaves orphean or detached processes\n\ +\taround, proot waits until all processes possibly terminate. This option forces\n\ +\tthe immediate termination of all tracee processes when the main command exits.", }, { .class = "Regular options", .arguments = { diff --git a/src/tracee/event.c b/src/tracee/event.c index bafc3c5e..707df636 100644 --- a/src/tracee/event.c +++ b/src/tracee/event.c @@ -389,13 +389,13 @@ int handle_tracee_event(Tracee *tracee, int tracee_status) if (WIFEXITED(tracee_status)) { last_exit_status = WEXITSTATUS(tracee_status); VERBOSE(tracee, 1, "pid %d: exited with status %d", pid, last_exit_status); - tracee->terminated = true; + terminate_tracee(tracee); } else if (WIFSIGNALED(tracee_status)) { check_architecture(tracee); VERBOSE(tracee, (int) (last_exit_status != -1), "pid %d: terminated with signal %d", pid, WTERMSIG(tracee_status)); - tracee->terminated = true; + terminate_tracee(tracee); } else if (WIFSTOPPED(tracee_status)) { /* Don't use WSTOPSIG() to extract the signal diff --git a/src/tracee/tracee.c b/src/tracee/tracee.c index aa172940..e872d88e 100644 --- a/src/tracee/tracee.c +++ b/src/tracee/tracee.c @@ -335,6 +335,22 @@ Tracee *get_tracee(const Tracee *current_tracee, pid_t pid, bool create) return (create ? new_tracee(pid) : NULL); } +/** + * Mark tracee as terminated and optionally take action. + */ +void terminate_tracee(Tracee *tracee) +{ + tracee->terminated = true; + + /* Case where the terminated tracee is marked + to kill all tracees on exit. + */ + if (tracee->killall_on_exit) { + VERBOSE(tracee, 1, "terminating all tracees on exit"); + kill_all_tracees(); + } +} + /** * Free all tracees marked as terminated. */ diff --git a/src/tracee/tracee.h b/src/tracee/tracee.h index f4c3a239..e098b92d 100644 --- a/src/tracee/tracee.h +++ b/src/tracee/tracee.h @@ -89,6 +89,10 @@ typedef struct tracee { * dedicated to terminated tracees instead. */ bool terminated; + /* Whether termination of this tracee implies an immediate kill + * of all tracees. */ + bool killall_on_exit; + /* Parent of this tracee, NULL if none. */ struct tracee *parent; @@ -266,6 +270,7 @@ extern Tracee *get_stopped_ptracee(const Tracee *ptracer, pid_t pid, extern bool has_ptracees(const Tracee *ptracer, pid_t pid, word_t wait_options); extern int new_child(Tracee *parent, word_t clone_flags); extern Tracee *new_dummy_tracee(TALLOC_CTX *context); +extern void terminate_tracee(Tracee *tracee); extern void free_terminated_tracees(); extern int swap_config(Tracee *tracee1, Tracee *tracee2); extern void kill_all_tracees(); diff --git a/tests/test-killexit.sh b/tests/test-killexit.sh new file mode 100644 index 00000000..cde92879 --- /dev/null +++ b/tests/test-killexit.sh @@ -0,0 +1,23 @@ +if [ -z `which setsid` ]; then + exit 125 +fi + +cleanup() { + _code=$? + trap - INT TERM EXIT + [ ! -f "${tmpfile-}" ] || rm -f "$tmpfile" + exit $_code +} +trap cleanup INT TERM EXIT + +tmpfile=`mktemp` + +# Check that kill on exit option is recognized +${PROOT} --kill-on-exit true + +# Check that detached sleep does not block proot +# I.e. in the file we must have "success" first, not "fail" +${PROOT} --kill-on-exit sh -c "setsid sh -c \"sleep 2; echo fail >>$tmpfile\" &" +echo "success" >>$tmpfile +read status <$tmpfile +[ "$status" = success ] || exit 1