Index: client-src/killpgrp.c
===================================================================
RCS file: /cvsroot/amanda/amanda/client-src/killpgrp.c,v
retrieving revision 1.8
diff -u -r1.8 killpgrp.c
--- client-src/killpgrp.c	1998/07/04 00:18:16	1.8
+++ client-src/killpgrp.c	2001/01/05 01:07:12
@@ -46,6 +46,7 @@
 int main P((int argc, char **argv));
 static void term_kill_soft P((int sig));
 static void term_kill_hard P((int sig));
+static void dblogerror P((char *));
 
 int main(argc, argv)
 int argc;
@@ -65,6 +66,8 @@
     }
 
     set_pname("killpgrp");
+    set_logerror(dblogerror);
+    erroutput_type = (ERR_INTERACTIVE|ERR_SYSLOG|ERR_AMANDALOG);
 
     dbopen();
     dbprintf(("%s: version %s\n", argv[0], version()));
@@ -147,4 +150,10 @@
 	dbprintf(("kill failed: %s\n", strerror(errno)));
 	dbprintf(("waiting until child terminates\n"));
     }
+}
+
+static void dblogerror(s)
+    char *s;
+{
+    dbprintf(("%s", s));
 }
Index: client-src/sendsize.c
===================================================================
RCS file: /cvsroot/amanda/amanda/client-src/sendsize.c,v
retrieving revision 1.94.2.6
diff -u -r1.94.2.6 sendsize.c
--- client-src/sendsize.c	1998/11/18 06:34:36	1.94.2.6
+++ client-src/sendsize.c	2001/01/05 01:07:12
@@ -112,6 +112,9 @@
     int fd;
     unsigned long malloc_hist_1, malloc_size_1;
     unsigned long malloc_hist_2, malloc_size_2;
+    time_t start_time;
+    time_t now;
+    int est_count = 0;
 
     /* initialize */
 
@@ -142,6 +145,7 @@
     /* handle all service requests */
 
     start_amandates(0);
+    time(&start_time);
 
     for(; (line = agets(stdin)) != NULL; free(line)) {
 #define sc "OPTIONS"
@@ -240,6 +244,7 @@
 	}
 
 	add_diskest(disk, level, exclude, spindle, prog);
+	est_count++;
     }
     amfree(line);
 
@@ -273,9 +278,24 @@
 #endif
     }
 
+    time(&now);
+    dbprintf(("%s: total time: %ld second%s\n",
+	      get_pname(),
+	      now - start_time,
+	      (now - start_time == 1) ? "" : "s"));
+    if(est_count == 0) {
+	est_count = 1;
+	now = start_time;
+    }
+    dbprintf(("%s: average: %ld second%s\n",
+	      get_pname(),
+	      (now - start_time) / est_count,
+	      ((now - start_time) / est_count == 1) ? "" : "s"));
     dbclose();
     return 0;
+
  err:
+
     printf("FORMAT ERROR IN REQUEST PACKET\n");
     if(err_extra) {
 	dbprintf(("REQ packet is bogus: %s\n", err_extra));
@@ -593,10 +613,9 @@
 char *disk;
 int level;
 {
-    int pipefd[2], nullfd, killctl[2];
+    int nullfd, pipefd[2], stdoutfd[2], killctl[2];
     pid_t dumppid;
-    long size;
-    FILE *dumpout;
+    long size = -1;
     char *dumpkeys = NULL;
     char *device = NULL;
     char *fstype = NULL;
@@ -605,6 +624,16 @@
     char *rundump_cmd = NULL;
     char level_str[NUM_STR_SIZE];
     int s;
+    int save_errno;
+    fd_set readset;
+    fd_set selectset;
+    int maxfd;
+    char stdoutbuf[1024];
+    int timer;
+    int kill_state;
+    struct timeval sleep_time;
+    time_t start_time;
+    time_t now;
 
     ap_snprintf(level_str, sizeof(level_str), "%d", level);
 
@@ -614,9 +643,30 @@
     cmd = vstralloc(libexecdir, "/rundump", versionsuffix(), NULL);
     rundump_cmd = stralloc(cmd);
 
-    nullfd = open("/dev/null", O_RDWR);
-    pipefd[0] = pipefd[1] = killctl[0] = killctl[1] = -1;
-    pipe(pipefd);
+    if((nullfd = open("/dev/null", O_RDWR)) < 0) {
+	save_errno = errno;
+	dbprintf(("%s: open(/dev/null): %s", get_pname(), strerror(errno)));
+	errno = save_errno;
+	error("%s: open(/dev/null): %s", get_pname(), strerror(errno));
+    }
+    if(pipe(pipefd) != 0) {
+	save_errno = errno;
+	dbprintf(("%s: pipe(pipefd): %s", get_pname(), strerror(errno)));
+	errno = save_errno;
+	error("%s: pipe(pipefd): %s", get_pname(), strerror(errno));
+    }
+    if(pipe(stdoutfd) != 0) {
+	save_errno = errno;
+	dbprintf(("%s: pipe(stdoutfd): %s", get_pname(), strerror(errno)));
+	errno = save_errno;
+	error("%s: pipe(stdoutfd): %s", get_pname(), strerror(errno));
+    }
+    if(pipe(killctl) != 0) {
+	save_errno = errno;
+	dbprintf(("%s: pipe(killctl): %s", get_pname(), strerror(errno)));
+	errno = save_errno;
+	error("%s: pipe(killctl): %s", get_pname(), strerror(errno));
+    }
 
 #ifdef XFSDUMP						/* { */
 #ifdef DUMP						/* { */
@@ -713,22 +763,20 @@
 	error("%s: no dump program available", get_pname());
     }
 
-    pipe(killctl);
-
     switch(dumppid = fork()) {
     case -1:
-	amfree(dumpkeys);
-	amfree(cmd);
-	amfree(rundump_cmd);
-	amfree(device);
-	return -1;
+	goto common_exit;
     default:
 	break; 
-    case 0:	/* child process */
+    case 0:
+	/*
+	 * This is the child process.  Put ourself in our own process
+	 * group so the parent can kill us with one call.  Fork off
+	 * another child and run killpgrp in it.  Killpgrp waits for
+	 * a pipe back to the original parent to close then kills us.
+	 */
 	if(SETPGRP == -1)
 	    SETPGRP_FAILED();
-	else if (killctl[0] == -1 || killctl[1] == -1)
-	    dbprintf(("pipe for killpgrp failed, trying without killpgrp\n"));
 	else {
 	    switch(fork()) {
 	    case -1:
@@ -736,36 +784,58 @@
 		break;
 
 	    default:
-	    {
-		char *killpgrp_cmd = vstralloc(libexecdir, "/killpgrp",
-					       versionsuffix(), NULL);
-		dbprintf(("running %s\n",killpgrp_cmd));
-		dup2(killctl[0], 0);
-		dup2(nullfd, 1);
-		dup2(nullfd, 2);
-		close(pipefd[0]);
-		close(pipefd[1]);
-		close(killctl[1]);
-		close(nullfd);
-		execle(killpgrp_cmd, killpgrp_cmd, (char *)0, safe_env());
-		dbprintf(("cannot execute %s\n", killpgrp_cmd));
+		/*
+		 * This is the parent process (one level below the
+		 * original parent).  Set up killpgrp to wait on stdin
+		 * connected to a pipe to the original parent.
+		 */
+		amfree(cmd);
+		cmd = vstralloc(libexecdir,
+				"/killpgrp",
+				versionsuffix(),
+				NULL);
+		dbprintf(("running %s\n", cmd));
+
+		close(pipefd[0]);		/* do not need this */
+		close(pipefd[1]);		/* do not need this */
+		close(killctl[1]);		/* do not need write side */
+
+		dup2(killctl[0], 0);		/* pipe to wait to close */
+		close(killctl[0]);		/* done with this */
+
+		dup2(nullfd, 1);		/* throw away stdout */
+		dup2(nullfd, 2);		/* throw away stderr */
+		close(nullfd);			/* done with this */
+
+		execle(cmd, cmd, (char *)0, safe_env());
+		dbprintf(("cannot execute %s\n", cmd));
 		exit(-1);
-	    }
 
 	    case 0:  /* child process */
 		break;
 	    }
 	}
+
+	/*
+	 * This is now the child of the child.  Killpgrp is our parent,
+	 * if it got started properly.  We set up stdout to go back to
+	 * the original parent, who may close it when it gets the size
+	 * line, which in turn will kill us with a SIGPIPE.
+	 */
+	aclose(stdoutfd[0]);			/* do not need read side */
+	aclose(pipefd[0]);			/* do not need read side */
+	aclose(killctl[0]);			/* do not need this */
+	aclose(killctl[1]);			/* do not need this */
+
+	dup2(nullfd, 0);			/* set stdin to /dev/null */
+	aclose(nullfd);				/* done with this */
 
-	dup2(nullfd, 0);
-	dup2(nullfd, 1);
-	dup2(pipefd[1], 2);
-	aclose(pipefd[0]);
-	if (killctl[0] != -1)
-	    aclose(killctl[0]);
-	if (killctl[1] != -1)
-	    aclose(killctl[1]);
+	dup2(stdoutfd[1], 1);			/* send stdout to parent */
+	aclose(stdoutfd[1]);			/* done with this */
 
+	dup2(pipefd[1], 2);			/* send stderr to parent */
+	aclose(pipefd[1]);			/* done with this */
+
 #ifdef XFSDUMP
 #ifdef DUMP
 	if (strcmp(fstype, "xfs") == 0)
@@ -812,83 +882,157 @@
 		    get_pname(), cmd));
 	  error("%s: exec %s failed or no dump program available",
 		get_pname(), cmd);
-	  exit(1);
 	}
     }
 
+    /*
+     * At this point we are back at the original parent.  We have three
+     * pipes open:
+     *
+     *   one to killpgrp who is waiting for the read side (our write side)
+     *   to close
+     *
+     *   one from stdout of the dump process, which we will soak up
+     *   until we get a size line, then close to cause a SIGPIPE
+     *
+     *   one from stderr of the dump process to watch for the size line
+     *   and to ultimately tell when the dump process has terminated
+     *   (EOF).
+     */
+
     amfree(dumpkeys);
     amfree(cmd);
     amfree(rundump_cmd);
 
-    aclose(pipefd[1]);
-    if (killctl[0] != -1)
-	aclose(killctl[0]);
-    dumpout = fdopen(pipefd[0],"r");
-
-    for(size = -1; (line = agets(dumpout)) != NULL; free(line)) {
-	dbprintf(("%s\n",line));
-	size = handle_dumpline(line);
-	if(size > -1) {
-	    amfree(line);
-	    if((line = agets(dumpout)) != NULL) {
-		dbprintf(("%s\n",line));
+    aclose(pipefd[1]);				/* do not need write side */
+    aclose(stdoutfd[1]);			/* do not need write side */
+    aclose(killctl[0]);				/* do not need read side */
+
+    FD_ZERO(&readset);
+    maxfd = -1;
+    FD_SET(pipefd[0], &readset);
+    if(pipefd[0] > maxfd) {
+	maxfd = pipefd[0];
+    }
+    FD_SET(stdoutfd[0], &readset);
+    if(stdoutfd[0] > maxfd) {
+	maxfd = stdoutfd[0];
+    }
+
+    timer = -1;
+    kill_state = -1;
+    time(&start_time);
+
+    while(1) {
+	memcpy(&selectset, &readset, sizeof(fd_set));
+	sleep_time.tv_sec = 1;
+	sleep_time.tv_usec = 0;
+	s = select(maxfd+1,
+		   (SELECT_ARG_TYPE *)(&selectset),
+		   NULL,
+		   NULL,
+		   &sleep_time);
+	if(s == -1) {
+	    save_errno = errno;
+	    dbprintf(("%s: select: %s", get_pname(), strerror(errno)));
+	    errno = save_errno;
+	    error("%s: select: %s", get_pname(), strerror(errno));
+	}
+	if(s == 0 && timer > 0) {
+	    /*
+	     * We timed out and were waiting on a kill to complete.  The
+	     * first time we send a KILL to the child process group.  If
+	     * that does not work within a few seconds, we give up and
+	     * just wait for it to complete.
+	     */
+	    timer--;
+	    if(timer == 0 && kill_state == 0) {
+		dbprintf(("sending SIGKILL to process group %ld\n",
+			  (long) dumppid));
+		if (kill(-dumppid, SIGKILL) == -1) {
+		    dbprintf(("kill failed: %s\n", strerror(errno)));
+		}
+		timer = 5;
+		kill_state++;
+	    } else if(timer == 0 && kill_state == 1) {
+		dbprintf(("cannot kill it, waiting for normal termination\n"));
+		kill_state++;
 	    }
-	    break;
 	}
-    }
-    amfree(line);
-
-    dbprintf((".....\n"));
-    if(size == -1)
-	dbprintf(("(no size line match in above dump output)\n.....\n"));
-    if(size == 0 && level == 0)
-	dbprintf(("(PC SHARE connection problem, is this disk really empty?)\n.....\n"));
-
-    if (killctl[1] != -1) {
-	dbprintf(("asking killpgrp to terminate\n"));
-	aclose(killctl[1]);
-	for(s = 5; s > 0; --s) {
-	    sleep(1);
-	    if (waitpid(dumppid, NULL, WNOHANG) != -1)
-		goto terminated;
+	if(FD_ISSET(pipefd[0], &selectset) || areads_dataready(pipefd[0])) {
+	    if((line = areads(pipefd[0])) == NULL) {
+		/*
+		 * The dump process has terminated.
+		 */
+		break;
+	    }
+	    dbprintf(("%s\n", line));
+	    if(size < 0 && (size = handle_dumpline(line)) > -1) {
+		/*
+		 * We got a size line.	Close the stdout connection
+		 * to the dump process to cause a SIGPIPE.  Close the
+		 * stdin connection to killpgrp to cause it to try and
+		 * kill things.
+		 */
+		time(&now);
+		dbprintf(("..... level %d estimate for %s is %ld KByte%s at %s",
+			  level,
+			  disk,
+			  size,
+			  (size == 1) ? "" : "s",
+			  ctime(&now)));
+		dbprintf(("closing stdout pipe and asking killpgrp to terminate\n"));
+		FD_CLR(stdoutfd[0], &readset);
+		aclose(stdoutfd[0]);		/* SIGPIPE to dump process */
+		aclose(killctl[1]);		/* let killpgrp have a shot */
+		timer = 5;
+		kill_state = 0;
+	    }
+	} else if(FD_ISSET(stdoutfd[0], &selectset)) {
+	    /*
+	     * This is normal stdout (dump image) to be thrown away.  We
+	     * have to collect it since we have multiple pipe connections
+	     * to dump and could otherwise get into a deadlock.
+	     */
+	    (void)read(stdoutfd[0], stdoutbuf, sizeof(stdoutbuf));
 	}
     }
-    
-    /*
-     * First, try to kill the dump process nicely.  If it ignores us
-     * for several seconds, hit it harder.
-     */
-    dbprintf(("sending SIGTERM to process group %ld\n", (long) dumppid));
-    if (kill(-dumppid, SIGTERM) == -1) {
-	dbprintf(("kill failed: %s\n", strerror(errno)));
-    }
-    /* Now check whether it dies */
-    for(s = 5; s > 0; --s) {
-	sleep(1);
-	if (waitpid(dumppid, NULL, WNOHANG) != -1)
-	    goto terminated;
-    }
 
-    dbprintf(("sending SIGKILL to process group %ld\n", (long) dumppid));
-    if (kill(-dumppid, SIGKILL) == -1) {
-	dbprintf(("kill failed: %s\n", strerror(errno)));
-    }
-    for(s = 5; s > 0; --s) {
-	sleep(1);
-	if (waitpid(dumppid, NULL, WNOHANG) != -1)
-	    goto terminated;
-    }
-
-    dbprintf(("cannot kill it, waiting for normal termination\n"));
-    wait(NULL);
-
- terminated:
+    if(size == -1) {
+	dbprintf((".....\n"));
+	dbprintf(("(no size line match in above dump output)\n"));
+	dbprintf((".....\n"));
+    }
+    if(size == 0 && level == 0) {
+	dbprintf(("(PC SHARE connection problem, is this disk really empty?)\n"));
+	dbprintf((".....\n"));
+    }
+
+    wait(NULL);					/* collect the child */
+    time(&now);
+    dbprintf(("..... level %d estimate for %s done in %ld second%s at %s",
+	      level,
+	      disk,
+	      now - start_time,
+	      (now - start_time == 1) ? "" : "s",
+	      ctime(&now)));
+
+common_exit:
+
+    if(nullfd >= 0) aclose(nullfd);
+    if(stdoutfd[0] >= 0) aclose(stdoutfd[0]);
+    if(stdoutfd[1] >= 0) aclose(stdoutfd[1]);
+    if(pipefd[0] >= 0) aclose(pipefd[0]);
+    if(pipefd[1] >= 0) aclose(pipefd[1]);
+    if(killctl[0] >= 0) aclose(killctl[0]);
+    if(killctl[1] >= 0) aclose(killctl[1]);
 
-    aclose(nullfd);
-    afclose(dumpout);
-
+    amfree(dumpkeys);
+    amfree(cmd);
+    amfree(rundump_cmd);
     amfree(device);
     amfree(fstype);
+    amfree(line);
 
     return size;
 }
Index: common-src/amanda.h
===================================================================
RCS file: /cvsroot/amanda/amanda/common-src/amanda.h,v
retrieving revision 1.64.2.1
diff -u -r1.64.2.1 amanda.h
--- common-src/amanda.h	1998/10/31 06:19:34	1.64.2.1
+++ common-src/amanda.h	2001/01/05 01:07:12
@@ -500,6 +500,8 @@
 extern char  *agets	      P((FILE *file));
 extern char  *areads	      P((int fd));
 #endif
+extern ssize_t  areads_dataready  P((int fd));
+extern void     areads_relbuf     P((int fd));
 
 /*
  * amfree(ptr) -- if allocated, release space and set ptr to NULL.
Index: common-src/file.c
===================================================================
RCS file: /cvsroot/amanda/amanda/common-src/file.c,v
retrieving revision 1.14
diff -u -r1.14 file.c
--- common-src/file.c	1998/07/04 00:18:43	1.14
+++ common-src/file.c	2001/01/05 01:07:12
@@ -266,6 +266,99 @@
 
 /*
  *=====================================================================
+ * Find/create a buffer for a particular file descriptor for use with
+ * areads().
+ *
+ * void areads_getbuf (int fd)
+ *
+ * entry:	fd = file descriptor to look up
+ * exit:	returns a pointer to the buffer, possibly new
+ *=====================================================================
+ */
+
+static struct areads_buffer {
+    char *buffer;
+    char *endptr;
+    ssize_t bufsize;
+} *areads_buffer = NULL;
+static int areads_bufcount = 0;
+static ssize_t areads_bufsize = BUFSIZ;		/* for the test program */
+
+static void
+areads_getbuf(fd)
+    int fd;
+{
+    struct areads_buffer *new;
+    ssize_t size;
+
+    assert(fd >= 0);
+    if(fd >= areads_bufcount) {
+	size = (fd + 1) * sizeof(*areads_buffer);
+	new = (struct areads_buffer *) alloc(size);
+	memset((char *)new, 0, size);
+	if(areads_buffer) {
+	    size = areads_bufcount * sizeof(*areads_buffer);
+	    memcpy(new, areads_buffer, size);
+	}
+	amfree(areads_buffer);
+	areads_buffer = new;
+	areads_bufcount = fd + 1;
+    }
+    if(areads_buffer[fd].buffer == NULL) {
+	areads_buffer[fd].bufsize = areads_bufsize;
+	areads_buffer[fd].buffer = alloc(areads_buffer[fd].bufsize + 1);
+	areads_buffer[fd].buffer[0] = '\0';
+	areads_buffer[fd].endptr = areads_buffer[fd].buffer;
+    }
+}
+
+/*
+ *=====================================================================
+ * Return the amount of data still in an areads buffer.
+ *
+ * ssize_t areads_dataready (int fd)
+ *
+ * entry:	fd = file descriptor to release buffer for
+ * exit:	returns number of bytes of data ready to process
+ *=====================================================================
+ */
+
+ssize_t
+areads_dataready(fd)
+    int fd;
+{
+    ssize_t r = 0;
+
+    if(fd >= 0 && fd < areads_bufcount && areads_buffer[fd].buffer != NULL) {
+	r = areads_buffer[fd].endptr - areads_buffer[fd].buffer;
+    }
+    return r;
+}
+
+/*
+ *=====================================================================
+ * Release a buffer for a particular file descriptor used by areads().
+ *
+ * void areads_relbuf (int fd)
+ *
+ * entry:	fd = file descriptor to release buffer for
+ * exit:	none
+ *=====================================================================
+ */
+
+void
+areads_relbuf(fd)
+    int fd;
+{
+    if(fd >= 0 && fd < areads_bufcount) {
+	amfree(areads_buffer[fd].buffer);
+	areads_buffer[fd].endptr = NULL;
+	areads_buffer[fd].bufsize = 0;
+    }
+}
+
+/*
+ *=====================================================================
  * Get the next line of input from a file descriptor.
  *
  * char *areads (int fd)
@@ -292,51 +385,57 @@
 {
     char *nl;
     char *line;
-    static char buffer[BUFSIZ+1];
-    static char *line_buffer = NULL;
-    char *t;
-    int r;
+    char *buffer;
+    char *endptr;
+    char *newbuf;
+    ssize_t buflen;
+    ssize_t size;
+    ssize_t r;
 
     malloc_enter(dbmalloc_caller_loc(s, l));
 
-    while(1) {
+    if(fd < 0) {
+	errno = EBADF;
+	return NULL;
+    }
+    areads_getbuf(fd);
+    buffer = areads_buffer[fd].buffer;
+    endptr = areads_buffer[fd].endptr;
+    buflen = areads_buffer[fd].bufsize - (endptr - buffer);
+    while((nl = strchr(buffer, '\n')) == NULL) {
 	/*
-	 * First, see if we have a line in the buffer.
+	 * No newline yet, so get more data.
 	 */
-	if(line_buffer) {
-	    if((nl = strchr(line_buffer, '\n')) != NULL) {
-		*nl++ = '\0';
-		line = stralloc(line_buffer);
-		if(*nl) {
-		    t = stralloc(nl);		/* save data still in buffer */
-		} else {
-		    t = NULL;
-		}
-		amfree(line_buffer);
-		line_buffer = t;
-		malloc_leave(dbmalloc_caller_loc(s, l));
-		return line;
-	    }
+	if (buflen == 0) {
+	    size = areads_buffer[fd].bufsize + areads_bufsize;
+	    newbuf = alloc(size + 1);
+	    memcpy (newbuf, buffer, areads_buffer[fd].bufsize + 1);
+	    areads_buffer[fd].buffer = newbuf;
+	    areads_buffer[fd].endptr = newbuf + areads_buffer[fd].bufsize;
+	    areads_buffer[fd].bufsize = size;
+	    buffer = areads_buffer[fd].buffer;
+	    endptr = areads_buffer[fd].endptr;
+	    buflen = areads_buffer[fd].bufsize - (endptr - buffer);
 	}
-	/*
-	 * Now, get more data and loop back to check for a completed
-	 * line again.
-	 */
-	if ((r = read(fd, buffer, sizeof(buffer)-1)) <= 0) {
+	if ((r = read(fd, endptr, buflen)) <= 0) {
 	    if(r == 0) {
-		errno = 0;			/* flag EOF instead of error */
+		errno = 0;		/* flag EOF instead of error */
 	    }
-	    amfree(line_buffer);
 	    malloc_leave(dbmalloc_caller_loc(s, l));
 	    return NULL;
-	}
-	buffer[r] = '\0';
-	if(line_buffer) {
-	    strappend(line_buffer, buffer);
-	} else {
-	    line_buffer = stralloc(buffer);
 	}
+	endptr[r] = '\0';		/* we always leave room for this */
+	endptr += r;
+	buflen -= r;
     }
+    *nl++ = '\0';
+    line = stralloc(buffer);
+    size = endptr - nl;			/* data still left in buffer */
+    memmove(buffer, nl, size);
+    areads_buffer[fd].endptr = buffer + size;
+    areads_buffer[fd].endptr[0] = '\0';
+    malloc_leave(dbmalloc_caller_loc(s, l));
+    return line;
 }
 
 #ifdef TEST
