2007-07-10
[nethome.git] / src / orphanripper.c
1 /*
2  * Copyright 2006-2007 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  * Children are identified by the open file descriptor.
20  * PGID or SID may be set by the children on their own.
21  * If we find a child by its file descriptor, we kill it will all its process
22  * tree (grandchildren).
23  * The child process is run with `2>&1' redirection (due to forkpty(3)).
24  * 2007-07-10  Jan Kratochvil  <jan.kratochvil@redhat.com>
25  */
26
27 #define _XOPEN_SOURCE 1
28 #define _XOPEN_SOURCE_EXTENDED 1
29 #define _BSD_SOURCE 1
30
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <sys/types.h>
34 #include <sys/wait.h>
35 #include <dirent.h>
36 #include <unistd.h>
37 #include <errno.h>
38 #include <ctype.h>
39 #include <string.h>
40 #include <limits.h>
41 #include <fcntl.h>
42 #include <assert.h>
43 #include <pty.h>
44
45 static const char *progname;
46
47 static int signal_child_hit = 0;
48
49 static void signal_child (int signo)
50 {
51   signal_child_hit = 1;
52 }
53
54 static char childptyname[LINE_MAX];
55
56 static int spawn (char **argv)
57 {
58   pid_t child, child_got;
59   int status, amaster, i;
60   char buf[LINE_MAX];
61   ssize_t buf_got;
62   struct sigaction act;
63
64   /* We do not use signal(2) to be sure we have SA_RESTART unset.  */
65   memset (&act, 0, sizeof (act));
66   act.sa_handler = signal_child;
67   i = sigemptyset (&act.sa_mask);
68   if (i != 0)
69     {
70       perror ("sigemotyset(3)");
71       exit (EXIT_FAILURE);
72     }
73   act.sa_flags = 0;
74   i = sigaction (SIGCHLD, &act, NULL);
75   if (i != 0)
76     {
77       perror ("sigaction(2)");
78       exit (EXIT_FAILURE);
79     }
80
81   child = forkpty (&amaster, childptyname, NULL, NULL);
82   switch (child)
83     {
84       case -1:
85         perror ("forkpty(3)");
86         exit (EXIT_FAILURE);
87       case 0:
88         execvp (argv[1], argv + 1);
89         perror ("execvp(2)");
90         exit (EXIT_FAILURE);
91       default:
92         break;
93     }
94   for (;;)
95     {
96       buf_got = read (amaster, buf, sizeof buf);
97       if (buf_got == -1)
98         {
99           assert (signal_child_hit != 0);
100           assert (errno == EINTR || errno == EIO);
101         }
102       if (buf_got <= 0)
103         break;
104       if (write (STDOUT_FILENO, buf, buf_got) != buf_got)
105         {
106           perror ("write(2)");
107           exit (EXIT_FAILURE);
108         }
109     }
110   child_got = waitpid (child, &status, 0);
111   if (child != child_got)
112     {
113       fprintf (stderr, "waitpid (%d) = %d: %m\n", (int) child, (int) child_got);
114       exit (EXIT_FAILURE);
115     }
116   if (!WIFEXITED (status))
117     {
118       fprintf (stderr, "waitpid (%d): !WIFEXITED (%d)\n", (int) child, status);
119       exit (EXIT_FAILURE);
120     }
121   /* Do not close the master FD as the child would have `/dev/pts/23 (deleted)'
122      entries which are not expected (and expecting ` (deleted)' would be
123      a race.  */
124 #if 0
125   i = close (amaster);
126   if (i != 0)
127     {
128       perror ("close (forkpty ()'s amaster)");
129       exit (EXIT_FAILURE);
130     }
131 #endif
132
133   return WEXITSTATUS (status);
134 }
135
136 /* Detected commandline may look weird due to a race:
137    Original command:
138         ./orphanripper sh -c 'sleep 1&' &
139    Correct output:
140         [1] 29610
141         ./orphanripper: Killed -9 orphan PID 29612 (PGID 29611): sleep 1
142    Raced output (sh(1) child still did not update its argv[]):
143         [1] 29613
144         ./orphanripper: Killed -9 orphan PID 29615 (PGID 29614): sh -c sleep 1&
145    We could delay a bit before ripping the children.  */
146 static const char *read_cmdline (pid_t pid)
147 {
148   char cmdline_fname[32];
149   static char cmdline[LINE_MAX];
150   int fd;
151   ssize_t got;
152   char *s;
153
154   if (snprintf (cmdline_fname, sizeof cmdline_fname, "/proc/%d/cmdline",
155       (int) pid) < 0)
156     return NULL;
157   fd = open (cmdline_fname, O_RDONLY);
158   if (fd == -1)
159     {
160       /* It may have already exited - ENOENT.  */
161 #if 0
162       fprintf (stderr, "%s: open (\"%s\"): %m\n", progname, cmdline_fname);
163 #endif
164       return NULL;
165     }
166   got = read (fd, cmdline, sizeof (cmdline) - 1);
167   if (got == -1)
168     fprintf (stderr, "%s: read (\"%s\"): %m\n", progname,
169        cmdline_fname);
170   if (close (fd) != 0)
171     fprintf (stderr, "%s: close (\"%s\"): %m\n", progname,
172        cmdline_fname);
173   if (got < 0)
174     return NULL;
175   /* Convert '\0' argument delimiters to spaces.  */
176   for (s = cmdline; s < cmdline + got; s++)
177     if (!*s)
178       *s = ' ';
179   /* Trim the trailing spaces (typically single '\0'->' ').  */
180   while (s > cmdline && isspace (s[-1]))
181     s--;
182   *s = 0;
183   return cmdline;
184 }
185
186 static int fd_fs_scan (pid_t pid, int (*func) (pid_t pid, const char *link))
187 {
188   DIR *dir;
189   struct dirent *dirent;
190   char dirname[64];
191   int rc = 0;
192
193   if (snprintf (dirname, sizeof dirname, "/proc/%d/fd", (int) pid) < 0)
194     {
195       perror ("snprintf(3)");
196       exit (EXIT_FAILURE);
197     }
198   dir = opendir (dirname);
199   if (dir == NULL)
200     {
201       if (errno == EACCES)
202         return 0;
203       fprintf (stderr, "%s: opendir (\"%s\"): %m\n", progname, dirname);
204       exit (EXIT_FAILURE);
205     }
206   while ((errno = 0, dirent = readdir (dir)))
207     {
208       char linkname[LINE_MAX], buf[LINE_MAX];
209       int linkname_len;
210       ssize_t buf_len;
211
212       /* FIXME: POSIX portability.  */
213       if ((dirent->d_type != DT_DIR && dirent->d_type != DT_LNK)
214           || (dirent->d_type == DT_DIR && strcmp (dirent->d_name, ".") != 0
215               && strcmp (dirent->d_name, "..") != 0)
216           || (dirent->d_type == DT_LNK && strspn (dirent->d_name, "0123456789")
217               != strlen (dirent->d_name)))
218         {
219           fprintf (stderr, "Unexpected entry \"%s\" on readdir (\"%s\"): %m\n",
220                    dirent->d_name, dirname);
221           continue;
222         }
223       if (dirent->d_type == DT_DIR)
224         continue;
225       linkname_len = snprintf (linkname, sizeof linkname, "%s/%s", dirname,
226                                dirent->d_name);
227       if (linkname_len <= 0 || linkname_len >= sizeof linkname)
228         {
229           fprintf (stderr, "Link content too long: `%s' / `%s'\n",
230                    dirent->d_name, dirent->d_name);
231           continue;
232         }
233       buf_len = readlink (linkname, buf, sizeof buf - 1);
234       if (buf_len <= 0 || buf_len >= sizeof buf - 1)
235         {
236           fprintf (stderr, "Error reading link \"%s\": %m\n",
237                    linkname);
238           continue;
239         }
240       buf[buf_len] = 0;
241       rc = (*func) (pid, buf);
242       if (rc != 0)
243         {
244           errno = 0;
245           break;
246         }
247     }
248   if (errno != 0)
249     {
250       fprintf (stderr, "%s: readdir (\"%s\"): %m\n", progname, dirname);
251       exit (EXIT_FAILURE);
252     }
253   if (closedir (dir) != 0)
254     {
255       fprintf (stderr, "%s: closedir (\"%s\"): %m\n", progname, dirname);
256       exit (EXIT_FAILURE);
257     }
258   return rc;
259 }
260
261 static void pid_fs_scan (void (*func) (pid_t pid, void *data), void *data)
262 {
263   DIR *dir;
264   struct dirent *dirent;
265
266   dir = opendir ("/proc");
267   if (dir == NULL)
268     {
269       perror ("opendir (\"/proc\")");
270       exit (EXIT_FAILURE);
271     }
272   while ((errno = 0, dirent = readdir (dir)))
273     {
274       /* FIXME: POSIX portability.  */
275       if (dirent->d_type != DT_DIR
276           || strspn (dirent->d_name, "0123456789") != strlen (dirent->d_name))
277           continue;
278       (*func) (atoi (dirent->d_name), data);
279     }
280   if (errno != 0)
281     {
282       perror ("readdir (\"/proc\")");
283       exit (EXIT_FAILURE);
284     }
285   if (closedir (dir) != 0)
286     {
287       perror ("closedir (\"/proc\")");
288       exit (EXIT_FAILURE);
289     }
290 }
291
292 static int rip_check (pid_t pid, const char *link)
293 {
294   assert (pid != getpid ());
295
296   return strcmp (link, childptyname) == 0;
297 }
298
299 struct pid
300   {
301     struct pid *next;
302     pid_t pid;
303   };
304 static struct pid *pid_list;
305
306 static int pid_found (pid_t pid)
307 {
308   struct pid *entry;
309
310   for (entry = pid_list; entry != NULL; entry = entry->next)
311     if (entry->pid == pid)
312       return 1;
313   return 0;
314 }
315
316 static void pid_record (pid_t pid)
317 {
318   struct pid *entry;
319
320   if (pid_found (pid))
321     return;
322
323   entry = malloc (sizeof (*entry));
324   if (entry == NULL)
325     {
326       fprintf (stderr, "%s: malloc: %m\n", progname);
327       exit (EXIT_FAILURE);
328     }
329   entry->pid = pid;
330   entry->next = pid_list;
331   pid_list = entry;
332 }
333
334 static void pid_forall (void (*func) (pid_t pid))
335 {
336   struct pid *entry;
337
338   for (entry = pid_list; entry != NULL; entry = entry->next)
339     (*func) (entry->pid);
340 }
341
342 /* Returns 0 on failure.  */
343 static pid_t pid_get_parent (pid_t pid)
344 {
345   char fname[64];
346   FILE *f;
347   char line[LINE_MAX];
348   pid_t retval = 0;
349
350   if (snprintf (fname, sizeof fname, "/proc/%d/status", (int) pid) < 0)
351     {
352       perror ("snprintf(3)");
353       exit (EXIT_FAILURE);
354     }
355   f = fopen (fname, "r");
356   if (f == NULL)
357     {
358       return 0;
359     }
360   while (errno = 0, fgets (line, sizeof line, f) == line)
361     {
362       if (strncmp (line, "PPid:\t", sizeof "PPid:\t" - 1) != 0)
363         continue;
364       retval = atoi (line + sizeof "PPid:\t" - 1);
365       errno = 0;
366       break;
367     }
368   if (errno != 0)
369     {
370       fprintf (stderr, "%s: fgets (\"%s\"): %m\n", progname, fname);
371       exit (EXIT_FAILURE);
372     }
373   if (fclose (f) != 0)
374     {
375       fprintf (stderr, "%s: fclose (\"%s\"): %m\n", progname, fname);
376       exit (EXIT_FAILURE);
377     }
378   return retval;
379 }
380
381 static void killtree (pid_t pid);
382
383 static void killtree_pid_fs_scan (pid_t pid, void *data)
384 {
385   pid_t parent_pid = *(pid_t *) data;
386
387   /* Optimization.  */
388   if (pid_found (pid))
389     return;
390
391   if (pid_get_parent (pid) != parent_pid)
392     return;
393
394   killtree (pid);
395 }
396
397 static void killtree (pid_t pid)
398 {
399   pid_record (pid);
400   pid_fs_scan (killtree_pid_fs_scan, &pid);
401 }
402
403 static void rip_pid_fs_scan (pid_t pid, void *data)
404 {
405   /* Shouldn't happen.  */
406   if (pid == getpid ())
407     return;
408
409   if (fd_fs_scan (pid, rip_check) != 0)
410     killtree (pid);
411 }
412
413 static void killproc (pid_t pid)
414 {
415   const char *cmdline;
416
417   cmdline = read_cmdline (pid);
418   if (kill (pid, 0) != 0 && errno == ESRCH)
419     return;
420   if (cmdline == NULL)
421     cmdline = "<error>";
422   fprintf (stderr, "%s: Killed -9 orphan PID %d: %s\n", progname, (int) pid, cmdline);
423   if (kill (pid, SIGKILL)) {
424     fprintf (stderr, "%s: kill (%d, SIGKILL): %m\n", progname,
425        (int) pid);
426     return;
427   }
428   /* Do not waitpid(2) as it cannot be our direct descendant and it gets
429      cleaned up by init(8).  */
430 #if 0
431   pid_t pid_got;
432   pid_got = waitpid (pid, NULL, 0);
433   if (pid != pid_got)
434     {
435       fprintf (stderr, "%s: waitpid (%d) != %d: %m\n", progname,
436          (int) pid, (int) pid_got);
437       return;
438     }
439 #endif
440 }
441
442 static void rip ()
443 {
444   pid_fs_scan (rip_pid_fs_scan, NULL);
445   pid_forall (killproc);
446 }
447
448 int main (int argc, char **argv)
449 {
450   int rc;
451
452   if (argc < 2 || strcmp (argv[1], "-h") == 0
453       || strcmp (argv[1], "--help") == 0)
454     {
455       fputs ("Syntax: orphanripper <execvp(3) commandline>\n", stdout);
456       exit (EXIT_FAILURE);
457     }
458   progname = argv[0];
459   rc = spawn (argv);
460   rip ();
461   return rc;
462 }