2006-12-12
[nethome.git] / src / orphanripper.c
1 /*
2  * Copyright 2006 Free Software Foundation, Inc.
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
17  *
18  * Reap any leftover children possibly holding file descriptors.
19  * 2006-12-12  Jan Kratochvil  <jan.kratochvil@redhat.com>
20  */
21
22
23 #define _XOPEN_SOURCE 1
24 #define _XOPEN_SOURCE_EXTENDED 1
25 #define _BSD_SOURCE 1
26
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <sys/types.h>
30 #include <sys/wait.h>
31 #include <dirent.h>
32 #include <unistd.h>
33 #include <errno.h>
34 #include <ctype.h>
35 #include <string.h>
36 #include <limits.h>
37 #include <fcntl.h>
38
39
40 static const char *progname;
41
42 static int spawn (char **argv, pid_t *child_pointer)
43 {
44         pid_t child, child_got;
45         int status;
46
47         switch (child = fork ()) {
48         case -1:
49                 perror ("fork(2)");
50                 exit (EXIT_FAILURE);
51         case 0:
52                 /* Do not setpgrp(2) in the parent process as the process-group
53                    is shared for the whole sh(1) pipeline we could be a part
54                    of.  The process-group is set according to PID of the first
55                    command in the pipeline.
56                    We would rip even vi(1) in the case of:
57                         ./orphanripper sh -c 'sleep 1&' | vi -
58                    */
59                 /* Do not setpgrp(2) as our pty would not be ours and we would
60                    get `SIGSTOP' later, particularly after spawning gdb(1).  */
61                 if (getpid() != setsid ()) {
62                         perror ("setsid");
63                         exit (EXIT_FAILURE);
64                 }
65                 execvp (argv[1], argv + 1);
66                 perror ("execvp(2)");
67                 exit (EXIT_FAILURE);
68         default:
69                 break;
70         }
71         if (child != (child_got = waitpid (child, &status, 0))) {
72                 fprintf (stderr, "waitpid(%d)=%d: %m\n", (int) child,
73                          (int) child_got);
74                 exit (EXIT_FAILURE);
75         }
76         if (!WIFEXITED (status)) {
77                 fprintf (stderr, "waitpid(2): !WIFEXITED(%d)\n", status);
78                 exit (EXIT_FAILURE);
79         }
80
81         if (child_pointer)
82                 *child_pointer = child;
83         return WEXITSTATUS (status);
84 }
85
86 /* Detected commandline may look weird due to a race:
87    Original command:
88         ./orphanripper sh -c 'sleep 1&' &
89    Correct output:
90         [1] 29610
91         ./orphanripper: Killed -9 orphan PID 29612 (PGID 29611): sleep 1
92    Raced output (sh(1) child still did not update its argv[]):
93         [1] 29613
94         ./orphanripper: Killed -9 orphan PID 29615 (PGID 29614): sh -c sleep 1&
95    We could delay a bit before ripping the children.  */
96 static const char *read_cmdline (pid_t pid)
97 {
98         char cmdline_fname[32];
99         static char cmdline[LINE_MAX];
100         int fd;
101         ssize_t got;
102         char *s;
103
104         if (snprintf (cmdline_fname, sizeof (cmdline_fname), "/proc/%d/cmdline",
105                   (int) pid) < 0)
106                 return NULL;
107         fd = open (cmdline_fname, O_RDONLY);
108         if (fd == -1) {
109                 fprintf (stderr, "%s: open (\"%s\"): %m\n", progname,
110                          cmdline_fname);
111                 return NULL;
112         }
113         got = read (fd, cmdline, sizeof (cmdline) - 1);
114         if (got == -1)
115                 fprintf (stderr, "%s: read (\"%s\"): %m\n", progname,
116                          cmdline_fname);
117         if (close (fd))
118                 fprintf (stderr, "%s: close (\"%s\"): %m\n", progname,
119                          cmdline_fname);
120         if (got < 0)
121                 return NULL;
122         /* Convert '\0' argument delimiters to spaces.  */
123         for (s = cmdline; s < cmdline + got; s++)
124                 if (!*s)
125                         *s = ' ';
126         /* Trim the trailing spaces (typically single '\0'->' ').  */
127         while (s > cmdline && isspace (s[-1]))
128                 s--;
129         *s = 0;
130         return cmdline;
131 }
132
133 static void rip_pid (pid_t pid, pid_t pgid_child)
134 {
135         pid_t pgid_pids;
136         const char *cmdline;
137
138         /* Don't shoot ourselves.  */
139         if (pid == getpid())
140                 return;
141         pgid_pids = getpgid (pid);
142         /* Ignore errors (permissions? untested).  */
143         if (pgid_pids == -1)
144                 return;
145         /* Not a process of ours.  */
146         if (pgid_pids != pgid_child)
147                 return;
148
149         cmdline = read_cmdline (pid);
150         if (!cmdline)
151                 cmdline = "<error>";
152         fprintf (stderr, "%s: Killed -9 orphan PID %d (PGID %d): %s\n",
153                  progname, (int) pid, (int) pgid_pids, cmdline);
154         if (kill (pid, SIGKILL)) {
155                 fprintf (stderr, "%s: kill (%d, SIGKILL): %m\n", progname,
156                          (int) pid);
157                 return;
158         }
159         /* Do not waitpid(2) as it cannot be our direct descendant and it gets
160            cleaned up by init(8).  */
161 #if 0
162         pid_t pid_got;
163         if (pid != (pid_got = waitpid (pid, NULL, 0))) {
164                 fprintf (stderr, "%s: waitpid (%d) != %d: %m\n", progname,
165                          (int) pid, (int) pid_got);
166                 return;
167         }
168 #endif
169 }
170
171 static void rip (pid_t pgid_child)
172 {
173         DIR *dir;
174         struct dirent *dirent;
175
176         dir = opendir ("/proc");
177         if (!dir) {
178                 perror ("opendir (\"/proc\")");
179                 exit (EXIT_FAILURE);
180         }
181         while ((errno = 0, dirent = readdir (dir))) {
182                 const char *cs;
183
184                 /* FIXME: POSIX portability.  */
185                 if (dirent->d_type != DT_DIR)
186                         continue;
187                 /* Check /^\d+$/:  */
188                 for (cs = dirent->d_name; *cs; cs++)
189                         if (!isdigit (*cs))
190                                 break;
191                 if (cs == dirent->d_name || *cs)
192                         continue;
193                 rip_pid (atoi (dirent->d_name), pgid_child);
194         }
195         if (errno) {
196                 perror ("readdir (\"/proc\")");
197                 exit (EXIT_FAILURE);
198         }
199         if (closedir (dir)) {
200                 perror ("closedir (\"/proc\")");
201                 exit (EXIT_FAILURE);
202         }
203
204 }
205
206 int main (int argc, char **argv)
207 {
208         int rc;
209         pid_t child;
210
211         if (argc < 2
212             || !strcmp (argv[1], "-h")
213             || !strcmp (argv[1], "--help")) {
214                 fputs("Syntax: orphanripper <execvp(3) commandline>\n", stdout);
215                 exit (EXIT_FAILURE);
216         }
217         progname = argv[0];
218         rc = spawn (argv, &child);
219         rip (child);
220         return rc;
221 }