2006-11-30
authorlace <>
Tue, 10 Jul 2007 14:32:44 +0000 (14:32 +0000)
committerlace <>
Tue, 10 Jul 2007 14:32:44 +0000 (14:32 +0000)
src/orphanripper.c [new file with mode: 0644]

diff --git a/src/orphanripper.c b/src/orphanripper.c
new file mode 100644 (file)
index 0000000..9613b92
--- /dev/null
@@ -0,0 +1,219 @@
+/*
+ * Copyright 2006 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Reap any leftover children possibly holding file descriptors.
+ * 2006-11-30  Jan Kratochvil  <jan.kratochvil@redhat.com>
+ */
+
+
+#define _XOPEN_SOURCE 1
+#define _XOPEN_SOURCE_EXTENDED 1
+#define _BSD_SOURCE 1
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <dirent.h>
+#include <unistd.h>
+#include <errno.h>
+#include <ctype.h>
+#include <string.h>
+#include <limits.h>
+#include <fcntl.h>
+
+
+static const char *progname;
+
+static int spawn (char **argv, pid_t *child_pointer)
+{
+       pid_t child, child_got;
+       int status;
+
+       switch (child = fork ()) {
+       case -1:
+               perror ("fork(2)");
+               exit (EXIT_FAILURE);
+       case 0:
+               /* Do not setpgrp(2) in the parent process as the process-group
+                  is shared for the whole sh(1) pipeline we could be a part
+                  of.  The process-group is set according to PID of the first
+                  command in the pipeline.
+                  We would rip even vi(1) in the case of:
+                       ./orphanripper sh -c 'sleep 1&' | vi -
+                  */
+               if (setpgrp ()) {
+                       perror ("setpgrp");
+                       exit (EXIT_FAILURE);
+               }
+               execvp (argv[1], argv + 1);
+               perror ("execvp(2)");
+               exit (EXIT_FAILURE);
+       default:
+               break;
+       }
+       if (child != (child_got = waitpid (child, &status, 0))) {
+               fprintf (stderr, "waitpid(%d)=%d: %m\n", (int) child,
+                        (int) child_got);
+               exit (EXIT_FAILURE);
+       }
+       if (!WIFEXITED (status)) {
+               fprintf (stderr, "waitpid(2): !WIFEXITED(%d)\n", status);
+               exit (EXIT_FAILURE);
+       }
+
+       if (child_pointer)
+               *child_pointer = child;
+       return WEXITSTATUS (status);
+}
+
+/* Detected commandline may look weird due to a race:
+   Original command:
+       ./orphanripper sh -c 'sleep 1&' &
+   Correct output:
+       [1] 29610
+       ./orphanripper: Killed -9 orphan PID 29612 (PGID 29611): sleep 1
+   Raced output (sh(1) child still did not update its argv[]):
+       [1] 29613
+       ./orphanripper: Killed -9 orphan PID 29615 (PGID 29614): sh -c sleep 1&
+   We could delay a bit before ripping the children.  */
+static const char *read_cmdline (pid_t pid)
+{
+       char cmdline_fname[32];
+       static char cmdline[LINE_MAX];
+       int fd;
+       ssize_t got;
+       char *s;
+
+       if (snprintf (cmdline_fname, sizeof (cmdline_fname), "/proc/%d/cmdline",
+                 (int) pid) < 0)
+               return NULL;
+       fd = open (cmdline_fname, O_RDONLY);
+       if (fd == -1) {
+               fprintf (stderr, "%s: open (\"%s\"): %m\n", progname,
+                        cmdline_fname);
+               return NULL;
+       }
+       got = read (fd, cmdline, sizeof (cmdline) - 1);
+       if (got == -1)
+               fprintf (stderr, "%s: read (\"%s\"): %m\n", progname,
+                        cmdline_fname);
+       if (close (fd))
+               fprintf (stderr, "%s: close (\"%s\"): %m\n", progname,
+                        cmdline_fname);
+       if (got < 0)
+               return NULL;
+       /* Convert '\0' argument delimiters to spaces.  */
+       for (s = cmdline; s < cmdline + got; s++)
+               if (!*s)
+                       *s = ' ';
+       /* Trim the trailing spaces (typically single '\0'->' ').  */
+       while (s > cmdline && isspace (s[-1]))
+               s--;
+       *s = 0;
+       return cmdline;
+}
+
+static void rip_pid (pid_t pid, pid_t pgid_child)
+{
+       pid_t pgid_pids;
+       const char *cmdline;
+
+       /* Don't shoot ourselves.  */
+       if (pid == getpid())
+               return;
+       pgid_pids = getpgid (pid);
+       /* Ignore errors (permissions? untested).  */
+       if (pgid_pids == -1)
+               return;
+       /* Not a process of ours.  */
+       if (pgid_pids != pgid_child)
+               return;
+
+       cmdline = read_cmdline (pid);
+       if (!cmdline)
+               cmdline = "<error>";
+       fprintf (stderr, "%s: Killed -9 orphan PID %d (PGID %d): %s\n",
+                progname, (int) pid, (int) pgid_pids, cmdline);
+       if (kill (pid, SIGKILL)) {
+               fprintf (stderr, "%s: kill (%d, SIGKILL): %m\n", progname,
+                        (int) pid);
+               return;
+       }
+       /* Do not waitpid(2) as it cannot be our direct descendant and it gets
+          cleaned up by init(8).  */
+#if 0
+       pid_t pid_got;
+       if (pid != (pid_got = waitpid (pid, NULL, 0))) {
+               fprintf (stderr, "%s: waitpid (%d) != %d: %m\n", progname,
+                        (int) pid, (int) pid_got);
+               return;
+       }
+#endif
+}
+
+static void rip (pid_t pgid_child)
+{
+       DIR *dir;
+       struct dirent *dirent;
+
+       dir = opendir ("/proc");
+       if (!dir) {
+               perror ("opendir (\"/proc\")");
+               exit (EXIT_FAILURE);
+       }
+       while ((errno = 0, dirent = readdir (dir))) {
+               const char *cs;
+
+               /* FIXME: POSIX portability.  */
+               if (dirent->d_type != DT_DIR)
+                       continue;
+               /* Check /^\d+$/:  */
+               for (cs = dirent->d_name; *cs; cs++)
+                       if (!isdigit (*cs))
+                               break;
+               if (cs == dirent->d_name || *cs)
+                       continue;
+               rip_pid (atoi (dirent->d_name), pgid_child);
+       }
+       if (errno) {
+               perror ("readdir (\"/proc\")");
+               exit (EXIT_FAILURE);
+       }
+       if (closedir (dir)) {
+               perror ("closedir (\"/proc\")");
+               exit (EXIT_FAILURE);
+       }
+
+}
+
+int main (int argc, char **argv)
+{
+       int rc;
+       pid_t child;
+
+       if (argc < 2
+           || !strcmp (argv[1], "-h")
+           || !strcmp (argv[1], "--help")) {
+               fputs("Syntax: orphanripper <execvp(3) commandline>\n", stdout);
+               exit (EXIT_FAILURE);
+       }
+       progname = argv[0];
+       rc = spawn (argv, &child);
+       rip (child);
+       return rc;
+}