Package: dpkg
Version: 1.15.5.6ubuntu4.3
Severity: wishlist
Tags: patch

Hi,

the attached patch adds the functionality to start-stop-daemon that makes it 
capable
of tracking and detecting the PID of programs it starts and which fork 
themselves. The 
manpage entries for the new arguments summarize the feature quite well:

-T, --trace-pid
    Can be used to trace the pid of programs that fork themselves but do not 
provide a
    pidfile that can be used. The location of the pidfile has to be provided 
using the
    --pidfile parameter and you have to specify either --expect-fork or 
--expect-daemon
     to mark which forking method the program uses. 

-F, --expect-fork
    Can be used along with --trace-pid to mark that the program does a single 
fork. Note 
    that if you specify this parameter but the program does a double fork, the 
pidfile will 
    contain the wrong value. 

-D, --expect-daemon
    Can be used along with --trace-pid to mark that the program does a double 
(daemon) fork. 
    Note that if you specify this parameter but the program does a single fork, 
start-stop-daemon 
    will wait forever for the second fork to happen. 

This feature could be used in two scenarios:

1) the program forks itself but it cannot output its PID into a file
2) it is not feasible to call the program a way that makes it output its pidfile
   (OK, I admit this does not sound so common, but it was actually this case 
that
    made me write this addition -- see 
http://gyp.blogs.balabit.com/2011/01/using-upstart-in-a-chroot/
    for details, if you're interested, but it's 99% unrelated to 
start-stop-daemon)

In both cases, start-stop-daemon can now be used to save the PID of the 
process, and
so --stop can work reliably.

I've found a proposed patch in bug #35117 back from 1999 that seems to address 
the same
problem, although in a different way, but it seems like it's been pretty much 
abandoned since then.

Also, please note that the dpkg and other package versions reported by 
reportbug are totally
irrelevant, I'm sending this report from my workstation but the patch is based 
off the dpkg HEAD
fetched from https://alioth.debian.org/anonscm/git/dpkg/dpkg.git The code's 
only been tested on i386 and
amd64 Ubuntu Lucids, though.

-- System Information:
Debian Release: squeeze/sid
  APT prefers lucid-updates
  APT policy: (500, 'lucid-updates'), (500, 'lucid-security'), (500, 'lucid')
Architecture: amd64 (x86_64)

Kernel: Linux 2.6.32-26-generic (SMP w/4 CPU cores)
Locale: LANG=en_US.UTF-8, LC_CTYPE=en_US.UTF-8 (charmap=UTF-8)
Shell: /bin/sh linked to /bin/dash

Versions of packages dpkg depends on:
ii  coreutils              7.4-2ubuntu2      The GNU core utilities
ii  libc6                  2.11.1-0ubuntu7.5 Embedded GNU C Library: Shared lib
ii  lzma                   4.43-14ubuntu2    Compression method of 7z format in

dpkg recommends no packages.

Versions of packages dpkg suggests:
ii  apt                    0.7.25.3ubuntu9.1 Advanced front-end for dpkg

-- no debconf information

*** /home/gyp/tmp/dpkg_tracepid_patch.diff
commit 719becf0340b8451a5d1f56e8956290932fdfb5f
Author: Peter Gyongyosi <[email protected]>
Date:   Fri Jan 21 17:10:33 2011 +0100

    added --trace-pid functionality to track PID of forking programs
    
    Signed-off-by: Peter Gyongyosi <[email protected]>

diff --git a/man/start-stop-daemon.8 b/man/start-stop-daemon.8
index 294fabc..eb47011 100644
--- a/man/start-stop-daemon.8
+++ b/man/start-stop-daemon.8
@@ -248,8 +248,36 @@ This feature may not work in all cases. Most notably when 
the program
 being executed forks from its main process. Because of this, it is usually
 only useful when combined with the
 .B \-\-background
+option. If the program forks itself but you still want to trace the pid,
+consider using the
+.B \-\-trace\-pid
 option.
 .TP
+.BR \-T ", " \-\-trace-pid
+Can be used to trace the pid of programs that fork themselves but do not
+provide a pidfile that can be used. The location of the pidfile has to be 
+provided using the
+.B \-\-pidfile
+parameter and you have to specify either
+.B \-\-expect-fork
+or
+.B \-\-expect-daemon
+to mark which forking method the program uses.
+.TP
+.BR \-F ", " \-\-expect\-fork
+Can be used along with 
+.B \-\-trace-pid
+to mark that the program does a single fork. Note that if you specify this
+parameter but the program does a double fork, the pidfile will contain the
+wrong value.
+.TP
+.BR \-D ", " \-\-expect\-daemon
+Can be used along with 
+.B \-\-trace-pid
+to mark that the program does a double (daemon) fork. Note that if you specify 
+this parameter but the program does a single fork, start-stop-daemon will wait
+forever for the second fork to happen.
+.TP
 .BR \-v ", " \-\-verbose
 Print verbose informational messages.
 .
diff --git a/utils/start-stop-daemon.c b/utils/start-stop-daemon.c
index adeafae..8d442bd 100644
--- a/utils/start-stop-daemon.c
+++ b/utils/start-stop-daemon.c
@@ -18,6 +18,8 @@
  *                 and Andreas Schuldei <[email protected]>
  *
  * Changes by Ian Jackson: added --retry (and associated rearrangements).
+ *
+ * Changes by Peter Gyongyosi <[email protected]>: added tracepid functionality.
  */
 
 #include <config.h>
@@ -107,6 +109,10 @@
 #include <error.h>
 #endif
 
+#include <libgen.h>
+#include <sys/ptrace.h>
+#include <sys/wait.h>
+
 #ifdef _POSIX_PRIORITY_SCHEDULING
 #include <sched.h>
 #else
@@ -144,6 +150,8 @@ static int start = 0;
 static int stop = 0;
 static int background = 0;
 static int mpidfile = 0;
+static int tracepid = 0;
+static int expected_forks = 0;
 static int signal_nr = SIGTERM;
 static int user_id = -1;
 static int runas_uid = -1;
@@ -361,6 +369,152 @@ pid_list_free(struct pid_list **list)
        *list = NULL;
 }
 
+
+static void
+write_pid(pid_t pidt)
+{
+  FILE *pidf = fopen(pidfile, "w");
+  if (pidf == NULL)
+          fatal("Unable to open pidfile '%s' for writing: %s",
+                pidfile, strerror(errno));
+  fprintf(pidf, "%d\n", pidt);
+  if (fflush(pidf))
+          fatal("unable to flush pidfile '%s'", pidfile);
+  if (fsync(fileno(pidf)))
+          fatal("unable to sync pidfile '%s'", pidfile);
+  fclose(pidf);
+}
+
+static void
+start_trace(pid_t pid)
+{
+       wait(NULL);
+
+       int retries;
+       const int max_retries = 10;
+
+       for (retries = 0; retries < max_retries; ++retries) {
+               if (ptrace (PTRACE_SETOPTIONS, pid, NULL,
+                                       PTRACE_O_TRACEFORK) >= 0) {
+                       ptrace(PTRACE_CONT, pid, NULL, 0);
+                       return;
+               }
+
+               // it's failed, try it again a bit later
+               if (retries < max_retries -1)
+                       sleep(1);
+       }
+
+       fatal("Error setting trace options: %s (pid: %d)\n", strerror(errno), 
pid);
+       exit(1);
+}
+
+static void
+detach_trace(pid_t pid)
+{
+       int retries;
+       const int max_retries = 10;
+
+       for (retries = 0; retries < max_retries; ++retries) {
+               if (ptrace (PTRACE_DETACH, pid, NULL, 0) >= 0)
+                       return;
+
+               // it's failed, try it again a bit later
+               if (retries < max_retries -1)
+                       sleep(1);
+       }
+
+       // NOTE: we don't treat it as a fatal error as it could happen in some 
race-y situations
+       warning("Error detaching from process: %s (pid: %d)\n", 
strerror(errno), pid);
+
+}
+
+static void
+continue_trace(pid_t pid)
+{
+       if (ptrace(PTRACE_CONT, pid, NULL, 0) < 0) {
+               warning("Error continuing process: %s (pid: %d)\n", 
strerror(errno), pid);
+               // NOTE: we don't treat it as a fatal error as it could happen 
in some race-y situations
+       }
+}
+
+static pid_t
+get_child_pid(pid_t pid)
+{
+       unsigned long ptrace_data;
+
+       if (ptrace (PTRACE_GETEVENTMSG, pid, NULL, &ptrace_data) < 0) {
+               warning("Error getting child pid: %s (pid: %d)\n", 
strerror(errno), pid);
+               return -1;
+       }
+
+       return ((pid_t) ptrace_data);
+}
+
+static void
+trace_pid(char *startas, char **argv)
+{
+       pid_t pid, child_pid;
+       int num_of_forks = 0;
+
+       pid = fork();
+       if (pid < 0) {
+         fatal("Unable to fork!\n");
+       }
+       if(pid == 0) {
+               ptrace(PTRACE_TRACEME, 0, NULL, NULL);
+               execvp(startas, argv);
+       }
+       else {
+               // write the initial pid
+               write_pid(pid);
+
+               // initial settings
+               start_trace(pid);
+
+               while (1) {
+                       siginfo_t info;
+
+                       waitid (P_ALL, 0, &info, WEXITED | WSTOPPED | 
WCONTINUED);
+
+                       if (info.si_code == CLD_EXITED) {
+                               fatal("Program exited prematurely!\n");
+                       }
+
+                       if (info.si_code == CLD_TRAPPED
+                                       && (   (  ((info.si_status & 0x7f) == 
SIGTRAP)
+                                                       && (info.si_status & 
~0x7f)
+                                                       && (info.si_status >> 
8) == PTRACE_EVENT_FORK)
+                                          )
+                                       || (  ((info.si_status & 0x7f) == 
SIGSTOP)
+                                               && (info.si_pid != pid)
+                                          )
+                          ) {
+                               child_pid = get_child_pid(pid);
+                               if (child_pid > 0) {
+                                       detach_trace(pid);
+                                       write_pid(child_pid);
+                                       if (++num_of_forks >= expected_forks) {
+                                               // our duty here is done
+                                               detach_trace(child_pid);
+                                               exit(0);
+                                       }
+                                       else {
+                                               start_trace(child_pid);
+                                               pid = child_pid;
+                                       }
+                               }
+                               else {
+                                 fatal("Unable to get pid for child process!");
+                               }
+                       }
+                       else {
+                               continue_trace(pid);
+                       }
+               }
+       }
+}
+
 static void
 usage(void)
 {
@@ -397,6 +551,9 @@ usage(void)
 "  -k|--umask <mask>             change the umask to <mask> before starting\n"
 "  -b|--background               force the process to detach\n"
 "  -m|--make-pidfile             create the pidfile before starting\n"
+"  -T|--trace-pid                try to trace the pid of forking processes\n"
+"  -F|--expect-fork              expect a single fork\n"
+"  -D|--expect-daemon            expect a double (daemon) fork\n"
 "  -R|--retry <schedule>         check whether processes die, and retry\n"
 "  -t|--test                     test mode, don't do anything\n"
 "  -o|--oknodo                   exit status 0 (not 1) if nothing done\n"
@@ -735,6 +892,9 @@ parse_options(int argc, char * const *argv)
                { "umask",        1, NULL, 'k'},
                { "background",   0, NULL, 'b'},
                { "make-pidfile", 0, NULL, 'm'},
+               { "trace-pid",    0, NULL, 'T'},
+               { "expect-fork",  0, NULL, 'F'},
+               { "expect-daemon",0, NULL, 'D'},
                { "retry",        1, NULL, 'R'},
                { "chdir",        1, NULL, 'd'},
                { NULL,           0, NULL, 0  }
@@ -748,7 +908,7 @@ parse_options(int argc, char * const *argv)
 
        for (;;) {
                c = getopt_long(argc, argv,
-                               "HKSVa:n:op:qr:s:tu:vx:c:N:P:I:k:bmR:g:d:",
+                               "HKSVa:n:op:qr:s:tu:vx:c:N:P:I:k:bmTFDR:g:d:",
                                longopts, NULL);
                if (c == -1)
                        break;
@@ -826,6 +986,15 @@ parse_options(int argc, char * const *argv)
                case 'm':  /* --make-pidfile */
                        mpidfile = 1;
                        break;
+               case 'T':  /* --trace-pid */
+                       tracepid = 1;
+                       break;
+               case 'F':  /* --expect-fork */
+                       expected_forks = 1;
+                       break;
+               case 'D':  /* --expect-daemon */
+                       expected_forks = 2;
+                       break;
                case 'R':  /* --retry <schedule>|<timeout> */
                        schedule_str = optarg;
                        break;
@@ -884,6 +1053,17 @@ parse_options(int argc, char * const *argv)
        if (background && !start)
                badusage("--background is only relevant with --start");
 
+       if (tracepid && expected_forks == 0)
+               badusage("you must specify either --expect-fork or 
--expect-daemon when using --trace-pid");
+
+       if (tracepid && pidfile == NULL)
+               badusage("--trace-pid requires --pidfile");
+
+       if (tracepid && background)
+               badusage("--trace-pid cannot be used with --background");
+
+
+
 }
 
 #if defined(OSHurd)
@@ -1588,18 +1768,9 @@ main(int argc, char **argv)
        if (umask_value >= 0)
                umask(umask_value);
        if (mpidfile && pidfile != NULL) {
-               /* User wants _us_ to make the pidfile. */
-               FILE *pidf = fopen(pidfile, "w");
+               /* User wants _us_ to make the pidfile. :) */
                pid_t pidt = getpid();
-               if (pidf == NULL)
-                       fatal("unable to open pidfile '%s' for writing",
-                             pidfile);
-               fprintf(pidf, "%d\n", pidt);
-               if (fflush(pidf))
-                       fatal("unable to flush pidfile '%s'", pidfile);
-               if (fsync(fileno(pidf)))
-                       fatal("unable to sync pidfile '%s'", pidfile);
-               fclose(pidf);
+               write_pid(pidt);
        }
        if (changeroot != NULL) {
                if (chdir(changeroot) < 0)
@@ -1646,6 +1817,11 @@ main(int argc, char **argv)
                for (i = get_open_fd_max() - 1; i >= 3; --i)
                        close(i);
        }
-       execv(startas, argv);
+
+       if (tracepid)
+         trace_pid(startas, argv);
+       else
+         execv(startas, argv);
+
        fatal("unable to start %s", startas);
 }



-- 
To UNSUBSCRIBE, email to [email protected]
with a subject of "unsubscribe". Trouble? Contact [email protected]

Reply via email to