I am also regularily annoyed by this problem, which seems to be too easy
to trigger simply by running unattended-upgrades.

Arthur de Jong has found the explanation: Whenever run-parts (with the
--report option) starts a script which leaks stdout or stderr, then
run-parts will hang forever in select().

This simple leaky script will demonstrate the issue:

#!/bin/sh

# closing stdin and stdout, but leaking stderr...
(exec setsid sleep 5 <&- >&-) &

echo $0 is exiting now
exit 0



run-parts --report will wait until sleep finishes when running this
script.  If the sleep is replaced by a daemon started by apt, or some
other permanently running application, then run-parts will wait forever.

Now, I will absolutely agree that the descriptor leak is a bug in the
package started by apt, but I also agree with Arthur de Jong that
run-parts could be smarter.  The fact that run-parts will hang only if
started with "--report" is an indication that this should be considered a
bug in run-parts as well, IMHO.  Using "--report" should not have side
effects. 

I have attached a primitive attempt to fix this.  Not sure if this is
entirely safe or not, but it's a starting point....  signalfd() would
have been perfect, but it's not very portable.



Bjørn

>From 6dd8546883b0d13d5b2c41d2d94097ce40c4a84a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bj=C3=B8rn=20Mork?= <bj...@mork.no>
Date: Mon, 21 Mar 2011 11:15:34 +0100
Subject: [PATCH] Close report pipes if child exits. closes: #379645
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Bjørn Mork <bj...@mork.no>
---
 run-parts.c |   31 ++++++++++++++++++-------------
 1 files changed, 18 insertions(+), 13 deletions(-)

diff --git a/run-parts.c b/run-parts.c
index 8278cd9..eb03d39 100644
--- a/run-parts.c
+++ b/run-parts.c
@@ -208,19 +208,17 @@ void run_part(char *progname)
 
     while (pout[0] >= 0 || perr[0] >= 0) {
 
-      do {
-	FD_ZERO(&set);
-	if (pout[0] >= 0)
-	  FD_SET(pout[0], &set);
-	if (perr[0] >= 0)
-	  FD_SET(perr[0], &set);
-	r = select(max, &set, 0, 0, 0);
-      } while (r < 0 && errno == EINTR);
-
-      if (r < 0) {
-	/* assert(errno != EINTR) */
-	error("select: %s", strerror(errno));
-	exit(1);
+      FD_ZERO(&set);
+      if (pout[0] >= 0)
+	FD_SET(pout[0], &set);
+      if (perr[0] >= 0)
+	FD_SET(perr[0], &set);
+      r = select(max, &set, 0, 0, 0);
+
+      if (r < 0 && errno != EINTR) {
+	  /* assert(errno != EINTR) */
+	  error("select: %s", strerror(errno));
+	  exit(1);
       }
       else if (r > 0) {
 	if (pout[0] >= 0 && FD_ISSET(pout[0], &set)) {
@@ -268,6 +266,13 @@ void run_part(char *progname)
 	/* assert(FALSE): select was called with infinite timeout, so
 	   it either returns successfully or is interrupted */
       }				/*if */
+      /* close pipe if child exited */
+      if (waitpid(pid, &result, WNOHANG)) {
+	  close(pout[0]);
+	  pout[0] = -1;
+	  close(perr[0]);
+	  perr[0] = -1;
+      }
     }				/*while */
   }
 
-- 
1.7.2.5

Reply via email to