Fixed backwards compatibility with RHEL-4 - missing ppoll(2).
[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 stale file descriptor or PGID / SID.
20  * Both can be missed but only the stale file descriptors are important for us.
21  * PGID / SID may be set by the children on their own.
22  * If we fine a candidate we kill it will all its process 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 /* For getpgid(2).  */
28 #define _GNU_SOURCE 1
29
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <sys/types.h>
33 #include <sys/wait.h>
34 #include <dirent.h>
35 #include <unistd.h>
36 #include <errno.h>
37 #include <ctype.h>
38 #include <string.h>
39 #include <limits.h>
40 #include <fcntl.h>
41 #include <assert.h>
42 #include <pty.h>
43 #include <poll.h>
44
45 #define LENGTH(x) (sizeof (x) / sizeof (*(x)))
46
47 static const char *progname;
48
49 static volatile int signal_child_hit = 0;
50
51 /* We use it to race-safely emulate ppoll(2) by poll(2).  */
52 static int pipefd[2];
53
54 static void signal_child (int signo)
55 {
56   int i;
57
58   signal_child_hit = 1;
59
60   assert (pipefd[1] != -1);
61   i = close (pipefd[1]);
62   assert (i == 0);
63   pipefd[1] = -1;
64 }
65
66 static char childptyname[LINE_MAX];
67 static pid_t child;
68
69 static int spawn (char **argv)
70 {
71   pid_t child_got;
72   int status, amaster, i;
73   struct sigaction act;
74   struct termios termios;
75
76   i = pipe (pipefd);
77   assert (i == 0);
78
79   /* We do not use signal(2) to be sure we have SA_RESTART set.  */
80   memset (&act, 0, sizeof (act));
81   act.sa_handler = signal_child;
82   i = sigemptyset (&act.sa_mask);
83   assert (i == 0);
84   act.sa_flags = SA_RESTART;
85   i = sigaction (SIGCHLD, &act, NULL);
86   assert (i == 0);
87
88   /* With TERMP passed as NULL we get "\n" -> "\r\n".  */
89   cfmakeraw (&termios);
90   child = forkpty (&amaster, childptyname, &termios, NULL);
91   switch (child)
92     {
93       case -1:
94         perror ("forkpty(3)");
95         exit (EXIT_FAILURE);
96       case 0:
97         i = close (pipefd[0]);
98         assert (i == 0);
99         i = close (pipefd[1]);
100         assert (i == 0);
101         /* Do not setpgrp(2) in the parent process as the process-group
102            is shared for the whole sh(1) pipeline we could be a part
103            of.  The process-group is set according to PID of the first
104            command in the pipeline.
105            We would rip even vi(1) in the case of:
106                 ./orphanripper sh -c 'sleep 1&' | vi -
107            */
108         /* Do not setpgrp(2) as our pty would not be ours and we would
109            get `SIGSTOP' later, particularly after spawning gdb(1).
110            setsid(3) was already executed by forkpty(3) and it would fail if
111            executed again.  */
112         if (getpid() != getpgrp ())
113           {
114             perror ("getpgrp(2)");
115             exit (EXIT_FAILURE);
116           }
117         execvp (argv[1], argv + 1);
118         perror ("execvp(2)");
119         exit (EXIT_FAILURE);
120       default:
121         break;
122     }
123   i = fcntl (amaster, F_SETFL, O_RDWR | O_NONBLOCK);
124   if (i != 0)
125     {
126       perror ("fcntl (amaster, F_SETFL, O_NONBLOCK)");
127       exit (EXIT_FAILURE);
128     }
129   for (;;)
130     {
131       struct pollfd pollfd[2];
132       char buf[LINE_MAX];
133       ssize_t buf_got;
134
135       pollfd[0].fd = amaster;
136       pollfd[0].events = POLLIN;
137       pollfd[1].fd = pipefd[0];
138       pollfd[1].events = POLLIN;
139       i = poll (pollfd, LENGTH (pollfd), -1);
140       if (i == -1 && errno == EINTR)
141         {
142           /* Weird but SA_RESTART sometimes does not work.  */
143           continue;
144         }
145       assert (i >= 1);
146       /* Data available?  Process it first.  */
147       if (pollfd[0].revents & POLLIN)
148         {
149           buf_got = read (amaster, buf, sizeof buf);
150           if (buf_got <= 0)
151             {
152               perror ("read (amaster)");
153               exit (EXIT_FAILURE);
154             }
155           if (write (STDOUT_FILENO, buf, buf_got) != buf_got)
156             {
157               perror ("write(2)");
158               exit (EXIT_FAILURE);
159             }
160         }
161       if (pollfd[0].revents & POLLHUP)
162         break;
163       if ((pollfd[0].revents &= ~POLLIN) != 0)
164         {
165           fprintf (stderr, "%s: ppoll(2): revents 0x%x\n", progname,
166                    (unsigned) pollfd[0].revents);
167           exit (EXIT_FAILURE);
168         }
169       /* Child exited?  */
170       if (pollfd[1].revents & POLLHUP)
171         break;
172       assert (pollfd[1].revents == 0);
173     }
174   /* WNOHANG still could fail.  */
175   child_got = waitpid (child, &status, 0);
176   if (child != child_got)
177     {
178       fprintf (stderr, "waitpid (%d) = %d: %m\n", (int) child, (int) child_got);
179       exit (EXIT_FAILURE);
180     }
181   if (!WIFEXITED (status))
182     {
183       fprintf (stderr, "waitpid (%d): !WIFEXITED (%d)\n", (int) child, status);
184       exit (EXIT_FAILURE);
185     }
186
187   assert (signal_child_hit != 0);
188   assert (pipefd[1] == -1);
189   i = close (pipefd[0]);
190   assert (i == 0);
191
192   /* Do not close the master FD as the child would have `/dev/pts/23 (deleted)'
193      entries which are not expected (and expecting ` (deleted)' would be
194      a race.  */
195 #if 0
196   i = close (amaster);
197   if (i != 0)
198     {
199       perror ("close (forkpty ()'s amaster)");
200       exit (EXIT_FAILURE);
201     }
202 #endif
203
204   return WEXITSTATUS (status);
205 }
206
207 /* Detected commandline may look weird due to a race:
208    Original command:
209         ./orphanripper sh -c 'sleep 1&' &
210    Correct output:
211         [1] 29610
212         ./orphanripper: Killed -9 orphan PID 29612 (PGID 29611): sleep 1
213    Raced output (sh(1) child still did not update its argv[]):
214         [1] 29613
215         ./orphanripper: Killed -9 orphan PID 29615 (PGID 29614): sh -c sleep 1&
216    We could delay a bit before ripping the children.  */
217 static const char *read_cmdline (pid_t pid)
218 {
219   char cmdline_fname[32];
220   static char cmdline[LINE_MAX];
221   int fd;
222   ssize_t got;
223   char *s;
224
225   if (snprintf (cmdline_fname, sizeof cmdline_fname, "/proc/%d/cmdline",
226       (int) pid) < 0)
227     return NULL;
228   fd = open (cmdline_fname, O_RDONLY);
229   if (fd == -1)
230     {
231       /* It may have already exited - ENOENT.  */
232 #if 0
233       fprintf (stderr, "%s: open (\"%s\"): %m\n", progname, cmdline_fname);
234 #endif
235       return NULL;
236     }
237   got = read (fd, cmdline, sizeof (cmdline) - 1);
238   if (got == -1)
239     fprintf (stderr, "%s: read (\"%s\"): %m\n", progname,
240        cmdline_fname);
241   if (close (fd) != 0)
242     fprintf (stderr, "%s: close (\"%s\"): %m\n", progname,
243        cmdline_fname);
244   if (got < 0)
245     return NULL;
246   /* Convert '\0' argument delimiters to spaces.  */
247   for (s = cmdline; s < cmdline + got; s++)
248     if (!*s)
249       *s = ' ';
250   /* Trim the trailing spaces (typically single '\0'->' ').  */
251   while (s > cmdline && isspace (s[-1]))
252     s--;
253   *s = 0;
254   return cmdline;
255 }
256
257 static int fd_fs_scan (pid_t pid, int (*func) (pid_t pid, const char *link))
258 {
259   DIR *dir;
260   struct dirent *dirent;
261   char dirname[64];
262   int rc = 0;
263
264   if (snprintf (dirname, sizeof dirname, "/proc/%d/fd", (int) pid) < 0)
265     {
266       perror ("snprintf(3)");
267       exit (EXIT_FAILURE);
268     }
269   dir = opendir (dirname);
270   if (dir == NULL)
271     {
272       if (errno == EACCES || errno == ENOENT)
273         return 0;
274       fprintf (stderr, "%s: opendir (\"%s\"): %m\n", progname, dirname);
275       exit (EXIT_FAILURE);
276     }
277   while ((errno = 0, dirent = readdir (dir)))
278     {
279       char linkname[LINE_MAX], buf[LINE_MAX];
280       int linkname_len;
281       ssize_t buf_len;
282
283       /* FIXME: POSIX portability.  */
284       if ((dirent->d_type != DT_DIR && dirent->d_type != DT_LNK)
285           || (dirent->d_type == DT_DIR && strcmp (dirent->d_name, ".") != 0
286               && strcmp (dirent->d_name, "..") != 0)
287           || (dirent->d_type == DT_LNK && strspn (dirent->d_name, "0123456789")
288               != strlen (dirent->d_name)))
289         {
290           /* There is a race, D_TYPE may be uninitialized if stat(2) fails.  */
291           if (dirent->d_type != DT_UNKNOWN)
292             fprintf (stderr, "Unexpected entry \"%s\" (d_type %u)"
293                              " on readdir (\"%s\"): %m\n",
294                      dirent->d_name, (unsigned) dirent->d_type, dirname);
295           continue;
296         }
297       if (dirent->d_type == DT_DIR)
298         continue;
299       linkname_len = snprintf (linkname, sizeof linkname, "%s/%s", dirname,
300                                dirent->d_name);
301       if (linkname_len <= 0 || linkname_len >= sizeof linkname)
302         {
303           fprintf (stderr, "Link content too long: `%s' / `%s'\n",
304                    dirent->d_name, dirent->d_name);
305           continue;
306         }
307       buf_len = readlink (linkname, buf, sizeof buf - 1);
308       if (buf_len <= 0 || buf_len >= sizeof buf - 1)
309         {
310           if (errno != ENOENT && errno != EACCES)
311             fprintf (stderr, "Error reading link \"%s\": %m\n",
312                      linkname);
313           continue;
314         }
315       buf[buf_len] = 0;
316       rc = (*func) (pid, buf);
317       if (rc != 0)
318         {
319           errno = 0;
320           break;
321         }
322     }
323   if (errno != 0)
324     {
325       fprintf (stderr, "%s: readdir (\"%s\"): %m\n", progname, dirname);
326       exit (EXIT_FAILURE);
327     }
328   if (closedir (dir) != 0)
329     {
330       fprintf (stderr, "%s: closedir (\"%s\"): %m\n", progname, dirname);
331       exit (EXIT_FAILURE);
332     }
333   return rc;
334 }
335
336 static void pid_fs_scan (void (*func) (pid_t pid, void *data), void *data)
337 {
338   DIR *dir;
339   struct dirent *dirent;
340
341   dir = opendir ("/proc");
342   if (dir == NULL)
343     {
344       perror ("opendir (\"/proc\")");
345       exit (EXIT_FAILURE);
346     }
347   while ((errno = 0, dirent = readdir (dir)))
348     {
349       /* FIXME: POSIX portability.  */
350       if (dirent->d_type != DT_DIR
351           || strspn (dirent->d_name, "0123456789") != strlen (dirent->d_name))
352           continue;
353       (*func) (atoi (dirent->d_name), data);
354     }
355   if (errno != 0)
356     {
357       perror ("readdir (\"/proc\")");
358       exit (EXIT_FAILURE);
359     }
360   if (closedir (dir) != 0)
361     {
362       perror ("closedir (\"/proc\")");
363       exit (EXIT_FAILURE);
364     }
365 }
366
367 static int rip_check (pid_t pid, const char *link)
368 {
369   assert (pid != getpid ());
370
371   return strcmp (link, childptyname) == 0;
372 }
373
374 struct pid
375   {
376     struct pid *next;
377     pid_t pid;
378   };
379 static struct pid *pid_list;
380
381 static int pid_found (pid_t pid)
382 {
383   struct pid *entry;
384
385   for (entry = pid_list; entry != NULL; entry = entry->next)
386     if (entry->pid == pid)
387       return 1;
388   return 0;
389 }
390
391 static void pid_record (pid_t pid)
392 {
393   struct pid *entry;
394
395   if (pid_found (pid))
396     return;
397
398   entry = malloc (sizeof (*entry));
399   if (entry == NULL)
400     {
401       fprintf (stderr, "%s: malloc: %m\n", progname);
402       exit (EXIT_FAILURE);
403     }
404   entry->pid = pid;
405   entry->next = pid_list;
406   pid_list = entry;
407 }
408
409 static void pid_forall (void (*func) (pid_t pid))
410 {
411   struct pid *entry;
412
413   for (entry = pid_list; entry != NULL; entry = entry->next)
414     (*func) (entry->pid);
415 }
416
417 /* Returns 0 on failure.  */
418 static pid_t pid_get_parent (pid_t pid)
419 {
420   char fname[64];
421   FILE *f;
422   char line[LINE_MAX];
423   pid_t retval = 0;
424
425   if (snprintf (fname, sizeof fname, "/proc/%d/status", (int) pid) < 0)
426     {
427       perror ("snprintf(3)");
428       exit (EXIT_FAILURE);
429     }
430   f = fopen (fname, "r");
431   if (f == NULL)
432     {
433       return 0;
434     }
435   while (errno = 0, fgets (line, sizeof line, f) == line)
436     {
437       if (strncmp (line, "PPid:\t", sizeof "PPid:\t" - 1) != 0)
438         continue;
439       retval = atoi (line + sizeof "PPid:\t" - 1);
440       errno = 0;
441       break;
442     }
443   if (errno != 0)
444     {
445       fprintf (stderr, "%s: fgets (\"%s\"): %m\n", progname, fname);
446       exit (EXIT_FAILURE);
447     }
448   if (fclose (f) != 0)
449     {
450       fprintf (stderr, "%s: fclose (\"%s\"): %m\n", progname, fname);
451       exit (EXIT_FAILURE);
452     }
453   return retval;
454 }
455
456 static void killtree (pid_t pid);
457
458 static void killtree_pid_fs_scan (pid_t pid, void *data)
459 {
460   pid_t parent_pid = *(pid_t *) data;
461
462   /* Optimization.  */
463   if (pid_found (pid))
464     return;
465
466   if (pid_get_parent (pid) != parent_pid)
467     return;
468
469   killtree (pid);
470 }
471
472 static void killtree (pid_t pid)
473 {
474   pid_record (pid);
475   pid_fs_scan (killtree_pid_fs_scan, &pid);
476 }
477
478 static void rip_pid_fs_scan (pid_t pid, void *data)
479 {
480   pid_t pgid;
481
482   /* Shouldn't happen.  */
483   if (pid == getpid ())
484     return;
485
486   /* Check both PGID and the stale file descriptors.  */
487   pgid = getpgid (pid);
488   if (pgid == child
489       || fd_fs_scan (pid, rip_check) != 0)
490     killtree (pid);
491 }
492
493 static void killproc (pid_t pid)
494 {
495   const char *cmdline;
496
497   cmdline = read_cmdline (pid);
498   /* Avoid printing the message for already gone processes.  */
499   if (kill (pid, 0) != 0 && errno == ESRCH)
500     return;
501   if (cmdline == NULL)
502     cmdline = "<error>";
503   fprintf (stderr, "%s: Killed -9 orphan PID %d: %s\n", progname, (int) pid, cmdline);
504   if (kill (pid, SIGKILL))
505     {
506       if (errno != ESRCH)
507         fprintf (stderr, "%s: kill (%d, SIGKILL): %m\n", progname, (int) pid);
508       return;
509     }
510   /* Do not waitpid(2) as it cannot be our direct descendant and it gets
511      cleaned up by init(8).  */
512 #if 0
513   pid_t pid_got;
514   pid_got = waitpid (pid, NULL, 0);
515   if (pid != pid_got)
516     {
517       fprintf (stderr, "%s: waitpid (%d) != %d: %m\n", progname,
518          (int) pid, (int) pid_got);
519       return;
520     }
521 #endif
522 }
523
524 static void rip ()
525 {
526   pid_fs_scan (rip_pid_fs_scan, NULL);
527   pid_forall (killproc);
528 }
529
530 int main (int argc, char **argv)
531 {
532   int rc;
533
534   if (argc < 2 || strcmp (argv[1], "-h") == 0
535       || strcmp (argv[1], "--help") == 0)
536     {
537       fputs ("Syntax: orphanripper <execvp(3) commandline>\n", stdout);
538       exit (EXIT_FAILURE);
539     }
540   progname = argv[0];
541   rc = spawn (argv);
542   rip ();
543   return rc;
544 }