/* * 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 */ #define _XOPEN_SOURCE 1 #define _XOPEN_SOURCE_EXTENDED 1 #define _BSD_SOURCE 1 #include #include #include #include #include #include #include #include #include #include #include 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 = ""; 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 \n", stdout); exit (EXIT_FAILURE); } progname = argv[0]; rc = spawn (argv, &child); rip (child); return rc; }