From 40c2cf283ed265da46fdc66556f02a2521ac6406 Mon Sep 17 00:00:00 2001 From: lace <> Date: Tue, 10 Jul 2007 14:33:42 +0000 Subject: [PATCH] 2007-07-10 It is now /proc/*/fd/ based, not SID/PGRP based. --- src/orphanripper.c | 551 ++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 396 insertions(+), 155 deletions(-) diff --git a/src/orphanripper.c b/src/orphanripper.c index 0b302f7..59fe41f 100644 --- a/src/orphanripper.c +++ b/src/orphanripper.c @@ -1,5 +1,5 @@ /* - * Copyright 2006 Free Software Foundation, Inc. + * Copyright 2006-2007 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 @@ -16,10 +16,14 @@ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * Reap any leftover children possibly holding file descriptors. - * 2006-12-12 Jan Kratochvil + * Children are identified by the open file descriptor. + * PGID or SID may be set by the children on their own. + * If we find a child by its file descriptor, we kill it will all its process + * tree (grandchildren). + * The child process is run with `2>&1' redirection (due to forkpty(3)). + * 2007-07-10 Jan Kratochvil */ - #define _XOPEN_SOURCE 1 #define _XOPEN_SOURCE_EXTENDED 1 #define _BSD_SOURCE 1 @@ -35,187 +39,424 @@ #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 - - */ - /* Do not setpgrp(2) as our pty would not be ours and we would - get `SIGSTOP' later, particularly after spawning gdb(1). */ - if (getpid() != setsid ()) { - perror ("setsid"); - 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); +static int signal_child_hit = 0; + +static void signal_child (int signo) +{ + signal_child_hit = 1; +} + +static char childptyname[LINE_MAX]; + +static int spawn (char **argv) +{ + pid_t child, child_got; + int status, amaster, i; + char buf[LINE_MAX]; + ssize_t buf_got; + struct sigaction act; + + /* We do not use signal(2) to be sure we have SA_RESTART unset. */ + memset (&act, 0, sizeof (act)); + act.sa_handler = signal_child; + i = sigemptyset (&act.sa_mask); + if (i != 0) + { + perror ("sigemotyset(3)"); + exit (EXIT_FAILURE); + } + act.sa_flags = 0; + i = sigaction (SIGCHLD, &act, NULL); + if (i != 0) + { + perror ("sigaction(2)"); + exit (EXIT_FAILURE); + } + + child = forkpty (&amaster, childptyname, NULL, NULL); + switch (child) + { + case -1: + perror ("forkpty(3)"); + exit (EXIT_FAILURE); + case 0: + execvp (argv[1], argv + 1); + perror ("execvp(2)"); + exit (EXIT_FAILURE); + default: + break; + } + for (;;) + { + buf_got = read (amaster, buf, sizeof buf); + if (buf_got == -1) + { + assert (signal_child_hit != 0); + assert (errno == EINTR || errno == EIO); } - if (!WIFEXITED (status)) { - fprintf (stderr, "waitpid(2): !WIFEXITED(%d)\n", status); - exit (EXIT_FAILURE); + if (buf_got <= 0) + break; + if (write (STDOUT_FILENO, buf, buf_got) != buf_got) + { + perror ("write(2)"); + exit (EXIT_FAILURE); } + } + child_got = waitpid (child, &status, 0); + if (child != child_got) + { + fprintf (stderr, "waitpid (%d) = %d: %m\n", (int) child, (int) child_got); + exit (EXIT_FAILURE); + } + if (!WIFEXITED (status)) + { + fprintf (stderr, "waitpid (%d): !WIFEXITED (%d)\n", (int) child, status); + exit (EXIT_FAILURE); + } + /* Do not close the master FD as the child would have `/dev/pts/23 (deleted)' + entries which are not expected (and expecting ` (deleted)' would be + a race. */ +#if 0 + i = close (amaster); + if (i != 0) + { + perror ("close (forkpty ()'s amaster)"); + exit (EXIT_FAILURE); + } +#endif - if (child_pointer) - *child_pointer = child; - return WEXITSTATUS (status); + return WEXITSTATUS (status); } /* Detected commandline may look weird due to a race: Original command: - ./orphanripper sh -c 'sleep 1&' & + ./orphanripper sh -c 'sleep 1&' & Correct output: - [1] 29610 - ./orphanripper: Killed -9 orphan PID 29612 (PGID 29611): sleep 1 + [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& + [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). */ + 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) + { + /* It may have already exited - ENOENT. */ #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; - } + fprintf (stderr, "%s: open (\"%s\"): %m\n", progname, cmdline_fname); #endif + 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) != 0) + 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_t pgid_child) +static int fd_fs_scan (pid_t pid, int (*func) (pid_t pid, const char *link)) { - DIR *dir; - struct dirent *dirent; + DIR *dir; + struct dirent *dirent; + char dirname[64]; + int rc = 0; - dir = opendir ("/proc"); - if (!dir) { - perror ("opendir (\"/proc\")"); - exit (EXIT_FAILURE); + if (snprintf (dirname, sizeof dirname, "/proc/%d/fd", (int) pid) < 0) + { + perror ("snprintf(3)"); + exit (EXIT_FAILURE); + } + dir = opendir (dirname); + if (dir == NULL) + { + if (errno == EACCES) + return 0; + fprintf (stderr, "%s: opendir (\"%s\"): %m\n", progname, dirname); + exit (EXIT_FAILURE); + } + while ((errno = 0, dirent = readdir (dir))) + { + char linkname[LINE_MAX], buf[LINE_MAX]; + int linkname_len; + ssize_t buf_len; + + /* FIXME: POSIX portability. */ + if ((dirent->d_type != DT_DIR && dirent->d_type != DT_LNK) + || (dirent->d_type == DT_DIR && strcmp (dirent->d_name, ".") != 0 + && strcmp (dirent->d_name, "..") != 0) + || (dirent->d_type == DT_LNK && strspn (dirent->d_name, "0123456789") + != strlen (dirent->d_name))) + { + fprintf (stderr, "Unexpected entry \"%s\" on readdir (\"%s\"): %m\n", + dirent->d_name, dirname); + continue; } - 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 (dirent->d_type == DT_DIR) + continue; + linkname_len = snprintf (linkname, sizeof linkname, "%s/%s", dirname, + dirent->d_name); + if (linkname_len <= 0 || linkname_len >= sizeof linkname) + { + fprintf (stderr, "Link content too long: `%s' / `%s'\n", + dirent->d_name, dirent->d_name); + continue; } - if (errno) { - perror ("readdir (\"/proc\")"); - exit (EXIT_FAILURE); + buf_len = readlink (linkname, buf, sizeof buf - 1); + if (buf_len <= 0 || buf_len >= sizeof buf - 1) + { + fprintf (stderr, "Error reading link \"%s\": %m\n", + linkname); + continue; } - if (closedir (dir)) { - perror ("closedir (\"/proc\")"); - exit (EXIT_FAILURE); + buf[buf_len] = 0; + rc = (*func) (pid, buf); + if (rc != 0) + { + errno = 0; + break; } + } + if (errno != 0) + { + fprintf (stderr, "%s: readdir (\"%s\"): %m\n", progname, dirname); + exit (EXIT_FAILURE); + } + if (closedir (dir) != 0) + { + fprintf (stderr, "%s: closedir (\"%s\"): %m\n", progname, dirname); + exit (EXIT_FAILURE); + } + return rc; +} +static void pid_fs_scan (void (*func) (pid_t pid, void *data), void *data) +{ + DIR *dir; + struct dirent *dirent; + + dir = opendir ("/proc"); + if (dir == NULL) + { + perror ("opendir (\"/proc\")"); + exit (EXIT_FAILURE); + } + while ((errno = 0, dirent = readdir (dir))) + { + /* FIXME: POSIX portability. */ + if (dirent->d_type != DT_DIR + || strspn (dirent->d_name, "0123456789") != strlen (dirent->d_name)) + continue; + (*func) (atoi (dirent->d_name), data); + } + if (errno != 0) + { + perror ("readdir (\"/proc\")"); + exit (EXIT_FAILURE); + } + if (closedir (dir) != 0) + { + perror ("closedir (\"/proc\")"); + exit (EXIT_FAILURE); + } +} + +static int rip_check (pid_t pid, const char *link) +{ + assert (pid != getpid ()); + + return strcmp (link, childptyname) == 0; +} + +struct pid + { + struct pid *next; + pid_t pid; + }; +static struct pid *pid_list; + +static int pid_found (pid_t pid) +{ + struct pid *entry; + + for (entry = pid_list; entry != NULL; entry = entry->next) + if (entry->pid == pid) + return 1; + return 0; +} + +static void pid_record (pid_t pid) +{ + struct pid *entry; + + if (pid_found (pid)) + return; + + entry = malloc (sizeof (*entry)); + if (entry == NULL) + { + fprintf (stderr, "%s: malloc: %m\n", progname); + exit (EXIT_FAILURE); + } + entry->pid = pid; + entry->next = pid_list; + pid_list = entry; +} + +static void pid_forall (void (*func) (pid_t pid)) +{ + struct pid *entry; + + for (entry = pid_list; entry != NULL; entry = entry->next) + (*func) (entry->pid); +} + +/* Returns 0 on failure. */ +static pid_t pid_get_parent (pid_t pid) +{ + char fname[64]; + FILE *f; + char line[LINE_MAX]; + pid_t retval = 0; + + if (snprintf (fname, sizeof fname, "/proc/%d/status", (int) pid) < 0) + { + perror ("snprintf(3)"); + exit (EXIT_FAILURE); + } + f = fopen (fname, "r"); + if (f == NULL) + { + return 0; + } + while (errno = 0, fgets (line, sizeof line, f) == line) + { + if (strncmp (line, "PPid:\t", sizeof "PPid:\t" - 1) != 0) + continue; + retval = atoi (line + sizeof "PPid:\t" - 1); + errno = 0; + break; + } + if (errno != 0) + { + fprintf (stderr, "%s: fgets (\"%s\"): %m\n", progname, fname); + exit (EXIT_FAILURE); + } + if (fclose (f) != 0) + { + fprintf (stderr, "%s: fclose (\"%s\"): %m\n", progname, fname); + exit (EXIT_FAILURE); + } + return retval; +} + +static void killtree (pid_t pid); + +static void killtree_pid_fs_scan (pid_t pid, void *data) +{ + pid_t parent_pid = *(pid_t *) data; + + /* Optimization. */ + if (pid_found (pid)) + return; + + if (pid_get_parent (pid) != parent_pid) + return; + + killtree (pid); +} + +static void killtree (pid_t pid) +{ + pid_record (pid); + pid_fs_scan (killtree_pid_fs_scan, &pid); +} + +static void rip_pid_fs_scan (pid_t pid, void *data) +{ + /* Shouldn't happen. */ + if (pid == getpid ()) + return; + + if (fd_fs_scan (pid, rip_check) != 0) + killtree (pid); +} + +static void killproc (pid_t pid) +{ + const char *cmdline; + + cmdline = read_cmdline (pid); + if (kill (pid, 0) != 0 && errno == ESRCH) + return; + if (cmdline == NULL) + cmdline = ""; + fprintf (stderr, "%s: Killed -9 orphan PID %d: %s\n", progname, (int) pid, 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; + pid_got = waitpid (pid, NULL, 0); + if (pid != pid_got) + { + fprintf (stderr, "%s: waitpid (%d) != %d: %m\n", progname, + (int) pid, (int) pid_got); + return; + } +#endif +} + +static void rip () +{ + pid_fs_scan (rip_pid_fs_scan, NULL); + pid_forall (killproc); } int main (int argc, char **argv) { - int rc; - pid_t child; + int rc; - 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; + if (argc < 2 || strcmp (argv[1], "-h") == 0 + || strcmp (argv[1], "--help") == 0) + { + fputs ("Syntax: orphanripper \n", stdout); + exit (EXIT_FAILURE); + } + progname = argv[0]; + rc = spawn (argv); + rip (); + return rc; } -- 1.8.3.1