[comp.mail.sendmail] sendmail and big dist lists

Craig_Everhart@TRANSARC.COM (07/19/90)

Here's a patch that I've been distributing to various sendmail source
repositories.  It deals with some of the problems that people have from
time to time as sendmail tries to deliver mail to a long list of users. 
I've sent this to several individuals that I know maintain repositories,
but there are doubtless lots of folks who don't update their local
sendmail sources very often from such repositories.  Hence this posting.

The problem addressed by the fix is that if sendmail delivers to a long
address list (in a single request) but crashes in the middle, it has no
record of the successful deliveries that it has made so far, so when it
eventually gets around to retrying that request, it re-delivers to all
the addresses to which it had been able to deliver earlier.  Not only
does this generate duplicate mail, but the re-delivery process also
requires additional queue processing time, so that it might continue to
take a long time even to attempt delivery to the last address in the
list.

The solution that this patch builds is simple and not very expensive:
after every successful mail delivery, the qf* file is rewritten,
omitting the recipient to whom delivery was successful.  The qf* file
gets shorter even as the request is processed.  A crash in the middle of
processing the list will result in duplicate delivery to at most one
host.  It doesn't require that much time to rewrite the qf* file;
certainly less time than the delivery itself takes.

The only real disadvantage I can imagine is that my sendmail sources
have diverged in many ways from a UCB version of about 1986. 
Nonetheless, it shouldn't be difficult to install this change.

As you can see, this change works by adding an additional flag parameter
to queueup(), then calling queueup() (from sendall()) with this
parameter set sometimes.  The queueup() flag parameter controls which
addresses are rewritten, looking at different flags.

So here's the patch.  I hope that you find it useful.

		Craig Everhart

----------------
*** deliver.c.old	Mon Jan  8 10:10:24 1990
--- deliver.c	Mon Jan  8 10:10:29 1990
***************
*** 1453,1459 ****
  	char mode;
  {
  	register ADDRESS *q;
! 	bool oldverbose;
  	int pid;
  
  	/* determine actual delivery mode */
--- 1453,1459 ----
  	char mode;
  {
  	register ADDRESS *q;
! 	bool oldverbose, DoingMore;
  	int pid;
  
  	/* determine actual delivery mode */
***************
*** 1503,1509 ****
  	if ((mode == SM_QUEUE || mode == SM_FORK ||
  	     (mode != SM_VERIFY && SuperSafe)) &&
  	    !bitset(EF_INQUEUE, e->e_flags))
! 		queueup(e, TRUE, mode == SM_QUEUE);
  #endif QUEUE
  
  	oldverbose = Verbose;
--- 1503,1509 ----
  	if ((mode == SM_QUEUE || mode == SM_FORK ||
  	     (mode != SM_VERIFY && SuperSafe)) &&
  	    !bitset(EF_INQUEUE, e->e_flags))
! 		queueup(e, TRUE, mode == SM_QUEUE, FALSE);
  #endif QUEUE
  
  	oldverbose = Verbose;
***************
*** 1547,1552 ****
--- 1547,1553 ----
  	**  Run through the list and send everything.
  	*/
  
+ 	DoingMore = FALSE;
  	for (q = e->e_sendqueue; q != NULL; q = q->q_next)
  	{
  		if (mode == SM_VERIFY)
***************
*** 1555,1562 ****
  			if (!bitset(QDONTSEND|QBADADDR, q->q_flags))
  				message(Arpa_Info, "deliverable");
  		}
! 		else
! 			(void) deliver(e, q);
  	}
  	Verbose = oldverbose;
  
--- 1556,1569 ----
  			if (!bitset(QDONTSEND|QBADADDR, q->q_flags))
  				message(Arpa_Info, "deliverable");
  		}
! 		else if (!bitset(QDONTSEND, q->q_flags))
! 		{
! 			int estat;
! 			if (DoingMore) queueup(e, TRUE, FALSE, TRUE);
! 			DoingMore = FALSE;
! 			estat = deliver(e, q);	/* Queueup if any delivered */
! 			if (estat == EX_OK) DoingMore = TRUE;
! 		}
  	}
  	Verbose = oldverbose;
  

*** envelope.c.old	Mon Jan  8 10:18:06 1990
--- envelope.c	Mon Jan  8 10:18:08 1990
***************
*** 147,153 ****
  	else if (queueit || !bitset(EF_INQUEUE, e->e_flags))
  	{
  #ifdef QUEUE
! 		queueup(e, FALSE, FALSE);
  #else QUEUE
  		syserr("dropenvelope: queueup");
  #endif QUEUE
--- 147,153 ----
  	else if (queueit || !bitset(EF_INQUEUE, e->e_flags))
  	{
  #ifdef QUEUE
! 		queueup(e, FALSE, FALSE, FALSE);
  #else QUEUE
  		syserr("dropenvelope: queueup");
  #endif QUEUE

*** queue.c.old	Mon Jan  8 10:19:05 1990
--- queue.c	Mon Jan  8 10:19:08 1990
***************
*** 48,53 ****
--- 48,57 ----
  **		queueall -- if TRUE, queue all addresses, rather than
  **			just those with the QQUEUEUP flag set.
  **		announce -- if TRUE, tell when you are queueing up.
+ ** (CFE)		Checkpoint -- if TRUE, we're doing a checkpoint of a
+ **			list currently being delivered.  Write the
+ **			addresses not yet tried as well as the queued
+ **			addresses.
  **
  **	Returns:
  **		none.
***************
*** 56,65 ****
  **		The current request are saved in a control file.
  */
  
! queueup(e, queueall, announce)
  	register ENVELOPE *e;
  	bool queueall;
  	bool announce;
  {
  	char *tf;
  	char *qf;
--- 60,70 ----
  **		The current request are saved in a control file.
  */
  
! queueup(e, queueall, announce, Checkpoint)
  	register ENVELOPE *e;
  	bool queueall;
  	bool announce;
+ 	bool Checkpoint;
  {
  	char *tf;
  	char *qf;
***************
*** 139,148 ****
  	/* output list of recipient addresses */
  	for (q = e->e_sendqueue; q != NULL; q = q->q_next)
  	{
! 		if (queueall ? !bitset(QDONTSEND, q->q_flags) :
! 			       bitset(QQUEUEUP, q->q_flags))
  		{
! 			if (ValidateRuleSet > 0 || q->q_user != NULL || q->q_user[0] != '\0') {
  				fprintf(tfp, "R%s\n", q->q_user);
  			} else {
  				fprintf(tfp, "R%s\n", q->q_paddr);
--- 144,165 ----
  	/* output list of recipient addresses */
  	for (q = e->e_sendqueue; q != NULL; q = q->q_next)
  	{
! 		int writeit;
! 
! /*		if (queueall ? !bitset(QDONTSEND, q->q_flags) :
! 			       bitset(QQUEUEUP, q->q_flags)) */
! 		if (Checkpoint) {	/* Just delete the already-sent addresses. */
! 			writeit = TRUE;
! 			if (bitset(QDONTSEND, q->q_flags)
! 			  && !bitset(QQUEUEUP, q->q_flags)
! 			  && !bitset(QBADADDR, q->q_flags)) writeit = FALSE;
! 		} else if (queueall)
! 			writeit = !bitset(QDONTSEND, q->q_flags);
! 		else
! 			writeit = bitset(QQUEUEUP, q->q_flags);
! 		if (writeit)
  		{
! 			if (ValidateRuleSet > 0 && q->q_user != NULL && q->q_user[0] != '\0') {
  				fprintf(tfp, "R%s\n", q->q_user);
  			} else {
  				fprintf(tfp, "R%s\n", q->q_paddr);
----------------