3

I have an existing service, which I'm not able to modify directly—let's say it's closed source, or too complex to edit, or autoupdates and therefore is not a good target for direct modifications.

I would like to run this cleanly in a Docker container, so I need a single foreground process which should live and die with the service, i.e. starting the process should start the service and when the service terminates (e.g. crashes), the root process should terminate as well, so that the Docker container terminates.

Is there a good pattern or existing tool to do this? Ideally, it would redirect the service's stdout/stderr to its own stdout/stderr.

The service has a regular init script and is started via /etc/init.d/myservice start.

What I've seen quite often is the container starting the service and then using tail -n0 -F on some core log file. That works for giving the container some kind of stdout, but if the service ever crashes, the container just goes silent, keeps running and never outputs anything again. There must be a better way, no?

One example for thinking about this could be svnserve. It actually has the --daemon --foreground option which is officially just for debugging, but works. But what if that didn't exist?

2 Answers 2

1

If the binary is dynamically linked then you can LD_PRELOAD a fork wrapper that does nothing except set a flag the first time it's called, and on subsequent calls sees the flag and acts normally.

If the binary is statically linked then you could ptrace it until its first fork call, skip that call and stop tracing.

On Linux, you can run the daemon in a dedicated PID namespace that just runs a monitoring script as PID 1 in the namespace, and the daemon. When the daemon exits, PID 1 in the namespace, i.e. the monitoring script, receives a SIGCLD.

2
  • It's not necessarily a binary, and even if it is is launched by a complex init script which I don't want to modify (because it auto-updates every week) and which I don't want to make assumptions of, i.e. that it launches a certain executable at its core (again, because that might be different in the next week). The PID namespacing is interesting, although I don't yet quite understand whether I can do that inside a Docker container. I was hoping that there's a helper tool which I can just hand the initialization script and which does the de-daemonizing for me. Jul 6, 2016 at 11:09
  • @hheimbuerger Namespaces nest, so there's nothing about being in a Docker container that would make it harder to use a namespace. Whatever method you choose, how the helper script works may matter: if the helper script itself is doing the daemonizing, it's a different problem. Even if the helper script merely runs the daemon binary, but then does other things, some solutions will cause those other things to be done when the daemon exits, others won't. Jul 6, 2016 at 17:21
0

On Linux, you can run it via a subreaper wrapper, which will act like the pid one / init program for any descendant subprocess, catching and waiting for any double-forked children. Simple example:

$ cc -Wall -s daemon.c -o daemon
$ cc -Wall -s undaemon.c -o undaemon
$ ./daemon sleep 10
   # the parent forks + exits immediately
$ ./undaemon ./daemon sleep 2
   # waits until all children have terminated

The advantage of this is that it doesn't need any extra privileges and will also work with setuid/setgid programs.

Another way to do that by recursively ptrace(2)-ing the children, which could be done with strace(1). However, this it both more heavyweight and may interact in strange ways with the traced programs. And of course, just like PID namespaces or LD_PRELOAD hacks, it doesn't work with setuid programs.

$ strace -fe trace=none ./daemon sleep 2
strace: Process 6743 attached
[pid  6742] +++ exited with 0 +++
+++ exited with 0 +++
  # also waits for all children

undaemon.c:

#define _DEFAULT_SOURCE /* for kill() */
#include <unistd.h>
#include <err.h>
#include <errno.h>
#include <signal.h>
#include <sys/prctl.h>
#include <sys/wait.h>

int main(int argc, char **argv){
        int s;
        if(prctl(PR_SET_CHILD_SUBREAPER, 1))
                err(1, "prctl(PR_SET_CHILD_SUBREAPER)");
        switch(fork()){
        case -1:
                err(1, "fork");
        case 0:
                if(!--argc || !*++argv) return 0;
                execvp(*argv, argv);
                err(1, "execvp %s", *argv);
        default:
                while(wait(&s) != -1 || errno == EINTR);
                if(errno != ECHILD) err(1, "wait");
                if(WIFSIGNALED(s)) kill(getpid(), WTERMSIG(s));
                _exit(WEXITSTATUS(s));
        }
}

daemon.c:

#define _DEFAULT_SOURCE
#include <unistd.h>
#include <stdlib.h>
#include <err.h>
int main(int argc, char **argv){
        if(daemon(1, 1)) err(1, "daemon");
        if(!--argc || !*++argv) exit(0);
        execvp(*argv, argv);
        err(1, "execvp %s", *argv);
}

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .