sendmail runs out of memory on long lists

Steven M. Schultz sms at wlv.imsd.contel.com
Sat Nov 25 09:24:17 AEST 1989


Subject: sendmail and long recipient lists
Index:	usr.lib/sendmail/src/Makefile.m4 2.10BSD

Description:
	Even with string extraction being used (using the sendmail
	preprocessor 'mkstrcc') 'sendmail' runs out of memory when 
	processing very long lists of recipients.

Repeat-By:
	Attempt to send a mail item which has 120+ people in the To:
	line.

Fix:
	No changes to the 'sendmail' source are required, instead
	two new source files are added and the Makefile.m4 file
	is patched.  If you do not have the latest Makefile.m4 and
	'mkstrcc' shell script send a mail item to "sms at wlv.imsd.contel.com"
	and i will mail them to you.

	Aside from 'sendmail's voracious appetite for memory, compounded
	by malloc()'s apparently not always being matched by free()'s, 
	one of the largest single contributors to the D space of 'sendmail'
	is the ctime(3) (and associated routines localtime(3), etc)
	function.  The tables utilized for the Daylight Savings Time automatic
	timezone adjustment are substantial.  The solution implemented
	by the modules below move the ctime(3) logic to a separate
	process (/usr/lib/ctimed) and use a local ctime.o in 'sendmail' to 
	communicate via a bidirectional pipe to the external process.

	This SAVES 2.5KB of D space!  The cost is low, one process table
	table entry occupied permanently, and a small ('sendmail' does not 
	call the ctime(3) routines more than twice or thrice per invocation 
	based on the debugging of 'ctimed') amount of overhead for each mail 
	item.

	Due to the peculiar forking sequence 'sendmail' performs upon
	startup, the 'ctimed' pid for the "sendmail -bd" process will
	be one lower than that of the daemon 'sendmail'.


#! /bin/sh
# This is a shell archive, meaning:
# 1. Remove everything above the #! /bin/sh line.
# 2. Save the resulting text in a file.
# 3. Execute the file with /bin/sh (not csh) to create:
#	patch
#	ctime.c
#	ctimed.c
# This archive created: Fri Nov 24 16:53:28 1989
export PATH; PATH=/bin:/usr/bin:$PATH
if test -f 'patch'
then
	echo shar: "will not over-write existing file 'patch'"
else
sed 's/^X//' << \SHAR_EOF > 'patch'
X*** Makefile.m4.ol	Mon Feb  6 12:02:24 1989
X--- Makefile.m4	Wed Nov  1 16:12:05 1989
X***************
X*** 26,38 ****
X  	savemail.o err.o readcf.o stab.o headers.o recipient.o \
X  	stats.o daemon.o usersmtp.o srvrsmtp.o queue.o \
X  	macro.o util.o clock.o trace.o envelope.o
X! OBJS2=	sysexits.o arpadate.o convtime.o
X  OBJS=	$(OBJS1) $(OBJS2) $(EXTRACT)
X  
X  SBASE=	conf.o collect.o parseaddr.o alias.o deliver.o stab.o headers.o \
X  	recipient.o stats.o srvrsmtp.o queue.o macro.o util.o clock.o \
X  	trace.o envelope.o sysexits.o arpadate.o convtime.o Version.o \
X! 	$(EXTRACT)
X  SOV1=	main.o readcf.o
X  SOV2=	daemon.o savemail.o usersmtp.o err.o
X  
X--- 26,38 ----
X  	savemail.o err.o readcf.o stab.o headers.o recipient.o \
X  	stats.o daemon.o usersmtp.o srvrsmtp.o queue.o \
X  	macro.o util.o clock.o trace.o envelope.o
X! OBJS2=	sysexits.o arpadate.o convtime.o ctime.o
X  OBJS=	$(OBJS1) $(OBJS2) $(EXTRACT)
X  
X  SBASE=	conf.o collect.o parseaddr.o alias.o deliver.o stab.o headers.o \
X  	recipient.o stats.o srvrsmtp.o queue.o macro.o util.o clock.o \
X  	trace.o envelope.o sysexits.o arpadate.o convtime.o Version.o \
X! 	ctime.o $(EXTRACT)
X  SOV1=	main.o readcf.o
X  SOV2=	daemon.o savemail.o usersmtp.o err.o
X  
X***************
X*** 41,49 ****
X  	sysexits.c util.c arpadate.c version.c collect.c \
X  	macro.c headers.c readcf.c stab.c recipient.c stats.c daemon.c \
X  	usersmtp.c srvrsmtp.c queue.c clock.c trace.c envelope.c
X! SRCS2=	TODO convtime.c
X  SRCS=	Version.c $(SRCS1) $(SRCS2)
X! ALL=	sendmail
X  
X  CHOWN=	-echo chown
X  CHMOD=	chmod
X--- 41,49 ----
X  	sysexits.c util.c arpadate.c version.c collect.c \
X  	macro.c headers.c readcf.c stab.c recipient.c stats.c daemon.c \
X  	usersmtp.c srvrsmtp.c queue.c clock.c trace.c envelope.c
X! SRCS2=	TODO convtime.c ctime.c
X  SRCS=	Version.c $(SRCS1) $(SRCS2)
X! ALL=	sendmail ctimed
X  
X  CHOWN=	-echo chown
X  CHMOD=	chmod
X***************
X*** 85,90 ****
X--- 85,93 ----
X  	$(CHMOD) $(OBJMODE) sendmail
X  	size sendmail; ls -l sendmail; ifdef(`m4SCCS', `$(WHAT) < Version.o')
X  
X+ ctimed:
X+ 	cc $(SEPFLAG) $(CFLAGS) ctimed.c -o ctimed
X+ 
X  install: all
X  	$(INSTALL) -m 4755 sendmail $(DESTDIR)/usr/lib
X  	chgrp kmem $(DESTDIR)/usr/lib/sendmail
X***************
X*** 93,98 ****
X--- 96,102 ----
X  		install -c -o bin -m 644 sendmail.sr \
X  			$(DESTDIR)/usr/lib/sendmail.sr; \
X  	fi
X+ 	install -c -s -o bin -m 0755 ctimed /usr/lib/ctimed
X  
X  version: newversion $(OBJS) Version.c
X  
X***************
X*** 137,143 ****
X  
X  clean:
X  	rm -f core sendmail rmail usersmtp uucp a.out XREF sendmail.cf
X! 	rm -f sendmail.sr *.o
X  
X  sources: $(SRCS)
X  
X--- 141,147 ----
X  
X  clean:
X  	rm -f core sendmail rmail usersmtp uucp a.out XREF sendmail.cf
X! 	rm -f sendmail.sr *.o ctimed
X  
X  sources: $(SRCS)
SHAR_EOF
fi
if test -f 'ctime.c'
then
	echo shar: "will not over-write existing file 'ctime.c'"
else
sed 's/^X//' << \SHAR_EOF > 'ctime.c'
X#include	<stdio.h>
X#include	<sysexits.h>
X#include	<sys/types.h>
X#include	<sys/time.h>
X
X#define	SEND_FD	W[1]
X#define	RECV_FD	R[0]
X
X#define	CTIME	1
X#define	ASCTIME	2
X#define	TZSET	3
X#define	LOCALTIME 4
X#define	GMTIME	5
X#define	OFFTIME	6
X
X	static	int	R[2], W[2], inited;
X	static	char	result[26];
X	static	struct	tm	tmtmp;
X
Xchar	*
Xctime(t)
X	time_t	*t;
X	{
X	u_char	fnc = CTIME;
X
X	sewer();
X	write(SEND_FD, &fnc, sizeof fnc);
X	write(SEND_FD, t, sizeof (*t));
X	getb(RECV_FD, result, 26);
X	return(result);
X	}
X
Xchar	*
Xasctime(tp)
X	struct	tm	*tp;
X	{
X	u_char	fnc = ASCTIME;
X
X	sewer();
X	write(SEND_FD, &fnc, sizeof fnc);
X	write(SEND_FD, tp, sizeof (*tp));
X	getb(RECV_FD, result, 26);
X	return(result);
X	}
X
Xvoid
Xtzset()
X	{
X	u_char	fnc = TZSET;
X
X	sewer();
X	write(SEND_FD, &fnc, sizeof fnc);
X	}
X
Xstruct	tm *
Xlocaltime(tp)
X	time_t	*tp;
X	{
X	u_char	fnc = LOCALTIME;
X
X	sewer();
X	write(SEND_FD, &fnc, sizeof fnc);
X	write(SEND_FD, tp, sizeof (*tp));
X	getb(RECV_FD, &tmtmp, sizeof tmtmp);
X	getb(RECV_FD, result, 24);
X	tmtmp.tm_zone = result;
X	return(&tmtmp);
X	}
X
Xstruct	tm *
Xgmtime(tp)
X	time_t	*tp;
X	{
X	u_char	fnc = GMTIME;
X
X	sewer();
X	write(SEND_FD, &fnc, sizeof fnc);
X	write(SEND_FD, tp, sizeof (*tp));
X	getb(RECV_FD, &tmtmp, sizeof tmtmp);
X	getb(RECV_FD, result, 24);
X	tmtmp.tm_zone = result;
X	return(&tmtmp);
X	}
X
Xstruct	tm *
Xofftime(clock, offset)
X	time_t	*clock;
X	long	offset;
X	{
X	u_char	fnc = OFFTIME;
X
X	sewer();
X	write(SEND_FD, &fnc, sizeof fnc);
X	write(SEND_FD, clock, sizeof (*clock));
X	write(SEND_FD, &offset, sizeof offset);
X	getb(RECV_FD, &tmtmp, sizeof tmtmp);
X	tmtmp.tm_zone = "";
X	return(&tmtmp);
X	}
X
Xgetb(f, p, n)
X	register int f, n;
X	register char *p;
X	{
X	int	i;
X
X	while	(n)
X		{
X		i = read(f, p, n);
X		if	(i <= 0)
X			return;
X		p += i;
X		n -= i;
X		}
X	}
X
Xsewer()
X	{
X	register int	pid, ourpid = getpid();
X
X	if	(inited == ourpid)
X		return;
X	if	(inited)
X		{
X		close(SEND_FD);
X		close(RECV_FD);
X		}
X	pipe(W);
X	pipe(R);
X	pid = vfork();
X	if	(pid == 0)
X		{			/* child */
X		dup2(W[0], 0);		/* parent write side to our stdin */
X		dup2(R[1], 1);		/* parent read side to our stdout */
X		close(SEND_FD);		/* copies made, close the... */
X		close(RECV_FD);		/* originals now */
X		execl("/usr/lib/ctimed", "ctimed", 0);
X		_exit(EX_OSFILE);
X		}
X	if	(pid == -1)
X		abort();		/* nothing else really to do */
X	close(W[0]);			/* close read side of SEND channel */
X	close(R[1]);			/* close write side of RECV channel */
X	inited = ourpid;		/* don't do this again in this proc */
X	}
SHAR_EOF
fi
if test -f 'ctimed.c'
then
	echo shar: "will not over-write existing file 'ctimed.c'"
else
sed 's/^X//' << \SHAR_EOF > 'ctimed.c'
X#include	<signal.h>
X#include	<stdio.h>
X#include	<setjmp.h>
X#include	<sys/ioctl.h>
X#include	<sys/types.h>
X#include	<sys/time.h>
X
X#define	CTIME	1
X#define	ASCTIME	2
X#define	TZSET	3
X#define	LOCALTIME 4
X#define	GMTIME	5
X#define	OFFTIME	6
X
Xextern	struct	tm	*offtime();
X
X	jmp_buf	env;
X	char	*cp, junk[52];
X	long	l, off;
X	int	timeout();
X	struct	tm	tmtmp, *tp;
X
Xmain()
X	{
X	register int i;
X	struct	itimerval it;
X	u_char	c;
X
X	signal(SIGPIPE, SIG_DFL);
X	for	(i = getdtablesize(); i > 2; i--)
X		close(i);
X/*
X * Need a timer running while we disassociate from the control terminal
X * in case of a modem line which has lost carrier.
X*/
X	timerclear(&it.it_interval);
X	it.it_value.tv_sec = 5;
X	it.it_value.tv_usec = 0;
X	signal(SIGALRM, timeout);
X	setitimer(ITIMER_REAL, &it, (struct itimerval *) NULL);
X	if	(setjmp(env) == 0)
X		{
X		i = open("/dev/tty", 0);
X		if	(i >= 0)
X			{
X			ioctl(i, TIOCNOTTY, NULL);
X			close(i);
X			}
X		}
X	signal(SIGALRM, SIG_IGN);
X	timerclear(&it.it_interval);
X	timerclear(&it.it_value);
X	setitimer(ITIMER_REAL, &it, (struct itimerval *) NULL);
X
X	while	(read(fileno(stdin), &c, 1) == 1)
X		{
X		switch	(c)
X			{
X			case	CTIME:
X				l = 0L;
X				getb(fileno(stdin), &l, sizeof l);
X				cp = ctime(&l);
X				write(fileno(stdout), cp, 26);
X				break;
X			case	ASCTIME:
X				getb(fileno(stdin), &tmtmp, sizeof tmtmp);
X				cp = asctime(&tmtmp);
X				write(fileno(stdout), cp, 26);
X				break;
X			case	TZSET:
X				(void) tzset();
X				break;
X			case	LOCALTIME:
X				l = 0L;
X				getb(fileno(stdin), &l, sizeof l);
X				tp = localtime(&l);
X				write(fileno(stdout), tp, sizeof (*tp));
X				strcpy(junk, tp->tm_zone);
X				junk[24] = '\0';
X				write(fileno(stdout), junk, 24);
X				break;
X			case	GMTIME:
X				l = 0L;
X				getb(fileno(stdin), &l, sizeof l);
X				tp = gmtime(&l);
X				write(fileno(stdout), tp, sizeof (*tp));
X				strcpy(junk, tp->tm_zone);
X				junk[24] = '\0';
X				write(fileno(stdout), junk, 24);
X				break;
X			case	OFFTIME:
X				getb(fileno(stdin), &l, sizeof l);
X				getb(fileno(stdin), &off, sizeof off);
X				tp = offtime(&l, off);
X				write(fileno(stdout), tp, sizeof (*tp));
X				break;
X			default:
X				abort("switch");
X			}
X		}
X	}
X
Xgetb(f, p, n)
X	int	f;
X	register char	*p;
X	register int	n;
X	{
X	register int	i;
X
X	while	(n)
X		{
X		i = read(f, p, n);
X		if	(i <= 0)
X			return;
X		p += i;
X		n -= i;
X		}
X	}
X
Xtimeout()
X	{
X
X	longjmp(env, 1);
X	}
SHAR_EOF
fi
exit 0
#	End of shell archive



More information about the Comp.bugs.2bsd mailing list