queue.c revision 90792
138032Speter/*
290792Sgshapiro * Copyright (c) 1998-2002 Sendmail, Inc. and its suppliers.
364562Sgshapiro *	All rights reserved.
438032Speter * Copyright (c) 1983, 1995-1997 Eric P. Allman.  All rights reserved.
538032Speter * Copyright (c) 1988, 1993
638032Speter *	The Regents of the University of California.  All rights reserved.
738032Speter *
838032Speter * By using this file, you agree to the terms and conditions set
938032Speter * forth in the LICENSE file which can be found at the top level of
1038032Speter * the sendmail distribution.
1138032Speter *
1238032Speter */
1338032Speter
1464562Sgshapiro#include <sendmail.h>
1564562Sgshapiro
1690792SgshapiroSM_RCSID("@(#)$Id: queue.c,v 8.834 2002/01/08 23:04:58 ca Exp $")
1738032Speter
1890792Sgshapiro#include <dirent.h>
1938032Speter
2090792Sgshapiro#if SM_CONF_SHM
2190792Sgshapiro# include <sm/shm.h>
2290792Sgshapiro#endif /* SM_CONF_SHM */
2338032Speter
2490792Sgshapiro# define RELEASE_QUEUE	(void) 0
2590792Sgshapiro# define ST_INODE(st)	(st).st_ino
2690792Sgshapiro
2790792Sgshapiro
2890792Sgshapiro/*
2990792Sgshapiro**  Historical notes:
3090792Sgshapiro**	QF_VERSION==4 was sendmail 8.10/8.11 without _FFR_QUEUEDELAY
3190792Sgshapiro**	QF_VERSION==5 was sendmail 8.10/8.11 with    _FFR_QUEUEDELAY
3290792Sgshapiro*/
3390792Sgshapiro
3490792Sgshapiro#if _FFR_QUEUEDELAY
3590792Sgshapiro# define QF_VERSION	7	/* version number of this queue format */
3664562Sgshapirostatic time_t	queuedelay __P((ENVELOPE *));
3790792Sgshapiro#define queuedelay_qfver_unsupported(qfver) false
3890792Sgshapiro#else /* _FFR_QUEUEDELAY */
3990792Sgshapiro# define QF_VERSION	6	/* version number of this queue format */
4090792Sgshapiro# define queuedelay(e)	MinQueueAge
4190792Sgshapiro#define queuedelay_qfver_unsupported(qfver) ((qfver) == 5 || (qfver) == 7)
4290792Sgshapiro#endif /* _FFR_QUEUEDELAY */
4390792Sgshapiro#if _FFR_QUARANTINE
4490792Sgshapirostatic char	queue_letter __P((ENVELOPE *, int));
4590792Sgshapirostatic bool	quarantine_queue_item __P((int, int, ENVELOPE *, char *));
4690792Sgshapiro#endif /* _FFR_QUARANTINE */
4764562Sgshapiro
4890792Sgshapiro/* Naming convention: qgrp: index of queue group, qg: QUEUEGROUP */
4990792Sgshapiro
5038032Speter/*
5138032Speter**  Work queue.
5238032Speter*/
5338032Speter
5438032Speterstruct work
5538032Speter{
5638032Speter	char		*w_name;	/* name of control file */
5738032Speter	char		*w_host;	/* name of recipient host */
5838032Speter	bool		w_lock;		/* is message locked? */
5938032Speter	bool		w_tooyoung;	/* is it too young to run? */
6038032Speter	long		w_pri;		/* priority of message, see below */
6190792Sgshapiro	time_t		w_ctime;	/* creation time */
6290792Sgshapiro	time_t		w_mtime;	/* modification time */
6390792Sgshapiro	int		w_qgrp;		/* queue group located in */
6490792Sgshapiro	int		w_qdir;		/* queue directory located in */
6538032Speter	struct work	*w_next;	/* next in queue */
6638032Speter};
6738032Speter
6838032Spetertypedef struct work	WORK;
6938032Speter
7090792Sgshapirostatic WORK	*WorkQ;		/* queue of things to be done */
7190792Sgshapirostatic int	NumWorkGroups;	/* number of work groups */
7238032Speter
7390792Sgshapiro/*
7490792Sgshapiro**  use of DoQueueRun:
7590792Sgshapiro**	NumQueue: indicates that a queue run is needed, look at individual bits
7690792Sgshapiro**	0 - NumQueue-1: indicates that a queue run for this queue group
7790792Sgshapiro**		is needed.
7890792Sgshapiro*/
7990792Sgshapiro
8090792Sgshapirostatic BITMAP256	volatile DoQueueRun;	/* non-interrupt time queue run needed */
8190792Sgshapiro
8290792Sgshapiro/*
8390792Sgshapiro**  Work group definition structure.
8490792Sgshapiro**	Each work group contains one or more queue groups. This is done
8590792Sgshapiro**	to manage the number of queue group runners active at the same time
8690792Sgshapiro**	to be within the constraints of MaxQueueChildren (if it is set).
8790792Sgshapiro**	The number of queue groups that can be run on the next work run
8890792Sgshapiro**	is kept track of. The queue groups are run in a round robin.
8990792Sgshapiro*/
9090792Sgshapiro
9190792Sgshapirostruct workgrp
9290792Sgshapiro{
9390792Sgshapiro	int		wg_numqgrp;	/* number of queue groups in work grp */
9490792Sgshapiro	int		wg_runners;	/* total runners */
9590792Sgshapiro	int		wg_curqgrp;	/* current queue group */
9690792Sgshapiro	QUEUEGRP	**wg_qgs;	/* array of queue groups */
9790792Sgshapiro	int		wg_maxact;	/* max # of active runners */
9890792Sgshapiro	time_t		wg_lowqintvl;	/* lowest queue interval */
9990792Sgshapiro	int		wg_restart;	/* needs restarting? */
10090792Sgshapiro	int		wg_restartcnt;	/* count of times restarted */
10190792Sgshapiro};
10290792Sgshapiro
10390792Sgshapirotypedef struct workgrp WORKGRP;
10490792Sgshapiro
10590792Sgshapirostatic WORKGRP	volatile WorkGrp[MAXWORKGROUPS + 1];	/* work groups */
10690792Sgshapiro
10790792Sgshapiro#if SM_HEAP_CHECK
10890792Sgshapirostatic SM_DEBUG_T DebugLeakQ = SM_DEBUG_INITIALIZER("leak_q",
10990792Sgshapiro	"@(#)$Debug: leak_q - trace memory leaks during queue processing $");
11090792Sgshapiro#endif /* SM_HEAP_CHECK */
11190792Sgshapiro
11290792Sgshapiro/*
11390792Sgshapiro**  We use EmptyString instead of "" to avoid
11490792Sgshapiro**  'zero-length format string' warnings from gcc
11590792Sgshapiro*/
11690792Sgshapiro
11790792Sgshapirostatic const char EmptyString[] = "";
11890792Sgshapiro
11990792Sgshapirostatic void	grow_wlist __P((int, int));
12090792Sgshapirostatic int	multiqueue_cache __P((char *, int, QUEUEGRP *, int, unsigned int *));
12190792Sgshapirostatic int	gatherq __P((int, int, bool, bool *, bool *));
12290792Sgshapirostatic int	sortq __P((int));
12390792Sgshapirostatic void	printctladdr __P((ADDRESS *, SM_FILE_T *));
12490792Sgshapirostatic bool	readqf __P((ENVELOPE *, bool));
12590792Sgshapirostatic void	restart_work_group __P((int));
12690792Sgshapirostatic void	runner_work __P((ENVELOPE *, int, bool, int, int));
12790792Sgshapirostatic void	schedule_queue_runs __P((bool, int));
12864562Sgshapirostatic char	*strrev __P((char *));
12990792Sgshapirostatic ADDRESS	*setctluser __P((char *, int, ENVELOPE *));
13090792Sgshapiro#if _FFR_RHS
13190792Sgshapirostatic int	sm_strshufflecmp __P((char *, char *));
13290792Sgshapirostatic void	init_shuffle_alphabet __P(());
13390792Sgshapiro#endif /* _FFR_RHS */
13464562Sgshapirostatic int	workcmpf0();
13564562Sgshapirostatic int	workcmpf1();
13664562Sgshapirostatic int	workcmpf2();
13764562Sgshapirostatic int	workcmpf3();
13864562Sgshapirostatic int	workcmpf4();
13990792Sgshapirostatic int	workcmpf5();
14090792Sgshapirostatic int	workcmpf6();
14190792Sgshapiro#if _FFR_RHS
14290792Sgshapirostatic int	workcmpf7();
14390792Sgshapiro#endif /* _FFR_RHS */
14438032Speter
14590792Sgshapiro#if RANDOMSHIFT
14690792Sgshapiro# define get_rand_mod(m)	((get_random() >> RANDOMSHIFT) % (m))
14790792Sgshapiro#else /* RANDOMSHIFT */
14890792Sgshapiro# define get_rand_mod(m)	(get_random() % (m))
14990792Sgshapiro#endif /* RANDOMSHIFT */
15090792Sgshapiro
15180785Sgshapiro/*
15290792Sgshapiro**  File system definition.
15390792Sgshapiro**	Used to keep track of how much free space is available
15490792Sgshapiro**	on a file system in which one or more queue directories reside.
15590792Sgshapiro*/
15690792Sgshapiro
15790792Sgshapirotypedef struct filesys_shared	FILESYS;
15890792Sgshapiro
15990792Sgshapirostruct filesys_shared
16090792Sgshapiro{
16190792Sgshapiro	dev_t	fs_dev;		/* unique device id */
16290792Sgshapiro	long	fs_avail;	/* number of free blocks available */
16390792Sgshapiro	long	fs_blksize;	/* block size, in bytes */
16490792Sgshapiro};
16590792Sgshapiro
16690792Sgshapiro/* probably kept in shared memory */
16790792Sgshapirostatic FILESYS	FileSys[MAXFILESYS];	/* queue file systems */
16890792Sgshapirostatic char	*FSPath[MAXFILESYS];	/* pathnames for file systems */
16990792Sgshapiro
17090792Sgshapiro#if SM_CONF_SHM
17190792Sgshapiro
17290792Sgshapiro/*
17390792Sgshapiro**  Shared memory data
17490792Sgshapiro**
17590792Sgshapiro**  Current layout:
17690792Sgshapiro**	size -- size of shared memory segment
17790792Sgshapiro**	pid -- pid of owner, should be a unique id to avoid misinterpretations
17890792Sgshapiro**		by other processes.
17990792Sgshapiro**	tag -- should be a unique id to avoid misinterpretations by others.
18090792Sgshapiro**		idea: hash over configuration data that will be stored here.
18190792Sgshapiro**	NumFileSys -- number of file systems.
18290792Sgshapiro**	FileSys -- (arrary of) structure for used file systems.
18390792Sgshapiro**	RSATmpCnt -- counter for number of uses of ephemeral RSA key.
18490792Sgshapiro**	QShm -- (array of) structure for information about queue directories.
18590792Sgshapiro*/
18690792Sgshapiro
18790792Sgshapiro/*
18890792Sgshapiro**  Queue data in shared memory
18990792Sgshapiro*/
19090792Sgshapiro
19190792Sgshapirotypedef struct queue_shared	QUEUE_SHM_T;
19290792Sgshapiro
19390792Sgshapirostruct queue_shared
19490792Sgshapiro{
19590792Sgshapiro	int	qs_entries;	/* number of entries */
19690792Sgshapiro	/* XXX more to follow? */
19790792Sgshapiro};
19890792Sgshapiro
19990792Sgshapirostatic void	*Pshm;		/* pointer to shared memory */
20090792Sgshapirostatic FILESYS	*PtrFileSys;	/* pointer to queue file system array */
20190792Sgshapiroint		ShmId = SM_SHM_NO_ID;	/* shared memory id */
20290792Sgshapirostatic QUEUE_SHM_T	*QShm;		/* pointer to shared queue data */
20390792Sgshapiro
20490792Sgshapiro# define SHM_OFF_PID(p)	(((char *) (p)) + sizeof(int))
20590792Sgshapiro# define SHM_OFF_TAG(p)	(((char *) (p)) + sizeof(pid_t) + sizeof(int))
20690792Sgshapiro# define SHM_OFF_HEAD	(sizeof(pid_t) + sizeof(int) * 2)
20790792Sgshapiro
20890792Sgshapiro/* how to access FileSys */
20990792Sgshapiro# define FILE_SYS(i)	(PtrFileSys[i])
21090792Sgshapiro
21190792Sgshapiro/* first entry is a tag, for now just the size */
21290792Sgshapiro# define OFF_FILE_SYS(p)	(((char *) (p)) + SHM_OFF_HEAD)
21390792Sgshapiro
21490792Sgshapiro/* offset for PNumFileSys */
21590792Sgshapiro# define OFF_NUM_FILE_SYS(p)	(((char *) (p)) + SHM_OFF_HEAD + sizeof(FileSys))
21690792Sgshapiro
21790792Sgshapiro/* offset for PRSATmpCnt */
21890792Sgshapiro# define OFF_RSA_TMP_CNT(p) (((char *) (p)) + SHM_OFF_HEAD + sizeof(FileSys) + sizeof(int))
21990792Sgshapiroint	*PRSATmpCnt;
22090792Sgshapiro
22190792Sgshapiro/* offset for queue_shm */
22290792Sgshapiro# define OFF_QUEUE_SHM(p) (((char *) (p)) + SHM_OFF_HEAD + sizeof(FileSys) + sizeof(int) * 2)
22390792Sgshapiro
22490792Sgshapiro#define QSHM_ENTRIES(i)	QShm[i].qs_entries
22590792Sgshapiro
22690792Sgshapiro/* basic size of shared memory segment */
22790792Sgshapiro# define SM_T_SIZE	(SHM_OFF_HEAD + sizeof(FileSys) + sizeof(int) * 2)
22890792Sgshapiro
22990792Sgshapirostatic unsigned int	hash_q __P((char *, unsigned int));
23090792Sgshapiro
23190792Sgshapiro/*
23290792Sgshapiro**  HASH_Q -- simple hash function
23390792Sgshapiro**
23490792Sgshapiro**	Parameters:
23590792Sgshapiro**		p -- string to hash.
23690792Sgshapiro**		h -- hash start value (from previous run).
23790792Sgshapiro**
23890792Sgshapiro**	Returns:
23990792Sgshapiro**		hash value.
24090792Sgshapiro*/
24190792Sgshapiro
24290792Sgshapirostatic unsigned int
24390792Sgshapirohash_q(p, h)
24490792Sgshapiro	char *p;
24590792Sgshapiro	unsigned int h;
24690792Sgshapiro{
24790792Sgshapiro	int c, d;
24890792Sgshapiro
24990792Sgshapiro	while (*p != '\0')
25090792Sgshapiro	{
25190792Sgshapiro		d = *p++;
25290792Sgshapiro		c = d;
25390792Sgshapiro		c ^= c<<6;
25490792Sgshapiro		h += (c<<11) ^ (c>>1);
25590792Sgshapiro		h ^= (d<<14) + (d<<7) + (d<<4) + d;
25690792Sgshapiro	}
25790792Sgshapiro	return h;
25890792Sgshapiro}
25990792Sgshapiro
26090792Sgshapiro#else /* SM_CONF_SHM */
26190792Sgshapiro# define FILE_SYS(i)	FileSys[i]
26290792Sgshapiro#endif /* SM_CONF_SHM */
26390792Sgshapiro
26490792Sgshapiro/* access to the various components of file system data */
26590792Sgshapiro#define FILE_SYS_NAME(i)	FSPath[i]
26690792Sgshapiro#define FILE_SYS_AVAIL(i)	FILE_SYS(i).fs_avail
26790792Sgshapiro#define FILE_SYS_BLKSIZE(i)	FILE_SYS(i).fs_blksize
26890792Sgshapiro#define FILE_SYS_DEV(i)	FILE_SYS(i).fs_dev
26990792Sgshapiro
27090792Sgshapiro/*
27180785Sgshapiro**  Current qf file field assignments:
27280785Sgshapiro**
27380785Sgshapiro**	A	AUTH= parameter
27480785Sgshapiro**	B	body type
27580785Sgshapiro**	C	controlling user
27680785Sgshapiro**	D	data file name
27790792Sgshapiro**	d	data file directory name (added in 8.12)
27880785Sgshapiro**	E	error recipient
27980785Sgshapiro**	F	flag bits
28090792Sgshapiro**	G	queue delay algorithm (_FFR_QUEUEDELAY)
28180785Sgshapiro**	H	header
28280785Sgshapiro**	I	data file's inode number
28380785Sgshapiro**	K	time of last delivery attempt
28480785Sgshapiro**	L	Solaris Content-Length: header (obsolete)
28580785Sgshapiro**	M	message (obsolete)
28680785Sgshapiro**	N	number of delivery attempts
28780785Sgshapiro**	P	message priority
28890792Sgshapiro**	q	quarantine reason (_FFR_QUARANTINE)
28980785Sgshapiro**	Q	original recipient (ORCPT=)
29090792Sgshapiro**	r	final recipient (Final-Recipient: DSN field)
29180785Sgshapiro**	R	recipient
29280785Sgshapiro**	S	sender
29380785Sgshapiro**	T	init time
29480785Sgshapiro**	V	queue file version
29590792Sgshapiro**	X	free (was: character set if _FFR_SAVE_CHARSET)
29690792Sgshapiro**	Y	current delay (_FFR_QUEUEDELAY)
29780785Sgshapiro**	Z	original envelope id from ESMTP
29890792Sgshapiro**	!	deliver by (added in 8.12)
29980785Sgshapiro**	$	define macro
30080785Sgshapiro**	.	terminate file
30180785Sgshapiro*/
30280785Sgshapiro
30390792Sgshapiro/*
30438032Speter**  QUEUEUP -- queue a message up for future transmission.
30538032Speter**
30638032Speter**	Parameters:
30738032Speter**		e -- the envelope to queue up.
30890792Sgshapiro**		announce -- if true, tell when you are queueing up.
30990792Sgshapiro**		msync -- if true, then fsync() if SuperSafe interactive mode.
31038032Speter**
31138032Speter**	Returns:
31238032Speter**		none.
31338032Speter**
31438032Speter**	Side Effects:
31590792Sgshapiro**		The current request is saved in a control file.
31638032Speter**		The queue file is left locked.
31738032Speter*/
31838032Speter
31938032Spetervoid
32090792Sgshapiroqueueup(e, announce, msync)
32138032Speter	register ENVELOPE *e;
32238032Speter	bool announce;
32390792Sgshapiro	bool msync;
32438032Speter{
32590792Sgshapiro	register SM_FILE_T *tfp;
32638032Speter	register HDR *h;
32738032Speter	register ADDRESS *q;
32864562Sgshapiro	int tfd = -1;
32938032Speter	int i;
33038032Speter	bool newid;
33138032Speter	register char *p;
33238032Speter	MAILER nullmailer;
33338032Speter	MCI mcibuf;
33490792Sgshapiro	char qf[MAXPATHLEN];
33564562Sgshapiro	char tf[MAXPATHLEN];
33690792Sgshapiro	char df[MAXPATHLEN];
33738032Speter	char buf[MAXLINE];
33838032Speter
33938032Speter	/*
34038032Speter	**  Create control file.
34138032Speter	*/
34238032Speter
34338032Speter	newid = (e->e_id == NULL) || !bitset(EF_INQUEUE, e->e_flags);
34490792Sgshapiro	(void) sm_strlcpy(tf, queuename(e, NEWQFL_LETTER), sizeof tf);
34538032Speter	tfp = e->e_lockfp;
34638032Speter	if (tfp == NULL)
34790792Sgshapiro		newid = false;
34838032Speter
34990792Sgshapiro	/* if newid, write the queue file directly (instead of temp file) */
35038032Speter	if (!newid)
35138032Speter	{
35290792Sgshapiro		const int flags = O_CREAT|O_WRONLY|O_EXCL;
35364562Sgshapiro
35438032Speter		/* get a locked tf file */
35538032Speter		for (i = 0; i < 128; i++)
35638032Speter		{
35764562Sgshapiro			if (tfd < 0)
35838032Speter			{
35990792Sgshapiro				MODE_T oldumask = 0;
36064562Sgshapiro
36164562Sgshapiro				if (bitset(S_IWGRP, QueueFileMode))
36264562Sgshapiro					oldumask = umask(002);
36364562Sgshapiro				tfd = open(tf, flags, QueueFileMode);
36464562Sgshapiro				if (bitset(S_IWGRP, QueueFileMode))
36564562Sgshapiro					(void) umask(oldumask);
36664562Sgshapiro
36764562Sgshapiro				if (tfd < 0)
36864562Sgshapiro				{
36964562Sgshapiro					if (errno != EEXIST)
37064562Sgshapiro						break;
37164562Sgshapiro					if (LogLevel > 0 && (i % 32) == 0)
37264562Sgshapiro						sm_syslog(LOG_ALERT, e->e_id,
37364562Sgshapiro							  "queueup: cannot create %s, uid=%d: %s",
37490792Sgshapiro							  tf, geteuid(),
37590792Sgshapiro							  sm_errstring(errno));
37664562Sgshapiro				}
37738032Speter			}
37864562Sgshapiro			if (tfd >= 0)
37938032Speter			{
38064562Sgshapiro				if (lockfile(tfd, tf, NULL, LOCK_EX|LOCK_NB))
38138032Speter					break;
38238032Speter				else if (LogLevel > 0 && (i % 32) == 0)
38338032Speter					sm_syslog(LOG_ALERT, e->e_id,
38464562Sgshapiro						  "queueup: cannot lock %s: %s",
38590792Sgshapiro						  tf, sm_errstring(errno));
38664562Sgshapiro				if ((i % 32) == 31)
38764562Sgshapiro				{
38864562Sgshapiro					(void) close(tfd);
38964562Sgshapiro					tfd = -1;
39064562Sgshapiro				}
39138032Speter			}
39238032Speter
39338032Speter			if ((i % 32) == 31)
39438032Speter			{
39538032Speter				/* save the old temp file away */
39664562Sgshapiro				(void) rename(tf, queuename(e, TEMPQF_LETTER));
39738032Speter			}
39838032Speter			else
39964562Sgshapiro				(void) sleep(i % 32);
40038032Speter		}
40190792Sgshapiro		if (tfd < 0 || (tfp = sm_io_open(SmFtStdiofd, SM_TIME_DEFAULT,
40290792Sgshapiro						 (void *) &tfd, SM_IO_WRONLY,
40390792Sgshapiro						 NULL)) == NULL)
40438032Speter		{
40564562Sgshapiro			int save_errno = errno;
40664562Sgshapiro
40790792Sgshapiro			printopenfds(true);
40864562Sgshapiro			errno = save_errno;
40938032Speter			syserr("!queueup: cannot create queue temp file %s, uid=%d",
41038032Speter				tf, geteuid());
41138032Speter		}
41238032Speter	}
41338032Speter
41438032Speter	if (tTd(40, 1))
41590792Sgshapiro		sm_dprintf("\n>>>>> queueing %s/%s%s >>>>>\n",
41690792Sgshapiro			   qid_printqueue(e->e_qgrp, e->e_qdir),
41790792Sgshapiro			   queuename(e, ANYQFL_LETTER),
41890792Sgshapiro			   newid ? " (new id)" : "");
41938032Speter	if (tTd(40, 3))
42038032Speter	{
42190792Sgshapiro		sm_dprintf("  e_flags=");
42238032Speter		printenvflags(e);
42338032Speter	}
42438032Speter	if (tTd(40, 32))
42538032Speter	{
42690792Sgshapiro		sm_dprintf("  sendq=");
42790792Sgshapiro		printaddr(e->e_sendqueue, true);
42838032Speter	}
42938032Speter	if (tTd(40, 9))
43038032Speter	{
43190792Sgshapiro		sm_dprintf("  tfp=");
43290792Sgshapiro		dumpfd(sm_io_getinfo(tfp, SM_IO_WHAT_FD, NULL), true, false);
43390792Sgshapiro		sm_dprintf("  lockfp=");
43438032Speter		if (e->e_lockfp == NULL)
43590792Sgshapiro			sm_dprintf("NULL\n");
43638032Speter		else
43790792Sgshapiro			dumpfd(sm_io_getinfo(e->e_lockfp, SM_IO_WHAT_FD, NULL),
43890792Sgshapiro			       true, false);
43938032Speter	}
44038032Speter
44138032Speter	/*
44238032Speter	**  If there is no data file yet, create one.
44338032Speter	*/
44438032Speter
44590792Sgshapiro	(void) sm_strlcpy(df, queuename(e, DATAFL_LETTER), sizeof df);
44664562Sgshapiro	if (bitset(EF_HAS_DF, e->e_flags))
44738032Speter	{
44890792Sgshapiro		if (e->e_dfp != NULL &&
44990792Sgshapiro		    SuperSafe != SAFE_REALLY &&
45090792Sgshapiro		    sm_io_setinfo(e->e_dfp, SM_BF_COMMIT, NULL) < 0 &&
45190792Sgshapiro		    errno != EINVAL)
45290792Sgshapiro		{
45364562Sgshapiro			syserr("!queueup: cannot commit data file %s, uid=%d",
45490792Sgshapiro			       queuename(e, DATAFL_LETTER), geteuid());
45590792Sgshapiro		}
45690792Sgshapiro		if (e->e_dfp != NULL &&
45790792Sgshapiro		    SuperSafe == SAFE_INTERACTIVE && msync)
45890792Sgshapiro		{
45990792Sgshapiro			if (tTd(40,32))
46090792Sgshapiro				sm_syslog(LOG_INFO, e->e_id,
46190792Sgshapiro					  "queueup: fsync(e->e_dfp)");
46290792Sgshapiro
46390792Sgshapiro			if (fsync(sm_io_getinfo(e->e_dfp, SM_IO_WHAT_FD,
46490792Sgshapiro						NULL)) < 0)
46590792Sgshapiro			{
46690792Sgshapiro				if (newid)
46790792Sgshapiro					syserr("!552 Error writing data file %s",
46890792Sgshapiro					       df);
46990792Sgshapiro				else
47090792Sgshapiro					syserr("!452 Error writing data file %s",
47190792Sgshapiro					       df);
47290792Sgshapiro			}
47390792Sgshapiro		}
47464562Sgshapiro	}
47564562Sgshapiro	else
47664562Sgshapiro	{
47764562Sgshapiro		int dfd;
47890792Sgshapiro		MODE_T oldumask = 0;
47990792Sgshapiro		register SM_FILE_T *dfp = NULL;
48038032Speter		struct stat stbuf;
48138032Speter
48290792Sgshapiro		if (e->e_dfp != NULL &&
48390792Sgshapiro		    sm_io_getinfo(e->e_dfp, SM_IO_WHAT_ISTYPE, BF_FILE_TYPE))
48464562Sgshapiro			syserr("committing over bf file");
48564562Sgshapiro
48690792Sgshapiro		if (bitset(S_IWGRP, QueueFileMode))
48790792Sgshapiro			oldumask = umask(002);
48890792Sgshapiro		dfd = open(df, O_WRONLY|O_CREAT|O_TRUNC, QueueFileMode);
48990792Sgshapiro		if (bitset(S_IWGRP, QueueFileMode))
49090792Sgshapiro			(void) umask(oldumask);
49190792Sgshapiro		if (dfd < 0 || (dfp = sm_io_open(SmFtStdiofd, SM_TIME_DEFAULT,
49290792Sgshapiro						 (void *) &dfd, SM_IO_WRONLY,
49390792Sgshapiro						 NULL)) == NULL)
49438032Speter			syserr("!queueup: cannot create data temp file %s, uid=%d",
49590792Sgshapiro				df, geteuid());
49664562Sgshapiro		if (fstat(dfd, &stbuf) < 0)
49738032Speter			e->e_dfino = -1;
49838032Speter		else
49938032Speter		{
50038032Speter			e->e_dfdev = stbuf.st_dev;
50190792Sgshapiro			e->e_dfino = ST_INODE(stbuf);
50238032Speter		}
50338032Speter		e->e_flags |= EF_HAS_DF;
50464562Sgshapiro		memset(&mcibuf, '\0', sizeof mcibuf);
50538032Speter		mcibuf.mci_out = dfp;
50638032Speter		mcibuf.mci_mailer = FileMailer;
50738032Speter		(*e->e_putbody)(&mcibuf, e, NULL);
50890792Sgshapiro
50990792Sgshapiro		if (SuperSafe == SAFE_REALLY ||
51090792Sgshapiro		    (SuperSafe == SAFE_INTERACTIVE && msync))
51190792Sgshapiro		{
51290792Sgshapiro			if (tTd(40,32))
51390792Sgshapiro				sm_syslog(LOG_INFO, e->e_id,
51490792Sgshapiro					  "queueup: fsync(dfp)");
51590792Sgshapiro
51690792Sgshapiro			if (fsync(sm_io_getinfo(dfp, SM_IO_WHAT_FD, NULL)) < 0)
51790792Sgshapiro			{
51890792Sgshapiro				if (newid)
51990792Sgshapiro					syserr("!552 Error writing data file %s",
52090792Sgshapiro					       df);
52190792Sgshapiro				else
52290792Sgshapiro					syserr("!452 Error writing data file %s",
52390792Sgshapiro					       df);
52490792Sgshapiro			}
52590792Sgshapiro		}
52690792Sgshapiro
52790792Sgshapiro		if (sm_io_close(dfp, SM_TIME_DEFAULT) < 0)
52864562Sgshapiro			syserr("!queueup: cannot save data temp file %s, uid=%d",
52990792Sgshapiro				df, geteuid());
53038032Speter		e->e_putbody = putbody;
53138032Speter	}
53238032Speter
53338032Speter	/*
53438032Speter	**  Output future work requests.
53538032Speter	**	Priority and creation time should be first, since
53690792Sgshapiro	**	they are required by gatherq.
53738032Speter	*/
53838032Speter
53938032Speter	/* output queue version number (must be first!) */
54090792Sgshapiro	(void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "V%d\n", QF_VERSION);
54138032Speter
54238032Speter	/* output creation time */
54390792Sgshapiro	(void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "T%ld\n", (long) e->e_ctime);
54438032Speter
54538032Speter	/* output last delivery time */
54690792Sgshapiro#if _FFR_QUEUEDELAY
54790792Sgshapiro	(void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "K%ld\n", (long) e->e_dtime);
54890792Sgshapiro	(void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "G%d\n", e->e_queuealg);
54990792Sgshapiro	(void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "Y%ld\n", (long) e->e_queuedelay);
55064562Sgshapiro	if (tTd(40, 64))
55164562Sgshapiro		sm_syslog(LOG_INFO, e->e_id,
55264562Sgshapiro			"queue alg: %d delay %ld next: %ld (now: %ld)\n",
55364562Sgshapiro			e->e_queuealg, e->e_queuedelay, e->e_dtime, curtime());
55490792Sgshapiro#else /* _FFR_QUEUEDELAY */
55590792Sgshapiro	(void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "K%ld\n", (long) e->e_dtime);
55690792Sgshapiro#endif /* _FFR_QUEUEDELAY */
55738032Speter
55838032Speter	/* output number of delivery attempts */
55990792Sgshapiro	(void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "N%d\n", e->e_ntries);
56038032Speter
56138032Speter	/* output message priority */
56290792Sgshapiro	(void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "P%ld\n", e->e_msgpriority);
56338032Speter
56490792Sgshapiro	/*
56590792Sgshapiro	**  If data file is in a different directory than the queue file,
56690792Sgshapiro	**  output a "d" record naming the directory of the data file.
56790792Sgshapiro	*/
56890792Sgshapiro
56990792Sgshapiro	if (e->e_dfqgrp != e->e_qgrp)
57090792Sgshapiro	{
57190792Sgshapiro		(void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "d%s\n",
57290792Sgshapiro			Queue[e->e_dfqgrp]->qg_qpaths[e->e_dfqdir].qp_name);
57390792Sgshapiro	}
57490792Sgshapiro
57538032Speter	/* output inode number of data file */
57638032Speter	/* XXX should probably include device major/minor too */
57738032Speter	if (e->e_dfino != -1)
57838032Speter	{
57990792Sgshapiro		(void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "I%ld/%ld/%llu\n",
58090792Sgshapiro				     (long) major(e->e_dfdev),
58190792Sgshapiro				     (long) minor(e->e_dfdev),
58290792Sgshapiro				     (ULONGLONG_T) e->e_dfino);
58338032Speter	}
58438032Speter
58538032Speter	/* output body type */
58638032Speter	if (e->e_bodytype != NULL)
58790792Sgshapiro		(void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "B%s\n",
58890792Sgshapiro				     denlstring(e->e_bodytype, true, false));
58938032Speter
59090792Sgshapiro#if _FFR_QUARANTINE
59190792Sgshapiro	/* quarantine reason */
59290792Sgshapiro	if (e->e_quarmsg != NULL)
59390792Sgshapiro		(void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "q%s\n",
59490792Sgshapiro				     denlstring(e->e_quarmsg, true, false));
59590792Sgshapiro#endif /* _FFR_QUARANTINE */
59638032Speter
59738032Speter	/* message from envelope, if it exists */
59838032Speter	if (e->e_message != NULL)
59990792Sgshapiro		(void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "M%s\n",
60090792Sgshapiro				     denlstring(e->e_message, true, false));
60138032Speter
60238032Speter	/* send various flag bits through */
60338032Speter	p = buf;
60438032Speter	if (bitset(EF_WARNING, e->e_flags))
60538032Speter		*p++ = 'w';
60638032Speter	if (bitset(EF_RESPONSE, e->e_flags))
60738032Speter		*p++ = 'r';
60838032Speter	if (bitset(EF_HAS8BIT, e->e_flags))
60938032Speter		*p++ = '8';
61038032Speter	if (bitset(EF_DELETE_BCC, e->e_flags))
61138032Speter		*p++ = 'b';
61238032Speter	if (bitset(EF_RET_PARAM, e->e_flags))
61338032Speter		*p++ = 'd';
61438032Speter	if (bitset(EF_NO_BODY_RETN, e->e_flags))
61538032Speter		*p++ = 'n';
61690792Sgshapiro	if (bitset(EF_SPLIT, e->e_flags))
61790792Sgshapiro		*p++ = 's';
61838032Speter	*p++ = '\0';
61938032Speter	if (buf[0] != '\0')
62090792Sgshapiro		(void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "F%s\n", buf);
62138032Speter
62264562Sgshapiro	/* save $={persistentMacros} macro values */
62390792Sgshapiro	queueup_macros(macid("{persistentMacros}"), tfp, e);
62438032Speter
62538032Speter	/* output name of sender */
62638032Speter	if (bitnset(M_UDBENVELOPE, e->e_from.q_mailer->m_flags))
62738032Speter		p = e->e_sender;
62838032Speter	else
62938032Speter		p = e->e_from.q_paddr;
63090792Sgshapiro	(void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "S%s\n",
63190792Sgshapiro			     denlstring(p, true, false));
63238032Speter
63338032Speter	/* output ESMTP-supplied "original" information */
63438032Speter	if (e->e_envid != NULL)
63590792Sgshapiro		(void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "Z%s\n",
63690792Sgshapiro				     denlstring(e->e_envid, true, false));
63738032Speter
63864562Sgshapiro	/* output AUTH= parameter */
63964562Sgshapiro	if (e->e_auth_param != NULL)
64090792Sgshapiro		(void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "A%s\n",
64190792Sgshapiro				     denlstring(e->e_auth_param, true, false));
64290792Sgshapiro	if (e->e_dlvr_flag != 0)
64390792Sgshapiro		(void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "!%c %ld\n",
64490792Sgshapiro				     (char) e->e_dlvr_flag, e->e_deliver_by);
64564562Sgshapiro
64638032Speter	/* output list of recipient addresses */
64738032Speter	printctladdr(NULL, NULL);
64838032Speter	for (q = e->e_sendqueue; q != NULL; q = q->q_next)
64938032Speter	{
65064562Sgshapiro		if (!QS_IS_UNDELIVERED(q->q_state))
65138032Speter			continue;
65264562Sgshapiro
65390792Sgshapiro		/* message for this recipient, if it exists */
65490792Sgshapiro		if (q->q_message != NULL)
65590792Sgshapiro			(void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "M%s\n",
65690792Sgshapiro					     denlstring(q->q_message, true,
65790792Sgshapiro							false));
65890792Sgshapiro
65938032Speter		printctladdr(q, tfp);
66038032Speter		if (q->q_orcpt != NULL)
66190792Sgshapiro			(void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "Q%s\n",
66290792Sgshapiro					     denlstring(q->q_orcpt, true,
66390792Sgshapiro							false));
66490792Sgshapiro		if (q->q_finalrcpt != NULL)
66590792Sgshapiro			(void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "r%s\n",
66690792Sgshapiro					     denlstring(q->q_finalrcpt, true,
66790792Sgshapiro							false));
66890792Sgshapiro		(void) sm_io_putc(tfp, SM_TIME_DEFAULT, 'R');
66938032Speter		if (bitset(QPRIMARY, q->q_flags))
67090792Sgshapiro			(void) sm_io_putc(tfp, SM_TIME_DEFAULT, 'P');
67138032Speter		if (bitset(QHASNOTIFY, q->q_flags))
67290792Sgshapiro			(void) sm_io_putc(tfp, SM_TIME_DEFAULT, 'N');
67338032Speter		if (bitset(QPINGONSUCCESS, q->q_flags))
67490792Sgshapiro			(void) sm_io_putc(tfp, SM_TIME_DEFAULT, 'S');
67538032Speter		if (bitset(QPINGONFAILURE, q->q_flags))
67690792Sgshapiro			(void) sm_io_putc(tfp, SM_TIME_DEFAULT, 'F');
67738032Speter		if (bitset(QPINGONDELAY, q->q_flags))
67890792Sgshapiro			(void) sm_io_putc(tfp, SM_TIME_DEFAULT, 'D');
67971345Sgshapiro		if (q->q_alias != NULL &&
68071345Sgshapiro		    bitset(QALIAS, q->q_alias->q_flags))
68190792Sgshapiro			(void) sm_io_putc(tfp, SM_TIME_DEFAULT, 'A');
68290792Sgshapiro		(void) sm_io_putc(tfp, SM_TIME_DEFAULT, ':');
68390792Sgshapiro		(void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "%s\n",
68490792Sgshapiro				     denlstring(q->q_paddr, true, false));
68538032Speter		if (announce)
68638032Speter		{
68790792Sgshapiro			char *tag = "queued";
68890792Sgshapiro
68990792Sgshapiro#if _FFR_QUARANTINE
69090792Sgshapiro			if (e->e_quarmsg != NULL)
69190792Sgshapiro				tag = "quarantined";
69290792Sgshapiro#endif /* _FFR_QUARANTINE */
69390792Sgshapiro
69438032Speter			e->e_to = q->q_paddr;
69590792Sgshapiro			message(tag);
69638032Speter			if (LogLevel > 8)
69764562Sgshapiro				logdelivery(q->q_mailer, NULL, q->q_status,
69890792Sgshapiro					    tag, NULL, (time_t) 0, e);
69938032Speter			e->e_to = NULL;
70038032Speter		}
70138032Speter		if (tTd(40, 1))
70238032Speter		{
70390792Sgshapiro			sm_dprintf("queueing ");
70490792Sgshapiro			printaddr(q, false);
70538032Speter		}
70638032Speter	}
70738032Speter
70838032Speter	/*
70938032Speter	**  Output headers for this message.
71038032Speter	**	Expand macros completely here.  Queue run will deal with
71138032Speter	**	everything as absolute headers.
71238032Speter	**		All headers that must be relative to the recipient
71338032Speter	**		can be cracked later.
71438032Speter	**	We set up a "null mailer" -- i.e., a mailer that will have
71538032Speter	**	no effect on the addresses as they are output.
71638032Speter	*/
71738032Speter
71864562Sgshapiro	memset((char *) &nullmailer, '\0', sizeof nullmailer);
71938032Speter	nullmailer.m_re_rwset = nullmailer.m_rh_rwset =
72038032Speter			nullmailer.m_se_rwset = nullmailer.m_sh_rwset = -1;
72138032Speter	nullmailer.m_eol = "\n";
72264562Sgshapiro	memset(&mcibuf, '\0', sizeof mcibuf);
72338032Speter	mcibuf.mci_mailer = &nullmailer;
72438032Speter	mcibuf.mci_out = tfp;
72538032Speter
72690792Sgshapiro	macdefine(&e->e_macro, A_PERM, 'g', "\201f");
72738032Speter	for (h = e->e_header; h != NULL; h = h->h_link)
72838032Speter	{
72943730Speter		if (h->h_value == NULL)
73038032Speter			continue;
73138032Speter
73238032Speter		/* don't output resent headers on non-resent messages */
73364562Sgshapiro		if (bitset(H_RESENT, h->h_flags) &&
73464562Sgshapiro		    !bitset(EF_RESENT, e->e_flags))
73538032Speter			continue;
73638032Speter
73738032Speter		/* expand macros; if null, don't output header at all */
73838032Speter		if (bitset(H_DEFAULT, h->h_flags))
73938032Speter		{
74038032Speter			(void) expand(h->h_value, buf, sizeof buf, e);
74138032Speter			if (buf[0] == '\0')
74238032Speter				continue;
74338032Speter		}
74438032Speter
74538032Speter		/* output this header */
74690792Sgshapiro		(void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "H?");
74738032Speter
74864562Sgshapiro		/* output conditional macro if present */
74964562Sgshapiro		if (h->h_macro != '\0')
75038032Speter		{
75164562Sgshapiro			if (bitset(0200, h->h_macro))
75290792Sgshapiro				(void) sm_io_fprintf(tfp, SM_TIME_DEFAULT,
75390792Sgshapiro						     "${%s}",
75490792Sgshapiro						      macname(bitidx(h->h_macro)));
75564562Sgshapiro			else
75690792Sgshapiro				(void) sm_io_fprintf(tfp, SM_TIME_DEFAULT,
75790792Sgshapiro						     "$%c", h->h_macro);
75864562Sgshapiro		}
75964562Sgshapiro		else if (!bitzerop(h->h_mflags) &&
76064562Sgshapiro			 bitset(H_CHECK|H_ACHECK, h->h_flags))
76164562Sgshapiro		{
76238032Speter			int j;
76338032Speter
76464562Sgshapiro			/* if conditional, output the set of conditions */
76538032Speter			for (j = '\0'; j <= '\177'; j++)
76638032Speter				if (bitnset(j, h->h_mflags))
76790792Sgshapiro					(void) sm_io_putc(tfp, SM_TIME_DEFAULT,
76890792Sgshapiro							  j);
76938032Speter		}
77090792Sgshapiro		(void) sm_io_putc(tfp, SM_TIME_DEFAULT, '?');
77138032Speter
77238032Speter		/* output the header: expand macros, convert addresses */
77364562Sgshapiro		if (bitset(H_DEFAULT, h->h_flags) &&
77464562Sgshapiro		    !bitset(H_BINDLATE, h->h_flags))
77538032Speter		{
77690792Sgshapiro			(void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "%s: %s\n",
77790792Sgshapiro					     h->h_field,
77890792Sgshapiro					     denlstring(buf, false, true));
77938032Speter		}
78064562Sgshapiro		else if (bitset(H_FROM|H_RCPT, h->h_flags) &&
78164562Sgshapiro			 !bitset(H_BINDLATE, h->h_flags))
78238032Speter		{
78338032Speter			bool oldstyle = bitset(EF_OLDSTYLE, e->e_flags);
78490792Sgshapiro			SM_FILE_T *savetrace = TrafficLogFile;
78538032Speter
78638032Speter			TrafficLogFile = NULL;
78738032Speter
78838032Speter			if (bitset(H_FROM, h->h_flags))
78990792Sgshapiro				oldstyle = false;
79038032Speter
79138032Speter			commaize(h, h->h_value, oldstyle, &mcibuf, e);
79238032Speter
79338032Speter			TrafficLogFile = savetrace;
79438032Speter		}
79538032Speter		else
79638032Speter		{
79790792Sgshapiro			(void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "%s: %s\n",
79890792Sgshapiro					     h->h_field,
79990792Sgshapiro					     denlstring(h->h_value, false,
80090792Sgshapiro							true));
80138032Speter		}
80238032Speter	}
80338032Speter
80438032Speter	/*
80538032Speter	**  Clean up.
80638032Speter	**
80738032Speter	**	Write a terminator record -- this is to prevent
80838032Speter	**	scurrilous crackers from appending any data.
80938032Speter	*/
81038032Speter
81190792Sgshapiro	(void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, ".\n");
81238032Speter
81390792Sgshapiro	if (sm_io_flush(tfp, SM_TIME_DEFAULT) != 0 ||
81490792Sgshapiro	    ((SuperSafe == SAFE_REALLY ||
81590792Sgshapiro	      (SuperSafe == SAFE_INTERACTIVE && msync)) &&
81690792Sgshapiro	     fsync(sm_io_getinfo(tfp, SM_IO_WHAT_FD, NULL)) < 0) ||
81790792Sgshapiro	    sm_io_error(tfp))
81838032Speter	{
81938032Speter		if (newid)
82038032Speter			syserr("!552 Error writing control file %s", tf);
82138032Speter		else
82238032Speter			syserr("!452 Error writing control file %s", tf);
82338032Speter	}
82438032Speter
82538032Speter	if (!newid)
82638032Speter	{
82790792Sgshapiro#if _FFR_QUARANTINE
82890792Sgshapiro		char new = queue_letter(e, ANYQFL_LETTER);
82990792Sgshapiro#endif /* _FFR_QUARANTINE */
83090792Sgshapiro
83190792Sgshapiro		/* rename (locked) tf to be (locked) [qh]f */
83290792Sgshapiro		(void) sm_strlcpy(qf, queuename(e, ANYQFL_LETTER),
83390792Sgshapiro				  sizeof qf);
83438032Speter		if (rename(tf, qf) < 0)
83538032Speter			syserr("cannot rename(%s, %s), uid=%d",
83638032Speter				tf, qf, geteuid());
83790792Sgshapiro# if _FFR_QUARANTINE
83890792Sgshapiro		else
83990792Sgshapiro		{
84090792Sgshapiro			/*
84190792Sgshapiro			**  Check if type has changed and only
84290792Sgshapiro			**  remove the old item if the rename above
84390792Sgshapiro			**  succeeded.
84490792Sgshapiro			*/
84590792Sgshapiro
84690792Sgshapiro			if (e->e_qfletter != '\0' &&
84790792Sgshapiro			    e->e_qfletter != new)
84890792Sgshapiro			{
84990792Sgshapiro				if (tTd(40, 5))
85090792Sgshapiro				{
85190792Sgshapiro					sm_dprintf("type changed from %c to %c\n",
85290792Sgshapiro						   e->e_qfletter, new);
85390792Sgshapiro				}
85490792Sgshapiro
85590792Sgshapiro				if (unlink(queuename(e, e->e_qfletter)) < 0)
85690792Sgshapiro				{
85790792Sgshapiro					/* XXX: something more drastic? */
85890792Sgshapiro					if (LogLevel > 0)
85990792Sgshapiro						sm_syslog(LOG_ERR, e->e_id,
86090792Sgshapiro							  "queueup: unlink(%s) failed: %s",
86190792Sgshapiro							  queuename(e, e->e_qfletter),
86290792Sgshapiro							  sm_errstring(errno));
86390792Sgshapiro				}
86490792Sgshapiro			}
86590792Sgshapiro		}
86690792Sgshapiro		e->e_qfletter = new;
86790792Sgshapiro# endif /* _FFR_QUARANTINE */
86890792Sgshapiro
86964562Sgshapiro		/*
87090792Sgshapiro		**  fsync() after renaming to make sure metadata is
87190792Sgshapiro		**  written to disk on filesystems in which renames are
87290792Sgshapiro		**  not guaranteed.
87364562Sgshapiro		*/
87464562Sgshapiro
87590792Sgshapiro		if (SuperSafe != SAFE_NO)
87690792Sgshapiro		{
87790792Sgshapiro			/* for softupdates */
87890792Sgshapiro			if (tfd >= 0 && fsync(tfd) < 0)
87990792Sgshapiro			{
88090792Sgshapiro				syserr("!queueup: cannot fsync queue temp file %s",
88190792Sgshapiro				       tf);
88290792Sgshapiro			}
88390792Sgshapiro			SYNC_DIR(qf, true);
88490792Sgshapiro		}
88564562Sgshapiro
88690792Sgshapiro		/* close and unlock old (locked) queue file */
88738032Speter		if (e->e_lockfp != NULL)
88890792Sgshapiro			(void) sm_io_close(e->e_lockfp, SM_TIME_DEFAULT);
88938032Speter		e->e_lockfp = tfp;
89090792Sgshapiro
89190792Sgshapiro		/* save log info */
89290792Sgshapiro		if (LogLevel > 79)
89390792Sgshapiro			sm_syslog(LOG_DEBUG, e->e_id, "queueup %s", qf);
89438032Speter	}
89538032Speter	else
89690792Sgshapiro	{
89790792Sgshapiro		/* save log info */
89890792Sgshapiro		if (LogLevel > 79)
89990792Sgshapiro			sm_syslog(LOG_DEBUG, e->e_id, "queueup %s", tf);
90090792Sgshapiro
90190792Sgshapiro#if _FFR_QUARANTINE
90290792Sgshapiro		e->e_qfletter = queue_letter(e, ANYQFL_LETTER);
90390792Sgshapiro#endif /* _FFR_QUARANTINE */
90490792Sgshapiro	}
90590792Sgshapiro
90638032Speter	errno = 0;
90738032Speter	e->e_flags |= EF_INQUEUE;
90838032Speter
90938032Speter	if (tTd(40, 1))
91090792Sgshapiro		sm_dprintf("<<<<< done queueing %s <<<<<\n\n", e->e_id);
91138032Speter	return;
91238032Speter}
91338032Speter
91490792Sgshapiro/*
91590792Sgshapiro**  PRINTCTLADDR -- print control address to file.
91690792Sgshapiro**
91790792Sgshapiro**	Parameters:
91890792Sgshapiro**		a -- address.
91990792Sgshapiro**		tfp -- file pointer.
92090792Sgshapiro**
92190792Sgshapiro**	Returns:
92290792Sgshapiro**		none.
92390792Sgshapiro**
92490792Sgshapiro**	Side Effects:
92590792Sgshapiro**		The control address (if changed) is printed to the file.
92690792Sgshapiro**		The last control address and uid are saved.
92790792Sgshapiro*/
92890792Sgshapiro
92964562Sgshapirostatic void
93038032Speterprintctladdr(a, tfp)
93138032Speter	register ADDRESS *a;
93290792Sgshapiro	SM_FILE_T *tfp;
93338032Speter{
93464562Sgshapiro	char *user;
93538032Speter	register ADDRESS *q;
93638032Speter	uid_t uid;
93738032Speter	gid_t gid;
93838032Speter	static ADDRESS *lastctladdr = NULL;
93938032Speter	static uid_t lastuid;
94038032Speter
94138032Speter	/* initialization */
94238032Speter	if (a == NULL || a->q_alias == NULL || tfp == NULL)
94338032Speter	{
94438032Speter		if (lastctladdr != NULL && tfp != NULL)
94590792Sgshapiro			(void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "C\n");
94638032Speter		lastctladdr = NULL;
94738032Speter		lastuid = 0;
94838032Speter		return;
94938032Speter	}
95038032Speter
95138032Speter	/* find the active uid */
95238032Speter	q = getctladdr(a);
95338032Speter	if (q == NULL)
95438032Speter	{
95564562Sgshapiro		user = NULL;
95638032Speter		uid = 0;
95738032Speter		gid = 0;
95838032Speter	}
95938032Speter	else
96038032Speter	{
96164562Sgshapiro		user = q->q_ruser != NULL ? q->q_ruser : q->q_user;
96238032Speter		uid = q->q_uid;
96338032Speter		gid = q->q_gid;
96438032Speter	}
96538032Speter	a = a->q_alias;
96638032Speter
96738032Speter	/* check to see if this is the same as last time */
96838032Speter	if (lastctladdr != NULL && uid == lastuid &&
96938032Speter	    strcmp(lastctladdr->q_paddr, a->q_paddr) == 0)
97038032Speter		return;
97138032Speter	lastuid = uid;
97238032Speter	lastctladdr = a;
97338032Speter
97464562Sgshapiro	if (uid == 0 || user == NULL || user[0] == '\0')
97590792Sgshapiro		(void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "C");
97638032Speter	else
97790792Sgshapiro		(void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "C%s:%ld:%ld",
97890792Sgshapiro				     denlstring(user, true, false), (long) uid,
97990792Sgshapiro				     (long) gid);
98090792Sgshapiro	(void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, ":%s\n",
98190792Sgshapiro			     denlstring(a->q_paddr, true, false));
98238032Speter}
98390792Sgshapiro
98490792Sgshapiro/*
98590792Sgshapiro**  RUNNERS_SIGTERM -- propagate a SIGTERM to queue runner process
98690792Sgshapiro**
98790792Sgshapiro**	This propagates the signal to the child processes that are queue
98890792Sgshapiro**	runners. This is for a queue runner "cleanup". After all of the
98990792Sgshapiro**	child queue runner processes are signaled (it should be SIGTERM
99090792Sgshapiro**	being the sig) then the old signal handler (Oldsh) is called
99190792Sgshapiro**	to handle any cleanup set for this process (provided it is not
99290792Sgshapiro**	SIG_DFL or SIG_IGN). The signal may not be handled immediately
99390792Sgshapiro**	if the BlockOldsh flag is set. If the current process doesn't
99490792Sgshapiro**	have a parent then handle the signal immediately, regardless of
99590792Sgshapiro**	BlockOldsh.
99690792Sgshapiro**
99790792Sgshapiro**	Parameters:
99890792Sgshapiro**		sig -- the signal number being sent
99990792Sgshapiro**
100090792Sgshapiro**	Returns:
100190792Sgshapiro**		none.
100290792Sgshapiro**
100390792Sgshapiro**	Side Effects:
100490792Sgshapiro**		Sets the NoMoreRunners boolean to true to stop more runners
100590792Sgshapiro**		from being started in runqueue().
100690792Sgshapiro**
100790792Sgshapiro**	NOTE:	THIS CAN BE CALLED FROM A SIGNAL HANDLER.  DO NOT ADD
100890792Sgshapiro**		ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
100990792Sgshapiro**		DOING.
101090792Sgshapiro*/
101190792Sgshapiro
101290792Sgshapirostatic bool		volatile NoMoreRunners = false;
101390792Sgshapirostatic sigfunc_t	Oldsh_term = SIG_DFL;
101490792Sgshapirostatic sigfunc_t	Oldsh_hup = SIG_DFL;
101590792Sgshapirostatic sigfunc_t	volatile Oldsh = SIG_DFL;
101690792Sgshapirostatic bool		BlockOldsh = false;
101790792Sgshapirostatic int		volatile Oldsig = 0;
101890792Sgshapirostatic SIGFUNC_DECL	runners_sigterm __P((int));
101990792Sgshapirostatic SIGFUNC_DECL	runners_sighup __P((int));
102090792Sgshapiro
102190792Sgshapirostatic SIGFUNC_DECL
102290792Sgshapirorunners_sigterm(sig)
102390792Sgshapiro	int sig;
102490792Sgshapiro{
102590792Sgshapiro	int save_errno = errno;
102690792Sgshapiro
102790792Sgshapiro	FIX_SYSV_SIGNAL(sig, runners_sigterm);
102890792Sgshapiro	errno = save_errno;
102990792Sgshapiro	CHECK_CRITICAL(sig);
103090792Sgshapiro	NoMoreRunners = true;
103190792Sgshapiro	Oldsh = Oldsh_term;
103290792Sgshapiro	Oldsig = sig;
103390792Sgshapiro	proc_list_signal(PROC_QUEUE, sig);
103490792Sgshapiro
103590792Sgshapiro	if (!BlockOldsh || getppid() <= 1)
103690792Sgshapiro	{
103790792Sgshapiro		/* Check that a valid 'old signal handler' is callable */
103890792Sgshapiro		if (Oldsh_term != SIG_DFL && Oldsh_term != SIG_IGN &&
103990792Sgshapiro		    Oldsh_term != runners_sigterm)
104090792Sgshapiro			(*Oldsh_term)(sig);
104190792Sgshapiro	}
104290792Sgshapiro	errno = save_errno;
104390792Sgshapiro	return SIGFUNC_RETURN;
104490792Sgshapiro}
104590792Sgshapiro/*
104690792Sgshapiro**  RUNNERS_SIGHUP -- propagate a SIGHUP to queue runner process
104790792Sgshapiro**
104890792Sgshapiro**	This propagates the signal to the child processes that are queue
104990792Sgshapiro**	runners. This is for a queue runner "cleanup". After all of the
105090792Sgshapiro**	child queue runner processes are signaled (it should be SIGHUP
105190792Sgshapiro**	being the sig) then the old signal handler (Oldsh) is called to
105290792Sgshapiro**	handle any cleanup set for this process (provided it is not SIG_DFL
105390792Sgshapiro**	or SIG_IGN). The signal may not be handled immediately if the
105490792Sgshapiro**	BlockOldsh flag is set. If the current process doesn't have
105590792Sgshapiro**	a parent then handle the signal immediately, regardless of
105690792Sgshapiro**	BlockOldsh.
105790792Sgshapiro**
105890792Sgshapiro**	Parameters:
105990792Sgshapiro**		sig -- the signal number being sent
106090792Sgshapiro**
106190792Sgshapiro**	Returns:
106290792Sgshapiro**		none.
106390792Sgshapiro**
106490792Sgshapiro**	Side Effects:
106590792Sgshapiro**		Sets the NoMoreRunners boolean to true to stop more runners
106690792Sgshapiro**		from being started in runqueue().
106790792Sgshapiro**
106890792Sgshapiro**	NOTE:	THIS CAN BE CALLED FROM A SIGNAL HANDLER.  DO NOT ADD
106990792Sgshapiro**		ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
107090792Sgshapiro**		DOING.
107190792Sgshapiro*/
107290792Sgshapiro
107390792Sgshapirostatic SIGFUNC_DECL
107490792Sgshapirorunners_sighup(sig)
107590792Sgshapiro	int sig;
107690792Sgshapiro{
107790792Sgshapiro	int save_errno = errno;
107890792Sgshapiro
107990792Sgshapiro	FIX_SYSV_SIGNAL(sig, runners_sighup);
108090792Sgshapiro	errno = save_errno;
108190792Sgshapiro	CHECK_CRITICAL(sig);
108290792Sgshapiro	NoMoreRunners = true;
108390792Sgshapiro	Oldsh = Oldsh_hup;
108490792Sgshapiro	Oldsig = sig;
108590792Sgshapiro	proc_list_signal(PROC_QUEUE, sig);
108690792Sgshapiro
108790792Sgshapiro	if (!BlockOldsh || getppid() <= 1)
108890792Sgshapiro	{
108990792Sgshapiro		/* Check that a valid 'old signal handler' is callable */
109090792Sgshapiro		if (Oldsh_hup != SIG_DFL && Oldsh_hup != SIG_IGN &&
109190792Sgshapiro		    Oldsh_hup != runners_sighup)
109290792Sgshapiro			(*Oldsh_hup)(sig);
109390792Sgshapiro	}
109490792Sgshapiro	errno = save_errno;
109590792Sgshapiro	return SIGFUNC_RETURN;
109690792Sgshapiro}
109790792Sgshapiro/*
109890792Sgshapiro**  MARK_WORK_GROUP_RESTART -- mark a work group as needing a restart
109990792Sgshapiro**
110090792Sgshapiro**  Sets a workgroup for restarting.
110190792Sgshapiro**
110290792Sgshapiro**	Parameters:
110390792Sgshapiro**		wgrp -- the work group id to restart.
110490792Sgshapiro**		reason -- why (signal?), -1 to turn off restart
110590792Sgshapiro**
110690792Sgshapiro**	Returns:
110790792Sgshapiro**		none.
110890792Sgshapiro**
110990792Sgshapiro**	Side effects:
111090792Sgshapiro**		May set global RestartWorkGroup to true.
111190792Sgshapiro**
111290792Sgshapiro**	NOTE:	THIS CAN BE CALLED FROM A SIGNAL HANDLER.  DO NOT ADD
111390792Sgshapiro**		ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
111490792Sgshapiro**		DOING.
111590792Sgshapiro*/
111690792Sgshapiro
111790792Sgshapirovoid
111890792Sgshapiromark_work_group_restart(wgrp, reason)
111990792Sgshapiro	int wgrp;
112090792Sgshapiro	int reason;
112190792Sgshapiro{
112290792Sgshapiro	if (wgrp < 0 || wgrp > NumWorkGroups)
112390792Sgshapiro		return;
112490792Sgshapiro
112590792Sgshapiro	WorkGrp[wgrp].wg_restart = reason;
112690792Sgshapiro	if (reason >= 0)
112790792Sgshapiro		RestartWorkGroup = true;
112890792Sgshapiro}
112990792Sgshapiro/*
113090792Sgshapiro**  RESTART_MARKED_WORK_GROUPS -- restart work groups marked as needing restart
113190792Sgshapiro**
113290792Sgshapiro**  Restart any workgroup marked as needing a restart provided more
113390792Sgshapiro**  runners are allowed.
113490792Sgshapiro**
113590792Sgshapiro**	Parameters:
113690792Sgshapiro**		none.
113790792Sgshapiro**
113890792Sgshapiro**	Returns:
113990792Sgshapiro**		none.
114090792Sgshapiro**
114190792Sgshapiro**	Side effects:
114290792Sgshapiro**		Sets global RestartWorkGroup to false.
114390792Sgshapiro*/
114490792Sgshapiro
114590792Sgshapirovoid
114690792Sgshapirorestart_marked_work_groups()
114790792Sgshapiro{
114890792Sgshapiro	int i;
114990792Sgshapiro	int wasblocked;
115090792Sgshapiro
115190792Sgshapiro	if (NoMoreRunners)
115290792Sgshapiro		return;
115390792Sgshapiro
115490792Sgshapiro	/* Block SIGCHLD so reapchild() doesn't mess with us */
115590792Sgshapiro	wasblocked = sm_blocksignal(SIGCHLD);
115690792Sgshapiro
115790792Sgshapiro	for (i = 0; i < NumWorkGroups; i++)
115890792Sgshapiro	{
115990792Sgshapiro		if (WorkGrp[i].wg_restart >= 0)
116090792Sgshapiro		{
116190792Sgshapiro			if (LogLevel > 8)
116290792Sgshapiro				sm_syslog(LOG_ERR, NOQID,
116390792Sgshapiro					  "restart queue runner=%d due to signal 0x%x",
116490792Sgshapiro					  i, WorkGrp[i].wg_restart);
116590792Sgshapiro			restart_work_group(i);
116690792Sgshapiro		}
116790792Sgshapiro	}
116890792Sgshapiro	RestartWorkGroup = false;
116990792Sgshapiro
117090792Sgshapiro	if (wasblocked == 0)
117190792Sgshapiro		(void) sm_releasesignal(SIGCHLD);
117290792Sgshapiro}
117390792Sgshapiro/*
117490792Sgshapiro**  RESTART_WORK_GROUP -- restart a specific work group
117590792Sgshapiro**
117690792Sgshapiro**  Restart a specific workgroup provided more runners are allowed.
117790792Sgshapiro**  If the requested work group has been restarted too many times log
117890792Sgshapiro**  this and refuse to restart.
117990792Sgshapiro**
118090792Sgshapiro**	Parameters:
118190792Sgshapiro**		wgrp -- the work group id to restart
118290792Sgshapiro**
118390792Sgshapiro**	Returns:
118490792Sgshapiro**		none.
118590792Sgshapiro**
118690792Sgshapiro**	Side Effects:
118790792Sgshapiro**		starts another process doing the work of wgrp
118890792Sgshapiro*/
118990792Sgshapiro
119090792Sgshapiro#define MAX_PERSIST_RESTART	10	/* max allowed number of restarts */
119190792Sgshapiro
119290792Sgshapirostatic void
119390792Sgshapirorestart_work_group(wgrp)
119490792Sgshapiro	int wgrp;
119590792Sgshapiro{
119690792Sgshapiro	if (NoMoreRunners ||
119790792Sgshapiro	    wgrp < 0 || wgrp > NumWorkGroups)
119890792Sgshapiro		return;
119990792Sgshapiro
120090792Sgshapiro	WorkGrp[wgrp].wg_restart = -1;
120190792Sgshapiro	if (WorkGrp[wgrp].wg_restartcnt < MAX_PERSIST_RESTART)
120290792Sgshapiro	{
120390792Sgshapiro		/* avoid overflow; increment here */
120490792Sgshapiro		WorkGrp[wgrp].wg_restartcnt++;
120590792Sgshapiro		(void) run_work_group(wgrp, true, false, true, true);
120690792Sgshapiro	}
120790792Sgshapiro	else
120890792Sgshapiro	{
120990792Sgshapiro		sm_syslog(LOG_ERR, NOQID,
121090792Sgshapiro			  "ERROR: persistent queue runner=%d restarted too many times, queue runner lost",
121190792Sgshapiro			  wgrp);
121290792Sgshapiro	}
121390792Sgshapiro}
121490792Sgshapiro/*
121590792Sgshapiro**  SCHEDULE_QUEUE_RUNS -- schedule the next queue run for a work group.
121690792Sgshapiro**
121790792Sgshapiro**	Parameters:
121890792Sgshapiro**		runall -- schedule even if individual bit is not set.
121990792Sgshapiro**		wgrp -- the work group id to schedule.
122090792Sgshapiro**
122190792Sgshapiro**	Returns:
122290792Sgshapiro**		nothing
122390792Sgshapiro*/
122490792Sgshapiro
122590792Sgshapiro#define INCR_MOD(v, m)	if (++v >= m)	\
122690792Sgshapiro				v = 0;	\
122790792Sgshapiro			else
122890792Sgshapiro
122990792Sgshapirostatic void
123090792Sgshapiroschedule_queue_runs(runall, wgrp)
123190792Sgshapiro	bool runall;
123290792Sgshapiro	int wgrp;
123390792Sgshapiro{
123490792Sgshapiro	int qgrp, cgrp, endgrp;
123590792Sgshapiro
123690792Sgshapiro	/*
123790792Sgshapiro	**  This is a bit ugly since we have to duplicate the
123890792Sgshapiro	**  code that "walks" through a work queue group.
123990792Sgshapiro	*/
124090792Sgshapiro
124190792Sgshapiro	cgrp = endgrp = WorkGrp[wgrp].wg_curqgrp;
124290792Sgshapiro	do
124390792Sgshapiro	{
124490792Sgshapiro		time_t qintvl;
124590792Sgshapiro
124690792Sgshapiro		qgrp = WorkGrp[wgrp].wg_qgs[cgrp]->qg_index;
124790792Sgshapiro		if (Queue[qgrp]->qg_queueintvl > 0)
124890792Sgshapiro			qintvl = Queue[qgrp]->qg_queueintvl;
124990792Sgshapiro		else if (QueueIntvl > 0)
125090792Sgshapiro			qintvl = QueueIntvl;
125190792Sgshapiro		else
125290792Sgshapiro			qintvl = (time_t) 0;
125390792Sgshapiro		if ((runall || bitnset(qgrp, DoQueueRun)) && qintvl > 0)
125490792Sgshapiro			(void) sm_setevent(qintvl, runqueueevent, qgrp);
125590792Sgshapiro#if _FFR_QUEUE_SCHED_DBG
125690792Sgshapiro		if (tTd(69, 10))
125790792Sgshapiro			sm_syslog(LOG_INFO, NOQID,
125890792Sgshapiro				"sqr: wgrp=%d, cgrp=%d, qgrp=%d, intvl=%ld, QI=%ld, runall=%d, bit=%d, sched=%d",
125990792Sgshapiro				wgrp, cgrp, qgrp, Queue[qgrp]->qg_queueintvl,
126090792Sgshapiro				QueueIntvl, runall, bitnset(qgrp, DoQueueRun),
126190792Sgshapiro				(runall || bitnset(qgrp, DoQueueRun)) &&
126290792Sgshapiro				qintvl > 0);
126390792Sgshapiro#endif /* _FFR_QUEUE_SCHED_DBG */
126490792Sgshapiro		clrbitn(qgrp, DoQueueRun);
126590792Sgshapiro		INCR_MOD(cgrp, WorkGrp[wgrp].wg_numqgrp);
126690792Sgshapiro	} while (endgrp != cgrp);
126790792Sgshapiro}
126890792Sgshapiro/*
126938032Speter**  RUNQUEUE -- run the jobs in the queue.
127038032Speter**
127138032Speter**	Gets the stuff out of the queue in some presumably logical
127238032Speter**	order and processes them.
127338032Speter**
127438032Speter**	Parameters:
127590792Sgshapiro**		forkflag -- true if the queue scanning should be done in
127638032Speter**			a child process.  We double-fork so it is not our
127738032Speter**			child and we don't have to clean up after it.
127890792Sgshapiro**			false can be ignored if we have multiple queues.
127990792Sgshapiro**		verbose -- if true, print out status information.
128090792Sgshapiro**		persistent -- persistent queue runner?
128190792Sgshapiro**		runall -- run all groups or only a subset (DoQueueRun)?
128238032Speter**
128338032Speter**	Returns:
128490792Sgshapiro**		true if the queue run successfully began.
128538032Speter**
128638032Speter**	Side Effects:
128790792Sgshapiro**		runs things in the mail queue using run_work_group().
128890792Sgshapiro**		maybe schedules next queue run.
128990792Sgshapiro**
129038032Speter*/
129138032Speter
129264562Sgshapirostatic ENVELOPE	QueueEnvelope;		/* the queue run envelope */
129364562Sgshapirostatic time_t	LastQueueTime = 0;	/* last time a queue ID assigned */
129464562Sgshapirostatic pid_t	LastQueuePid = -1;	/* last PID which had a queue ID */
129538032Speter
129664562Sgshapiro/* values for qp_supdirs */
129764562Sgshapiro#define QP_NOSUB	0x0000	/* No subdirectories */
129864562Sgshapiro#define QP_SUBDF	0x0001	/* "df" subdirectory */
129964562Sgshapiro#define QP_SUBQF	0x0002	/* "qf" subdirectory */
130064562Sgshapiro#define QP_SUBXF	0x0004	/* "xf" subdirectory */
130164562Sgshapiro
130238032Speterbool
130390792Sgshapirorunqueue(forkflag, verbose, persistent, runall)
130438032Speter	bool forkflag;
130538032Speter	bool verbose;
130690792Sgshapiro	bool persistent;
130790792Sgshapiro	bool runall;
130838032Speter{
130964562Sgshapiro	int i;
131090792Sgshapiro	bool ret = true;
131164562Sgshapiro	static int curnum = 0;
131290792Sgshapiro	sigfunc_t cursh;
131390792Sgshapiro#if SM_HEAP_CHECK
131490792Sgshapiro	SM_NONVOLATILE int oldgroup = 0;
131564562Sgshapiro
131690792Sgshapiro	if (sm_debug_active(&DebugLeakQ, 1))
131790792Sgshapiro	{
131890792Sgshapiro		oldgroup = sm_heap_group();
131990792Sgshapiro		sm_heap_newgroup();
132090792Sgshapiro		sm_dprintf("runqueue() heap group #%d\n", sm_heap_group());
132190792Sgshapiro	}
132290792Sgshapiro#endif /* SM_HEAP_CHECK */
132371345Sgshapiro
132490792Sgshapiro	/* queue run has been started, don't do any more this time */
132590792Sgshapiro	clrbitn(NumQueue, DoQueueRun);
132671345Sgshapiro
132790792Sgshapiro	/* more than one queue or more than one directory per queue */
132890792Sgshapiro	if (!forkflag && !verbose &&
132990792Sgshapiro	    (WorkGrp[0].wg_qgs[0]->qg_numqueues > 1 || NumWorkGroups > 1 ||
133090792Sgshapiro	     WorkGrp[0].wg_numqgrp > 1))
133190792Sgshapiro		forkflag = true;
133264562Sgshapiro
133390792Sgshapiro	/*
133490792Sgshapiro	**  For controlling queue runners via signals sent to this process.
133590792Sgshapiro	**  Oldsh* will get called too by runners_sig* (if it is not SIG_IGN
133690792Sgshapiro	**  or SIG_DFL) to preserve cleanup behavior. Now that this process
133790792Sgshapiro	**  will have children (and perhaps grandchildren) this handler will
133890792Sgshapiro	**  be left in place. This is because this process, once it has
133990792Sgshapiro	**  finished spinning off queue runners, may go back to doing something
134090792Sgshapiro	**  else (like being a daemon). And we still want on a SIG{TERM,HUP} to
134190792Sgshapiro	**  clean up the child queue runners. Only install 'runners_sig*' once
134290792Sgshapiro	**  else we'll get stuck looping forever.
134390792Sgshapiro	*/
134490792Sgshapiro
134590792Sgshapiro	cursh = sm_signal(SIGTERM, runners_sigterm);
134690792Sgshapiro	if (cursh != runners_sigterm)
134790792Sgshapiro		Oldsh_term = cursh;
134890792Sgshapiro	cursh = sm_signal(SIGHUP, runners_sighup);
134990792Sgshapiro	if (cursh != runners_sighup)
135090792Sgshapiro		Oldsh_hup = cursh;
135190792Sgshapiro
135290792Sgshapiro	for (i = 0; i < NumWorkGroups && !NoMoreRunners; i++)
135364562Sgshapiro	{
135464562Sgshapiro		/*
135590792Sgshapiro		**  If MaxQueueChildren active then test whether the start
135690792Sgshapiro		**  of the next queue group's additional queue runners (maximum)
135790792Sgshapiro		**  will result in MaxQueueChildren being exceeded.
135890792Sgshapiro		**
135990792Sgshapiro		**  Note: do not use continue; even though another workgroup
136090792Sgshapiro		**	may have fewer queue runners, this would be "unfair",
136190792Sgshapiro		**	i.e., this work group might "starve" then.
136264562Sgshapiro		*/
136364562Sgshapiro
136490792Sgshapiro#if _FFR_QUEUE_SCHED_DBG
136590792Sgshapiro		if (tTd(69, 10))
136690792Sgshapiro			sm_syslog(LOG_INFO, NOQID,
136790792Sgshapiro				"rq: curnum=%d, MaxQueueChildren=%d, CurRunners=%d, WorkGrp[curnum].wg_maxact=%d",
136890792Sgshapiro				curnum, MaxQueueChildren, CurRunners,
136990792Sgshapiro				WorkGrp[curnum].wg_maxact);
137090792Sgshapiro#endif /* _FFR_QUEUE_SCHED_DBG */
137190792Sgshapiro		if (MaxQueueChildren > 0 &&
137290792Sgshapiro		    CurRunners + WorkGrp[curnum].wg_maxact > MaxQueueChildren)
137390792Sgshapiro			break;
137464562Sgshapiro
137564562Sgshapiro		/*
137690792Sgshapiro		**  Pick up where we left off (curnum), in case we
137790792Sgshapiro		**  used up all the children last time without finishing.
137890792Sgshapiro		**  This give a round-robin fairness to queue runs.
137990792Sgshapiro		*/
138090792Sgshapiro
138190792Sgshapiro		ret = run_work_group(curnum, forkflag, verbose, persistent,
138290792Sgshapiro				     runall);
138390792Sgshapiro
138490792Sgshapiro		/*
138564562Sgshapiro		**  Failure means a message was printed for ETRN
138664562Sgshapiro		**  and subsequent queues are likely to fail as well.
138764562Sgshapiro		*/
138864562Sgshapiro
138964562Sgshapiro		if (!ret)
139064562Sgshapiro			break;
139164562Sgshapiro
139290792Sgshapiro		/* Success means the runner count needs to be updated. */
139390792Sgshapiro		CurRunners += WorkGrp[curnum].wg_maxact;
139490792Sgshapiro		if (!persistent)
139590792Sgshapiro			schedule_queue_runs(runall, curnum);
139690792Sgshapiro		INCR_MOD(curnum, NumWorkGroups);
139764562Sgshapiro	}
139890792Sgshapiro
139990792Sgshapiro	/* schedule left over queue runs */
140090792Sgshapiro	if (i < NumWorkGroups && !NoMoreRunners && !persistent)
140190792Sgshapiro	{
140290792Sgshapiro		int h;
140390792Sgshapiro
140490792Sgshapiro		for (h = curnum; i < NumWorkGroups; i++)
140590792Sgshapiro		{
140690792Sgshapiro			schedule_queue_runs(runall, h);
140790792Sgshapiro			INCR_MOD(h, NumWorkGroups);
140890792Sgshapiro		}
140990792Sgshapiro	}
141090792Sgshapiro
141190792Sgshapiro
141290792Sgshapiro#if SM_HEAP_CHECK
141390792Sgshapiro	if (sm_debug_active(&DebugLeakQ, 1))
141490792Sgshapiro		sm_heap_setgroup(oldgroup);
141590792Sgshapiro#endif /* SM_HEAP_CHECK */
141664562Sgshapiro	return ret;
141764562Sgshapiro}
141890792Sgshapiro/*
141990792Sgshapiro**  RUNNER_WORK -- have a queue runner do its work
142064562Sgshapiro**
142190792Sgshapiro**  Have a queue runner do its work a list of entries.
142290792Sgshapiro**  When work isn't directly being done then this process can take a signal
142390792Sgshapiro**  and terminate immediately (in a clean fashion of course).
142490792Sgshapiro**  When work is directly being done, it's not to be interrupted
142590792Sgshapiro**  immediately: the work should be allowed to finish at a clean point
142690792Sgshapiro**  before termination (in a clean fashion of course).
142790792Sgshapiro**
142890792Sgshapiro**	Parameters:
142990792Sgshapiro**		e -- envelope.
143090792Sgshapiro**		sequenceno -- 'th process to run WorkQ.
143190792Sgshapiro**		didfork -- did the calling process fork()?
143290792Sgshapiro**		skip -- process only each skip'th item.
143390792Sgshapiro**		njobs -- number of jobs in WorkQ.
143490792Sgshapiro**
143590792Sgshapiro**	Returns:
143690792Sgshapiro**		none.
143790792Sgshapiro**
143890792Sgshapiro**	Side Effects:
143990792Sgshapiro**		runs things in the mail queue.
144090792Sgshapiro*/
144190792Sgshapiro
144290792Sgshapiro/* Get new load average every 30 seconds. */
144390792Sgshapiro#define GET_NEW_LA_TIME	30
144490792Sgshapiro
144590792Sgshapirostatic void
144690792Sgshapirorunner_work(e, sequenceno, didfork, skip, njobs)
144790792Sgshapiro	register ENVELOPE *e;
144890792Sgshapiro	int sequenceno;
144990792Sgshapiro	bool didfork;
145090792Sgshapiro	int skip;
145190792Sgshapiro	int njobs;
145290792Sgshapiro{
145390792Sgshapiro	int n;
145490792Sgshapiro	WORK *w;
145590792Sgshapiro	time_t current_la_time, now;
145690792Sgshapiro
145790792Sgshapiro	current_la_time = curtime();
145890792Sgshapiro
145990792Sgshapiro	/*
146090792Sgshapiro	**  Here we temporarily block the second calling of the handlers.
146190792Sgshapiro	**  This allows us to handle the signal without terminating in the
146290792Sgshapiro	**  middle of direct work. If a signal does come, the test for
146390792Sgshapiro	**  NoMoreRunners will find it.
146490792Sgshapiro	*/
146590792Sgshapiro
146690792Sgshapiro	BlockOldsh = true;
146790792Sgshapiro
146890792Sgshapiro	/* process them once at a time */
146990792Sgshapiro	while (WorkQ != NULL)
147090792Sgshapiro	{
147190792Sgshapiro#if SM_HEAP_CHECK
147290792Sgshapiro		SM_NONVOLATILE int oldgroup = 0;
147390792Sgshapiro
147490792Sgshapiro		if (sm_debug_active(&DebugLeakQ, 1))
147590792Sgshapiro		{
147690792Sgshapiro			oldgroup = sm_heap_group();
147790792Sgshapiro			sm_heap_newgroup();
147890792Sgshapiro			sm_dprintf("run_queue_group() heap group #%d\n",
147990792Sgshapiro				sm_heap_group());
148090792Sgshapiro		}
148190792Sgshapiro#endif /* SM_HEAP_CHECK */
148290792Sgshapiro
148390792Sgshapiro		/* do no more work */
148490792Sgshapiro		if (NoMoreRunners)
148590792Sgshapiro		{
148690792Sgshapiro			/* Check that a valid signal handler is callable */
148790792Sgshapiro			if (Oldsh != SIG_DFL && Oldsh != SIG_IGN &&
148890792Sgshapiro			    Oldsh != runners_sighup &&
148990792Sgshapiro			    Oldsh != runners_sigterm)
149090792Sgshapiro				(*Oldsh)(Oldsig);
149190792Sgshapiro			break;
149290792Sgshapiro		}
149390792Sgshapiro
149490792Sgshapiro		w = WorkQ; /* assign current work item */
149590792Sgshapiro
149690792Sgshapiro		/*
149790792Sgshapiro		**  Set the head of the WorkQ to the next work item.
149890792Sgshapiro		**  It is set 'skip' ahead (the number of parallel queue
149990792Sgshapiro		**  runners working on WorkQ together) since each runner
150090792Sgshapiro		**  works on every 'skip'th (N-th) item.
150190792Sgshapiro		*/
150290792Sgshapiro
150390792Sgshapiro		for (n = 0; n < skip && WorkQ != NULL; n++)
150490792Sgshapiro			WorkQ = WorkQ->w_next;
150590792Sgshapiro		e->e_to = NULL;
150690792Sgshapiro
150790792Sgshapiro		/*
150890792Sgshapiro		**  Ignore jobs that are too expensive for the moment.
150990792Sgshapiro		**
151090792Sgshapiro		**	Get new load average every GET_NEW_LA_TIME seconds.
151190792Sgshapiro		*/
151290792Sgshapiro
151390792Sgshapiro		now = curtime();
151490792Sgshapiro		if (current_la_time < now - GET_NEW_LA_TIME)
151590792Sgshapiro		{
151690792Sgshapiro			sm_getla();
151790792Sgshapiro			current_la_time = now;
151890792Sgshapiro		}
151990792Sgshapiro		if (shouldqueue(WkRecipFact, current_la_time))
152090792Sgshapiro		{
152190792Sgshapiro			char *msg = "Aborting queue run: load average too high";
152290792Sgshapiro
152390792Sgshapiro			if (Verbose)
152490792Sgshapiro				message("%s", msg);
152590792Sgshapiro			if (LogLevel > 8)
152690792Sgshapiro				sm_syslog(LOG_INFO, NOQID, "runqueue: %s", msg);
152790792Sgshapiro			break;
152890792Sgshapiro		}
152990792Sgshapiro		if (shouldqueue(w->w_pri, w->w_ctime))
153090792Sgshapiro		{
153190792Sgshapiro			if (Verbose)
153290792Sgshapiro				message(EmptyString);
153390792Sgshapiro			if (QueueSortOrder == QSO_BYPRIORITY)
153490792Sgshapiro			{
153590792Sgshapiro				if (Verbose)
153690792Sgshapiro					message("Skipping %s/%s (sequence %d of %d) and flushing rest of queue",
153790792Sgshapiro						qid_printqueue(w->w_qgrp,
153890792Sgshapiro							       w->w_qdir),
153990792Sgshapiro						w->w_name + 2, sequenceno,
154090792Sgshapiro						njobs);
154190792Sgshapiro				if (LogLevel > 8)
154290792Sgshapiro					sm_syslog(LOG_INFO, NOQID,
154390792Sgshapiro						  "runqueue: Flushing queue from %s/%s (pri %ld, LA %d, %d of %d)",
154490792Sgshapiro						  qid_printqueue(w->w_qgrp,
154590792Sgshapiro								 w->w_qdir),
154690792Sgshapiro						  w->w_name + 2, w->w_pri,
154790792Sgshapiro						  CurrentLA, sequenceno,
154890792Sgshapiro						  njobs);
154990792Sgshapiro				break;
155090792Sgshapiro			}
155190792Sgshapiro			else if (Verbose)
155290792Sgshapiro				message("Skipping %s/%s (sequence %d of %d)",
155390792Sgshapiro					qid_printqueue(w->w_qgrp, w->w_qdir),
155490792Sgshapiro					w->w_name + 2, sequenceno, njobs);
155590792Sgshapiro		}
155690792Sgshapiro		else
155790792Sgshapiro		{
155890792Sgshapiro			if (Verbose)
155990792Sgshapiro			{
156090792Sgshapiro				message(EmptyString);
156190792Sgshapiro				message("Running %s/%s (sequence %d of %d)",
156290792Sgshapiro					qid_printqueue(w->w_qgrp, w->w_qdir),
156390792Sgshapiro					w->w_name + 2, sequenceno, njobs);
156490792Sgshapiro			}
156590792Sgshapiro			if (didfork && MaxQueueChildren > 0)
156690792Sgshapiro			{
156790792Sgshapiro				sm_blocksignal(SIGCHLD);
156890792Sgshapiro				(void) sm_signal(SIGCHLD, reapchild);
156990792Sgshapiro			}
157090792Sgshapiro			if (tTd(63, 100))
157190792Sgshapiro				sm_syslog(LOG_DEBUG, NOQID,
157290792Sgshapiro					  "runqueue %s dowork(%s)",
157390792Sgshapiro					  qid_printqueue(w->w_qgrp, w->w_qdir),
157490792Sgshapiro					  w->w_name + 2);
157590792Sgshapiro
157690792Sgshapiro			(void) dowork(w->w_qgrp, w->w_qdir, w->w_name + 2,
157790792Sgshapiro				      false, false, e);
157890792Sgshapiro			errno = 0;
157990792Sgshapiro		}
158090792Sgshapiro		sm_free(w->w_name); /* XXX */
158190792Sgshapiro		if (w->w_host != NULL)
158290792Sgshapiro			sm_free(w->w_host); /* XXX */
158390792Sgshapiro		sm_free((char *) w); /* XXX */
158490792Sgshapiro		sequenceno += skip; /* next sequence number */
158590792Sgshapiro#if SM_HEAP_CHECK
158690792Sgshapiro		if (sm_debug_active(&DebugLeakQ, 1))
158790792Sgshapiro			sm_heap_setgroup(oldgroup);
158890792Sgshapiro#endif /* SM_HEAP_CHECK */
158990792Sgshapiro	}
159090792Sgshapiro
159190792Sgshapiro	BlockOldsh = false;
159290792Sgshapiro
159390792Sgshapiro	/* check the signals didn't happen during the revert */
159490792Sgshapiro	if (NoMoreRunners)
159590792Sgshapiro	{
159690792Sgshapiro		/* Check that a valid signal handler is callable */
159790792Sgshapiro		if (Oldsh != SIG_DFL && Oldsh != SIG_IGN &&
159890792Sgshapiro		    Oldsh != runners_sighup && Oldsh != runners_sigterm)
159990792Sgshapiro			(*Oldsh)(Oldsig);
160090792Sgshapiro	}
160190792Sgshapiro
160290792Sgshapiro	Oldsh = SIG_DFL; /* after the NoMoreRunners check */
160390792Sgshapiro}
160490792Sgshapiro/*
160590792Sgshapiro**  RUN_WORK_GROUP -- run the jobs in a queue group from a work group.
160690792Sgshapiro**
160764562Sgshapiro**	Gets the stuff out of the queue in some presumably logical
160864562Sgshapiro**	order and processes them.
160964562Sgshapiro**
161064562Sgshapiro**	Parameters:
161190792Sgshapiro**		wgrp -- work group to process.
161290792Sgshapiro**		forkflag -- true if the queue scanning should be done in
161364562Sgshapiro**			a child process.  We double-fork so it is not our
161464562Sgshapiro**			child and we don't have to clean up after it.
161590792Sgshapiro**		verbose -- if true, print out status information.
161690792Sgshapiro**		persistent -- persistent queue runner?
161790792Sgshapiro**		runall -- true: run all of the queue groups in this work group
161864562Sgshapiro**
161964562Sgshapiro**	Returns:
162090792Sgshapiro**		true if the queue run successfully began.
162164562Sgshapiro**
162264562Sgshapiro**	Side Effects:
162364562Sgshapiro**		runs things in the mail queue.
162464562Sgshapiro*/
162564562Sgshapiro
162690792Sgshapiro/* Minimum sleep time for persistent queue runners */
162790792Sgshapiro#define MIN_SLEEP_TIME	5
162890792Sgshapiro
162990792Sgshapirobool
163090792Sgshapirorun_work_group(wgrp, forkflag, verbose, persistent, runall)
163190792Sgshapiro	int wgrp;
163264562Sgshapiro	bool forkflag;
163364562Sgshapiro	bool verbose;
163490792Sgshapiro	bool persistent;
163590792Sgshapiro	bool runall;
163664562Sgshapiro{
163738032Speter	register ENVELOPE *e;
163890792Sgshapiro	int njobs, qdir;
163990792Sgshapiro	int sequenceno = 1;
164090792Sgshapiro	int qgrp, endgrp, h, i;
164190792Sgshapiro	time_t current_la_time;
164290792Sgshapiro	bool full, more;
164390792Sgshapiro	SM_RPOOL_T *rpool;
164490792Sgshapiro	extern void rmexpstab __P((void));
164538032Speter	extern ENVELOPE BlankEnvelope;
164690792Sgshapiro	extern SIGFUNC_DECL reapchild __P((int));
164738032Speter
164890792Sgshapiro	if (wgrp < 0)
164990792Sgshapiro		return false;
165090792Sgshapiro
165138032Speter	/*
165238032Speter	**  If no work will ever be selected, don't even bother reading
165338032Speter	**  the queue.
165438032Speter	*/
165538032Speter
165690792Sgshapiro	sm_getla();	/* get load average */
165738032Speter	current_la_time = curtime();
165838032Speter
165990792Sgshapiro	if (!persistent && shouldqueue(WkRecipFact, current_la_time))
166038032Speter	{
166138032Speter		char *msg = "Skipping queue run -- load average too high";
166238032Speter
166338032Speter		if (verbose)
166438032Speter			message("458 %s\n", msg);
166538032Speter		if (LogLevel > 8)
166690792Sgshapiro			sm_syslog(LOG_INFO, NOQID, "runqueue: %s", msg);
166790792Sgshapiro		return false;
166838032Speter	}
166938032Speter
167038032Speter	/*
167138032Speter	**  See if we already have too many children.
167238032Speter	*/
167338032Speter
167490792Sgshapiro	if (forkflag && WorkGrp[wgrp].wg_lowqintvl > 0 && !persistent &&
167538032Speter	    MaxChildren > 0 && CurChildren >= MaxChildren)
167638032Speter	{
167764562Sgshapiro		char *msg = "Skipping queue run -- too many children";
167864562Sgshapiro
167964562Sgshapiro		if (verbose)
168064562Sgshapiro			message("458 %s (%d)\n", msg, CurChildren);
168164562Sgshapiro		if (LogLevel > 8)
168290792Sgshapiro			sm_syslog(LOG_INFO, NOQID, "runqueue: %s (%d)",
168364562Sgshapiro				  msg, CurChildren);
168490792Sgshapiro		return false;
168538032Speter	}
168638032Speter
168738032Speter	/*
168838032Speter	**  See if we want to go off and do other useful work.
168938032Speter	*/
169038032Speter
169138032Speter	if (forkflag)
169238032Speter	{
169338032Speter		pid_t pid;
169438032Speter
169590792Sgshapiro		(void) sm_blocksignal(SIGCHLD);
169690792Sgshapiro		(void) sm_signal(SIGCHLD, reapchild);
169738032Speter
169838032Speter		pid = dofork();
169938032Speter		if (pid == -1)
170038032Speter		{
170138032Speter			const char *msg = "Skipping queue run -- fork() failed";
170290792Sgshapiro			const char *err = sm_errstring(errno);
170338032Speter
170438032Speter			if (verbose)
170538032Speter				message("458 %s: %s\n", msg, err);
170638032Speter			if (LogLevel > 8)
170790792Sgshapiro				sm_syslog(LOG_INFO, NOQID, "runqueue: %s: %s",
170864562Sgshapiro					  msg, err);
170990792Sgshapiro			(void) sm_releasesignal(SIGCHLD);
171090792Sgshapiro			return false;
171138032Speter		}
171238032Speter		if (pid != 0)
171338032Speter		{
171438032Speter			/* parent -- pick up intermediate zombie */
171590792Sgshapiro			(void) sm_blocksignal(SIGALRM);
171690792Sgshapiro
171790792Sgshapiro			/* wgrp only used when queue runners are persistent */
171890792Sgshapiro			proc_list_add(pid, "Queue runner", PROC_QUEUE,
171990792Sgshapiro				      WorkGrp[wgrp].wg_maxact,
172090792Sgshapiro				      persistent ? wgrp : -1);
172190792Sgshapiro			(void) sm_releasesignal(SIGALRM);
172290792Sgshapiro			(void) sm_releasesignal(SIGCHLD);
172390792Sgshapiro			return true;
172438032Speter		}
172590792Sgshapiro
172664562Sgshapiro		/* child -- clean up signals */
172777349Sgshapiro
172877349Sgshapiro		/* Reset global flags */
172977349Sgshapiro		RestartRequest = NULL;
173090792Sgshapiro		RestartWorkGroup = false;
173177349Sgshapiro		ShutdownRequest = NULL;
173277349Sgshapiro		PendingSignal = 0;
173390792Sgshapiro		CurrentPid = getpid();
173477349Sgshapiro
173590792Sgshapiro		/*
173690792Sgshapiro		**  Initialize exception stack and default exception
173790792Sgshapiro		**  handler for child process.
173890792Sgshapiro		*/
173990792Sgshapiro
174090792Sgshapiro		sm_exc_newthread(fatal_error);
174142575Speter		clrcontrol();
174238032Speter		proc_list_clear();
174342575Speter
174442575Speter		/* Add parent process as first child item */
174590792Sgshapiro		proc_list_add(CurrentPid, "Queue runner child process",
174690792Sgshapiro			      PROC_QUEUE_CHILD, 0, -1);
174790792Sgshapiro		(void) sm_releasesignal(SIGCHLD);
174890792Sgshapiro		(void) sm_signal(SIGCHLD, SIG_DFL);
174990792Sgshapiro		(void) sm_signal(SIGHUP, SIG_DFL);
175090792Sgshapiro		(void) sm_signal(SIGTERM, intsig);
175138032Speter	}
175238032Speter
175338032Speter	/*
175438032Speter	**  Release any resources used by the daemon code.
175538032Speter	*/
175638032Speter
175738032Speter	clrdaemon();
175838032Speter
175938032Speter	/* force it to run expensive jobs */
176090792Sgshapiro	NoConnect = false;
176138032Speter
176238032Speter	/* drop privileges */
176338032Speter	if (geteuid() == (uid_t) 0)
176490792Sgshapiro		(void) drop_privileges(false);
176538032Speter
176638032Speter	/*
176738032Speter	**  Create ourselves an envelope
176838032Speter	*/
176938032Speter
177038032Speter	CurEnv = &QueueEnvelope;
177190792Sgshapiro	rpool = sm_rpool_new_x(NULL);
177290792Sgshapiro	e = newenvelope(&QueueEnvelope, CurEnv, rpool);
177338032Speter	e->e_flags = BlankEnvelope.e_flags;
177473188Sgshapiro	e->e_parent = NULL;
177538032Speter
177638032Speter	/* make sure we have disconnected from parent */
177738032Speter	if (forkflag)
177838032Speter	{
177938032Speter		disconnect(1, e);
178090792Sgshapiro		QuickAbort = false;
178138032Speter	}
178238032Speter
178338032Speter	/*
178438032Speter	**  If we are running part of the queue, always ignore stored
178538032Speter	**  host status.
178638032Speter	*/
178738032Speter
178838032Speter	if (QueueLimitId != NULL || QueueLimitSender != NULL ||
178990792Sgshapiro#if _FFR_QUARANTINE
179090792Sgshapiro	    QueueLimitQuarantine != NULL ||
179190792Sgshapiro#endif /* _FFR_QUARANTINE */
179238032Speter	    QueueLimitRecipient != NULL)
179338032Speter	{
179490792Sgshapiro		IgnoreHostStatus = true;
179538032Speter		MinQueueAge = 0;
179638032Speter	}
179738032Speter
179838032Speter	/*
179990792Sgshapiro	**  Here is where we choose the queue group from the work group.
180090792Sgshapiro	**  The caller of the "domorework" label must setup a new envelope.
180190792Sgshapiro	*/
180290792Sgshapiro
180390792Sgshapiro	endgrp = WorkGrp[wgrp].wg_curqgrp; /* to not spin endlessly */
180490792Sgshapiro
180590792Sgshapiro  domorework:
180690792Sgshapiro
180790792Sgshapiro	/*
180890792Sgshapiro	**  Run a queue group if:
180990792Sgshapiro	**  runall is set or the bit for this group is set.
181090792Sgshapiro	*/
181190792Sgshapiro
181290792Sgshapiro	for (;;)
181390792Sgshapiro	{
181490792Sgshapiro		/*
181590792Sgshapiro		**  Find the next queue group within the work group that
181690792Sgshapiro		**  has been marked as needing a run.
181790792Sgshapiro		*/
181890792Sgshapiro
181990792Sgshapiro		qgrp = WorkGrp[wgrp].wg_qgs[WorkGrp[wgrp].wg_curqgrp]->qg_index;
182090792Sgshapiro		WorkGrp[wgrp].wg_curqgrp++; /* advance */
182190792Sgshapiro		WorkGrp[wgrp].wg_curqgrp %= WorkGrp[wgrp].wg_numqgrp; /* wrap */
182290792Sgshapiro		if (runall || bitnset(qgrp, DoQueueRun))
182390792Sgshapiro			break;
182490792Sgshapiro		if (endgrp == WorkGrp[wgrp].wg_curqgrp)
182590792Sgshapiro		{
182690792Sgshapiro			e->e_id = NULL;
182790792Sgshapiro			if (forkflag)
182890792Sgshapiro				finis(true, true, ExitStat);
182990792Sgshapiro			return true; /* we're done */
183090792Sgshapiro		}
183190792Sgshapiro	}
183290792Sgshapiro
183390792Sgshapiro	qdir = Queue[qgrp]->qg_curnum; /* round-robin init of queue position */
183490792Sgshapiro#if _FFR_QUEUE_SCHED_DBG
183590792Sgshapiro	if (tTd(69, 12))
183690792Sgshapiro		sm_syslog(LOG_INFO, NOQID,
183790792Sgshapiro			"rwg: wgrp=%d, qgrp=%d, qdir=%d, name=%s, curqgrp=%d, numgrps=%d",
183890792Sgshapiro			wgrp, qgrp, qdir, qid_printqueue(qgrp, qdir),
183990792Sgshapiro			WorkGrp[wgrp].wg_curqgrp, WorkGrp[wgrp].wg_numqgrp);
184090792Sgshapiro#endif /* _FFR_QUEUE_SCHED_DBG */
184190792Sgshapiro
184290792Sgshapiro#if HASNICE
184390792Sgshapiro	/* tweak niceness of queue runs */
184490792Sgshapiro	if (Queue[qgrp]->qg_nice > 0)
184590792Sgshapiro		(void) nice(Queue[qgrp]->qg_nice);
184690792Sgshapiro#endif /* HASNICE */
184790792Sgshapiro
184890792Sgshapiro	/* XXX running queue group... */
184990792Sgshapiro	sm_setproctitle(true, CurEnv, "running queue: %s",
185090792Sgshapiro			qid_printqueue(qgrp, qdir));
185190792Sgshapiro
185290792Sgshapiro	if (LogLevel > 69 || tTd(63, 99))
185390792Sgshapiro		sm_syslog(LOG_DEBUG, NOQID,
185490792Sgshapiro			  "runqueue %s, pid=%d, forkflag=%d",
185590792Sgshapiro			  qid_printqueue(qgrp, qdir), (int) CurrentPid,
185690792Sgshapiro			  forkflag);
185790792Sgshapiro
185890792Sgshapiro	/*
185938032Speter	**  Start making passes through the queue.
186038032Speter	**	First, read and sort the entire queue.
186138032Speter	**	Then, process the work in that order.
186238032Speter	**		But if you take too long, start over.
186338032Speter	*/
186438032Speter
186590792Sgshapiro	for (i = 0; i < Queue[qgrp]->qg_numqueues; i++)
186690792Sgshapiro	{
186790792Sgshapiro		h = gatherq(qgrp, qdir, false, &full, &more);
186890792Sgshapiro#if SM_CONF_SHM
186990792Sgshapiro		if (ShmId != SM_SHM_NO_ID)
187090792Sgshapiro			QSHM_ENTRIES(Queue[qgrp]->qg_qpaths[qdir].qp_idx) = h;
187190792Sgshapiro#endif /* SM_CONF_SHM */
187290792Sgshapiro		/* If there are no more items in this queue advance */
187390792Sgshapiro		if (!more)
187490792Sgshapiro		{
187590792Sgshapiro			/* A round-robin advance */
187690792Sgshapiro			qdir++;
187790792Sgshapiro			qdir %= Queue[qgrp]->qg_numqueues;
187890792Sgshapiro		}
187990792Sgshapiro
188090792Sgshapiro		/* Has the WorkList reached the limit? */
188190792Sgshapiro		if (full)
188290792Sgshapiro			break; /* don't try to gather more */
188390792Sgshapiro	}
188490792Sgshapiro
188538032Speter	/* order the existing work requests */
188690792Sgshapiro	njobs = sortq(Queue[qgrp]->qg_maxlist);
188790792Sgshapiro	Queue[qgrp]->qg_curnum = qdir; /* update */
188838032Speter
188964562Sgshapiro
189090792Sgshapiro	if (!Verbose && bitnset(QD_FORK, Queue[qgrp]->qg_flags))
189138032Speter	{
189290792Sgshapiro		int loop, maxrunners;
189390792Sgshapiro		pid_t pid;
189438032Speter
189538032Speter		/*
189690792Sgshapiro		**  For this WorkQ we want to fork off N children (maxrunners)
189790792Sgshapiro		**  at this point. Each child has a copy of WorkQ. Each child
189890792Sgshapiro		**  will process every N-th item. The parent will wait for all
189990792Sgshapiro		**  of the children to finish before moving on to the next
190090792Sgshapiro		**  queue group within the work group. This saves us forking
190190792Sgshapiro		**  a new runner-child for each work item.
190290792Sgshapiro		**  It's valid for qg_maxqrun == 0 since this may be an
190390792Sgshapiro		**  explicit "don't run this queue" setting.
190438032Speter		*/
190538032Speter
190690792Sgshapiro		maxrunners = Queue[qgrp]->qg_maxqrun;
190790792Sgshapiro
190890792Sgshapiro		/* No need to have more runners then there are jobs */
190990792Sgshapiro		if (maxrunners > njobs)
191090792Sgshapiro			maxrunners = njobs;
191190792Sgshapiro		for (loop = 0; loop < maxrunners; loop++)
191238032Speter		{
191390792Sgshapiro			/*
191490792Sgshapiro			**  Since the delivery may happen in a child and the
191590792Sgshapiro			**  parent does not wait, the parent may close the
191690792Sgshapiro			**  maps thereby removing any shared memory used by
191790792Sgshapiro			**  the map.  Therefore, close the maps now so the
191890792Sgshapiro			**  child will dynamically open them if necessary.
191990792Sgshapiro			*/
192090792Sgshapiro
192190792Sgshapiro			closemaps(false);
192290792Sgshapiro
192390792Sgshapiro			pid = fork();
192490792Sgshapiro			if (pid < 0)
192590792Sgshapiro			{
192690792Sgshapiro				syserr("run_work_group: cannot fork");
192790792Sgshapiro				return 0;
192890792Sgshapiro			}
192990792Sgshapiro			else if (pid > 0)
193090792Sgshapiro			{
193190792Sgshapiro				/* parent -- clean out connection cache */
193290792Sgshapiro				mci_flush(false, NULL);
193390792Sgshapiro				WorkQ = WorkQ->w_next; /* for the skip */
193490792Sgshapiro				sequenceno++;
193590792Sgshapiro				proc_list_add(pid, "Queue child runner process",
193690792Sgshapiro					      PROC_QUEUE_CHILD, 0, -1);
193790792Sgshapiro
193890792Sgshapiro				/* No additional work, no additional runners */
193990792Sgshapiro				if (WorkQ == NULL)
194090792Sgshapiro					break;
194190792Sgshapiro			}
194290792Sgshapiro			else
194390792Sgshapiro			{
194490792Sgshapiro				/* child -- Reset global flags */
194590792Sgshapiro				RestartRequest = NULL;
194690792Sgshapiro				RestartWorkGroup = false;
194790792Sgshapiro				ShutdownRequest = NULL;
194890792Sgshapiro				PendingSignal = 0;
194990792Sgshapiro				CurrentPid = getpid();
195090792Sgshapiro
195190792Sgshapiro				/*
195290792Sgshapiro				**  Initialize exception stack and default
195390792Sgshapiro				**  exception handler for child process.
195490792Sgshapiro				**  When fork()'d the child now has a private
195590792Sgshapiro				**  copy of WorkQ at its current position.
195690792Sgshapiro				*/
195790792Sgshapiro
195890792Sgshapiro				sm_exc_newthread(fatal_error);
195990792Sgshapiro
196090792Sgshapiro				/*
196190792Sgshapiro				**  SMTP processes (whether -bd or -bs) set
196290792Sgshapiro				**  SIGCHLD to reapchild to collect
196390792Sgshapiro				**  children status.  However, at delivery
196490792Sgshapiro				**  time, that status must be collected
196590792Sgshapiro				**  by sm_wait() to be dealt with properly
196690792Sgshapiro				**  (check success of delivery based
196790792Sgshapiro				**  on status code, etc).  Therefore, if we
196890792Sgshapiro				**  are an SMTP process, reset SIGCHLD
196990792Sgshapiro				**  back to the default so reapchild
197090792Sgshapiro				**  doesn't collect status before
197190792Sgshapiro				**  sm_wait().
197290792Sgshapiro				*/
197390792Sgshapiro
197490792Sgshapiro				if (OpMode == MD_SMTP ||
197590792Sgshapiro				    OpMode == MD_DAEMON ||
197690792Sgshapiro				    MaxQueueChildren > 0)
197790792Sgshapiro				{
197890792Sgshapiro					proc_list_clear();
197990792Sgshapiro					sm_releasesignal(SIGCHLD);
198090792Sgshapiro					(void) sm_signal(SIGCHLD, SIG_DFL);
198190792Sgshapiro				}
198290792Sgshapiro
198390792Sgshapiro				/* child -- error messages to the transcript */
198490792Sgshapiro				QuickAbort = OnlyOneError = false;
198590792Sgshapiro				runner_work(e, sequenceno, true,
198690792Sgshapiro					    maxrunners, njobs);
198790792Sgshapiro
198890792Sgshapiro				/* This child is done */
198990792Sgshapiro				finis(true, true, ExitStat);
199090792Sgshapiro				/* NOTREACHED */
199190792Sgshapiro			}
199238032Speter		}
199390792Sgshapiro
199490792Sgshapiro		sm_releasesignal(SIGCHLD);
199590792Sgshapiro
199690792Sgshapiro		/*
199790792Sgshapiro		**  Wait until all of the runners have completed before
199890792Sgshapiro		**  seeing if there is another queue group in the
199990792Sgshapiro		**  work group to process.
200090792Sgshapiro		**  XXX Future enhancement: don't wait() for all children
200190792Sgshapiro		**  here, just go ahead and make sure that overall the number
200290792Sgshapiro		**  of children is not exceeded.
200390792Sgshapiro		*/
200490792Sgshapiro
200590792Sgshapiro		while (CurChildren > 0)
200638032Speter		{
200790792Sgshapiro			int status;
200890792Sgshapiro			pid_t ret;
200938032Speter
201090792Sgshapiro			while ((ret = sm_wait(&status)) <= 0)
201190792Sgshapiro				continue;
201290792Sgshapiro			proc_list_drop(ret, status, NULL);
201338032Speter		}
201490792Sgshapiro	}
201590792Sgshapiro	else
201690792Sgshapiro	{
201790792Sgshapiro		/*
201890792Sgshapiro		**  When current process will not fork children to do the work,
201990792Sgshapiro		**  it will do the work itself. The 'skip' will be 1 since
202090792Sgshapiro		**  there are no child runners to divide the work across.
202190792Sgshapiro		*/
202290792Sgshapiro
202390792Sgshapiro		runner_work(e, sequenceno, false, 1, njobs);
202490792Sgshapiro	}
202590792Sgshapiro
202690792Sgshapiro	/* free memory allocated by newenvelope() above */
202790792Sgshapiro	sm_rpool_free(rpool);
202890792Sgshapiro	QueueEnvelope.e_rpool = NULL;
202990792Sgshapiro
203090792Sgshapiro	/* Are there still more queues in the work group to process? */
203190792Sgshapiro	if (endgrp != WorkGrp[wgrp].wg_curqgrp)
203290792Sgshapiro	{
203390792Sgshapiro		rpool = sm_rpool_new_x(NULL);
203490792Sgshapiro		e = newenvelope(&QueueEnvelope, CurEnv, rpool);
203590792Sgshapiro		e->e_flags = BlankEnvelope.e_flags;
203690792Sgshapiro		goto domorework;
203790792Sgshapiro	}
203890792Sgshapiro
203990792Sgshapiro	/* No more queues in work group to process. Now check persistent. */
204090792Sgshapiro	if (persistent)
204190792Sgshapiro	{
204290792Sgshapiro		time_t now;
204390792Sgshapiro
204490792Sgshapiro		sequenceno = 1;
204590792Sgshapiro		sm_setproctitle(true, CurEnv, "running queue: %s",
204690792Sgshapiro				qid_printqueue(qgrp, qdir));
204790792Sgshapiro
204890792Sgshapiro		/*
204990792Sgshapiro		**  close bogus maps, i.e., maps which caused a tempfail,
205090792Sgshapiro		**	so we get fresh map connections on the next lookup.
205190792Sgshapiro		**  closemaps() is also called when children are started.
205290792Sgshapiro		*/
205390792Sgshapiro
205490792Sgshapiro		closemaps(true);
205590792Sgshapiro
205690792Sgshapiro		/* Close any cached connections. */
205790792Sgshapiro		mci_flush(true, NULL);
205890792Sgshapiro
205990792Sgshapiro		/* Clean out expired related entries. */
206090792Sgshapiro		rmexpstab();
206190792Sgshapiro
206290792Sgshapiro#if NAMED_BIND
206390792Sgshapiro		/* Update MX records for FallBackMX. */
206490792Sgshapiro		if (FallBackMX != NULL)
206590792Sgshapiro			(void) getfallbackmxrr(FallBackMX);
206690792Sgshapiro#endif /* NAMED_BIND */
206790792Sgshapiro
206890792Sgshapiro#if USERDB
206990792Sgshapiro		/* close UserDatabase */
207090792Sgshapiro		_udbx_close();
207190792Sgshapiro#endif /* USERDB */
207290792Sgshapiro
207390792Sgshapiro#if SM_HEAP_CHECK
207490792Sgshapiro		if (sm_debug_active(&SmHeapCheck, 2)
207590792Sgshapiro		    && access("memdump", F_OK) == 0
207690792Sgshapiro		   )
207738032Speter		{
207890792Sgshapiro			SM_FILE_T *out;
207990792Sgshapiro
208090792Sgshapiro			remove("memdump");
208190792Sgshapiro			out = sm_io_open(SmFtStdio, SM_TIME_DEFAULT,
208290792Sgshapiro					 "memdump.out", SM_IO_APPEND, NULL);
208390792Sgshapiro			if (out != NULL)
208438032Speter			{
208590792Sgshapiro				(void) sm_io_fprintf(out, SM_TIME_DEFAULT, "----------------------\n");
208690792Sgshapiro				sm_heap_report(out,
208790792Sgshapiro					sm_debug_level(&SmHeapCheck) - 1);
208890792Sgshapiro				(void) sm_io_close(out, SM_TIME_DEFAULT);
208938032Speter			}
209038032Speter		}
209190792Sgshapiro#endif /* SM_HEAP_CHECK */
209290792Sgshapiro
209390792Sgshapiro		/* let me rest for a second to catch my breath */
209490792Sgshapiro		if (njobs == 0 && WorkGrp[wgrp].wg_lowqintvl < MIN_SLEEP_TIME)
209590792Sgshapiro			sleep(MIN_SLEEP_TIME);
209690792Sgshapiro		else if (WorkGrp[wgrp].wg_lowqintvl <= 0)
209790792Sgshapiro			sleep(QueueIntvl > 0 ? QueueIntvl : MIN_SLEEP_TIME);
209838032Speter		else
209990792Sgshapiro			sleep(WorkGrp[wgrp].wg_lowqintvl);
210038032Speter
210190792Sgshapiro		/*
210290792Sgshapiro		**  Get the LA outside the WorkQ loop if necessary.
210390792Sgshapiro		**  In a persistent queue runner the code is repeated over
210490792Sgshapiro		**  and over but gatherq() may ignore entries due to
210590792Sgshapiro		**  shouldqueue() (do we really have to do this twice?).
210690792Sgshapiro		**  Hence the queue runners would just idle around when once
210790792Sgshapiro		**  CurrentLA caused all entries in a queue to be ignored.
210890792Sgshapiro		*/
210964562Sgshapiro
211090792Sgshapiro		now = curtime();
211190792Sgshapiro		if (njobs == 0 && current_la_time < now - GET_NEW_LA_TIME)
211290792Sgshapiro		{
211390792Sgshapiro			sm_getla();
211490792Sgshapiro			current_la_time = now;
211538032Speter		}
211690792Sgshapiro		rpool = sm_rpool_new_x(NULL);
211790792Sgshapiro		e = newenvelope(&QueueEnvelope, CurEnv, rpool);
211890792Sgshapiro		e->e_flags = BlankEnvelope.e_flags;
211990792Sgshapiro		goto domorework;
212038032Speter	}
212138032Speter
212238032Speter	/* exit without the usual cleanup */
212338032Speter	e->e_id = NULL;
212464562Sgshapiro	if (forkflag)
212590792Sgshapiro		finis(true, true, ExitStat);
212664562Sgshapiro	/* NOTREACHED */
212790792Sgshapiro	return true;
212838032Speter}
212938032Speter
213038032Speter/*
213190792Sgshapiro**  DOQUEUERUN -- do a queue run?
213290792Sgshapiro*/
213390792Sgshapiro
213490792Sgshapirobool
213590792Sgshapirodoqueuerun()
213690792Sgshapiro{
213790792Sgshapiro	return bitnset(NumQueue, DoQueueRun);
213890792Sgshapiro}
213990792Sgshapiro
214090792Sgshapiro/*
214190792Sgshapiro**  RUNQUEUEEVENT -- stub for use in sm_setevent
214277349Sgshapiro**
214390792Sgshapiro**  Sets the bit to indicate that on the next run this queue should be
214490792Sgshapiro**  processed. The work group that the queue group is a member of has its
214590792Sgshapiro**  count of queue's to process updated.
214690792Sgshapiro**
214777349Sgshapiro**	Parameters:
214890792Sgshapiro**		qgrp -- the index of the queue group.
214977349Sgshapiro**
215077349Sgshapiro**	Returns:
215177349Sgshapiro**		none.
215277349Sgshapiro**
215390792Sgshapiro**	Side Effects:
215490792Sgshapiro**		The work group that the queue group is a member of has its
215590792Sgshapiro**		count of queues to process updated.
215690792Sgshapiro**		The invocation of this function via an alarm may interrupt
215790792Sgshapiro**		a set of actions. Thus errno may be set in that context.
215890792Sgshapiro**		We need to restore errno at the end of this function to ensure
215990792Sgshapiro**		that any work done here that sets errno doesn't return a
216090792Sgshapiro**		misleading/false errno value. Errno may	be EINTR upon entry to
216190792Sgshapiro**		this function because of non-restartable/continuable system
216290792Sgshapiro**		API was active. Iff this is true we will override errno as
216390792Sgshapiro**		a timeout (as a more accurate error message).
216490792Sgshapiro**
216577349Sgshapiro**	NOTE:	THIS CAN BE CALLED FROM A SIGNAL HANDLER.  DO NOT ADD
216677349Sgshapiro**		ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
216777349Sgshapiro**		DOING.
216838032Speter*/
216938032Speter
217090792Sgshapirovoid
217190792Sgshapirorunqueueevent(qgrp)
217290792Sgshapiro	int qgrp;
217338032Speter{
217490792Sgshapiro	int i;
217590792Sgshapiro	int save_errno = errno;
217690792Sgshapiro
217790792Sgshapiro	/*
217890792Sgshapiro	**  Set the general bit that we want a queue run,
217990792Sgshapiro	**  tested in doqueuerun()
218090792Sgshapiro	*/
218190792Sgshapiro
218290792Sgshapiro	setbitn(NumQueue, DoQueueRun);
218390792Sgshapiro
218490792Sgshapiro	/* if it is a specific group: set that bit */
218590792Sgshapiro	if (qgrp != NOQGRP)
218690792Sgshapiro	{
218790792Sgshapiro		setbitn(qgrp, DoQueueRun);
218890792Sgshapiro		goto ret;
218990792Sgshapiro	}
219090792Sgshapiro
219190792Sgshapiro	/* for all others: set the bit if it doesn't have a queue interval */
219290792Sgshapiro	for (i = 0; i < NumQueue; i++)
219390792Sgshapiro	{
219490792Sgshapiro		if (Queue[i]->qg_queueintvl <= 0)
219590792Sgshapiro			setbitn(i, DoQueueRun);
219690792Sgshapiro	}
219790792Sgshapiro
219890792Sgshapiro  ret:
219990792Sgshapiro	errno = save_errno;
220090792Sgshapiro	if (errno == EINTR)
220190792Sgshapiro		errno = ETIMEDOUT;
220238032Speter}
220390792Sgshapiro/*
220490792Sgshapiro**  GATHERQ -- gather messages from the message queue(s) the work queue.
220538032Speter**
220638032Speter**	Parameters:
220790792Sgshapiro**		qgrp -- the index of the queue group.
220890792Sgshapiro**		qdir -- the index of the queue directory.
220938032Speter**		doall -- if set, include everything in the queue (even
221038032Speter**			the jobs that cannot be run because the load
221190792Sgshapiro**			average is too high, or MaxQueueRun is reached).
221290792Sgshapiro**			Otherwise, exclude those jobs.
221390792Sgshapiro**		full -- (optional) to be set 'true' if WorkList is full
221490792Sgshapiro**		more -- (optional) to be set 'true' if there are still more
221590792Sgshapiro**			messages in this queue not added to WorkList
221638032Speter**
221738032Speter**	Returns:
221838032Speter**		The number of request in the queue (not necessarily
221990792Sgshapiro**		the number of requests in WorkList however).
222038032Speter**
222138032Speter**	Side Effects:
222290792Sgshapiro**		prepares available work into WorkList
222338032Speter*/
222438032Speter
222590792Sgshapiro#define NEED_P		0001	/* 'P': priority */
222690792Sgshapiro#define NEED_T		0002	/* 'T': time */
222790792Sgshapiro#define NEED_R		0004	/* 'R': recipient */
222890792Sgshapiro#define NEED_S		0010	/* 'S': sender */
222990792Sgshapiro#define NEED_H		0020	/* host */
223090792Sgshapiro#if _FFR_QUARANTINE
223190792Sgshapiro# define HAS_QUARANTINE		0040	/* has an unexpected 'q' line */
223290792Sgshapiro# define NEED_QUARANTINE	0100	/* 'q': reason */
223390792Sgshapiro#endif /* _FFR_QUARANTINE */
223438032Speter
223590792Sgshapirostatic WORK	*WorkList = NULL;	/* list of unsort work */
223690792Sgshapirostatic int	WorkListSize = 0;	/* current max size of WorkList */
223790792Sgshapirostatic int	WorkListCount = 0;	/* # of work items in WorkList */
223838032Speter
223964562Sgshapirostatic int
224090792Sgshapirogatherq(qgrp, qdir, doall, full, more)
224190792Sgshapiro	int qgrp;
224290792Sgshapiro	int qdir;
224338032Speter	bool doall;
224490792Sgshapiro	bool *full;
224590792Sgshapiro	bool *more;
224638032Speter{
224738032Speter	register struct dirent *d;
224838032Speter	register WORK *w;
224938032Speter	register char *p;
225038032Speter	DIR *f;
225190792Sgshapiro	int i, num_ent;
225290792Sgshapiro	int wn;
225338032Speter	QUEUE_CHAR *check;
225464562Sgshapiro	char qd[MAXPATHLEN];
225564562Sgshapiro	char qf[MAXPATHLEN];
225664562Sgshapiro
225790792Sgshapiro	wn = WorkListCount - 1;
225890792Sgshapiro	num_ent = 0;
225990792Sgshapiro	if (qdir == NOQDIR)
226090792Sgshapiro		(void) sm_strlcpy(qd, ".", sizeof qd);
226164562Sgshapiro	else
226290792Sgshapiro		(void) sm_strlcpyn(qd, sizeof qd, 2,
226390792Sgshapiro			Queue[qgrp]->qg_qpaths[qdir].qp_name,
226490792Sgshapiro			(bitset(QP_SUBQF,
226590792Sgshapiro				Queue[qgrp]->qg_qpaths[qdir].qp_subdirs)
226690792Sgshapiro					? "/qf" : ""));
226764562Sgshapiro
226838032Speter	if (tTd(41, 1))
226938032Speter	{
227090792Sgshapiro		sm_dprintf("gatherq:\n");
227138032Speter
227238032Speter		check = QueueLimitId;
227338032Speter		while (check != NULL)
227438032Speter		{
227590792Sgshapiro			sm_dprintf("\tQueueLimitId = %s%s\n",
227690792Sgshapiro				check->queue_negate ? "!" : "",
227764562Sgshapiro				check->queue_match);
227838032Speter			check = check->queue_next;
227938032Speter		}
228038032Speter
228138032Speter		check = QueueLimitSender;
228238032Speter		while (check != NULL)
228338032Speter		{
228490792Sgshapiro			sm_dprintf("\tQueueLimitSender = %s%s\n",
228590792Sgshapiro				check->queue_negate ? "!" : "",
228664562Sgshapiro				check->queue_match);
228738032Speter			check = check->queue_next;
228838032Speter		}
228938032Speter
229038032Speter		check = QueueLimitRecipient;
229138032Speter		while (check != NULL)
229238032Speter		{
229390792Sgshapiro			sm_dprintf("\tQueueLimitRecipient = %s%s\n",
229490792Sgshapiro				check->queue_negate ? "!" : "",
229564562Sgshapiro				check->queue_match);
229638032Speter			check = check->queue_next;
229738032Speter		}
229838032Speter
229990792Sgshapiro#if _FFR_QUARANTINE
230090792Sgshapiro		if (QueueMode == QM_QUARANTINE)
230190792Sgshapiro		{
230290792Sgshapiro			check = QueueLimitQuarantine;
230390792Sgshapiro			while (check != NULL)
230490792Sgshapiro			{
230590792Sgshapiro				sm_dprintf("\tQueueLimitQuarantine = %s%s\n",
230690792Sgshapiro					   check->queue_negate ? "!" : "",
230790792Sgshapiro					   check->queue_match);
230890792Sgshapiro				check = check->queue_next;
230990792Sgshapiro			}
231090792Sgshapiro		}
231190792Sgshapiro#endif /* _FFR_QUARANTINE */
231238032Speter	}
231338032Speter
231438032Speter	/* open the queue directory */
231564562Sgshapiro	f = opendir(qd);
231638032Speter	if (f == NULL)
231738032Speter	{
231890792Sgshapiro		syserr("gatherq: cannot open \"%s\"",
231990792Sgshapiro			qid_printqueue(qgrp, qdir));
232090792Sgshapiro		if (full != NULL)
232190792Sgshapiro			*full = WorkListCount >= MaxQueueRun && MaxQueueRun > 0;
232290792Sgshapiro		if (more != NULL)
232390792Sgshapiro			*more = false;
232464562Sgshapiro		return 0;
232538032Speter	}
232638032Speter
232738032Speter	/*
232838032Speter	**  Read the work directory.
232938032Speter	*/
233038032Speter
233138032Speter	while ((d = readdir(f)) != NULL)
233238032Speter	{
233390792Sgshapiro		SM_FILE_T *cf;
233438032Speter		int qfver = 0;
233538032Speter		char lbuf[MAXNAME + 1];
233664562Sgshapiro		struct stat sbuf;
233738032Speter
233838032Speter		if (tTd(41, 50))
233990792Sgshapiro			sm_dprintf("gatherq: checking %s..", d->d_name);
234038032Speter
234138032Speter		/* is this an interesting entry? */
234290792Sgshapiro#if _FFR_QUARANTINE
234390792Sgshapiro		if (!(((QueueMode == QM_NORMAL &&
234490792Sgshapiro			d->d_name[0] == NORMQF_LETTER) ||
234590792Sgshapiro		       (QueueMode == QM_QUARANTINE &&
234690792Sgshapiro			d->d_name[0] == QUARQF_LETTER) ||
234790792Sgshapiro		       (QueueMode == QM_LOST &&
234890792Sgshapiro			d->d_name[0] == LOSEQF_LETTER)) &&
234990792Sgshapiro		      d->d_name[1] == 'f'))
235090792Sgshapiro#else /* _FFR_QUARANTINE */
235190792Sgshapiro		if (d->d_name[0] != NORMQF_LETTER || d->d_name[1] != 'f')
235290792Sgshapiro#endif /* _FFR_QUARANTINE */
235390792Sgshapiro		{
235490792Sgshapiro			if (tTd(41, 50))
235590792Sgshapiro				sm_dprintf("  skipping\n");
235638032Speter			continue;
235790792Sgshapiro		}
235890792Sgshapiro		if (tTd(41, 50))
235990792Sgshapiro			sm_dprintf("\n");
236038032Speter
236164562Sgshapiro		if (strlen(d->d_name) >= MAXQFNAME)
236242575Speter		{
236342575Speter			if (Verbose)
236490792Sgshapiro				(void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
236590792Sgshapiro						     "gatherq: %s too long, %d max characters\n",
236690792Sgshapiro						     d->d_name, MAXQFNAME);
236742575Speter			if (LogLevel > 0)
236842575Speter				sm_syslog(LOG_ALERT, NOQID,
236990792Sgshapiro					  "gatherq: %s too long, %d max characters",
237064562Sgshapiro					  d->d_name, MAXQFNAME);
237138032Speter			continue;
237242575Speter		}
237338032Speter
237438032Speter		check = QueueLimitId;
237538032Speter		while (check != NULL)
237638032Speter		{
237790792Sgshapiro			if (strcontainedin(true, check->queue_match,
237890792Sgshapiro					   d->d_name) != check->queue_negate)
237938032Speter				break;
238038032Speter			else
238138032Speter				check = check->queue_next;
238238032Speter		}
238338032Speter		if (QueueLimitId != NULL && check == NULL)
238438032Speter			continue;
238538032Speter
238664562Sgshapiro		/* grow work list if necessary */
238738032Speter		if (++wn >= MaxQueueRun && MaxQueueRun > 0)
238838032Speter		{
238938032Speter			if (wn == MaxQueueRun && LogLevel > 0)
239064562Sgshapiro				sm_syslog(LOG_WARNING, NOQID,
239164562Sgshapiro					  "WorkList for %s maxed out at %d",
239290792Sgshapiro					  qid_printqueue(qgrp, qdir),
239364562Sgshapiro					  MaxQueueRun);
239490792Sgshapiro			if (doall)
239590792Sgshapiro				continue;	/* just count entries */
239690792Sgshapiro			break;
239738032Speter		}
239838032Speter		if (wn >= WorkListSize)
239938032Speter		{
240090792Sgshapiro			grow_wlist(qgrp, qdir);
240138032Speter			if (wn >= WorkListSize)
240238032Speter				continue;
240338032Speter		}
240490792Sgshapiro		SM_ASSERT(wn >= 0);
240564562Sgshapiro		w = &WorkList[wn];
240638032Speter
240790792Sgshapiro		(void) sm_strlcpyn(qf, sizeof qf, 3, qd, "/", d->d_name);
240864562Sgshapiro		if (stat(qf, &sbuf) < 0)
240964562Sgshapiro		{
241064562Sgshapiro			if (errno != ENOENT)
241164562Sgshapiro				sm_syslog(LOG_INFO, NOQID,
241290792Sgshapiro					  "gatherq: can't stat %s/%s",
241390792Sgshapiro					  qid_printqueue(qgrp, qdir),
241490792Sgshapiro					  d->d_name);
241564562Sgshapiro			wn--;
241664562Sgshapiro			continue;
241764562Sgshapiro		}
241864562Sgshapiro		if (!bitset(S_IFREG, sbuf.st_mode))
241964562Sgshapiro		{
242064562Sgshapiro			/* Yikes!  Skip it or we will hang on open! */
242190792Sgshapiro			if (!((d->d_name[0] == DATAFL_LETTER ||
242290792Sgshapiro			       d->d_name[0] == NORMQF_LETTER ||
242390792Sgshapiro#if _FFR_QUARANTINE
242490792Sgshapiro			       d->d_name[0] == QUARQF_LETTER ||
242590792Sgshapiro			       d->d_name[0] == LOSEQF_LETTER ||
242690792Sgshapiro#endif /* _FFR_QUARANTINE */
242790792Sgshapiro			       d->d_name[0] == XSCRPT_LETTER) &&
242890792Sgshapiro			      d->d_name[1] == 'f' && d->d_name[2] == '\0'))
242990792Sgshapiro				syserr("gatherq: %s/%s is not a regular file",
243090792Sgshapiro				       qid_printqueue(qgrp, qdir), d->d_name);
243164562Sgshapiro			wn--;
243264562Sgshapiro			continue;
243364562Sgshapiro		}
243464562Sgshapiro
243564562Sgshapiro		/* avoid work if possible */
243690792Sgshapiro		if ((QueueSortOrder == QSO_BYFILENAME ||
243790792Sgshapiro		     QueueSortOrder == QSO_BYMODTIME ||
243890792Sgshapiro		     QueueSortOrder == QSO_RANDOM) &&
243990792Sgshapiro#if _FFR_QUARANTINE
244090792Sgshapiro		    QueueLimitQuarantine == NULL &&
244190792Sgshapiro#endif /* _FFR_QUARANTINE */
244266494Sgshapiro		    QueueLimitSender == NULL &&
244366494Sgshapiro		    QueueLimitRecipient == NULL)
244464562Sgshapiro		{
244590792Sgshapiro			w->w_qgrp = qgrp;
244690792Sgshapiro			w->w_qdir = qdir;
244764562Sgshapiro			w->w_name = newstr(d->d_name);
244864562Sgshapiro			w->w_host = NULL;
244990792Sgshapiro			w->w_lock = w->w_tooyoung = false;
245064562Sgshapiro			w->w_pri = 0;
245164562Sgshapiro			w->w_ctime = 0;
245290792Sgshapiro			w->w_mtime = sbuf.st_mtime;
245390792Sgshapiro			++num_ent;
245464562Sgshapiro			continue;
245564562Sgshapiro		}
245664562Sgshapiro
245764562Sgshapiro		/* open control file */
245890792Sgshapiro		cf = sm_io_open(SmFtStdio, SM_TIME_DEFAULT, qf, SM_IO_RDONLY,
245990792Sgshapiro				NULL);
246090792Sgshapiro		if (cf == NULL && OpMode != MD_PRINT)
246138032Speter		{
246238032Speter			/* this may be some random person sending hir msgs */
246338032Speter			if (tTd(41, 2))
246490792Sgshapiro				sm_dprintf("gatherq: cannot open %s: %s\n",
246590792Sgshapiro					d->d_name, sm_errstring(errno));
246638032Speter			errno = 0;
246738032Speter			wn--;
246838032Speter			continue;
246938032Speter		}
247090792Sgshapiro		w->w_qgrp = qgrp;
247190792Sgshapiro		w->w_qdir = qdir;
247238032Speter		w->w_name = newstr(d->d_name);
247338032Speter		w->w_host = NULL;
247490792Sgshapiro		if (cf != NULL)
247590792Sgshapiro		{
247690792Sgshapiro			w->w_lock = !lockfile(sm_io_getinfo(cf, SM_IO_WHAT_FD,
247790792Sgshapiro							    NULL),
247890792Sgshapiro					      w->w_name, NULL,
247990792Sgshapiro					      LOCK_SH|LOCK_NB);
248090792Sgshapiro		}
248190792Sgshapiro		w->w_tooyoung = false;
248238032Speter
248338032Speter		/* make sure jobs in creation don't clog queue */
248438032Speter		w->w_pri = 0x7fffffff;
248538032Speter		w->w_ctime = 0;
248690792Sgshapiro		w->w_mtime = sbuf.st_mtime;
248738032Speter
248838032Speter		/* extract useful information */
248990792Sgshapiro		i = NEED_P|NEED_T;
249090792Sgshapiro		if (QueueSortOrder == QSO_BYHOST
249190792Sgshapiro#if _FFR_RHS
249290792Sgshapiro		    || QueueSortOrder == QSO_BYSHUFFLE
249390792Sgshapiro#endif /* _FFR_RHS */
249490792Sgshapiro		   )
249571345Sgshapiro		{
249671345Sgshapiro			/* need w_host set for host sort order */
249771345Sgshapiro			i |= NEED_H;
249871345Sgshapiro		}
249938032Speter		if (QueueLimitSender != NULL)
250038032Speter			i |= NEED_S;
250164562Sgshapiro		if (QueueLimitRecipient != NULL)
250238032Speter			i |= NEED_R;
250390792Sgshapiro#if _FFR_QUARANTINE
250490792Sgshapiro		if (QueueLimitQuarantine != NULL)
250590792Sgshapiro			i |= NEED_QUARANTINE;
250690792Sgshapiro#endif /* _FFR_QUARANTINE */
250790792Sgshapiro		while (cf != NULL && i != 0 &&
250890792Sgshapiro		       sm_io_fgets(cf, SM_TIME_DEFAULT, lbuf,
250990792Sgshapiro				   sizeof lbuf) != NULL)
251038032Speter		{
251138032Speter			int c;
251238032Speter			time_t age;
251338032Speter
251438032Speter			p = strchr(lbuf, '\n');
251538032Speter			if (p != NULL)
251638032Speter				*p = '\0';
251738032Speter			else
251838032Speter			{
251938032Speter				/* flush rest of overly long line */
252090792Sgshapiro				while ((c = sm_io_getc(cf, SM_TIME_DEFAULT))
252190792Sgshapiro				       != SM_IO_EOF && c != '\n')
252238032Speter					continue;
252338032Speter			}
252438032Speter
252538032Speter			switch (lbuf[0])
252638032Speter			{
252738032Speter			  case 'V':
252838032Speter				qfver = atoi(&lbuf[1]);
252938032Speter				break;
253038032Speter
253138032Speter			  case 'P':
253238032Speter				w->w_pri = atol(&lbuf[1]);
253338032Speter				i &= ~NEED_P;
253438032Speter				break;
253538032Speter
253638032Speter			  case 'T':
253738032Speter				w->w_ctime = atol(&lbuf[1]);
253838032Speter				i &= ~NEED_T;
253938032Speter				break;
254038032Speter
254190792Sgshapiro#if _FFR_QUARANTINE
254290792Sgshapiro			  case 'q':
254390792Sgshapiro				if (QueueMode != QM_QUARANTINE &&
254490792Sgshapiro				    QueueMode != QM_LOST)
254590792Sgshapiro				{
254690792Sgshapiro					if (tTd(41, 49))
254790792Sgshapiro						sm_dprintf("%s not marked as quarantined but has a 'q' line\n",
254890792Sgshapiro							   w->w_name);
254990792Sgshapiro					i |= HAS_QUARANTINE;
255090792Sgshapiro				}
255190792Sgshapiro				else if (QueueMode == QM_QUARANTINE)
255290792Sgshapiro				{
255390792Sgshapiro					if (QueueLimitQuarantine == NULL)
255490792Sgshapiro					{
255590792Sgshapiro						i &= ~NEED_QUARANTINE;
255690792Sgshapiro						break;
255790792Sgshapiro					}
255890792Sgshapiro					p = &lbuf[1];
255990792Sgshapiro					check = QueueLimitQuarantine;
256090792Sgshapiro					while (check != NULL)
256190792Sgshapiro					{
256290792Sgshapiro						if (strcontainedin(false,
256390792Sgshapiro								   check->queue_match,
256490792Sgshapiro								   p) !=
256590792Sgshapiro						    check->queue_negate)
256690792Sgshapiro							break;
256790792Sgshapiro						else
256890792Sgshapiro							check = check->queue_next;
256990792Sgshapiro					}
257090792Sgshapiro					if (check != NULL)
257190792Sgshapiro						i &= ~NEED_QUARANTINE;
257290792Sgshapiro				}
257390792Sgshapiro				break;
257490792Sgshapiro#endif /* _FFR_QUARANTINE */
257590792Sgshapiro
257638032Speter			  case 'R':
257738032Speter				if (w->w_host == NULL &&
257838032Speter				    (p = strrchr(&lbuf[1], '@')) != NULL)
257964562Sgshapiro				{
258090792Sgshapiro#if _FFR_RHS
258190792Sgshapiro					if (QueueSortOrder == QSO_BYSHUFFLE)
258290792Sgshapiro						w->w_host = newstr(&p[1]);
258390792Sgshapiro					else
258490792Sgshapiro#endif /* _FFR_RHS */
258590792Sgshapiro						w->w_host = strrev(&p[1]);
258664562Sgshapiro					makelower(w->w_host);
258771345Sgshapiro					i &= ~NEED_H;
258864562Sgshapiro				}
258938032Speter				if (QueueLimitRecipient == NULL)
259038032Speter				{
259138032Speter					i &= ~NEED_R;
259238032Speter					break;
259338032Speter				}
259438032Speter				if (qfver > 0)
259538032Speter				{
259638032Speter					p = strchr(&lbuf[1], ':');
259738032Speter					if (p == NULL)
259838032Speter						p = &lbuf[1];
259938032Speter				}
260038032Speter				else
260138032Speter					p = &lbuf[1];
260238032Speter				check = QueueLimitRecipient;
260338032Speter				while (check != NULL)
260438032Speter				{
260590792Sgshapiro					if (strcontainedin(true,
260690792Sgshapiro							   check->queue_match,
260790792Sgshapiro							   p) !=
260890792Sgshapiro					    check->queue_negate)
260938032Speter						break;
261038032Speter					else
261138032Speter						check = check->queue_next;
261238032Speter				}
261338032Speter				if (check != NULL)
261438032Speter					i &= ~NEED_R;
261538032Speter				break;
261638032Speter
261738032Speter			  case 'S':
261864562Sgshapiro				check = QueueLimitSender;
261964562Sgshapiro				while (check != NULL)
262064562Sgshapiro				{
262190792Sgshapiro					if (strcontainedin(true,
262290792Sgshapiro							   check->queue_match,
262390792Sgshapiro							   &lbuf[1]) !=
262490792Sgshapiro					    check->queue_negate)
262564562Sgshapiro						break;
262664562Sgshapiro					else
262764562Sgshapiro						check = check->queue_next;
262864562Sgshapiro				}
262964562Sgshapiro				if (check != NULL)
263064562Sgshapiro					i &= ~NEED_S;
263138032Speter				break;
263238032Speter
263338032Speter			  case 'K':
263438032Speter				age = curtime() - (time_t) atol(&lbuf[1]);
263538032Speter				if (age >= 0 && MinQueueAge > 0 &&
263638032Speter				    age < MinQueueAge)
263790792Sgshapiro					w->w_tooyoung = true;
263838032Speter				break;
263938032Speter
264038032Speter			  case 'N':
264138032Speter				if (atol(&lbuf[1]) == 0)
264290792Sgshapiro					w->w_tooyoung = false;
264338032Speter				break;
264464562Sgshapiro
264590792Sgshapiro#if _FFR_QUEUEDELAY
264664562Sgshapiro/*
264764562Sgshapiro			  case 'G':
264864562Sgshapiro				queuealg = atoi(lbuf[1]);
264964562Sgshapiro				break;
265064562Sgshapiro			  case 'Y':
265164562Sgshapiro				queuedelay = (time_t) atol(&lbuf[1]);
265264562Sgshapiro				break;
265364562Sgshapiro*/
265490792Sgshapiro#endif /* _FFR_QUEUEDELAY */
265538032Speter			}
265638032Speter		}
265790792Sgshapiro		if (cf != NULL)
265890792Sgshapiro			(void) sm_io_close(cf, SM_TIME_DEFAULT);
265938032Speter
266038032Speter		if ((!doall && shouldqueue(w->w_pri, w->w_ctime)) ||
266190792Sgshapiro#if _FFR_QUARANTINE
266290792Sgshapiro		    bitset(HAS_QUARANTINE, i) ||
266390792Sgshapiro		    bitset(NEED_QUARANTINE, i) ||
266490792Sgshapiro#endif /* _FFR_QUARANTINE */
266538032Speter		    bitset(NEED_R|NEED_S, i))
266638032Speter		{
266738032Speter			/* don't even bother sorting this job in */
266838032Speter			if (tTd(41, 49))
266990792Sgshapiro				sm_dprintf("skipping %s (%x)\n", w->w_name, i);
267090792Sgshapiro			sm_free(w->w_name); /* XXX */
267190792Sgshapiro			if (w->w_host != NULL)
267290792Sgshapiro				sm_free(w->w_host); /* XXX */
267338032Speter			wn--;
267438032Speter		}
267590792Sgshapiro		else
267690792Sgshapiro			++num_ent;
267738032Speter	}
267838032Speter	(void) closedir(f);
267938032Speter	wn++;
268038032Speter
268190792Sgshapiro	i = wn - WorkListCount;
268290792Sgshapiro	WorkListCount += SM_MIN(num_ent, WorkListSize);
268390792Sgshapiro
268490792Sgshapiro	if (more != NULL)
268590792Sgshapiro		*more = WorkListCount < wn;
268690792Sgshapiro
268790792Sgshapiro	if (full != NULL)
268890792Sgshapiro		*full = (wn >= MaxQueueRun && MaxQueueRun > 0) ||
268990792Sgshapiro			(WorkList == NULL && wn > 0);
269090792Sgshapiro
269190792Sgshapiro	return i;
269290792Sgshapiro}
269390792Sgshapiro/*
269490792Sgshapiro**  SORTQ -- sort the work list
269590792Sgshapiro**
269690792Sgshapiro**	First the old WorkQ is cleared away. Then the WorkList is sorted
269790792Sgshapiro**	for all items so that important (higher sorting value) items are not
269890792Sgshapiro**	trunctated off. Then the most important items are moved from
269990792Sgshapiro**	WorkList to WorkQ. The lower count of 'max' or MaxListCount items
270090792Sgshapiro**	are moved.
270190792Sgshapiro**
270290792Sgshapiro**	Parameters:
270390792Sgshapiro**		max -- maximum number of items to be placed in WorkQ
270490792Sgshapiro**
270590792Sgshapiro**	Returns:
270690792Sgshapiro**		the number of items in WorkQ
270790792Sgshapiro**
270890792Sgshapiro**	Side Effects:
270990792Sgshapiro**		WorkQ gets released and filled with new work. WorkList
271090792Sgshapiro**		gets released. Work items get sorted in order.
271190792Sgshapiro*/
271290792Sgshapiro
271390792Sgshapirostatic int
271490792Sgshapirosortq(max)
271590792Sgshapiro	int max;
271690792Sgshapiro{
271790792Sgshapiro	register int i;			/* local counter */
271890792Sgshapiro	register WORK *w;		/* tmp item pointer */
271990792Sgshapiro	int wc = WorkListCount;		/* trim size for WorkQ */
272090792Sgshapiro
272190792Sgshapiro	if (WorkQ != NULL)
272290792Sgshapiro	{
272390792Sgshapiro		/* Clear out old WorkQ. */
272490792Sgshapiro		for (w = WorkQ; w != NULL; )
272590792Sgshapiro		{
272690792Sgshapiro			register WORK *nw = w->w_next;
272790792Sgshapiro
272890792Sgshapiro			WorkQ = nw;
272990792Sgshapiro			sm_free(w->w_name); /* XXX */
273090792Sgshapiro			if (w->w_host != NULL)
273190792Sgshapiro				sm_free(w->w_host); /* XXX */
273290792Sgshapiro			sm_free((char *) w); /* XXX */
273390792Sgshapiro			w = nw;
273490792Sgshapiro		}
273590792Sgshapiro		sm_free((char *) WorkQ);
273690792Sgshapiro		WorkQ = NULL;
273790792Sgshapiro	}
273890792Sgshapiro
273990792Sgshapiro	if (WorkList == NULL || wc <= 0)
274064562Sgshapiro		return 0;
274138032Speter
274290792Sgshapiro	/* Check if the per queue group item limit will be exceeded */
274390792Sgshapiro	if (wc > max && max > 0)
274490792Sgshapiro		wc = max;
274590792Sgshapiro
274690792Sgshapiro	/*
274790792Sgshapiro	**  The sort now takes place using all of the items in WorkList.
274890792Sgshapiro	**  The list gets trimmed to the most important items after the sort.
274990792Sgshapiro	**  If the trim were to happen before the sort then one or more
275090792Sgshapiro	**  important items might get truncated off -- not what we want.
275190792Sgshapiro	*/
275290792Sgshapiro
275364562Sgshapiro	if (QueueSortOrder == QSO_BYHOST)
275438032Speter	{
275538032Speter		/*
275638032Speter		**  Sort the work directory for the first time,
275738032Speter		**  based on host name, lock status, and priority.
275838032Speter		*/
275938032Speter
276038032Speter		qsort((char *) WorkList, wc, sizeof *WorkList, workcmpf1);
276138032Speter
276238032Speter		/*
276338032Speter		**  If one message to host is locked, "lock" all messages
276438032Speter		**  to that host.
276538032Speter		*/
276638032Speter
276738032Speter		i = 0;
276838032Speter		while (i < wc)
276938032Speter		{
277038032Speter			if (!WorkList[i].w_lock)
277138032Speter			{
277238032Speter				i++;
277338032Speter				continue;
277438032Speter			}
277538032Speter			w = &WorkList[i];
277638032Speter			while (++i < wc)
277738032Speter			{
277838032Speter				if (WorkList[i].w_host == NULL &&
277938032Speter				    w->w_host == NULL)
278090792Sgshapiro					WorkList[i].w_lock = true;
278138032Speter				else if (WorkList[i].w_host != NULL &&
278238032Speter					 w->w_host != NULL &&
278390792Sgshapiro					 sm_strcasecmp(WorkList[i].w_host,
278490792Sgshapiro						       w->w_host) == 0)
278590792Sgshapiro					WorkList[i].w_lock = true;
278638032Speter				else
278738032Speter					break;
278838032Speter			}
278938032Speter		}
279038032Speter
279138032Speter		/*
279238032Speter		**  Sort the work directory for the second time,
279338032Speter		**  based on lock status, host name, and priority.
279438032Speter		*/
279538032Speter
279638032Speter		qsort((char *) WorkList, wc, sizeof *WorkList, workcmpf2);
279738032Speter	}
279864562Sgshapiro	else if (QueueSortOrder == QSO_BYTIME)
279938032Speter	{
280038032Speter		/*
280138032Speter		**  Simple sort based on submission time only.
280238032Speter		*/
280338032Speter
280438032Speter		qsort((char *) WorkList, wc, sizeof *WorkList, workcmpf3);
280538032Speter	}
280664562Sgshapiro	else if (QueueSortOrder == QSO_BYFILENAME)
280764562Sgshapiro	{
280864562Sgshapiro		/*
280990792Sgshapiro		**  Sort based on queue filename.
281064562Sgshapiro		*/
281164562Sgshapiro
281264562Sgshapiro		qsort((char *) WorkList, wc, sizeof *WorkList, workcmpf4);
281364562Sgshapiro	}
281490792Sgshapiro	else if (QueueSortOrder == QSO_RANDOM)
281590792Sgshapiro	{
281690792Sgshapiro		/*
281790792Sgshapiro		**  Sort randomly.
281890792Sgshapiro		**	workcmpf5() returns a random 1 or -1.
281990792Sgshapiro		**	As long as nobody does a verification pass over the
282090792Sgshapiro		**	sorted list, we should be golden.
282190792Sgshapiro		*/
282290792Sgshapiro
282390792Sgshapiro		qsort((char *) WorkList, wc, sizeof *WorkList, workcmpf5);
282490792Sgshapiro	}
282590792Sgshapiro	else if (QueueSortOrder == QSO_BYMODTIME)
282690792Sgshapiro	{
282790792Sgshapiro		/*
282890792Sgshapiro		**  Simple sort based on modification time of queue file.
282990792Sgshapiro		**  This puts the oldest items first.
283090792Sgshapiro		*/
283190792Sgshapiro
283290792Sgshapiro		qsort((char *) WorkList, wc, sizeof *WorkList, workcmpf6);
283390792Sgshapiro	}
283490792Sgshapiro#if _FFR_RHS
283590792Sgshapiro	else if (QueueSortOrder == QSO_BYSHUFFLE)
283690792Sgshapiro	{
283790792Sgshapiro		/*
283890792Sgshapiro		**  Simple sort based on shuffled host name.
283990792Sgshapiro		*/
284090792Sgshapiro
284190792Sgshapiro		init_shuffle_alphabet();
284290792Sgshapiro		qsort((char *) WorkList, wc, sizeof *WorkList, workcmpf7);
284390792Sgshapiro	}
284490792Sgshapiro#endif /* _FFR_RHS */
284538032Speter	else
284638032Speter	{
284738032Speter		/*
284838032Speter		**  Simple sort based on queue priority only.
284938032Speter		*/
285038032Speter
285138032Speter		qsort((char *) WorkList, wc, sizeof *WorkList, workcmpf0);
285238032Speter	}
285338032Speter
285438032Speter	/*
285538032Speter	**  Convert the work list into canonical form.
285638032Speter	**	Should be turning it into a list of envelopes here perhaps.
285790792Sgshapiro	**  Only take the most important items up to the per queue group
285890792Sgshapiro	**  maximum.
285938032Speter	*/
286038032Speter
286138032Speter	for (i = wc; --i >= 0; )
286238032Speter	{
286338032Speter		w = (WORK *) xalloc(sizeof *w);
286490792Sgshapiro		w->w_qgrp = WorkList[i].w_qgrp;
286590792Sgshapiro		w->w_qdir = WorkList[i].w_qdir;
286638032Speter		w->w_name = WorkList[i].w_name;
286738032Speter		w->w_host = WorkList[i].w_host;
286838032Speter		w->w_lock = WorkList[i].w_lock;
286938032Speter		w->w_tooyoung = WorkList[i].w_tooyoung;
287038032Speter		w->w_pri = WorkList[i].w_pri;
287138032Speter		w->w_ctime = WorkList[i].w_ctime;
287290792Sgshapiro		w->w_mtime = WorkList[i].w_mtime;
287338032Speter		w->w_next = WorkQ;
287438032Speter		WorkQ = w;
287538032Speter	}
287638032Speter	if (WorkList != NULL)
287790792Sgshapiro		sm_free(WorkList); /* XXX */
287838032Speter	WorkList = NULL;
287938032Speter	WorkListSize = 0;
288090792Sgshapiro	WorkListCount = 0;
288138032Speter
288238032Speter	if (tTd(40, 1))
288338032Speter	{
288438032Speter		for (w = WorkQ; w != NULL; w = w->w_next)
288564562Sgshapiro		{
288664562Sgshapiro			if (w->w_host != NULL)
288790792Sgshapiro				sm_dprintf("%22s: pri=%ld %s\n",
288864562Sgshapiro					w->w_name, w->w_pri, w->w_host);
288964562Sgshapiro			else
289090792Sgshapiro				sm_dprintf("%32s: pri=%ld\n",
289164562Sgshapiro					w->w_name, w->w_pri);
289264562Sgshapiro		}
289338032Speter	}
289438032Speter
289590792Sgshapiro	return wc; /* return number of WorkQ items */
289638032Speter}
289790792Sgshapiro/*
289838032Speter**  GROW_WLIST -- make the work list larger
289938032Speter**
290038032Speter**	Parameters:
290190792Sgshapiro**		qgrp -- the index for the queue group.
290290792Sgshapiro**		qdir -- the index for the queue directory.
290338032Speter**
290438032Speter**	Returns:
290538032Speter**		none.
290638032Speter**
290738032Speter**	Side Effects:
290838032Speter**		Adds another QUEUESEGSIZE entries to WorkList if possible.
290938032Speter**		It can fail if there isn't enough memory, so WorkListSize
291038032Speter**		should be checked again upon return.
291138032Speter*/
291238032Speter
291364562Sgshapirostatic void
291490792Sgshapirogrow_wlist(qgrp, qdir)
291590792Sgshapiro	int qgrp;
291690792Sgshapiro	int qdir;
291738032Speter{
291838032Speter	if (tTd(41, 1))
291990792Sgshapiro		sm_dprintf("grow_wlist: WorkListSize=%d\n", WorkListSize);
292038032Speter	if (WorkList == NULL)
292138032Speter	{
292264562Sgshapiro		WorkList = (WORK *) xalloc((sizeof *WorkList) *
292364562Sgshapiro					   (QUEUESEGSIZE + 1));
292438032Speter		WorkListSize = QUEUESEGSIZE;
292538032Speter	}
292638032Speter	else
292738032Speter	{
292838032Speter		int newsize = WorkListSize + QUEUESEGSIZE;
292990792Sgshapiro		WORK *newlist = (WORK *) sm_realloc((char *) WorkList,
293090792Sgshapiro					  (unsigned) sizeof(WORK) * (newsize + 1));
293138032Speter
293238032Speter		if (newlist != NULL)
293338032Speter		{
293438032Speter			WorkListSize = newsize;
293538032Speter			WorkList = newlist;
293638032Speter			if (LogLevel > 1)
293738032Speter			{
293864562Sgshapiro				sm_syslog(LOG_INFO, NOQID,
293964562Sgshapiro					  "grew WorkList for %s to %d",
294090792Sgshapiro					  qid_printqueue(qgrp, qdir),
294164562Sgshapiro					  WorkListSize);
294238032Speter			}
294338032Speter		}
294438032Speter		else if (LogLevel > 0)
294538032Speter		{
294638032Speter			sm_syslog(LOG_ALERT, NOQID,
294764562Sgshapiro				  "FAILED to grow WorkList for %s to %d",
294890792Sgshapiro				  qid_printqueue(qgrp, qdir), newsize);
294938032Speter		}
295038032Speter	}
295138032Speter	if (tTd(41, 1))
295290792Sgshapiro		sm_dprintf("grow_wlist: WorkListSize now %d\n", WorkListSize);
295338032Speter}
295490792Sgshapiro/*
295538032Speter**  WORKCMPF0 -- simple priority-only compare function.
295638032Speter**
295738032Speter**	Parameters:
295838032Speter**		a -- the first argument.
295938032Speter**		b -- the second argument.
296038032Speter**
296138032Speter**	Returns:
296238032Speter**		-1 if a < b
296338032Speter**		 0 if a == b
296438032Speter**		+1 if a > b
296538032Speter**
296638032Speter*/
296738032Speter
296864562Sgshapirostatic int
296938032Speterworkcmpf0(a, b)
297038032Speter	register WORK *a;
297138032Speter	register WORK *b;
297238032Speter{
297338032Speter	long pa = a->w_pri;
297438032Speter	long pb = b->w_pri;
297538032Speter
297638032Speter	if (pa == pb)
297738032Speter		return 0;
297838032Speter	else if (pa > pb)
297938032Speter		return 1;
298038032Speter	else
298138032Speter		return -1;
298238032Speter}
298390792Sgshapiro/*
298438032Speter**  WORKCMPF1 -- first compare function for ordering work based on host name.
298538032Speter**
298638032Speter**	Sorts on host name, lock status, and priority in that order.
298738032Speter**
298838032Speter**	Parameters:
298938032Speter**		a -- the first argument.
299038032Speter**		b -- the second argument.
299138032Speter**
299238032Speter**	Returns:
299338032Speter**		<0 if a < b
299438032Speter**		 0 if a == b
299538032Speter**		>0 if a > b
299638032Speter**
299738032Speter*/
299838032Speter
299964562Sgshapirostatic int
300038032Speterworkcmpf1(a, b)
300138032Speter	register WORK *a;
300238032Speter	register WORK *b;
300338032Speter{
300438032Speter	int i;
300538032Speter
300638032Speter	/* host name */
300738032Speter	if (a->w_host != NULL && b->w_host == NULL)
300838032Speter		return 1;
300938032Speter	else if (a->w_host == NULL && b->w_host != NULL)
301038032Speter		return -1;
301138032Speter	if (a->w_host != NULL && b->w_host != NULL &&
301238032Speter	    (i = sm_strcasecmp(a->w_host, b->w_host)) != 0)
301338032Speter		return i;
301438032Speter
301538032Speter	/* lock status */
301638032Speter	if (a->w_lock != b->w_lock)
301738032Speter		return b->w_lock - a->w_lock;
301838032Speter
301938032Speter	/* job priority */
302073188Sgshapiro	return workcmpf0(a, b);
302138032Speter}
302290792Sgshapiro/*
302338032Speter**  WORKCMPF2 -- second compare function for ordering work based on host name.
302438032Speter**
302538032Speter**	Sorts on lock status, host name, and priority in that order.
302638032Speter**
302738032Speter**	Parameters:
302838032Speter**		a -- the first argument.
302938032Speter**		b -- the second argument.
303038032Speter**
303138032Speter**	Returns:
303238032Speter**		<0 if a < b
303338032Speter**		 0 if a == b
303438032Speter**		>0 if a > b
303538032Speter**
303638032Speter*/
303738032Speter
303864562Sgshapirostatic int
303938032Speterworkcmpf2(a, b)
304038032Speter	register WORK *a;
304138032Speter	register WORK *b;
304238032Speter{
304338032Speter	int i;
304438032Speter
304538032Speter	/* lock status */
304638032Speter	if (a->w_lock != b->w_lock)
304738032Speter		return a->w_lock - b->w_lock;
304838032Speter
304938032Speter	/* host name */
305038032Speter	if (a->w_host != NULL && b->w_host == NULL)
305138032Speter		return 1;
305238032Speter	else if (a->w_host == NULL && b->w_host != NULL)
305338032Speter		return -1;
305438032Speter	if (a->w_host != NULL && b->w_host != NULL &&
305538032Speter	    (i = sm_strcasecmp(a->w_host, b->w_host)) != 0)
305638032Speter		return i;
305738032Speter
305838032Speter	/* job priority */
305973188Sgshapiro	return workcmpf0(a, b);
306038032Speter}
306190792Sgshapiro/*
306238032Speter**  WORKCMPF3 -- simple submission-time-only compare function.
306338032Speter**
306438032Speter**	Parameters:
306538032Speter**		a -- the first argument.
306638032Speter**		b -- the second argument.
306738032Speter**
306838032Speter**	Returns:
306938032Speter**		-1 if a < b
307038032Speter**		 0 if a == b
307138032Speter**		+1 if a > b
307238032Speter**
307338032Speter*/
307438032Speter
307564562Sgshapirostatic int
307638032Speterworkcmpf3(a, b)
307738032Speter	register WORK *a;
307838032Speter	register WORK *b;
307938032Speter{
308038032Speter	if (a->w_ctime > b->w_ctime)
308138032Speter		return 1;
308238032Speter	else if (a->w_ctime < b->w_ctime)
308338032Speter		return -1;
308438032Speter	else
308538032Speter		return 0;
308638032Speter}
308790792Sgshapiro/*
308864562Sgshapiro**  WORKCMPF4 -- compare based on file name
308964562Sgshapiro**
309064562Sgshapiro**	Parameters:
309164562Sgshapiro**		a -- the first argument.
309264562Sgshapiro**		b -- the second argument.
309364562Sgshapiro**
309464562Sgshapiro**	Returns:
309564562Sgshapiro**		-1 if a < b
309664562Sgshapiro**		 0 if a == b
309764562Sgshapiro**		+1 if a > b
309864562Sgshapiro**
309964562Sgshapiro*/
310064562Sgshapiro
310164562Sgshapirostatic int
310264562Sgshapiroworkcmpf4(a, b)
310364562Sgshapiro	register WORK *a;
310464562Sgshapiro	register WORK *b;
310564562Sgshapiro{
310664562Sgshapiro	return strcmp(a->w_name, b->w_name);
310764562Sgshapiro}
310890792Sgshapiro/*
310990792Sgshapiro**  WORKCMPF5 -- compare based on assigned random number
311090792Sgshapiro**
311190792Sgshapiro**	Parameters:
311290792Sgshapiro**		a -- the first argument (ignored).
311390792Sgshapiro**		b -- the second argument (ignored).
311490792Sgshapiro**
311590792Sgshapiro**	Returns:
311690792Sgshapiro**		randomly 1/-1
311790792Sgshapiro*/
311890792Sgshapiro
311990792Sgshapiro/* ARGSUSED0 */
312090792Sgshapirostatic int
312190792Sgshapiroworkcmpf5(a, b)
312290792Sgshapiro	register WORK *a;
312390792Sgshapiro	register WORK *b;
312490792Sgshapiro{
312590792Sgshapiro	return (get_rand_mod(2)) ? 1 : -1;
312690792Sgshapiro}
312790792Sgshapiro/*
312890792Sgshapiro**  WORKCMPF6 -- simple modification-time-only compare function.
312990792Sgshapiro**
313090792Sgshapiro**	Parameters:
313190792Sgshapiro**		a -- the first argument.
313290792Sgshapiro**		b -- the second argument.
313390792Sgshapiro**
313490792Sgshapiro**	Returns:
313590792Sgshapiro**		-1 if a < b
313690792Sgshapiro**		 0 if a == b
313790792Sgshapiro**		+1 if a > b
313890792Sgshapiro**
313990792Sgshapiro*/
314090792Sgshapiro
314190792Sgshapirostatic int
314290792Sgshapiroworkcmpf6(a, b)
314390792Sgshapiro	register WORK *a;
314490792Sgshapiro	register WORK *b;
314590792Sgshapiro{
314690792Sgshapiro	if (a->w_mtime > b->w_mtime)
314790792Sgshapiro		return 1;
314890792Sgshapiro	else if (a->w_mtime < b->w_mtime)
314990792Sgshapiro		return -1;
315090792Sgshapiro	else
315190792Sgshapiro		return 0;
315290792Sgshapiro}
315390792Sgshapiro#if _FFR_RHS
315490792Sgshapiro/*
315590792Sgshapiro**  WORKCMPF7 -- compare function for ordering work based on shuffled host name.
315690792Sgshapiro**
315790792Sgshapiro**	Sorts on lock status, host name, and priority in that order.
315890792Sgshapiro**
315990792Sgshapiro**	Parameters:
316090792Sgshapiro**		a -- the first argument.
316190792Sgshapiro**		b -- the second argument.
316290792Sgshapiro**
316390792Sgshapiro**	Returns:
316490792Sgshapiro**		<0 if a < b
316590792Sgshapiro**		 0 if a == b
316690792Sgshapiro**		>0 if a > b
316790792Sgshapiro**
316890792Sgshapiro*/
316990792Sgshapiro
317090792Sgshapirostatic int
317190792Sgshapiroworkcmpf7(a, b)
317290792Sgshapiro	register WORK *a;
317390792Sgshapiro	register WORK *b;
317490792Sgshapiro{
317590792Sgshapiro	int i;
317690792Sgshapiro
317790792Sgshapiro	/* lock status */
317890792Sgshapiro	if (a->w_lock != b->w_lock)
317990792Sgshapiro		return a->w_lock - b->w_lock;
318090792Sgshapiro
318190792Sgshapiro	/* host name */
318290792Sgshapiro	if (a->w_host != NULL && b->w_host == NULL)
318390792Sgshapiro		return 1;
318490792Sgshapiro	else if (a->w_host == NULL && b->w_host != NULL)
318590792Sgshapiro		return -1;
318690792Sgshapiro	if (a->w_host != NULL && b->w_host != NULL &&
318790792Sgshapiro	    (i = sm_strshufflecmp(a->w_host, b->w_host)) != 0)
318890792Sgshapiro		return i;
318990792Sgshapiro
319090792Sgshapiro	/* job priority */
319190792Sgshapiro	return workcmpf0(a, b);
319290792Sgshapiro}
319390792Sgshapiro#endif /* _FFR_RHS */
319490792Sgshapiro/*
319564562Sgshapiro**  STRREV -- reverse string
319664562Sgshapiro**
319764562Sgshapiro**	Returns a pointer to a new string that is the reverse of
319864562Sgshapiro**	the string pointed to by fwd.  The space for the new
319964562Sgshapiro**	string is obtained using xalloc().
320064562Sgshapiro**
320164562Sgshapiro**	Parameters:
320264562Sgshapiro**		fwd -- the string to reverse.
320364562Sgshapiro**
320464562Sgshapiro**	Returns:
320564562Sgshapiro**		the reversed string.
320664562Sgshapiro*/
320764562Sgshapiro
320864562Sgshapirostatic char *
320964562Sgshapirostrrev(fwd)
321064562Sgshapiro	char *fwd;
321164562Sgshapiro{
321264562Sgshapiro	char *rev = NULL;
321364562Sgshapiro	int len, cnt;
321464562Sgshapiro
321564562Sgshapiro	len = strlen(fwd);
321664562Sgshapiro	rev = xalloc(len + 1);
321764562Sgshapiro	for (cnt = 0; cnt < len; ++cnt)
321864562Sgshapiro		rev[cnt] = fwd[len - cnt - 1];
321964562Sgshapiro	rev[len] = '\0';
322064562Sgshapiro	return rev;
322164562Sgshapiro}
322290792Sgshapiro
322390792Sgshapiro#if _FFR_RHS
322490792Sgshapiro
322590792Sgshapiro#define NASCII	128
322690792Sgshapiro#define NCHAR	256
322790792Sgshapiro
322890792Sgshapirostatic unsigned char ShuffledAlphabet[NCHAR];
322990792Sgshapiro
323090792Sgshapirovoid
323190792Sgshapiroinit_shuffle_alphabet()
323290792Sgshapiro{
323390792Sgshapiro	static bool init = false;
323490792Sgshapiro	int i;
323590792Sgshapiro
323690792Sgshapiro	if (init)
323790792Sgshapiro		return;
323890792Sgshapiro
323990792Sgshapiro	/* fill the ShuffledAlphabet */
324090792Sgshapiro	for (i = 0; i < NCHAR; i++)
324190792Sgshapiro		ShuffledAlphabet[i] = i;
324290792Sgshapiro
324390792Sgshapiro	/* mix it */
324490792Sgshapiro	for (i = 1; i < NCHAR; i++)
324590792Sgshapiro	{
324690792Sgshapiro		register int j = get_random() % NCHAR;
324790792Sgshapiro		register int tmp;
324890792Sgshapiro
324990792Sgshapiro		tmp = ShuffledAlphabet[j];
325090792Sgshapiro		ShuffledAlphabet[j] = ShuffledAlphabet[i];
325190792Sgshapiro		ShuffledAlphabet[i] = tmp;
325290792Sgshapiro	}
325390792Sgshapiro
325490792Sgshapiro	/* make it case insensitive */
325590792Sgshapiro	for (i = 'A'; i <= 'Z'; i++)
325690792Sgshapiro		ShuffledAlphabet[i] = ShuffledAlphabet[i + 'a' - 'A'];
325790792Sgshapiro
325890792Sgshapiro	/* fill the upper part */
325990792Sgshapiro	for (i = 0; i < NCHAR; i++)
326090792Sgshapiro		ShuffledAlphabet[i + NCHAR] = ShuffledAlphabet[i];
326190792Sgshapiro	init = true;
326290792Sgshapiro}
326390792Sgshapiro
326490792Sgshapirostatic int
326590792Sgshapirosm_strshufflecmp(a, b)
326690792Sgshapiro	char *a;
326790792Sgshapiro	char *b;
326890792Sgshapiro{
326990792Sgshapiro	const unsigned char *us1 = (const unsigned char *) a;
327090792Sgshapiro	const unsigned char *us2 = (const unsigned char *) b;
327190792Sgshapiro
327290792Sgshapiro	while (ShuffledAlphabet[*us1] == ShuffledAlphabet[*us2++])
327390792Sgshapiro	{
327490792Sgshapiro		if (*us1++ == '\0')
327590792Sgshapiro			return 0;
327690792Sgshapiro	}
327790792Sgshapiro	return (ShuffledAlphabet[*us1] - ShuffledAlphabet[*--us2]);
327890792Sgshapiro}
327990792Sgshapiro#endif /* _FFR_RHS */
328090792Sgshapiro
328190792Sgshapiro/*
328238032Speter**  DOWORK -- do a work request.
328338032Speter**
328438032Speter**	Parameters:
328590792Sgshapiro**		qgrp -- the index of the queue group for the job.
328690792Sgshapiro**		qdir -- the index of the queue directory for the job.
328738032Speter**		id -- the ID of the job to run.
328838032Speter**		forkflag -- if set, run this in background.
328938032Speter**		requeueflag -- if set, reinstantiate the queue quickly.
329038032Speter**			This is used when expanding aliases in the queue.
329138032Speter**			If forkflag is also set, it doesn't wait for the
329238032Speter**			child.
329338032Speter**		e - the envelope in which to run it.
329438032Speter**
329538032Speter**	Returns:
329638032Speter**		process id of process that is running the queue job.
329738032Speter**
329838032Speter**	Side Effects:
329938032Speter**		The work request is satisfied if possible.
330038032Speter*/
330138032Speter
330238032Speterpid_t
330390792Sgshapirodowork(qgrp, qdir, id, forkflag, requeueflag, e)
330490792Sgshapiro	int qgrp;
330590792Sgshapiro	int qdir;
330638032Speter	char *id;
330738032Speter	bool forkflag;
330838032Speter	bool requeueflag;
330938032Speter	register ENVELOPE *e;
331038032Speter{
331138032Speter	register pid_t pid;
331290792Sgshapiro	SM_RPOOL_T *rpool;
331338032Speter
331438032Speter	if (tTd(40, 1))
331590792Sgshapiro		sm_dprintf("dowork(%s/%s)\n", qid_printqueue(qgrp, qdir), id);
331638032Speter
331738032Speter	/*
331838032Speter	**  Fork for work.
331938032Speter	*/
332038032Speter
332138032Speter	if (forkflag)
332238032Speter	{
332364562Sgshapiro		/*
332464562Sgshapiro		**  Since the delivery may happen in a child and the
332564562Sgshapiro		**  parent does not wait, the parent may close the
332664562Sgshapiro		**  maps thereby removing any shared memory used by
332764562Sgshapiro		**  the map.  Therefore, close the maps now so the
332864562Sgshapiro		**  child will dynamically open them if necessary.
332964562Sgshapiro		*/
333064562Sgshapiro
333190792Sgshapiro		closemaps(false);
333264562Sgshapiro
333338032Speter		pid = fork();
333438032Speter		if (pid < 0)
333538032Speter		{
333638032Speter			syserr("dowork: cannot fork");
333738032Speter			return 0;
333838032Speter		}
333938032Speter		else if (pid > 0)
334038032Speter		{
334138032Speter			/* parent -- clean out connection cache */
334290792Sgshapiro			mci_flush(false, NULL);
334338032Speter		}
334438032Speter		else
334538032Speter		{
334690792Sgshapiro			/*
334790792Sgshapiro			**  Initialize exception stack and default exception
334890792Sgshapiro			**  handler for child process.
334990792Sgshapiro			*/
335090792Sgshapiro
335190792Sgshapiro			/* Reset global flags */
335290792Sgshapiro			RestartRequest = NULL;
335390792Sgshapiro			RestartWorkGroup = false;
335490792Sgshapiro			ShutdownRequest = NULL;
335590792Sgshapiro			PendingSignal = 0;
335690792Sgshapiro			CurrentPid = getpid();
335790792Sgshapiro			sm_exc_newthread(fatal_error);
335890792Sgshapiro
335990792Sgshapiro			/*
336090792Sgshapiro			**  See note above about SMTP processes and SIGCHLD.
336190792Sgshapiro			*/
336290792Sgshapiro
336390792Sgshapiro			if (OpMode == MD_SMTP ||
336490792Sgshapiro			    OpMode == MD_DAEMON ||
336590792Sgshapiro			    MaxQueueChildren > 0)
336690792Sgshapiro			{
336790792Sgshapiro				proc_list_clear();
336890792Sgshapiro				sm_releasesignal(SIGCHLD);
336990792Sgshapiro				(void) sm_signal(SIGCHLD, SIG_DFL);
337090792Sgshapiro			}
337190792Sgshapiro
337238032Speter			/* child -- error messages to the transcript */
337390792Sgshapiro			QuickAbort = OnlyOneError = false;
337438032Speter		}
337538032Speter	}
337638032Speter	else
337738032Speter	{
337838032Speter		pid = 0;
337938032Speter	}
338038032Speter
338138032Speter	if (pid == 0)
338238032Speter	{
338338032Speter		/*
338438032Speter		**  CHILD
338538032Speter		**	Lock the control file to avoid duplicate deliveries.
338638032Speter		**		Then run the file as though we had just read it.
338738032Speter		**	We save an idea of the temporary name so we
338838032Speter		**		can recover on interrupt.
338938032Speter		*/
339038032Speter
339190792Sgshapiro		if (forkflag)
339290792Sgshapiro		{
339390792Sgshapiro			/* Reset global flags */
339490792Sgshapiro			RestartRequest = NULL;
339590792Sgshapiro			RestartWorkGroup = false;
339690792Sgshapiro			ShutdownRequest = NULL;
339790792Sgshapiro			PendingSignal = 0;
339890792Sgshapiro		}
339977349Sgshapiro
340038032Speter		/* set basic modes, etc. */
340190792Sgshapiro		sm_clear_events();
340264562Sgshapiro		clearstats();
340390792Sgshapiro		rpool = sm_rpool_new_x(NULL);
340490792Sgshapiro		clearenvelope(e, false, rpool);
340538032Speter		e->e_flags |= EF_QUEUERUN|EF_GLOBALERRS;
340664562Sgshapiro		set_delivery_mode(SM_DELIVER, e);
340738032Speter		e->e_errormode = EM_MAIL;
340838032Speter		e->e_id = id;
340990792Sgshapiro		e->e_qgrp = qgrp;
341090792Sgshapiro		e->e_qdir = qdir;
341190792Sgshapiro		GrabTo = UseErrorsTo = false;
341238032Speter		ExitStat = EX_OK;
341338032Speter		if (forkflag)
341438032Speter		{
341538032Speter			disconnect(1, e);
341690792Sgshapiro			set_op_mode(MD_QUEUERUN);
341738032Speter		}
341890792Sgshapiro		sm_setproctitle(true, e, "%s from queue", qid_printname(e));
341938032Speter		if (LogLevel > 76)
342090792Sgshapiro			sm_syslog(LOG_DEBUG, e->e_id, "dowork, pid=%d",
342190792Sgshapiro				  (int) CurrentPid);
342238032Speter
342338032Speter		/* don't use the headers from sendmail.cf... */
342438032Speter		e->e_header = NULL;
342538032Speter
342638032Speter		/* read the queue control file -- return if locked */
342790792Sgshapiro		if (!readqf(e, false))
342838032Speter		{
342938032Speter			if (tTd(40, 4) && e->e_id != NULL)
343090792Sgshapiro				sm_dprintf("readqf(%s) failed\n",
343164562Sgshapiro					qid_printname(e));
343238032Speter			e->e_id = NULL;
343338032Speter			if (forkflag)
343490792Sgshapiro				finis(false, true, EX_OK);
343538032Speter			else
343690792Sgshapiro			{
343790792Sgshapiro				/* adding this frees 8 bytes */
343890792Sgshapiro				clearenvelope(e, false, rpool);
343990792Sgshapiro
344090792Sgshapiro				/* adding this frees 12 bytes */
344190792Sgshapiro				sm_rpool_free(rpool);
344290792Sgshapiro				e->e_rpool = NULL;
344338032Speter				return 0;
344490792Sgshapiro			}
344538032Speter		}
344638032Speter
344738032Speter		e->e_flags |= EF_INQUEUE;
344890792Sgshapiro		eatheader(e, requeueflag, true);
344938032Speter
345038032Speter		if (requeueflag)
345190792Sgshapiro			queueup(e, false, false);
345238032Speter
345338032Speter		/* do the delivery */
345438032Speter		sendall(e, SM_DELIVER);
345538032Speter
345638032Speter		/* finish up and exit */
345738032Speter		if (forkflag)
345890792Sgshapiro			finis(true, true, ExitStat);
345938032Speter		else
346090792Sgshapiro		{
346190792Sgshapiro			dropenvelope(e, true, false);
346290792Sgshapiro			sm_rpool_free(rpool);
346390792Sgshapiro			e->e_rpool = NULL;
346490792Sgshapiro		}
346538032Speter	}
346638032Speter	e->e_id = NULL;
346738032Speter	return pid;
346838032Speter}
346990792Sgshapiro
347090792Sgshapiro/*
347190792Sgshapiro**  DOWORKLIST -- process a list of envelopes as work requests
347290792Sgshapiro**
347390792Sgshapiro**	Similar to dowork(), except that after forking, it processes an
347490792Sgshapiro**	envelope and its siblings, treating each envelope as a work request.
347590792Sgshapiro**
347690792Sgshapiro**	Parameters:
347790792Sgshapiro**		el -- envelope to be processed including its siblings.
347890792Sgshapiro**		forkflag -- if set, run this in background.
347990792Sgshapiro**		requeueflag -- if set, reinstantiate the queue quickly.
348090792Sgshapiro**			This is used when expanding aliases in the queue.
348190792Sgshapiro**			If forkflag is also set, it doesn't wait for the
348290792Sgshapiro**			child.
348390792Sgshapiro**
348490792Sgshapiro**	Returns:
348590792Sgshapiro**		process id of process that is running the queue job.
348690792Sgshapiro**
348790792Sgshapiro**	Side Effects:
348890792Sgshapiro**		The work request is satisfied if possible.
348990792Sgshapiro*/
349090792Sgshapiro
349190792Sgshapiropid_t
349290792Sgshapirodoworklist(el, forkflag, requeueflag)
349390792Sgshapiro	ENVELOPE *el;
349490792Sgshapiro	bool forkflag;
349590792Sgshapiro	bool requeueflag;
349690792Sgshapiro{
349790792Sgshapiro	register pid_t pid;
349890792Sgshapiro	ENVELOPE *ei;
349990792Sgshapiro
350090792Sgshapiro	if (tTd(40, 1))
350190792Sgshapiro		sm_dprintf("doworklist()\n");
350290792Sgshapiro
350390792Sgshapiro	/*
350490792Sgshapiro	**  Fork for work.
350590792Sgshapiro	*/
350690792Sgshapiro
350790792Sgshapiro	if (forkflag)
350890792Sgshapiro	{
350990792Sgshapiro		/*
351090792Sgshapiro		**  Since the delivery may happen in a child and the
351190792Sgshapiro		**  parent does not wait, the parent may close the
351290792Sgshapiro		**  maps thereby removing any shared memory used by
351390792Sgshapiro		**  the map.  Therefore, close the maps now so the
351490792Sgshapiro		**  child will dynamically open them if necessary.
351590792Sgshapiro		*/
351690792Sgshapiro
351790792Sgshapiro		closemaps(false);
351890792Sgshapiro
351990792Sgshapiro		pid = fork();
352090792Sgshapiro		if (pid < 0)
352190792Sgshapiro		{
352290792Sgshapiro			syserr("doworklist: cannot fork");
352390792Sgshapiro			return 0;
352490792Sgshapiro		}
352590792Sgshapiro		else if (pid > 0)
352690792Sgshapiro		{
352790792Sgshapiro			/* parent -- clean out connection cache */
352890792Sgshapiro			mci_flush(false, NULL);
352990792Sgshapiro		}
353090792Sgshapiro		else
353190792Sgshapiro		{
353290792Sgshapiro			/*
353390792Sgshapiro			**  Initialize exception stack and default exception
353490792Sgshapiro			**  handler for child process.
353590792Sgshapiro			*/
353690792Sgshapiro
353790792Sgshapiro			/* Reset global flags */
353890792Sgshapiro			RestartRequest = NULL;
353990792Sgshapiro			RestartWorkGroup = false;
354090792Sgshapiro			ShutdownRequest = NULL;
354190792Sgshapiro			PendingSignal = 0;
354290792Sgshapiro			CurrentPid = getpid();
354390792Sgshapiro			sm_exc_newthread(fatal_error);
354490792Sgshapiro
354590792Sgshapiro			/*
354690792Sgshapiro			**  See note above about SMTP processes and SIGCHLD.
354790792Sgshapiro			*/
354890792Sgshapiro
354990792Sgshapiro			if (OpMode == MD_SMTP ||
355090792Sgshapiro			    OpMode == MD_DAEMON ||
355190792Sgshapiro			    MaxQueueChildren > 0)
355290792Sgshapiro			{
355390792Sgshapiro				proc_list_clear();
355490792Sgshapiro				sm_releasesignal(SIGCHLD);
355590792Sgshapiro				(void) sm_signal(SIGCHLD, SIG_DFL);
355690792Sgshapiro			}
355790792Sgshapiro
355890792Sgshapiro			/* child -- error messages to the transcript */
355990792Sgshapiro			QuickAbort = OnlyOneError = false;
356090792Sgshapiro		}
356190792Sgshapiro	}
356290792Sgshapiro	else
356390792Sgshapiro	{
356490792Sgshapiro		pid = 0;
356590792Sgshapiro	}
356690792Sgshapiro
356790792Sgshapiro	if (pid != 0)
356890792Sgshapiro		return pid;
356990792Sgshapiro
357090792Sgshapiro	/*
357190792Sgshapiro	**  IN CHILD
357290792Sgshapiro	**	Lock the control file to avoid duplicate deliveries.
357390792Sgshapiro	**		Then run the file as though we had just read it.
357490792Sgshapiro	**	We save an idea of the temporary name so we
357590792Sgshapiro	**		can recover on interrupt.
357690792Sgshapiro	*/
357790792Sgshapiro
357890792Sgshapiro	if (forkflag)
357990792Sgshapiro	{
358090792Sgshapiro		/* Reset global flags */
358190792Sgshapiro		RestartRequest = NULL;
358290792Sgshapiro		RestartWorkGroup = false;
358390792Sgshapiro		ShutdownRequest = NULL;
358490792Sgshapiro		PendingSignal = 0;
358590792Sgshapiro	}
358690792Sgshapiro
358790792Sgshapiro	/* set basic modes, etc. */
358890792Sgshapiro	sm_clear_events();
358990792Sgshapiro	clearstats();
359090792Sgshapiro	GrabTo = UseErrorsTo = false;
359190792Sgshapiro	ExitStat = EX_OK;
359290792Sgshapiro	if (forkflag)
359390792Sgshapiro	{
359490792Sgshapiro		disconnect(1, el);
359590792Sgshapiro		set_op_mode(MD_QUEUERUN);
359690792Sgshapiro	}
359790792Sgshapiro	if (LogLevel > 76)
359890792Sgshapiro		sm_syslog(LOG_DEBUG, el->e_id, "doworklist, pid=%d",
359990792Sgshapiro			  (int) CurrentPid);
360090792Sgshapiro
360190792Sgshapiro	for (ei = el; ei != NULL; ei = ei->e_sibling)
360290792Sgshapiro	{
360390792Sgshapiro		ENVELOPE e;
360490792Sgshapiro		SM_RPOOL_T *rpool;
360590792Sgshapiro
360690792Sgshapiro		if (WILL_BE_QUEUED(ei->e_sendmode))
360790792Sgshapiro			continue;
360890792Sgshapiro#if _FFR_QUARANTINE
360990792Sgshapiro		else if (QueueMode != QM_QUARANTINE &&
361090792Sgshapiro			 ei->e_quarmsg != NULL)
361190792Sgshapiro			continue;
361290792Sgshapiro#endif /* _FFR_QUARANTINE */
361390792Sgshapiro
361490792Sgshapiro		rpool = sm_rpool_new_x(NULL);
361590792Sgshapiro		clearenvelope(&e, true, rpool);
361690792Sgshapiro		e.e_flags |= EF_QUEUERUN|EF_GLOBALERRS;
361790792Sgshapiro		set_delivery_mode(SM_DELIVER, &e);
361890792Sgshapiro		e.e_errormode = EM_MAIL;
361990792Sgshapiro		e.e_id = ei->e_id;
362090792Sgshapiro		e.e_qgrp = ei->e_qgrp;
362190792Sgshapiro		e.e_qdir = ei->e_qdir;
362290792Sgshapiro		openxscript(&e);
362390792Sgshapiro		sm_setproctitle(true, &e, "%s from queue", qid_printname(&e));
362490792Sgshapiro
362590792Sgshapiro		/* don't use the headers from sendmail.cf... */
362690792Sgshapiro		e.e_header = NULL;
362790792Sgshapiro		CurEnv = &e;
362890792Sgshapiro
362990792Sgshapiro		/* read the queue control file -- return if locked */
363090792Sgshapiro		if (readqf(&e, false))
363190792Sgshapiro		{
363290792Sgshapiro			e.e_flags |= EF_INQUEUE;
363390792Sgshapiro			eatheader(&e, requeueflag, true);
363490792Sgshapiro
363590792Sgshapiro			if (requeueflag)
363690792Sgshapiro				queueup(&e, false, false);
363790792Sgshapiro
363890792Sgshapiro			/* do the delivery */
363990792Sgshapiro			sendall(&e, SM_DELIVER);
364090792Sgshapiro			dropenvelope(&e, true, false);
364190792Sgshapiro		}
364290792Sgshapiro		else
364390792Sgshapiro		{
364490792Sgshapiro			if (tTd(40, 4) && e.e_id != NULL)
364590792Sgshapiro				sm_dprintf("readqf(%s) failed\n",
364690792Sgshapiro					qid_printname(&e));
364790792Sgshapiro		}
364890792Sgshapiro		sm_rpool_free(rpool);
364990792Sgshapiro		ei->e_id = NULL;
365090792Sgshapiro	}
365190792Sgshapiro
365290792Sgshapiro	/* restore CurEnv */
365390792Sgshapiro	CurEnv = el;
365490792Sgshapiro
365590792Sgshapiro	/* finish up and exit */
365690792Sgshapiro	if (forkflag)
365790792Sgshapiro		finis(true, true, ExitStat);
365890792Sgshapiro	return 0;
365990792Sgshapiro}
366090792Sgshapiro/*
366138032Speter**  READQF -- read queue file and set up environment.
366238032Speter**
366338032Speter**	Parameters:
366438032Speter**		e -- the envelope of the job to run.
366590792Sgshapiro**		openonly -- only open the qf (returned as e_lockfp)
366638032Speter**
366738032Speter**	Returns:
366890792Sgshapiro**		true if it successfully read the queue file.
366990792Sgshapiro**		false otherwise.
367038032Speter**
367138032Speter**	Side Effects:
367238032Speter**		The queue file is returned locked.
367338032Speter*/
367438032Speter
367564562Sgshapirostatic bool
367690792Sgshapiroreadqf(e, openonly)
367738032Speter	register ENVELOPE *e;
367890792Sgshapiro	bool openonly;
367938032Speter{
368090792Sgshapiro	register SM_FILE_T *qfp;
368138032Speter	ADDRESS *ctladdr;
368277349Sgshapiro	struct stat st, stf;
368338032Speter	char *bp;
368438032Speter	int qfver = 0;
368538032Speter	long hdrsize = 0;
368638032Speter	register char *p;
368790792Sgshapiro	char *frcpt = NULL;
368838032Speter	char *orcpt = NULL;
368990792Sgshapiro	bool nomore = false;
369090792Sgshapiro	bool bogus = false;
369164562Sgshapiro	MODE_T qsafe;
369264562Sgshapiro	char qf[MAXPATHLEN];
369338032Speter	char buf[MAXLINE];
369438032Speter
369538032Speter	/*
369638032Speter	**  Read and process the file.
369738032Speter	*/
369838032Speter
369990792Sgshapiro	(void) sm_strlcpy(qf, queuename(e, ANYQFL_LETTER), sizeof qf);
370090792Sgshapiro	qfp = sm_io_open(SmFtStdio, SM_TIME_DEFAULT, qf, SM_IO_RDWR, NULL);
370138032Speter	if (qfp == NULL)
370238032Speter	{
370364562Sgshapiro		int save_errno = errno;
370464562Sgshapiro
370538032Speter		if (tTd(40, 8))
370690792Sgshapiro			sm_dprintf("readqf(%s): sm_io_open failure (%s)\n",
370790792Sgshapiro				qf, sm_errstring(errno));
370864562Sgshapiro		errno = save_errno;
370964562Sgshapiro		if (errno != ENOENT
371064562Sgshapiro		    )
371138032Speter			syserr("readqf: no control file %s", qf);
371290792Sgshapiro		RELEASE_QUEUE;
371390792Sgshapiro		return false;
371438032Speter	}
371538032Speter
371690792Sgshapiro	if (!lockfile(sm_io_getinfo(qfp, SM_IO_WHAT_FD, NULL), qf, NULL,
371790792Sgshapiro		      LOCK_EX|LOCK_NB))
371838032Speter	{
371938032Speter		/* being processed by another queuer */
372064562Sgshapiro		if (Verbose)
372190792Sgshapiro			(void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
372290792Sgshapiro					     "%s: locked\n", e->e_id);
372364562Sgshapiro		if (tTd(40, 8))
372490792Sgshapiro			sm_dprintf("%s: locked\n", e->e_id);
372538032Speter		if (LogLevel > 19)
372638032Speter			sm_syslog(LOG_DEBUG, e->e_id, "locked");
372790792Sgshapiro		(void) sm_io_close(qfp, SM_TIME_DEFAULT);
372890792Sgshapiro		RELEASE_QUEUE;
372990792Sgshapiro		return false;
373038032Speter	}
373138032Speter
373238032Speter	/*
373377349Sgshapiro	**  Prevent locking race condition.
373477349Sgshapiro	**
373577349Sgshapiro	**  Process A: readqf(): qfp = fopen(qffile)
373677349Sgshapiro	**  Process B: queueup(): rename(tf, qf)
373777349Sgshapiro	**  Process B: unlocks(tf)
373877349Sgshapiro	**  Process A: lockfile(qf);
373977349Sgshapiro	**
374077349Sgshapiro	**  Process A (us) has the old qf file (before the rename deleted
374177349Sgshapiro	**  the directory entry) and will be delivering based on old data.
374277349Sgshapiro	**  This can lead to multiple deliveries of the same recipients.
374377349Sgshapiro	**
374477349Sgshapiro	**  Catch this by checking if the underlying qf file has changed
374577349Sgshapiro	**  *after* acquiring our lock and if so, act as though the file
374677349Sgshapiro	**  was still locked (i.e., just return like the lockfile() case
374777349Sgshapiro	**  above.
374838032Speter	*/
374938032Speter
375077349Sgshapiro	if (stat(qf, &stf) < 0 ||
375190792Sgshapiro	    fstat(sm_io_getinfo(qfp, SM_IO_WHAT_FD, NULL), &st) < 0)
375238032Speter	{
375338032Speter		/* must have been being processed by someone else */
375438032Speter		if (tTd(40, 8))
375590792Sgshapiro			sm_dprintf("readqf(%s): [f]stat failure (%s)\n",
375690792Sgshapiro				qf, sm_errstring(errno));
375790792Sgshapiro		(void) sm_io_close(qfp, SM_TIME_DEFAULT);
375890792Sgshapiro		RELEASE_QUEUE;
375990792Sgshapiro		return false;
376038032Speter	}
376138032Speter
376277349Sgshapiro	if (st.st_nlink != stf.st_nlink ||
376377349Sgshapiro	    st.st_dev != stf.st_dev ||
376490792Sgshapiro	    ST_INODE(st) != ST_INODE(stf) ||
376590792Sgshapiro#if HAS_ST_GEN && 0		/* AFS returns garbage in st_gen */
376677349Sgshapiro	    st.st_gen != stf.st_gen ||
376790792Sgshapiro#endif /* HAS_ST_GEN && 0 */
376877349Sgshapiro	    st.st_uid != stf.st_uid ||
376977349Sgshapiro	    st.st_gid != stf.st_gid ||
377077349Sgshapiro	    st.st_size != stf.st_size)
377177349Sgshapiro	{
377277349Sgshapiro		/* changed after opened */
377377349Sgshapiro		if (Verbose)
377490792Sgshapiro			(void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
377590792Sgshapiro					     "%s: changed\n", e->e_id);
377677349Sgshapiro		if (tTd(40, 8))
377790792Sgshapiro			sm_dprintf("%s: changed\n", e->e_id);
377877349Sgshapiro		if (LogLevel > 19)
377977349Sgshapiro			sm_syslog(LOG_DEBUG, e->e_id, "changed");
378090792Sgshapiro		(void) sm_io_close(qfp, SM_TIME_DEFAULT);
378190792Sgshapiro		RELEASE_QUEUE;
378290792Sgshapiro		return false;
378377349Sgshapiro	}
378477349Sgshapiro
378577349Sgshapiro	/*
378677349Sgshapiro	**  Check the queue file for plausibility to avoid attacks.
378777349Sgshapiro	*/
378877349Sgshapiro
378964562Sgshapiro	qsafe = S_IWOTH|S_IWGRP;
379064562Sgshapiro	if (bitset(S_IWGRP, QueueFileMode))
379164562Sgshapiro		qsafe &= ~S_IWGRP;
379264562Sgshapiro
379390792Sgshapiro	bogus = st.st_uid != geteuid() &&
379490792Sgshapiro		st.st_uid != TrustedUid &&
379590792Sgshapiro		geteuid() != RealUid;
379690792Sgshapiro
379790792Sgshapiro	/*
379890792Sgshapiro	**  If this qf file results from a set-group-ID binary, then
379990792Sgshapiro	**  we check whether the directory is group-writable,
380090792Sgshapiro	**  the queue file mode contains the group-writable bit, and
380190792Sgshapiro	**  the groups are the same.
380290792Sgshapiro	**  Notice: this requires that the set-group-ID binary is used to
380390792Sgshapiro	**  run the queue!
380490792Sgshapiro	*/
380590792Sgshapiro
380690792Sgshapiro	if (bogus && st.st_gid == getegid() && UseMSP)
380738032Speter	{
380890792Sgshapiro		char delim;
380990792Sgshapiro		struct stat dst;
381090792Sgshapiro
381190792Sgshapiro		bp = SM_LAST_DIR_DELIM(qf);
381290792Sgshapiro		if (bp == NULL)
381390792Sgshapiro			delim = '\0';
381490792Sgshapiro		else
381590792Sgshapiro		{
381690792Sgshapiro			delim = *bp;
381790792Sgshapiro			*bp = '\0';
381890792Sgshapiro		}
381990792Sgshapiro		if (stat(delim == '\0' ? "." : qf, &dst) < 0)
382090792Sgshapiro			syserr("readqf: cannot stat directory %s",
382190792Sgshapiro				delim == '\0' ? "." : qf);
382290792Sgshapiro		else
382390792Sgshapiro		{
382490792Sgshapiro			bogus = !(bitset(S_IWGRP, QueueFileMode) &&
382590792Sgshapiro				  bitset(S_IWGRP, dst.st_mode) &&
382690792Sgshapiro				  dst.st_gid == st.st_gid);
382790792Sgshapiro		}
382890792Sgshapiro		if (delim != '\0')
382990792Sgshapiro			*bp = delim;
383090792Sgshapiro	}
383190792Sgshapiro	if (!bogus)
383290792Sgshapiro		bogus = bitset(qsafe, st.st_mode);
383390792Sgshapiro	if (bogus)
383490792Sgshapiro	{
383538032Speter		if (LogLevel > 0)
383638032Speter		{
383738032Speter			sm_syslog(LOG_ALERT, e->e_id,
383890792Sgshapiro				  "bogus queue file, uid=%d, gid=%d, mode=%o",
383990792Sgshapiro				  st.st_uid, st.st_gid, st.st_mode);
384038032Speter		}
384138032Speter		if (tTd(40, 8))
384290792Sgshapiro			sm_dprintf("readqf(%s): bogus file\n", qf);
384390792Sgshapiro		e->e_flags |= EF_INQUEUE;
384490792Sgshapiro		if (!openonly)
384590792Sgshapiro			loseqfile(e, "bogus file uid/gid in mqueue");
384690792Sgshapiro		(void) sm_io_close(qfp, SM_TIME_DEFAULT);
384790792Sgshapiro		RELEASE_QUEUE;
384890792Sgshapiro		return false;
384938032Speter	}
385038032Speter
385138032Speter	if (st.st_size == 0)
385238032Speter	{
385338032Speter		/* must be a bogus file -- if also old, just remove it */
385490792Sgshapiro		if (!openonly && st.st_ctime + 10 * 60 < curtime())
385538032Speter		{
385690792Sgshapiro			(void) xunlink(queuename(e, DATAFL_LETTER));
385790792Sgshapiro			(void) xunlink(queuename(e, ANYQFL_LETTER));
385838032Speter		}
385990792Sgshapiro		(void) sm_io_close(qfp, SM_TIME_DEFAULT);
386090792Sgshapiro		RELEASE_QUEUE;
386190792Sgshapiro		return false;
386238032Speter	}
386338032Speter
386438032Speter	if (st.st_nlink == 0)
386538032Speter	{
386638032Speter		/*
386738032Speter		**  Race condition -- we got a file just as it was being
386838032Speter		**  unlinked.  Just assume it is zero length.
386938032Speter		*/
387038032Speter
387190792Sgshapiro		(void) sm_io_close(qfp, SM_TIME_DEFAULT);
387290792Sgshapiro		RELEASE_QUEUE;
387390792Sgshapiro		return false;
387438032Speter	}
387538032Speter
387690792Sgshapiro#if _FFR_TRUSTED_QF
387790792Sgshapiro	/*
387890792Sgshapiro	**  If we don't own the file mark it as unsafe.
387990792Sgshapiro	**  However, allow TrustedUser to own it as well
388090792Sgshapiro	**  in case TrustedUser manipulates the queue.
388190792Sgshapiro	*/
388290792Sgshapiro
388390792Sgshapiro	if (st.st_uid != geteuid() && st.st_uid != TrustedUid)
388490792Sgshapiro		e->e_flags |= EF_UNSAFE;
388590792Sgshapiro#else /* _FFR_TRUSTED_QF */
388690792Sgshapiro	/* If we don't own the file mark it as unsafe */
388790792Sgshapiro	if (st.st_uid != geteuid())
388890792Sgshapiro		e->e_flags |= EF_UNSAFE;
388990792Sgshapiro#endif /* _FFR_TRUSTED_QF */
389090792Sgshapiro
389138032Speter	/* good file -- save this lock */
389238032Speter	e->e_lockfp = qfp;
389338032Speter
389490792Sgshapiro	/* Just wanted the open file */
389590792Sgshapiro	if (openonly)
389690792Sgshapiro		return true;
389790792Sgshapiro
389838032Speter	/* do basic system initialization */
389938032Speter	initsys(e);
390090792Sgshapiro	macdefine(&e->e_macro, A_PERM, 'i', e->e_id);
390138032Speter
390238032Speter	LineNumber = 0;
390338032Speter	e->e_flags |= EF_GLOBALERRS;
390490792Sgshapiro	set_op_mode(MD_QUEUERUN);
390538032Speter	ctladdr = NULL;
390690792Sgshapiro#if _FFR_QUARANTINE
390790792Sgshapiro	e->e_qfletter = queue_letter(e, ANYQFL_LETTER);
390890792Sgshapiro#endif /* _FFR_QUARANTINE */
390990792Sgshapiro	e->e_dfqgrp = e->e_qgrp;
391090792Sgshapiro	e->e_dfqdir = e->e_qdir;
391190792Sgshapiro#if _FFR_QUEUE_MACRO
391290792Sgshapiro	macdefine(&e->e_macro, A_TEMP, macid("{queue}"),
391390792Sgshapiro		  qid_printqueue(e->e_qgrp, e->e_qdir));
391490792Sgshapiro#endif /* _FFR_QUEUE_MACRO */
391538032Speter	e->e_dfino = -1;
391638032Speter	e->e_msgsize = -1;
391790792Sgshapiro#if _FFR_QUEUEDELAY
391864562Sgshapiro	e->e_queuealg = QD_LINEAR;
391964562Sgshapiro	e->e_queuedelay = (time_t) 0;
392090792Sgshapiro#endif /* _FFR_QUEUEDELAY */
392138032Speter	while ((bp = fgetfolded(buf, sizeof buf, qfp)) != NULL)
392238032Speter	{
392390792Sgshapiro		unsigned long qflags;
392438032Speter		ADDRESS *q;
392590792Sgshapiro		int r;
392671345Sgshapiro		time_t now;
392738032Speter		auto char *ep;
392838032Speter
392938032Speter		if (tTd(40, 4))
393090792Sgshapiro			sm_dprintf("+++++ %s\n", bp);
393138032Speter		if (nomore)
393238032Speter		{
393338032Speter			/* hack attack */
393490792Sgshapiro  hackattack:
393590792Sgshapiro			syserr("SECURITY ALERT: extra or bogus data in queue file: %s",
393690792Sgshapiro			       bp);
393790792Sgshapiro			(void) sm_io_close(qfp, SM_TIME_DEFAULT);
393890792Sgshapiro
393990792Sgshapiro			/* the file is already on disk */
394090792Sgshapiro			e->e_flags |= EF_INQUEUE;
394138032Speter			loseqfile(e, "bogus queue line");
394290792Sgshapiro			RELEASE_QUEUE;
394390792Sgshapiro			return false;
394438032Speter		}
394538032Speter		switch (bp[0])
394638032Speter		{
394790792Sgshapiro		  case 'A':		/* AUTH= parameter */
394890792Sgshapiro			if (!xtextok(&bp[1]))
394990792Sgshapiro				goto hackattack;
395090792Sgshapiro			e->e_auth_param = sm_rpool_strdup_x(e->e_rpool, &bp[1]);
395190792Sgshapiro			break;
395238032Speter
395390792Sgshapiro		  case 'B':		/* body type */
395490792Sgshapiro			r = check_bodytype(&bp[1]);
395590792Sgshapiro			if (!BODYTYPE_VALID(r))
395690792Sgshapiro				goto hackattack;
395790792Sgshapiro			e->e_bodytype = sm_rpool_strdup_x(e->e_rpool, &bp[1]);
395890792Sgshapiro			break;
395990792Sgshapiro
396038032Speter		  case 'C':		/* specify controlling user */
396190792Sgshapiro			ctladdr = setctluser(&bp[1], qfver, e);
396238032Speter			break;
396338032Speter
396490792Sgshapiro		  case 'D':		/* data file name */
396590792Sgshapiro			/* obsolete -- ignore */
396638032Speter			break;
396738032Speter
396890792Sgshapiro		  case 'd':		/* data file directory name */
396938032Speter			{
397090792Sgshapiro				int qgrp, qdir;
397190792Sgshapiro
397290792Sgshapiro#if _FFR_MSP_PARANOIA
397390792Sgshapiro				/* forbid queue groups in MSP? */
397490792Sgshapiro				if (UseMSP)
397590792Sgshapiro					goto hackattack;
397690792Sgshapiro#endif /* _FFR_MSP_PARANOIA */
397790792Sgshapiro				for (qgrp = 0;
397890792Sgshapiro				     qgrp < NumQueue && Queue[qgrp] != NULL;
397990792Sgshapiro				     ++qgrp)
398038032Speter				{
398190792Sgshapiro					for (qdir = 0;
398290792Sgshapiro					     qdir < Queue[qgrp]->qg_numqueues;
398390792Sgshapiro					     ++qdir)
398438032Speter					{
398590792Sgshapiro						if (strcmp(&bp[1],
398690792Sgshapiro							   Queue[qgrp]->qg_qpaths[qdir].qp_name)
398790792Sgshapiro						    == 0)
398890792Sgshapiro						{
398990792Sgshapiro							e->e_dfqgrp = qgrp;
399090792Sgshapiro							e->e_dfqdir = qdir;
399190792Sgshapiro							goto done;
399290792Sgshapiro						}
399338032Speter					}
399438032Speter				}
399590792Sgshapiro				loseqfile(e, "bogus queue file directory");
399690792Sgshapiro				RELEASE_QUEUE;
399790792Sgshapiro				return false;
399890792Sgshapiro			  done:
399990792Sgshapiro				break;
400038032Speter			}
400138032Speter
400238032Speter		  case 'E':		/* specify error recipient */
400338032Speter			/* no longer used */
400438032Speter			break;
400538032Speter
400690792Sgshapiro		  case 'F':		/* flag bits */
400790792Sgshapiro			if (strncmp(bp, "From ", 5) == 0)
400890792Sgshapiro			{
400990792Sgshapiro				/* we are being spoofed! */
401090792Sgshapiro				syserr("SECURITY ALERT: bogus qf line %s", bp);
401190792Sgshapiro				(void) sm_io_close(qfp, SM_TIME_DEFAULT);
401290792Sgshapiro				loseqfile(e, "bogus queue line");
401390792Sgshapiro				RELEASE_QUEUE;
401490792Sgshapiro				return false;
401590792Sgshapiro			}
401690792Sgshapiro			for (p = &bp[1]; *p != '\0'; p++)
401790792Sgshapiro			{
401890792Sgshapiro				switch (*p)
401990792Sgshapiro				{
402090792Sgshapiro				  case '8':	/* has 8 bit data */
402190792Sgshapiro					e->e_flags |= EF_HAS8BIT;
402290792Sgshapiro					break;
402338032Speter
402490792Sgshapiro				  case 'b':	/* delete Bcc: header */
402590792Sgshapiro					e->e_flags |= EF_DELETE_BCC;
402690792Sgshapiro					break;
402738032Speter
402890792Sgshapiro				  case 'd':	/* envelope has DSN RET= */
402990792Sgshapiro					e->e_flags |= EF_RET_PARAM;
403090792Sgshapiro					break;
403138032Speter
403290792Sgshapiro				  case 'n':	/* don't return body */
403390792Sgshapiro					e->e_flags |= EF_NO_BODY_RETN;
403490792Sgshapiro					break;
403590792Sgshapiro
403690792Sgshapiro				  case 'r':	/* response */
403790792Sgshapiro					e->e_flags |= EF_RESPONSE;
403890792Sgshapiro					break;
403990792Sgshapiro
404090792Sgshapiro				  case 's':	/* split */
404190792Sgshapiro					e->e_flags |= EF_SPLIT;
404290792Sgshapiro					break;
404390792Sgshapiro
404490792Sgshapiro				  case 'w':	/* warning sent */
404590792Sgshapiro					e->e_flags |= EF_WARNING;
404690792Sgshapiro					break;
404790792Sgshapiro				}
404890792Sgshapiro			}
404938032Speter			break;
405038032Speter
405190792Sgshapiro#if _FFR_QUEUEDELAY
405290792Sgshapiro		  case 'G':		/* queue delay algorithm */
405390792Sgshapiro			e->e_queuealg = atoi(&buf[1]);
405438032Speter			break;
405590792Sgshapiro#endif /* _FFR_QUEUEDELAY */
405638032Speter
405790792Sgshapiro#if _FFR_QUARANTINE
405890792Sgshapiro		  case 'q':		/* quarantine reason */
405990792Sgshapiro			e->e_quarmsg = sm_rpool_strdup_x(e->e_rpool, &bp[1]);
406090792Sgshapiro			macdefine(&e->e_macro, A_PERM,
406190792Sgshapiro				  macid("{quarantine}"), e->e_quarmsg);
406238032Speter			break;
406390792Sgshapiro#endif /* _FFR_QUARANTINE */
406438032Speter
406590792Sgshapiro		  case 'H':		/* header */
406690792Sgshapiro			(void) chompheader(&bp[1], CHHDR_QUEUE, NULL, e);
406790792Sgshapiro			hdrsize += strlen(&bp[1]);
406838032Speter			break;
406938032Speter
407038032Speter		  case 'I':		/* data file's inode number */
407138032Speter			/* regenerated below */
407238032Speter			break;
407338032Speter
407480785Sgshapiro		  case 'K':		/* time of last delivery attempt */
407538032Speter			e->e_dtime = atol(&buf[1]);
407638032Speter			break;
407738032Speter
407890792Sgshapiro		  case 'L':		/* Solaris Content-Length: */
407990792Sgshapiro		  case 'M':		/* message */
408090792Sgshapiro			/* ignore this; we want a new message next time */
408164562Sgshapiro			break;
408264562Sgshapiro
408338032Speter		  case 'N':		/* number of delivery attempts */
408438032Speter			e->e_ntries = atoi(&buf[1]);
408538032Speter
408638032Speter			/* if this has been tried recently, let it be */
408771345Sgshapiro			now = curtime();
408871345Sgshapiro			if (e->e_ntries > 0 && e->e_dtime <= now &&
408971345Sgshapiro			    now < e->e_dtime + queuedelay(e))
409038032Speter			{
409164562Sgshapiro				char *howlong;
409238032Speter
409390792Sgshapiro				howlong = pintvl(now - e->e_dtime, true);
409464562Sgshapiro				if (Verbose)
409590792Sgshapiro					(void) sm_io_fprintf(smioout,
409690792Sgshapiro							     SM_TIME_DEFAULT,
409790792Sgshapiro							     "%s: too young (%s)\n",
409890792Sgshapiro							     e->e_id, howlong);
409964562Sgshapiro				if (tTd(40, 8))
410090792Sgshapiro					sm_dprintf("%s: too young (%s)\n",
410138032Speter						e->e_id, howlong);
410238032Speter				if (LogLevel > 19)
410338032Speter					sm_syslog(LOG_DEBUG, e->e_id,
410464562Sgshapiro						  "too young (%s)",
410564562Sgshapiro						  howlong);
410638032Speter				e->e_id = NULL;
410738032Speter				unlockqueue(e);
410890792Sgshapiro				RELEASE_QUEUE;
410990792Sgshapiro				return false;
411038032Speter			}
411190792Sgshapiro			macdefine(&e->e_macro, A_TEMP,
411290792Sgshapiro				macid("{ntries}"), &buf[1]);
411364562Sgshapiro
411490792Sgshapiro#if NAMED_BIND
411564562Sgshapiro			/* adjust BIND parameters immediately */
411664562Sgshapiro			if (e->e_ntries == 0)
411764562Sgshapiro			{
411864562Sgshapiro				_res.retry = TimeOuts.res_retry[RES_TO_FIRST];
411964562Sgshapiro				_res.retrans = TimeOuts.res_retrans[RES_TO_FIRST];
412064562Sgshapiro			}
412164562Sgshapiro			else
412264562Sgshapiro			{
412364562Sgshapiro				_res.retry = TimeOuts.res_retry[RES_TO_NORMAL];
412464562Sgshapiro				_res.retrans = TimeOuts.res_retrans[RES_TO_NORMAL];
412564562Sgshapiro			}
412690792Sgshapiro#endif /* NAMED_BIND */
412738032Speter			break;
412838032Speter
412938032Speter		  case 'P':		/* message priority */
413038032Speter			e->e_msgpriority = atol(&bp[1]) + WkTimeFact;
413138032Speter			break;
413238032Speter
413390792Sgshapiro		  case 'Q':		/* original recipient */
413490792Sgshapiro			orcpt = sm_rpool_strdup_x(e->e_rpool, &bp[1]);
413590792Sgshapiro			break;
413690792Sgshapiro
413790792Sgshapiro		  case 'r':		/* original recipient */
413890792Sgshapiro			frcpt = sm_rpool_strdup_x(e->e_rpool, &bp[1]);
413990792Sgshapiro			break;
414090792Sgshapiro
414190792Sgshapiro		  case 'R':		/* specify recipient */
414290792Sgshapiro			p = bp;
414390792Sgshapiro			qflags = 0;
414490792Sgshapiro			if (qfver >= 1)
414538032Speter			{
414690792Sgshapiro				/* get flag bits */
414790792Sgshapiro				while (*++p != '\0' && *p != ':')
414838032Speter				{
414990792Sgshapiro					switch (*p)
415090792Sgshapiro					{
415190792Sgshapiro					  case 'N':
415290792Sgshapiro						qflags |= QHASNOTIFY;
415390792Sgshapiro						break;
415438032Speter
415590792Sgshapiro					  case 'S':
415690792Sgshapiro						qflags |= QPINGONSUCCESS;
415790792Sgshapiro						break;
415838032Speter
415990792Sgshapiro					  case 'F':
416090792Sgshapiro						qflags |= QPINGONFAILURE;
416190792Sgshapiro						break;
416238032Speter
416390792Sgshapiro					  case 'D':
416490792Sgshapiro						qflags |= QPINGONDELAY;
416590792Sgshapiro						break;
416638032Speter
416790792Sgshapiro					  case 'P':
416890792Sgshapiro						qflags |= QPRIMARY;
416990792Sgshapiro						break;
417038032Speter
417190792Sgshapiro					  case 'A':
417290792Sgshapiro						if (ctladdr != NULL)
417390792Sgshapiro							ctladdr->q_flags |= QALIAS;
417490792Sgshapiro						break;
417590792Sgshapiro
417690792Sgshapiro					  default: /* ignore or complain? */
417790792Sgshapiro						break;
417890792Sgshapiro					}
417938032Speter				}
418038032Speter			}
418190792Sgshapiro			else
418290792Sgshapiro				qflags |= QPRIMARY;
418390792Sgshapiro			q = parseaddr(++p, NULLADDR, RF_COPYALL, '\0', NULL, e,
418490792Sgshapiro				      true);
418590792Sgshapiro			if (q != NULL)
418690792Sgshapiro			{
418790792Sgshapiro				q->q_alias = ctladdr;
418890792Sgshapiro				if (qfver >= 1)
418990792Sgshapiro					q->q_flags &= ~Q_PINGFLAGS;
419090792Sgshapiro				q->q_flags |= qflags;
419190792Sgshapiro				q->q_finalrcpt = frcpt;
419290792Sgshapiro				q->q_orcpt = orcpt;
419390792Sgshapiro				(void) recipient(q, &e->e_sendqueue, 0, e);
419490792Sgshapiro			}
419590792Sgshapiro			frcpt = NULL;
419690792Sgshapiro			orcpt = NULL;
419738032Speter			break;
419838032Speter
419990792Sgshapiro		  case 'S':		/* sender */
420090792Sgshapiro			setsender(sm_rpool_strdup_x(e->e_rpool, &bp[1]),
420190792Sgshapiro				  e, NULL, '\0', true);
420238032Speter			break;
420338032Speter
420490792Sgshapiro		  case 'T':		/* init time */
420590792Sgshapiro			e->e_ctime = atol(&bp[1]);
420664562Sgshapiro			break;
420764562Sgshapiro
420890792Sgshapiro		  case 'V':		/* queue file version number */
420990792Sgshapiro			qfver = atoi(&bp[1]);
421090792Sgshapiro			if (queuedelay_qfver_unsupported(qfver))
421190792Sgshapiro				syserr("queue file version %d not supported: %s",
421290792Sgshapiro				       qfver,
421390792Sgshapiro				       "sendmail not compiled with _FFR_QUEUEDELAY");
421490792Sgshapiro			if (qfver <= QF_VERSION)
421590792Sgshapiro				break;
421690792Sgshapiro			syserr("Version number in queue file (%d) greater than max (%d)",
421790792Sgshapiro				qfver, QF_VERSION);
421890792Sgshapiro			(void) sm_io_close(qfp, SM_TIME_DEFAULT);
421990792Sgshapiro			loseqfile(e, "unsupported queue file version");
422090792Sgshapiro			RELEASE_QUEUE;
422190792Sgshapiro			return false;
422290792Sgshapiro			/* NOTREACHED */
422390792Sgshapiro			break;
422490792Sgshapiro
422590792Sgshapiro#if _FFR_QUEUEDELAY
422690792Sgshapiro		  case 'Y':		/* current delay */
422790792Sgshapiro			e->e_queuedelay = (time_t) atol(&buf[1]);
422890792Sgshapiro			break;
422990792Sgshapiro#endif /* _FFR_QUEUEDELAY */
423090792Sgshapiro
423190792Sgshapiro		  case 'Z':		/* original envelope id from ESMTP */
423290792Sgshapiro			e->e_envid = sm_rpool_strdup_x(e->e_rpool, &bp[1]);
423390792Sgshapiro			macdefine(&e->e_macro, A_PERM,
423490792Sgshapiro				macid("{dsn_envid}"), e->e_envid);
423590792Sgshapiro			break;
423690792Sgshapiro
423790792Sgshapiro		  case '!':		/* deliver by */
423890792Sgshapiro
423990792Sgshapiro			/* format: flag (1 char) space long-integer */
424090792Sgshapiro			e->e_dlvr_flag = buf[1];
424190792Sgshapiro			e->e_deliver_by = strtol(&buf[3], NULL, 10);
424290792Sgshapiro
424338032Speter		  case '$':		/* define macro */
424464562Sgshapiro			{
424564562Sgshapiro				char *p;
424664562Sgshapiro
424790792Sgshapiro				/* XXX elimate p? */
424890792Sgshapiro				r = macid_parse(&bp[1], &ep);
424990792Sgshapiro				if (r == 0)
425071345Sgshapiro					break;
425190792Sgshapiro				p = sm_rpool_strdup_x(e->e_rpool, ep);
425290792Sgshapiro				macdefine(&e->e_macro, A_PERM, r, p);
425364562Sgshapiro			}
425438032Speter			break;
425538032Speter
425638032Speter		  case '.':		/* terminate file */
425790792Sgshapiro			nomore = true;
425838032Speter			break;
425938032Speter
426038032Speter		  default:
426138032Speter			syserr("readqf: %s: line %d: bad line \"%s\"",
426238032Speter				qf, LineNumber, shortenstring(bp, MAXSHORTSTR));
426390792Sgshapiro			(void) sm_io_close(qfp, SM_TIME_DEFAULT);
426438032Speter			loseqfile(e, "unrecognized line");
426590792Sgshapiro			RELEASE_QUEUE;
426690792Sgshapiro			return false;
426738032Speter		}
426838032Speter
426938032Speter		if (bp != buf)
427090792Sgshapiro			sm_free(bp); /* XXX */
427138032Speter	}
427238032Speter
427338032Speter	/*
427438032Speter	**  If we haven't read any lines, this queue file is empty.
427538032Speter	**  Arrange to remove it without referencing any null pointers.
427638032Speter	*/
427738032Speter
427838032Speter	if (LineNumber == 0)
427938032Speter	{
428038032Speter		errno = 0;
428190792Sgshapiro		e->e_flags |= EF_CLRQUEUE|EF_FATALERRS|EF_RESPONSE;
428290792Sgshapiro		RELEASE_QUEUE;
428390792Sgshapiro		return true;
428438032Speter	}
428538032Speter
428690792Sgshapiro	/* Check to make sure we have a complete queue file read */
428790792Sgshapiro	if (!nomore)
428890792Sgshapiro	{
428990792Sgshapiro		syserr("readqf: %s: incomplete queue file read", qf);
429090792Sgshapiro		(void) sm_io_close(qfp, SM_TIME_DEFAULT);
429190792Sgshapiro		RELEASE_QUEUE;
429290792Sgshapiro		return false;
429390792Sgshapiro	}
429490792Sgshapiro
429564562Sgshapiro	/* possibly set ${dsn_ret} macro */
429664562Sgshapiro	if (bitset(EF_RET_PARAM, e->e_flags))
429764562Sgshapiro	{
429864562Sgshapiro		if (bitset(EF_NO_BODY_RETN, e->e_flags))
429990792Sgshapiro			macdefine(&e->e_macro, A_PERM,
430090792Sgshapiro				macid("{dsn_ret}"), "hdrs");
430164562Sgshapiro		else
430290792Sgshapiro			macdefine(&e->e_macro, A_PERM,
430390792Sgshapiro				macid("{dsn_ret}"), "full");
430464562Sgshapiro	}
430564562Sgshapiro
430638032Speter	/*
430738032Speter	**  Arrange to read the data file.
430838032Speter	*/
430938032Speter
431090792Sgshapiro	p = queuename(e, DATAFL_LETTER);
431190792Sgshapiro	e->e_dfp = sm_io_open(SmFtStdio, SM_TIME_DEFAULT, p, SM_IO_RDONLY,
431290792Sgshapiro			      NULL);
431338032Speter	if (e->e_dfp == NULL)
431438032Speter	{
431538032Speter		syserr("readqf: cannot open %s", p);
431638032Speter	}
431738032Speter	else
431838032Speter	{
431938032Speter		e->e_flags |= EF_HAS_DF;
432090792Sgshapiro		if (fstat(sm_io_getinfo(e->e_dfp, SM_IO_WHAT_FD, NULL), &st)
432190792Sgshapiro		    >= 0)
432238032Speter		{
432338032Speter			e->e_msgsize = st.st_size + hdrsize;
432438032Speter			e->e_dfdev = st.st_dev;
432590792Sgshapiro			e->e_dfino = ST_INODE(st);
432638032Speter		}
432738032Speter	}
432838032Speter
432990792Sgshapiro	RELEASE_QUEUE;
433090792Sgshapiro	return true;
433138032Speter}
433290792Sgshapiro/*
433364562Sgshapiro**  PRTSTR -- print a string, "unprintable" characters are shown as \oct
433464562Sgshapiro**
433564562Sgshapiro**	Parameters:
433664562Sgshapiro**		s -- string to print
433764562Sgshapiro**		ml -- maximum length of output
433864562Sgshapiro**
433964562Sgshapiro**	Returns:
434090792Sgshapiro**		number of entries
434164562Sgshapiro**
434264562Sgshapiro**	Side Effects:
434364562Sgshapiro**		Prints a string on stdout.
434464562Sgshapiro*/
434564562Sgshapiro
434664562Sgshapirostatic void
434764562Sgshapiroprtstr(s, ml)
434864562Sgshapiro	char *s;
434964562Sgshapiro	int ml;
435064562Sgshapiro{
435190792Sgshapiro	int c;
435264562Sgshapiro
435364562Sgshapiro	if (s == NULL)
435464562Sgshapiro		return;
435564562Sgshapiro	while (ml-- > 0 && ((c = *s++) != '\0'))
435664562Sgshapiro	{
435764562Sgshapiro		if (c == '\\')
435864562Sgshapiro		{
435964562Sgshapiro			if (ml-- > 0)
436064562Sgshapiro			{
436190792Sgshapiro				(void) sm_io_putc(smioout, SM_TIME_DEFAULT, c);
436290792Sgshapiro				(void) sm_io_putc(smioout, SM_TIME_DEFAULT, c);
436364562Sgshapiro			}
436464562Sgshapiro		}
436564562Sgshapiro		else if (isascii(c) && isprint(c))
436690792Sgshapiro			(void) sm_io_putc(smioout, SM_TIME_DEFAULT, c);
436764562Sgshapiro		else
436864562Sgshapiro		{
436964562Sgshapiro			if ((ml -= 3) > 0)
437090792Sgshapiro				(void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
437190792Sgshapiro						     "\\%03o", c & 0xFF);
437264562Sgshapiro		}
437364562Sgshapiro	}
437464562Sgshapiro}
437590792Sgshapiro/*
437690792Sgshapiro**  PRINTNQE -- print out number of entries in the mail queue
437790792Sgshapiro**
437890792Sgshapiro**	Parameters:
437990792Sgshapiro**		out -- output file pointer.
438090792Sgshapiro**		prefix -- string to output in front of each line.
438190792Sgshapiro**
438290792Sgshapiro**	Returns:
438390792Sgshapiro**		none.
438490792Sgshapiro*/
438590792Sgshapiro
438690792Sgshapirovoid
438790792Sgshapiroprintnqe(out, prefix)
438890792Sgshapiro	SM_FILE_T *out;
438990792Sgshapiro	char *prefix;
439090792Sgshapiro{
439190792Sgshapiro#if SM_CONF_SHM
439290792Sgshapiro	int i, k = 0, nrequests = 0;
439390792Sgshapiro	bool unknown = false;
439490792Sgshapiro
439590792Sgshapiro	if (ShmId == SM_SHM_NO_ID)
439690792Sgshapiro	{
439790792Sgshapiro		if (prefix == NULL)
439890792Sgshapiro			(void) sm_io_fprintf(out, SM_TIME_DEFAULT,
439990792Sgshapiro					"Data unavailable: shared memory not updated\n");
440090792Sgshapiro		else
440190792Sgshapiro			(void) sm_io_fprintf(out, SM_TIME_DEFAULT,
440290792Sgshapiro					"%sNOTCONFIGURED:-1\r\n", prefix);
440390792Sgshapiro		return;
440490792Sgshapiro	}
440590792Sgshapiro	for (i = 0; i < NumQueue && Queue[i] != NULL; i++)
440690792Sgshapiro	{
440790792Sgshapiro		int j;
440890792Sgshapiro
440990792Sgshapiro		k++;
441090792Sgshapiro		for (j = 0; j < Queue[i]->qg_numqueues; j++)
441190792Sgshapiro		{
441290792Sgshapiro			int n;
441390792Sgshapiro
441490792Sgshapiro			if (StopRequest)
441590792Sgshapiro				stop_sendmail();
441690792Sgshapiro
441790792Sgshapiro			n = QSHM_ENTRIES(Queue[i]->qg_qpaths[j].qp_idx);
441890792Sgshapiro			if (prefix != NULL)
441990792Sgshapiro				(void) sm_io_fprintf(out, SM_TIME_DEFAULT,
442090792Sgshapiro					"%s%s:%d\r\n",
442190792Sgshapiro					prefix, qid_printqueue(i, j), n);
442290792Sgshapiro			else if (n < 0)
442390792Sgshapiro			{
442490792Sgshapiro				(void) sm_io_fprintf(out, SM_TIME_DEFAULT,
442590792Sgshapiro					"%s: unknown number of entries\n",
442690792Sgshapiro					qid_printqueue(i, j));
442790792Sgshapiro				unknown = true;
442890792Sgshapiro			}
442990792Sgshapiro			else if (n == 0)
443090792Sgshapiro			{
443190792Sgshapiro				(void) sm_io_fprintf(out, SM_TIME_DEFAULT,
443290792Sgshapiro					"%s is empty\n",
443390792Sgshapiro					qid_printqueue(i, j));
443490792Sgshapiro			}
443590792Sgshapiro			else if (n > 0)
443690792Sgshapiro			{
443790792Sgshapiro				(void) sm_io_fprintf(out, SM_TIME_DEFAULT,
443890792Sgshapiro					"%s: entries=%d\n",
443990792Sgshapiro					qid_printqueue(i, j), n);
444090792Sgshapiro				nrequests += n;
444190792Sgshapiro				k++;
444290792Sgshapiro			}
444390792Sgshapiro		}
444490792Sgshapiro	}
444590792Sgshapiro	if (prefix == NULL && k > 1)
444690792Sgshapiro		(void) sm_io_fprintf(out, SM_TIME_DEFAULT,
444790792Sgshapiro				     "\t\tTotal requests: %d%s\n",
444890792Sgshapiro				     nrequests, unknown ? " (about)" : "");
444990792Sgshapiro#else /* SM_CONF_SHM */
445090792Sgshapiro	if (prefix == NULL)
445190792Sgshapiro		(void) sm_io_fprintf(out, SM_TIME_DEFAULT,
445290792Sgshapiro			     "Data unavailable without shared memory support\n");
445390792Sgshapiro	else
445490792Sgshapiro		(void) sm_io_fprintf(out, SM_TIME_DEFAULT,
445590792Sgshapiro			     "%sNOTAVAILABLE:-1\r\n", prefix);
445690792Sgshapiro#endif /* SM_CONF_SHM */
445790792Sgshapiro}
445890792Sgshapiro/*
445938032Speter**  PRINTQUEUE -- print out a representation of the mail queue
446038032Speter**
446138032Speter**	Parameters:
446238032Speter**		none.
446338032Speter**
446438032Speter**	Returns:
446538032Speter**		none.
446638032Speter**
446738032Speter**	Side Effects:
446838032Speter**		Prints a listing of the mail queue on the standard output.
446938032Speter*/
447038032Speter
447138032Spetervoid
447238032Speterprintqueue()
447338032Speter{
447490792Sgshapiro	int i, k = 0, nrequests = 0;
447564562Sgshapiro
447690792Sgshapiro	for (i = 0; i < NumQueue && Queue[i] != NULL; i++)
447777349Sgshapiro	{
447890792Sgshapiro		int j;
447990792Sgshapiro
448090792Sgshapiro		k++;
448190792Sgshapiro		for (j = 0; j < Queue[i]->qg_numqueues; j++)
448290792Sgshapiro		{
448390792Sgshapiro			if (StopRequest)
448490792Sgshapiro				stop_sendmail();
448590792Sgshapiro			nrequests += print_single_queue(i, j);
448690792Sgshapiro			k++;
448790792Sgshapiro		}
448877349Sgshapiro	}
448990792Sgshapiro	if (k > 1)
449090792Sgshapiro		(void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
449190792Sgshapiro				     "\t\tTotal requests: %d\n",
449290792Sgshapiro				     nrequests);
449364562Sgshapiro}
449490792Sgshapiro/*
449564562Sgshapiro**  PRINT_SINGLE_QUEUE -- print out a representation of a single mail queue
449664562Sgshapiro**
449764562Sgshapiro**	Parameters:
449890792Sgshapiro**		qgrp -- the index of the queue group.
449990792Sgshapiro**		qdir -- the queue directory.
450064562Sgshapiro**
450164562Sgshapiro**	Returns:
450290792Sgshapiro**		number of requests in mail queue.
450364562Sgshapiro**
450464562Sgshapiro**	Side Effects:
450564562Sgshapiro**		Prints a listing of the mail queue on the standard output.
450664562Sgshapiro*/
450764562Sgshapiro
450890792Sgshapiroint
450990792Sgshapiroprint_single_queue(qgrp, qdir)
451090792Sgshapiro	int qgrp;
451190792Sgshapiro	int qdir;
451264562Sgshapiro{
451338032Speter	register WORK *w;
451490792Sgshapiro	SM_FILE_T *f;
451538032Speter	int nrequests;
451664562Sgshapiro	char qd[MAXPATHLEN];
451764562Sgshapiro	char qddf[MAXPATHLEN];
451838032Speter	char buf[MAXLINE];
451938032Speter
452090792Sgshapiro	if (qdir == NOQDIR)
452164562Sgshapiro	{
452290792Sgshapiro		(void) sm_strlcpy(qd, ".", sizeof qd);
452390792Sgshapiro		(void) sm_strlcpy(qddf, ".", sizeof qddf);
452464562Sgshapiro	}
452564562Sgshapiro	else
452664562Sgshapiro	{
452790792Sgshapiro		(void) sm_strlcpyn(qd, sizeof qd, 2,
452890792Sgshapiro			Queue[qgrp]->qg_qpaths[qdir].qp_name,
452990792Sgshapiro			(bitset(QP_SUBQF,
453090792Sgshapiro				Queue[qgrp]->qg_qpaths[qdir].qp_subdirs)
453190792Sgshapiro					? "/qf" : ""));
453290792Sgshapiro		(void) sm_strlcpyn(qddf, sizeof qddf, 2,
453390792Sgshapiro			Queue[qgrp]->qg_qpaths[qdir].qp_name,
453490792Sgshapiro			(bitset(QP_SUBDF,
453590792Sgshapiro				Queue[qgrp]->qg_qpaths[qdir].qp_subdirs)
453690792Sgshapiro					? "/df" : ""));
453764562Sgshapiro	}
453864562Sgshapiro
453938032Speter	/*
454038032Speter	**  Check for permission to print the queue
454138032Speter	*/
454238032Speter
454338032Speter	if (bitset(PRIV_RESTRICTMAILQ, PrivacyFlags) && RealUid != 0)
454438032Speter	{
454538032Speter		struct stat st;
454690792Sgshapiro#ifdef NGROUPS_MAX
454738032Speter		int n;
454838032Speter		extern GIDSET_T InitialGidSet[NGROUPS_MAX];
454990792Sgshapiro#endif /* NGROUPS_MAX */
455038032Speter
455164562Sgshapiro		if (stat(qd, &st) < 0)
455238032Speter		{
455390792Sgshapiro			syserr("Cannot stat %s",
455490792Sgshapiro				qid_printqueue(qgrp, qdir));
455564562Sgshapiro			return 0;
455638032Speter		}
455790792Sgshapiro#ifdef NGROUPS_MAX
455838032Speter		n = NGROUPS_MAX;
455938032Speter		while (--n >= 0)
456038032Speter		{
456138032Speter			if (InitialGidSet[n] == st.st_gid)
456238032Speter				break;
456338032Speter		}
456438032Speter		if (n < 0 && RealGid != st.st_gid)
456590792Sgshapiro#else /* NGROUPS_MAX */
456638032Speter		if (RealGid != st.st_gid)
456790792Sgshapiro#endif /* NGROUPS_MAX */
456838032Speter		{
456938032Speter			usrerr("510 You are not permitted to see the queue");
457038032Speter			setstat(EX_NOPERM);
457164562Sgshapiro			return 0;
457238032Speter		}
457338032Speter	}
457438032Speter
457538032Speter	/*
457638032Speter	**  Read and order the queue.
457738032Speter	*/
457838032Speter
457990792Sgshapiro	nrequests = gatherq(qgrp, qdir, true, NULL, NULL);
458090792Sgshapiro	(void) sortq(Queue[qgrp]->qg_maxlist);
458138032Speter
458238032Speter	/*
458338032Speter	**  Print the work list that we have read.
458438032Speter	*/
458538032Speter
458638032Speter	/* first see if there is anything */
458738032Speter	if (nrequests <= 0)
458838032Speter	{
458990792Sgshapiro		(void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "%s is empty\n",
459090792Sgshapiro				     qid_printqueue(qgrp, qdir));
459164562Sgshapiro		return 0;
459238032Speter	}
459338032Speter
459490792Sgshapiro	sm_getla();	/* get load average */
459538032Speter
459690792Sgshapiro	(void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "\t\t%s (%d request%s",
459790792Sgshapiro			     qid_printqueue(qgrp, qdir),
459890792Sgshapiro			     nrequests, nrequests == 1 ? "" : "s");
459938032Speter	if (MaxQueueRun > 0 && nrequests > MaxQueueRun)
460090792Sgshapiro		(void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
460190792Sgshapiro				     ", only %d printed", MaxQueueRun);
460238032Speter	if (Verbose)
460390792Sgshapiro		(void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
460490792Sgshapiro			")\n-----Q-ID----- --Size-- -Priority- ---Q-Time--- --------Sender/Recipient--------\n");
460538032Speter	else
460690792Sgshapiro		(void) sm_io_fprintf(smioout,  SM_TIME_DEFAULT,
460790792Sgshapiro			")\n-----Q-ID----- --Size-- -----Q-Time----- ------------Sender/Recipient-----------\n");
460838032Speter	for (w = WorkQ; w != NULL; w = w->w_next)
460938032Speter	{
461038032Speter		struct stat st;
461138032Speter		auto time_t submittime = 0;
461238032Speter		long dfsize;
461338032Speter		int flags = 0;
461438032Speter		int qfver;
461590792Sgshapiro#if _FFR_QUARANTINE
461690792Sgshapiro		char quarmsg[MAXLINE];
461790792Sgshapiro#endif /* _FFR_QUARANTINE */
461838032Speter		char statmsg[MAXLINE];
461938032Speter		char bodytype[MAXNAME + 1];
462064562Sgshapiro		char qf[MAXPATHLEN];
462138032Speter
462277349Sgshapiro		if (StopRequest)
462377349Sgshapiro			stop_sendmail();
462477349Sgshapiro
462590792Sgshapiro		(void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "%13s",
462690792Sgshapiro				     w->w_name + 2);
462790792Sgshapiro		(void) sm_strlcpyn(qf, sizeof qf, 3, qd, "/", w->w_name);
462890792Sgshapiro		f = sm_io_open(SmFtStdio, SM_TIME_DEFAULT, qf, SM_IO_RDONLY,
462990792Sgshapiro			       NULL);
463038032Speter		if (f == NULL)
463138032Speter		{
463290792Sgshapiro			if (errno == EPERM)
463390792Sgshapiro				(void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
463490792Sgshapiro						     " (permission denied)\n");
463590792Sgshapiro			else if (errno == ENOENT)
463690792Sgshapiro				(void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
463790792Sgshapiro						     " (job completed)\n");
463890792Sgshapiro			else
463990792Sgshapiro				(void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
464090792Sgshapiro						     " (%s)\n",
464190792Sgshapiro						     sm_errstring(errno));
464238032Speter			errno = 0;
464338032Speter			continue;
464438032Speter		}
464590792Sgshapiro		w->w_name[0] = DATAFL_LETTER;
464690792Sgshapiro		(void) sm_strlcpyn(qf, sizeof qf, 3, qddf, "/", w->w_name);
464764562Sgshapiro		if (stat(qf, &st) >= 0)
464838032Speter			dfsize = st.st_size;
464938032Speter		else
465090792Sgshapiro		{
465190792Sgshapiro			ENVELOPE e;
465290792Sgshapiro
465390792Sgshapiro			/*
465490792Sgshapiro			**  Maybe the df file can't be statted because
465590792Sgshapiro			**  it is in a different directory than the qf file.
465690792Sgshapiro			**  In order to find out, we must read the qf file.
465790792Sgshapiro			*/
465890792Sgshapiro
465990792Sgshapiro			newenvelope(&e, &BlankEnvelope, sm_rpool_new_x(NULL));
466090792Sgshapiro			e.e_id = w->w_name + 2;
466190792Sgshapiro			e.e_qgrp = qgrp;
466290792Sgshapiro			e.e_qdir = qdir;
466338032Speter			dfsize = -1;
466490792Sgshapiro			if (readqf(&e, false))
466590792Sgshapiro			{
466690792Sgshapiro				char *df = queuename(&e, DATAFL_LETTER);
466790792Sgshapiro				if (stat(df, &st) >= 0)
466890792Sgshapiro					dfsize = st.st_size;
466990792Sgshapiro			}
467090792Sgshapiro			if (e.e_lockfp != NULL)
467190792Sgshapiro			{
467290792Sgshapiro				(void) sm_io_close(e.e_lockfp, SM_TIME_DEFAULT);
467390792Sgshapiro				e.e_lockfp = NULL;
467490792Sgshapiro			}
467590792Sgshapiro			clearenvelope(&e, false, e.e_rpool);
467690792Sgshapiro			sm_rpool_free(e.e_rpool);
467790792Sgshapiro		}
467838032Speter		if (w->w_lock)
467990792Sgshapiro			(void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "*");
468090792Sgshapiro#if _FFR_QUARANTINE
468190792Sgshapiro		else if (QueueMode == QM_LOST)
468290792Sgshapiro			(void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "?");
468390792Sgshapiro#endif /* _FFR_QUARANTINE */
468438032Speter		else if (w->w_tooyoung)
468590792Sgshapiro			(void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "-");
468638032Speter		else if (shouldqueue(w->w_pri, w->w_ctime))
468790792Sgshapiro			(void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "X");
468838032Speter		else
468990792Sgshapiro			(void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, " ");
469090792Sgshapiro
469138032Speter		errno = 0;
469238032Speter
469390792Sgshapiro#if _FFR_QUARANTINE
469490792Sgshapiro		quarmsg[0] = '\0';
469590792Sgshapiro#endif /* _FFR_QUARANTINE */
469638032Speter		statmsg[0] = bodytype[0] = '\0';
469738032Speter		qfver = 0;
469890792Sgshapiro		while (sm_io_fgets(f, SM_TIME_DEFAULT, buf, sizeof buf) != NULL)
469938032Speter		{
470038032Speter			register int i;
470138032Speter			register char *p;
470238032Speter
470377349Sgshapiro			if (StopRequest)
470477349Sgshapiro				stop_sendmail();
470577349Sgshapiro
470690792Sgshapiro			fixcrlf(buf, true);
470738032Speter			switch (buf[0])
470838032Speter			{
470938032Speter			  case 'V':	/* queue file version */
471038032Speter				qfver = atoi(&buf[1]);
471138032Speter				break;
471238032Speter
471338032Speter			  case 'M':	/* error message */
471438032Speter				if ((i = strlen(&buf[1])) >= sizeof statmsg)
471538032Speter					i = sizeof statmsg - 1;
471664562Sgshapiro				memmove(statmsg, &buf[1], i);
471738032Speter				statmsg[i] = '\0';
471838032Speter				break;
471938032Speter
472090792Sgshapiro#if _FFR_QUARANTINE
472190792Sgshapiro			  case 'q':	/* quarantine reason */
472290792Sgshapiro				if ((i = strlen(&buf[1])) >= sizeof quarmsg)
472390792Sgshapiro					i = sizeof quarmsg - 1;
472490792Sgshapiro				memmove(quarmsg, &buf[1], i);
472590792Sgshapiro				quarmsg[i] = '\0';
472690792Sgshapiro				break;
472790792Sgshapiro#endif /* _FFR_QUARANTINE */
472890792Sgshapiro
472938032Speter			  case 'B':	/* body type */
473038032Speter				if ((i = strlen(&buf[1])) >= sizeof bodytype)
473138032Speter					i = sizeof bodytype - 1;
473264562Sgshapiro				memmove(bodytype, &buf[1], i);
473338032Speter				bodytype[i] = '\0';
473438032Speter				break;
473538032Speter
473638032Speter			  case 'S':	/* sender name */
473738032Speter				if (Verbose)
473864562Sgshapiro				{
473990792Sgshapiro					(void) sm_io_fprintf(smioout,
474090792Sgshapiro						SM_TIME_DEFAULT,
474190792Sgshapiro						"%8ld %10ld%c%.12s ",
474290792Sgshapiro						dfsize,
474390792Sgshapiro						w->w_pri,
474490792Sgshapiro						bitset(EF_WARNING, flags)
474590792Sgshapiro							? '+' : ' ',
474690792Sgshapiro						ctime(&submittime) + 4);
474764562Sgshapiro					prtstr(&buf[1], 78);
474864562Sgshapiro				}
474938032Speter				else
475064562Sgshapiro				{
475190792Sgshapiro					(void) sm_io_fprintf(smioout,
475290792Sgshapiro						SM_TIME_DEFAULT,
475390792Sgshapiro						"%8ld %.16s ",
475490792Sgshapiro						dfsize,
475590792Sgshapiro						ctime(&submittime));
475690792Sgshapiro					prtstr(&buf[1], 39);
475764562Sgshapiro				}
475890792Sgshapiro#if _FFR_QUARANTINE
475990792Sgshapiro				if (quarmsg[0] != '\0')
476090792Sgshapiro				{
476190792Sgshapiro					(void) sm_io_fprintf(smioout,
476290792Sgshapiro							     SM_TIME_DEFAULT,
476390792Sgshapiro							     "\n     QUARANTINE: %.*s",
476490792Sgshapiro							     Verbose ? 100 : 60,
476590792Sgshapiro							     quarmsg);
476690792Sgshapiro					quarmsg[0] = '\0';
476790792Sgshapiro				}
476890792Sgshapiro#endif /* _FFR_QUARANTINE */
476938032Speter				if (statmsg[0] != '\0' || bodytype[0] != '\0')
477038032Speter				{
477190792Sgshapiro					(void) sm_io_fprintf(smioout,
477290792Sgshapiro						SM_TIME_DEFAULT,
477390792Sgshapiro						"\n    %10.10s",
477490792Sgshapiro						bodytype);
477538032Speter					if (statmsg[0] != '\0')
477690792Sgshapiro						(void) sm_io_fprintf(smioout,
477790792Sgshapiro							SM_TIME_DEFAULT,
477890792Sgshapiro							"   (%.*s)",
477990792Sgshapiro							Verbose ? 100 : 60,
478090792Sgshapiro							statmsg);
478190792Sgshapiro					statmsg[0] = '\0';
478238032Speter				}
478338032Speter				break;
478438032Speter
478538032Speter			  case 'C':	/* controlling user */
478638032Speter				if (Verbose)
478790792Sgshapiro					(void) sm_io_fprintf(smioout,
478890792Sgshapiro						SM_TIME_DEFAULT,
478990792Sgshapiro						"\n\t\t\t\t\t\t(---%.64s---)",
479090792Sgshapiro						&buf[1]);
479138032Speter				break;
479238032Speter
479338032Speter			  case 'R':	/* recipient name */
479438032Speter				p = &buf[1];
479538032Speter				if (qfver >= 1)
479638032Speter				{
479738032Speter					p = strchr(p, ':');
479838032Speter					if (p == NULL)
479938032Speter						break;
480038032Speter					p++;
480138032Speter				}
480238032Speter				if (Verbose)
480364562Sgshapiro				{
480490792Sgshapiro					(void) sm_io_fprintf(smioout,
480590792Sgshapiro							SM_TIME_DEFAULT,
480690792Sgshapiro							"\n\t\t\t\t\t\t");
480790792Sgshapiro					prtstr(p, 71);
480864562Sgshapiro				}
480938032Speter				else
481064562Sgshapiro				{
481190792Sgshapiro					(void) sm_io_fprintf(smioout,
481290792Sgshapiro							SM_TIME_DEFAULT,
481390792Sgshapiro							"\n\t\t\t\t\t ");
481490792Sgshapiro					prtstr(p, 38);
481564562Sgshapiro				}
481690792Sgshapiro				if (Verbose && statmsg[0] != '\0')
481790792Sgshapiro				{
481890792Sgshapiro					(void) sm_io_fprintf(smioout,
481990792Sgshapiro							SM_TIME_DEFAULT,
482090792Sgshapiro							"\n\t\t (%.100s)",
482190792Sgshapiro							statmsg);
482290792Sgshapiro					statmsg[0] = '\0';
482390792Sgshapiro				}
482438032Speter				break;
482538032Speter
482638032Speter			  case 'T':	/* creation time */
482738032Speter				submittime = atol(&buf[1]);
482838032Speter				break;
482938032Speter
483038032Speter			  case 'F':	/* flag bits */
483138032Speter				for (p = &buf[1]; *p != '\0'; p++)
483238032Speter				{
483338032Speter					switch (*p)
483438032Speter					{
483538032Speter					  case 'w':
483638032Speter						flags |= EF_WARNING;
483738032Speter						break;
483838032Speter					}
483938032Speter				}
484038032Speter			}
484138032Speter		}
484238032Speter		if (submittime == (time_t) 0)
484390792Sgshapiro			(void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
484490792Sgshapiro					     " (no control file)");
484590792Sgshapiro		(void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "\n");
484690792Sgshapiro		(void) sm_io_close(f, SM_TIME_DEFAULT);
484738032Speter	}
484864562Sgshapiro	return nrequests;
484938032Speter}
485090792Sgshapiro
485190792Sgshapiro#if _FFR_QUARANTINE
485290792Sgshapiro/*
485390792Sgshapiro**  QUEUE_LETTER -- get the proper queue letter for the current QueueMode.
485490792Sgshapiro**
485590792Sgshapiro**	Parameters:
485690792Sgshapiro**		e -- envelope to build it in/from.
485790792Sgshapiro**		type -- the file type, used as the first character
485890792Sgshapiro**			of the file name.
485990792Sgshapiro**
486090792Sgshapiro**	Returns:
486190792Sgshapiro**		the letter to use
486290792Sgshapiro*/
486390792Sgshapiro
486490792Sgshapirostatic char
486590792Sgshapiroqueue_letter(e, type)
486690792Sgshapiro	ENVELOPE *e;
486790792Sgshapiro	int type;
486890792Sgshapiro{
486990792Sgshapiro	/* Change type according to QueueMode */
487090792Sgshapiro	if (type == ANYQFL_LETTER)
487190792Sgshapiro	{
487290792Sgshapiro		if (e->e_quarmsg != NULL)
487390792Sgshapiro			type = QUARQF_LETTER;
487490792Sgshapiro		else
487590792Sgshapiro		{
487690792Sgshapiro			switch (QueueMode)
487790792Sgshapiro			{
487890792Sgshapiro			  case QM_NORMAL:
487990792Sgshapiro				type = NORMQF_LETTER;
488090792Sgshapiro				break;
488190792Sgshapiro
488290792Sgshapiro			  case QM_QUARANTINE:
488390792Sgshapiro				type = QUARQF_LETTER;
488490792Sgshapiro				break;
488590792Sgshapiro
488690792Sgshapiro			  case QM_LOST:
488790792Sgshapiro				type = LOSEQF_LETTER;
488890792Sgshapiro				break;
488990792Sgshapiro
489090792Sgshapiro			  default:
489190792Sgshapiro				/* should never happen */
489290792Sgshapiro				abort();
489390792Sgshapiro				/* NOTREACHED */
489490792Sgshapiro			}
489590792Sgshapiro		}
489690792Sgshapiro	}
489790792Sgshapiro	return type;
489890792Sgshapiro}
489990792Sgshapiro#endif /* _FFR_QUARANTINE */
490090792Sgshapiro
490190792Sgshapiro/*
490238032Speter**  QUEUENAME -- build a file name in the queue directory for this envelope.
490338032Speter**
490438032Speter**	Parameters:
490538032Speter**		e -- envelope to build it in/from.
490638032Speter**		type -- the file type, used as the first character
490738032Speter**			of the file name.
490838032Speter**
490938032Speter**	Returns:
491064562Sgshapiro**		a pointer to the queue name (in a static buffer).
491138032Speter**
491238032Speter**	Side Effects:
491364562Sgshapiro**		If no id code is already assigned, queuename() will
491464562Sgshapiro**		assign an id code with assign_queueid().  If no queue
491564562Sgshapiro**		directory is assigned, one will be set with setnewqueue().
491638032Speter*/
491738032Speter
491838032Speterchar *
491938032Speterqueuename(e, type)
492038032Speter	register ENVELOPE *e;
492138032Speter	int type;
492238032Speter{
492390792Sgshapiro	int qd, qg;
492490792Sgshapiro	char *sub = "/";
492590792Sgshapiro	char pref[3];
492664562Sgshapiro	static char buf[MAXPATHLEN];
492738032Speter
492864562Sgshapiro	/* Assign an ID if needed */
492938032Speter	if (e->e_id == NULL)
493064562Sgshapiro		assign_queueid(e);
493164562Sgshapiro
493290792Sgshapiro#if _FFR_QUARANTINE
493390792Sgshapiro	type = queue_letter(e, type);
493490792Sgshapiro#endif /* _FFR_QUARANTINE */
493564562Sgshapiro
493690792Sgshapiro	/* begin of filename */
493790792Sgshapiro	pref[0] = (char) type;
493890792Sgshapiro	pref[1] = 'f';
493990792Sgshapiro	pref[2] = '\0';
494090792Sgshapiro
494190792Sgshapiro	/* Assign a queue group/directory if needed */
494290792Sgshapiro	if (type == XSCRPT_LETTER)
494390792Sgshapiro	{
494490792Sgshapiro		/*
494590792Sgshapiro		**  We don't want to call setnewqueue() if we are fetching
494690792Sgshapiro		**  the pathname of the transcript file, because setnewqueue
494790792Sgshapiro		**  chooses a queue, and sometimes we need to write to the
494890792Sgshapiro		**  transcript file before we have gathered enough information
494990792Sgshapiro		**  to choose a queue.
495090792Sgshapiro		*/
495190792Sgshapiro
495290792Sgshapiro		if (e->e_xfqgrp == NOQGRP || e->e_xfqdir == NOQDIR)
495390792Sgshapiro		{
495490792Sgshapiro			if (e->e_qgrp != NOQGRP && e->e_qdir != NOQDIR)
495590792Sgshapiro			{
495690792Sgshapiro				e->e_xfqgrp = e->e_qgrp;
495790792Sgshapiro				e->e_xfqdir = e->e_qdir;
495890792Sgshapiro			}
495990792Sgshapiro			else
496090792Sgshapiro			{
496190792Sgshapiro				e->e_xfqgrp = 0;
496290792Sgshapiro				if (Queue[e->e_xfqgrp]->qg_numqueues <= 1)
496390792Sgshapiro					e->e_xfqdir = 0;
496490792Sgshapiro				else
496590792Sgshapiro				{
496690792Sgshapiro					e->e_xfqdir = get_rand_mod(
496790792Sgshapiro					      Queue[e->e_xfqgrp]->qg_numqueues);
496890792Sgshapiro				}
496990792Sgshapiro			}
497090792Sgshapiro		}
497190792Sgshapiro		qd = e->e_xfqdir;
497290792Sgshapiro		qg = e->e_xfqgrp;
497390792Sgshapiro	}
497464562Sgshapiro	else
497538032Speter	{
497690792Sgshapiro		if (e->e_qgrp == NOQGRP || e->e_qdir == NOQDIR)
497790792Sgshapiro			setnewqueue(e);
497890792Sgshapiro		if (type ==  DATAFL_LETTER)
497990792Sgshapiro		{
498090792Sgshapiro			qd = e->e_dfqdir;
498190792Sgshapiro			qg = e->e_dfqgrp;
498290792Sgshapiro		}
498390792Sgshapiro		else
498490792Sgshapiro		{
498590792Sgshapiro			qd = e->e_qdir;
498690792Sgshapiro			qg = e->e_qgrp;
498790792Sgshapiro		}
498890792Sgshapiro	}
498990792Sgshapiro
499090792Sgshapiro	if (e->e_qdir == NOQDIR)
499190792Sgshapiro		(void) sm_strlcpyn(buf, sizeof buf, 2, pref, e->e_id);
499290792Sgshapiro	else
499390792Sgshapiro	{
499464562Sgshapiro		switch (type)
499564562Sgshapiro		{
499690792Sgshapiro		  case DATAFL_LETTER:
499790792Sgshapiro			if (bitset(QP_SUBDF, Queue[qg]->qg_qpaths[qd].qp_subdirs))
499890792Sgshapiro				sub = "/df/";
499964562Sgshapiro			break;
500038032Speter
500190792Sgshapiro#if _FFR_QUARANTINE
500290792Sgshapiro		  case QUARQF_LETTER:
500390792Sgshapiro#endif /* _FFR_QUARANTINE */
500471345Sgshapiro		  case TEMPQF_LETTER:
500590792Sgshapiro		  case NEWQFL_LETTER:
500671345Sgshapiro		  case LOSEQF_LETTER:
500790792Sgshapiro		  case NORMQF_LETTER:
500890792Sgshapiro			if (bitset(QP_SUBQF, Queue[qg]->qg_qpaths[qd].qp_subdirs))
500990792Sgshapiro				sub = "/qf/";
501064562Sgshapiro			break;
501164562Sgshapiro
501290792Sgshapiro		  case XSCRPT_LETTER:
501390792Sgshapiro			if (bitset(QP_SUBXF, Queue[qg]->qg_qpaths[qd].qp_subdirs))
501490792Sgshapiro				sub = "/xf/";
501564562Sgshapiro			break;
501690792Sgshapiro
501790792Sgshapiro		  default:
501890792Sgshapiro			sm_abort("queuename: bad queue file type %d", type);
501938032Speter		}
502038032Speter
502190792Sgshapiro		(void) sm_strlcpyn(buf, sizeof buf, 4,
502290792Sgshapiro				Queue[qg]->qg_qpaths[qd].qp_name,
502390792Sgshapiro				sub, pref, e->e_id);
502464562Sgshapiro	}
502538032Speter
502664562Sgshapiro	if (tTd(7, 2))
502790792Sgshapiro		sm_dprintf("queuename: %s\n", buf);
502864562Sgshapiro	return buf;
502964562Sgshapiro}
503090792Sgshapiro/*
503164562Sgshapiro**  ASSIGN_QUEUEID -- assign a queue ID for this envelope.
503264562Sgshapiro**
503364562Sgshapiro**	Assigns an id code if one does not already exist.
503464562Sgshapiro**	This code assumes that nothing will remain in the queue for
503564562Sgshapiro**	longer than 60 years.  It is critical that files with the given
503690792Sgshapiro**	name do not already exist in the queue.
503790792Sgshapiro**	[No longer initializes e_qdir to NOQDIR.]
503864562Sgshapiro**
503964562Sgshapiro**	Parameters:
504064562Sgshapiro**		e -- envelope to set it in.
504164562Sgshapiro**
504264562Sgshapiro**	Returns:
504364562Sgshapiro**		none.
504464562Sgshapiro*/
504538032Speter
504677349Sgshapirostatic const char QueueIdChars[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwx";
504777349Sgshapiro# define QIC_LEN	60
504890792Sgshapiro# define queuenextid() CurrentPid
504938032Speter
505090792Sgshapiro
505164562Sgshapirovoid
505264562Sgshapiroassign_queueid(e)
505364562Sgshapiro	register ENVELOPE *e;
505464562Sgshapiro{
505590792Sgshapiro	pid_t pid = queuenextid();
505690792Sgshapiro	static int cX = 0;
505764562Sgshapiro	static long random_offset;
505864562Sgshapiro	struct tm *tm;
505964562Sgshapiro	char idbuf[MAXQFNAME - 2];
506090792Sgshapiro	int seq;
506138032Speter
506264562Sgshapiro	if (e->e_id != NULL)
506364562Sgshapiro		return;
506438032Speter
506564562Sgshapiro	/* see if we need to get a new base time/pid */
506690792Sgshapiro	if (cX >= QIC_LEN * QIC_LEN || LastQueueTime == 0 ||
506790792Sgshapiro	    LastQueuePid != pid)
506864562Sgshapiro	{
506964562Sgshapiro		time_t then = LastQueueTime;
507064562Sgshapiro
507164562Sgshapiro		/* if the first time through, pick a random offset */
507264562Sgshapiro		if (LastQueueTime == 0)
507364562Sgshapiro			random_offset = get_random();
507464562Sgshapiro
507564562Sgshapiro		while ((LastQueueTime = curtime()) == then &&
507664562Sgshapiro		       LastQueuePid == pid)
507738032Speter		{
507864562Sgshapiro			(void) sleep(1);
507938032Speter		}
508090792Sgshapiro		LastQueuePid = queuenextid();
508164562Sgshapiro		cX = 0;
508238032Speter	}
508390792Sgshapiro
508490792Sgshapiro	/*
508590792Sgshapiro	**  Generate a new sequence number between 0 and QIC_LEN*QIC_LEN-1.
508690792Sgshapiro	**  This lets us generate up to QIC_LEN*QIC_LEN unique queue ids
508790792Sgshapiro	**  per second, per process.  With envelope splitting,
508890792Sgshapiro	**  a single message can consume many queue ids.
508990792Sgshapiro	*/
509090792Sgshapiro
509190792Sgshapiro	seq = (int)((cX + random_offset) % (QIC_LEN * QIC_LEN));
509290792Sgshapiro	++cX;
509364562Sgshapiro	if (tTd(7, 50))
509490792Sgshapiro		sm_dprintf("assign_queueid: random_offset = %ld (%d)\n",
509590792Sgshapiro			random_offset, seq);
509638032Speter
509764562Sgshapiro	tm = gmtime(&LastQueueTime);
509877349Sgshapiro	idbuf[0] = QueueIdChars[tm->tm_year % QIC_LEN];
509977349Sgshapiro	idbuf[1] = QueueIdChars[tm->tm_mon];
510077349Sgshapiro	idbuf[2] = QueueIdChars[tm->tm_mday];
510177349Sgshapiro	idbuf[3] = QueueIdChars[tm->tm_hour];
510277349Sgshapiro	idbuf[4] = QueueIdChars[tm->tm_min];
510377349Sgshapiro	idbuf[5] = QueueIdChars[tm->tm_sec];
510490792Sgshapiro	idbuf[6] = QueueIdChars[seq / QIC_LEN];
510590792Sgshapiro	idbuf[7] = QueueIdChars[seq % QIC_LEN];
510690792Sgshapiro	(void) sm_snprintf(&idbuf[8], sizeof idbuf - 8, "%06d",
510790792Sgshapiro			   (int) LastQueuePid);
510890792Sgshapiro	e->e_id = sm_rpool_strdup_x(e->e_rpool, idbuf);
510990792Sgshapiro	macdefine(&e->e_macro, A_PERM, 'i', e->e_id);
511090792Sgshapiro#if 0
511190792Sgshapiro	/* XXX: inherited from MainEnvelope */
511290792Sgshapiro	e->e_qgrp = NOQGRP;  /* too early to do anything else */
511390792Sgshapiro	e->e_qdir = NOQDIR;
511490792Sgshapiro	e->e_xfqgrp = NOQGRP;
511590792Sgshapiro#endif /* 0 */
511690792Sgshapiro#if _FFR_QUARANTINE
511790792Sgshapiro	/* New ID means it's not on disk yet */
511890792Sgshapiro	e->e_qfletter = '\0';
511990792Sgshapiro#endif /* _FFR_QUARANTINE */
512064562Sgshapiro	if (tTd(7, 1))
512190792Sgshapiro		sm_dprintf("assign_queueid: assigned id %s, e=%p\n",
512290792Sgshapiro			e->e_id, e);
512364562Sgshapiro	if (LogLevel > 93)
512464562Sgshapiro		sm_syslog(LOG_DEBUG, e->e_id, "assigned id");
512538032Speter}
512690792Sgshapiro/*
512764562Sgshapiro**  SYNC_QUEUE_TIME -- Assure exclusive PID in any given second
512864562Sgshapiro**
512964562Sgshapiro**	Make sure one PID can't be used by two processes in any one second.
513064562Sgshapiro**
513164562Sgshapiro**		If the system rotates PIDs fast enough, may get the
513264562Sgshapiro**		same pid in the same second for two distinct processes.
513364562Sgshapiro**		This will interfere with the queue file naming system.
513464562Sgshapiro**
513564562Sgshapiro**	Parameters:
513664562Sgshapiro**		none
513764562Sgshapiro**
513864562Sgshapiro**	Returns:
513964562Sgshapiro**		none
514064562Sgshapiro*/
514190792Sgshapiro
514264562Sgshapirovoid
514364562Sgshapirosync_queue_time()
514464562Sgshapiro{
514590792Sgshapiro#if FAST_PID_RECYCLE
514664562Sgshapiro	if (OpMode != MD_TEST &&
514764562Sgshapiro	    OpMode != MD_VERIFY &&
514864562Sgshapiro	    LastQueueTime > 0 &&
514990792Sgshapiro	    LastQueuePid == CurrentPid &&
515064562Sgshapiro	    curtime() == LastQueueTime)
515164562Sgshapiro		(void) sleep(1);
515290792Sgshapiro#endif /* FAST_PID_RECYCLE */
515364562Sgshapiro}
515490792Sgshapiro/*
515538032Speter**  UNLOCKQUEUE -- unlock the queue entry for a specified envelope
515638032Speter**
515738032Speter**	Parameters:
515838032Speter**		e -- the envelope to unlock.
515938032Speter**
516038032Speter**	Returns:
516138032Speter**		none
516238032Speter**
516338032Speter**	Side Effects:
516438032Speter**		unlocks the queue for `e'.
516538032Speter*/
516638032Speter
516738032Spetervoid
516838032Speterunlockqueue(e)
516938032Speter	ENVELOPE *e;
517038032Speter{
517138032Speter	if (tTd(51, 4))
517290792Sgshapiro		sm_dprintf("unlockqueue(%s)\n",
517338032Speter			e->e_id == NULL ? "NOQUEUE" : e->e_id);
517438032Speter
517564562Sgshapiro
517638032Speter	/* if there is a lock file in the envelope, close it */
517738032Speter	if (e->e_lockfp != NULL)
517890792Sgshapiro		(void) sm_io_close(e->e_lockfp, SM_TIME_DEFAULT);
517938032Speter	e->e_lockfp = NULL;
518038032Speter
518138032Speter	/* don't create a queue id if we don't already have one */
518238032Speter	if (e->e_id == NULL)
518338032Speter		return;
518438032Speter
518538032Speter	/* remove the transcript */
518638032Speter	if (LogLevel > 87)
518738032Speter		sm_syslog(LOG_DEBUG, e->e_id, "unlock");
518838032Speter	if (!tTd(51, 104))
518990792Sgshapiro		(void) xunlink(queuename(e, XSCRPT_LETTER));
519038032Speter}
519190792Sgshapiro/*
519238032Speter**  SETCTLUSER -- create a controlling address
519338032Speter**
519438032Speter**	Create a fake "address" given only a local login name; this is
519538032Speter**	used as a "controlling user" for future recipient addresses.
519638032Speter**
519738032Speter**	Parameters:
519838032Speter**		user -- the user name of the controlling user.
519990792Sgshapiro**		qfver -- the version stamp of this queue file.
520090792Sgshapiro**		e -- envelope
520138032Speter**
520238032Speter**	Returns:
520390792Sgshapiro**		An address descriptor for the controlling user,
520490792Sgshapiro**		using storage allocated from e->e_rpool.
520538032Speter**
520638032Speter*/
520738032Speter
520864562Sgshapirostatic ADDRESS *
520990792Sgshapirosetctluser(user, qfver, e)
521038032Speter	char *user;
521138032Speter	int qfver;
521290792Sgshapiro	ENVELOPE *e;
521338032Speter{
521438032Speter	register ADDRESS *a;
521538032Speter	struct passwd *pw;
521638032Speter	char *p;
521738032Speter
521838032Speter	/*
521938032Speter	**  See if this clears our concept of controlling user.
522038032Speter	*/
522138032Speter
522238032Speter	if (user == NULL || *user == '\0')
522338032Speter		return NULL;
522438032Speter
522538032Speter	/*
522638032Speter	**  Set up addr fields for controlling user.
522738032Speter	*/
522838032Speter
522990792Sgshapiro	a = (ADDRESS *) sm_rpool_malloc_x(e->e_rpool, sizeof *a);
523064562Sgshapiro	memset((char *) a, '\0', sizeof *a);
523138032Speter
523290792Sgshapiro	if (*user == ':')
523338032Speter	{
523438032Speter		p = &user[1];
523590792Sgshapiro		a->q_user = sm_rpool_strdup_x(e->e_rpool, p);
523638032Speter	}
523738032Speter	else
523838032Speter	{
523938032Speter		p = strtok(user, ":");
524090792Sgshapiro		a->q_user = sm_rpool_strdup_x(e->e_rpool, user);
524138032Speter		if (qfver >= 2)
524238032Speter		{
524338032Speter			if ((p = strtok(NULL, ":")) != NULL)
524438032Speter				a->q_uid = atoi(p);
524538032Speter			if ((p = strtok(NULL, ":")) != NULL)
524638032Speter				a->q_gid = atoi(p);
524738032Speter			if ((p = strtok(NULL, ":")) != NULL)
524880785Sgshapiro			{
524980785Sgshapiro				char *o;
525080785Sgshapiro
525138032Speter				a->q_flags |= QGOODUID;
525280785Sgshapiro
525380785Sgshapiro				/* if there is another ':': restore it */
525480785Sgshapiro				if ((o = strtok(NULL, ":")) != NULL && o > p)
525580785Sgshapiro					o[-1] = ':';
525680785Sgshapiro			}
525738032Speter		}
525838032Speter		else if ((pw = sm_getpwnam(user)) != NULL)
525938032Speter		{
526066494Sgshapiro			if (*pw->pw_dir == '\0')
526166494Sgshapiro				a->q_home = NULL;
526266494Sgshapiro			else if (strcmp(pw->pw_dir, "/") == 0)
526338032Speter				a->q_home = "";
526438032Speter			else
526590792Sgshapiro				a->q_home = sm_rpool_strdup_x(e->e_rpool, pw->pw_dir);
526638032Speter			a->q_uid = pw->pw_uid;
526738032Speter			a->q_gid = pw->pw_gid;
526838032Speter			a->q_flags |= QGOODUID;
526938032Speter		}
527038032Speter	}
527138032Speter
527264562Sgshapiro	a->q_flags |= QPRIMARY;		/* flag as a "ctladdr" */
527338032Speter	a->q_mailer = LocalMailer;
527438032Speter	if (p == NULL)
527590792Sgshapiro		a->q_paddr = sm_rpool_strdup_x(e->e_rpool, a->q_user);
527638032Speter	else
527790792Sgshapiro		a->q_paddr = sm_rpool_strdup_x(e->e_rpool, p);
527838032Speter	return a;
527938032Speter}
528090792Sgshapiro/*
528190792Sgshapiro**  LOSEQFILE -- rename queue file with LOSEQF_LETTER & try to let someone know
528238032Speter**
528338032Speter**	Parameters:
528438032Speter**		e -- the envelope (e->e_id will be used).
528538032Speter**		why -- reported to whomever can hear.
528638032Speter**
528738032Speter**	Returns:
528838032Speter**		none.
528938032Speter*/
529038032Speter
529138032Spetervoid
529238032Speterloseqfile(e, why)
529338032Speter	register ENVELOPE *e;
529438032Speter	char *why;
529538032Speter{
529690792Sgshapiro	bool loseit = true;
529738032Speter	char *p;
529864562Sgshapiro	char buf[MAXPATHLEN];
529938032Speter
530038032Speter	if (e == NULL || e->e_id == NULL)
530138032Speter		return;
530290792Sgshapiro	p = queuename(e, ANYQFL_LETTER);
530390792Sgshapiro	if (sm_strlcpy(buf, p, sizeof buf) >= sizeof buf)
530438032Speter		return;
530590792Sgshapiro	if (!bitset(EF_INQUEUE, e->e_flags))
530690792Sgshapiro		queueup(e, false, true);
530790792Sgshapiro#if _FFR_QUARANTINE
530890792Sgshapiro	else if (QueueMode == QM_LOST)
530990792Sgshapiro		loseit = false;
531090792Sgshapiro#endif /* _FFR_QUARANTINE */
531190792Sgshapiro
531290792Sgshapiro	/* if already lost, no need to re-lose */
531390792Sgshapiro	if (loseit)
531490792Sgshapiro	{
531590792Sgshapiro		p = queuename(e, LOSEQF_LETTER);
531690792Sgshapiro		if (rename(buf, p) < 0)
531790792Sgshapiro			syserr("cannot rename(%s, %s), uid=%d",
531890792Sgshapiro			       buf, p, geteuid());
531990792Sgshapiro		else if (LogLevel > 0)
532090792Sgshapiro			sm_syslog(LOG_ALERT, e->e_id,
532190792Sgshapiro				  "Losing %s: %s", buf, why);
532290792Sgshapiro	}
532390792Sgshapiro	if (e->e_dfp != NULL)
532490792Sgshapiro	{
532590792Sgshapiro		(void) sm_io_close(e->e_dfp, SM_TIME_DEFAULT);
532690792Sgshapiro		e->e_dfp = NULL;
532790792Sgshapiro	}
532890792Sgshapiro	e->e_flags &= ~EF_HAS_DF;
532938032Speter}
533090792Sgshapiro/*
533190792Sgshapiro**  NAME2QID -- translate a queue group name to a queue group id
533290792Sgshapiro**
533390792Sgshapiro**	Parameters:
533490792Sgshapiro**		queuename -- name of queue group.
533590792Sgshapiro**
533690792Sgshapiro**	Returns:
533790792Sgshapiro**		queue group id if found.
533890792Sgshapiro**		NOQGRP otherwise.
533990792Sgshapiro*/
534090792Sgshapiro
534190792Sgshapiroint
534290792Sgshapironame2qid(queuename)
534390792Sgshapiro	char *queuename;
534490792Sgshapiro{
534590792Sgshapiro	register STAB *s;
534690792Sgshapiro
534790792Sgshapiro	s = stab(queuename, ST_QUEUE, ST_FIND);
534890792Sgshapiro	if (s == NULL)
534990792Sgshapiro		return NOQGRP;
535090792Sgshapiro	return s->s_quegrp->qg_index;
535190792Sgshapiro}
535290792Sgshapiro/*
535364562Sgshapiro**  QID_PRINTNAME -- create externally printable version of queue id
535464562Sgshapiro**
535564562Sgshapiro**	Parameters:
535664562Sgshapiro**		e -- the envelope.
535764562Sgshapiro**
535864562Sgshapiro**	Returns:
535964562Sgshapiro**		a printable version
536064562Sgshapiro*/
536164562Sgshapiro
536264562Sgshapirochar *
536364562Sgshapiroqid_printname(e)
536464562Sgshapiro	ENVELOPE *e;
536564562Sgshapiro{
536664562Sgshapiro	char *id;
536764562Sgshapiro	static char idbuf[MAXQFNAME + 34];
536864562Sgshapiro
536964562Sgshapiro	if (e == NULL)
537064562Sgshapiro		return "";
537164562Sgshapiro
537264562Sgshapiro	if (e->e_id == NULL)
537364562Sgshapiro		id = "";
537464562Sgshapiro	else
537564562Sgshapiro		id = e->e_id;
537664562Sgshapiro
537790792Sgshapiro	if (e->e_qdir == NOQDIR)
537864562Sgshapiro		return id;
537964562Sgshapiro
538090792Sgshapiro	(void) sm_snprintf(idbuf, sizeof idbuf, "%.32s/%s",
538190792Sgshapiro			   Queue[e->e_qgrp]->qg_qpaths[e->e_qdir].qp_name,
538290792Sgshapiro			   id);
538364562Sgshapiro	return idbuf;
538464562Sgshapiro}
538590792Sgshapiro/*
538690792Sgshapiro**  QID_PRINTQUEUE -- create full version of queue directory for data files
538764562Sgshapiro**
538864562Sgshapiro**	Parameters:
538990792Sgshapiro**		qgrp -- index in queue group.
539090792Sgshapiro**		qdir -- the short version of the queue directory
539164562Sgshapiro**
539264562Sgshapiro**	Returns:
539390792Sgshapiro**		the full pathname to the queue (might point to a static var)
539464562Sgshapiro*/
539564562Sgshapiro
539664562Sgshapirochar *
539790792Sgshapiroqid_printqueue(qgrp, qdir)
539890792Sgshapiro	int qgrp;
539990792Sgshapiro	int qdir;
540064562Sgshapiro{
540164562Sgshapiro	char *subdir;
540264562Sgshapiro	static char dir[MAXPATHLEN];
540364562Sgshapiro
540490792Sgshapiro	if (qdir == NOQDIR)
540590792Sgshapiro		return Queue[qgrp]->qg_qdir;
540664562Sgshapiro
540790792Sgshapiro	if (strcmp(Queue[qgrp]->qg_qpaths[qdir].qp_name, ".") == 0)
540864562Sgshapiro		subdir = NULL;
540964562Sgshapiro	else
541090792Sgshapiro		subdir = Queue[qgrp]->qg_qpaths[qdir].qp_name;
541164562Sgshapiro
541290792Sgshapiro	(void) sm_strlcpyn(dir, sizeof dir, 4,
541390792Sgshapiro			Queue[qgrp]->qg_qdir,
541464562Sgshapiro			subdir == NULL ? "" : "/",
541564562Sgshapiro			subdir == NULL ? "" : subdir,
541690792Sgshapiro			(bitset(QP_SUBDF,
541790792Sgshapiro				Queue[qgrp]->qg_qpaths[qdir].qp_subdirs)
541890792Sgshapiro					? "/df" : ""));
541964562Sgshapiro	return dir;
542064562Sgshapiro}
542190792Sgshapiro
542290792Sgshapiro/*
542390792Sgshapiro**  PICKQDIR -- Pick a queue directory from a queue group
542464562Sgshapiro**
542590792Sgshapiro**	Parameters:
542690792Sgshapiro**		qg -- queue group
542790792Sgshapiro**		fsize -- file size in bytes
542890792Sgshapiro**		e -- envelope, or NULL
542964562Sgshapiro**
543090792Sgshapiro**	Result:
543190792Sgshapiro**		NOQDIR if no queue directory in qg has enough free space to
543290792Sgshapiro**		hold a file of size 'fsize', otherwise the index of
543390792Sgshapiro**		a randomly selected queue directory which resides on a
543490792Sgshapiro**		file system with enough disk space.
543590792Sgshapiro**		XXX This could be extended to select a queuedir with
543690792Sgshapiro**			a few (the fewest?) number of entries. That data
543790792Sgshapiro**			is available if shared memory is used.
543864562Sgshapiro**
543990792Sgshapiro**	Side Effects:
544090792Sgshapiro**		If the request fails and e != NULL then sm_syslog is called.
544190792Sgshapiro*/
544290792Sgshapiro
544390792Sgshapiroint
544490792Sgshapiropickqdir(qg, fsize, e)
544590792Sgshapiro	QUEUEGRP *qg;
544690792Sgshapiro	long fsize;
544790792Sgshapiro	ENVELOPE *e;
544890792Sgshapiro{
544990792Sgshapiro	int qdir;
545090792Sgshapiro	int i;
545190792Sgshapiro	long avail = 0;
545290792Sgshapiro
545390792Sgshapiro	/* Pick a random directory, as a starting point. */
545490792Sgshapiro	if (qg->qg_numqueues <= 1)
545590792Sgshapiro		qdir = 0;
545690792Sgshapiro	else
545790792Sgshapiro		qdir = get_rand_mod(qg->qg_numqueues);
545890792Sgshapiro
545990792Sgshapiro	if (MinBlocksFree <= 0 && fsize <= 0)
546090792Sgshapiro		return qdir;
546190792Sgshapiro
546290792Sgshapiro	/*
546390792Sgshapiro	**  Now iterate over the queue directories,
546490792Sgshapiro	**  looking for a directory with enough space for this message.
546590792Sgshapiro	*/
546690792Sgshapiro
546790792Sgshapiro	i = qdir;
546890792Sgshapiro	do
546990792Sgshapiro	{
547090792Sgshapiro		QPATHS *qp = &qg->qg_qpaths[i];
547190792Sgshapiro		long needed = 0;
547290792Sgshapiro		long fsavail = 0;
547390792Sgshapiro
547490792Sgshapiro		if (fsize > 0)
547590792Sgshapiro			needed += fsize / FILE_SYS_BLKSIZE(qp->qp_fsysidx)
547690792Sgshapiro				  + ((fsize % FILE_SYS_BLKSIZE(qp->qp_fsysidx)
547790792Sgshapiro				      > 0) ? 1 : 0);
547890792Sgshapiro		if (MinBlocksFree > 0)
547990792Sgshapiro			needed += MinBlocksFree;
548090792Sgshapiro		fsavail = FILE_SYS_AVAIL(qp->qp_fsysidx);
548190792Sgshapiro#if SM_CONF_SHM
548290792Sgshapiro		if (fsavail <= 0)
548390792Sgshapiro		{
548490792Sgshapiro			long blksize;
548590792Sgshapiro
548690792Sgshapiro			/*
548790792Sgshapiro			**  might be not correctly updated,
548890792Sgshapiro			**  let's try to get the info directly.
548990792Sgshapiro			*/
549090792Sgshapiro
549190792Sgshapiro			fsavail = freediskspace(FILE_SYS_NAME(qp->qp_fsysidx),
549290792Sgshapiro						&blksize);
549390792Sgshapiro			if (fsavail < 0)
549490792Sgshapiro				fsavail = 0;
549590792Sgshapiro		}
549690792Sgshapiro#endif /* SM_CONF_SHM */
549790792Sgshapiro		if (needed <= fsavail)
549890792Sgshapiro			return i;
549990792Sgshapiro		if (avail < fsavail)
550090792Sgshapiro			avail = fsavail;
550190792Sgshapiro
550290792Sgshapiro		if (qg->qg_numqueues > 0)
550390792Sgshapiro			i = (i + 1) % qg->qg_numqueues;
550490792Sgshapiro	} while (i != qdir);
550590792Sgshapiro
550690792Sgshapiro	if (e != NULL && LogLevel > 0)
550790792Sgshapiro		sm_syslog(LOG_ALERT, e->e_id,
550890792Sgshapiro			"low on space (%s needs %ld bytes + %ld blocks in %s), max avail: %ld",
550990792Sgshapiro			CurHostName == NULL ? "SMTP-DAEMON" : CurHostName,
551090792Sgshapiro			fsize, MinBlocksFree,
551190792Sgshapiro			qg->qg_qdir, avail);
551290792Sgshapiro	return NOQDIR;
551390792Sgshapiro}
551490792Sgshapiro/*
551590792Sgshapiro**  SETNEWQUEUE -- Sets a new queue group and directory
551690792Sgshapiro**
551790792Sgshapiro**	Assign a queue group and directory to an envelope and store the
551890792Sgshapiro**	directory in e->e_qdir.
551990792Sgshapiro**
552064562Sgshapiro**	Parameters:
552164562Sgshapiro**		e -- envelope to assign a queue for.
552264562Sgshapiro**
552364562Sgshapiro**	Returns:
552490792Sgshapiro**		true if successful
552590792Sgshapiro**		false otherwise
552690792Sgshapiro**
552790792Sgshapiro**	Side Effects:
552890792Sgshapiro**		On success, e->e_qgrp and e->e_qdir are non-negative.
552990792Sgshapiro**		On failure (not enough disk space),
553090792Sgshapiro**		e->qgrp = NOQGRP, e->e_qdir = NOQDIR
553190792Sgshapiro**		and usrerr() is invoked (which could raise an exception).
553264562Sgshapiro*/
553364562Sgshapiro
553490792Sgshapirobool
553564562Sgshapirosetnewqueue(e)
553664562Sgshapiro	ENVELOPE *e;
553764562Sgshapiro{
553864562Sgshapiro	if (tTd(41, 20))
553990792Sgshapiro		sm_dprintf("setnewqueue: called\n");
554064562Sgshapiro
554190792Sgshapiro	/* not set somewhere else */
554290792Sgshapiro	if (e->e_qgrp == NOQGRP)
554364562Sgshapiro	{
554490792Sgshapiro		/*
554590792Sgshapiro		**  Use the queue group of the first recipient, as set by
554690792Sgshapiro		**  the "queuegroup" rule set.  If that is not defined, then
554790792Sgshapiro		**  use the queue group of the mailer of the first recipient.
554890792Sgshapiro		**  If that is not defined either, then use the default
554990792Sgshapiro		**  queue group.
555090792Sgshapiro		*/
555190792Sgshapiro
555290792Sgshapiro		if (e->e_sendqueue == NULL)
555390792Sgshapiro			e->e_qgrp = 0;
555490792Sgshapiro		else if (e->e_sendqueue->q_qgrp >= 0)
555590792Sgshapiro			e->e_qgrp = e->e_sendqueue->q_qgrp;
555690792Sgshapiro		else if (e->e_sendqueue->q_mailer != NULL &&
555790792Sgshapiro			 ISVALIDQGRP(e->e_sendqueue->q_mailer->m_qgrp))
555890792Sgshapiro			e->e_qgrp = e->e_sendqueue->q_mailer->m_qgrp;
555990792Sgshapiro		else
556090792Sgshapiro			e->e_qgrp = 0;
556190792Sgshapiro		e->e_dfqgrp = e->e_qgrp;
556290792Sgshapiro	}
556390792Sgshapiro
556490792Sgshapiro	if (ISVALIDQDIR(e->e_qdir) && ISVALIDQDIR(e->e_dfqdir))
556590792Sgshapiro	{
556664562Sgshapiro		if (tTd(41, 20))
556790792Sgshapiro			sm_dprintf("setnewqueue: e_qdir already assigned (%s)\n",
556890792Sgshapiro				qid_printqueue(e->e_qgrp, e->e_qdir));
556990792Sgshapiro		return true;
557064562Sgshapiro	}
557164562Sgshapiro
557290792Sgshapiro	filesys_update();
557390792Sgshapiro	e->e_qdir = pickqdir(Queue[e->e_qgrp], e->e_msgsize, e);
557490792Sgshapiro	if (e->e_qdir == NOQDIR)
557564562Sgshapiro	{
557690792Sgshapiro		e->e_qgrp = NOQGRP;
557790792Sgshapiro		if (!bitset(EF_FATALERRS, e->e_flags))
557890792Sgshapiro			usrerr("452 4.4.5 Insufficient disk space; try again later");
557990792Sgshapiro		e->e_flags |= EF_FATALERRS;
558090792Sgshapiro		return false;
558164562Sgshapiro	}
558264562Sgshapiro
558364562Sgshapiro	if (tTd(41, 3))
558490792Sgshapiro		sm_dprintf("setnewqueue: Assigned queue directory %s\n",
558590792Sgshapiro			qid_printqueue(e->e_qgrp, e->e_qdir));
558690792Sgshapiro
558790792Sgshapiro	if (e->e_xfqgrp == NOQGRP || e->e_xfqdir == NOQDIR)
558890792Sgshapiro	{
558990792Sgshapiro		e->e_xfqgrp = e->e_qgrp;
559090792Sgshapiro		e->e_xfqdir = e->e_qdir;
559190792Sgshapiro	}
559290792Sgshapiro	e->e_dfqdir = e->e_qdir;
559390792Sgshapiro	return true;
559464562Sgshapiro}
559590792Sgshapiro/*
559664562Sgshapiro**  CHKQDIR -- check a queue directory
559764562Sgshapiro**
559864562Sgshapiro**	Parameters:
559964562Sgshapiro**		name -- name of queue directory
560064562Sgshapiro**		sff -- flags for safefile()
560164562Sgshapiro**
560264562Sgshapiro**	Returns:
560364562Sgshapiro**		is it a queue directory?
560464562Sgshapiro*/
560564562Sgshapiro
560664562Sgshapirostatic bool
560764562Sgshapirochkqdir(name, sff)
560864562Sgshapiro	char *name;
560964562Sgshapiro	long sff;
561064562Sgshapiro{
561164562Sgshapiro	struct stat statb;
561264562Sgshapiro	int i;
561364562Sgshapiro
561466494Sgshapiro	/* skip over . and .. directories */
561566494Sgshapiro	if (name[0] == '.' &&
561677349Sgshapiro	    (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')))
561790792Sgshapiro		return false;
561890792Sgshapiro#if HASLSTAT
561964562Sgshapiro	if (lstat(name, &statb) < 0)
562090792Sgshapiro#else /* HASLSTAT */
562164562Sgshapiro	if (stat(name, &statb) < 0)
562290792Sgshapiro#endif /* HASLSTAT */
562364562Sgshapiro	{
562464562Sgshapiro		if (tTd(41, 2))
562590792Sgshapiro			sm_dprintf("chkqdir: stat(\"%s\"): %s\n",
562690792Sgshapiro				   name, sm_errstring(errno));
562790792Sgshapiro		return false;
562864562Sgshapiro	}
562990792Sgshapiro#if HASLSTAT
563064562Sgshapiro	if (S_ISLNK(statb.st_mode))
563164562Sgshapiro	{
563264562Sgshapiro		/*
563364562Sgshapiro		**  For a symlink we need to make sure the
563464562Sgshapiro		**  target is a directory
563564562Sgshapiro		*/
563690792Sgshapiro
563764562Sgshapiro		if (stat(name, &statb) < 0)
563864562Sgshapiro		{
563964562Sgshapiro			if (tTd(41, 2))
564090792Sgshapiro				sm_dprintf("chkqdir: stat(\"%s\"): %s\n",
564190792Sgshapiro					   name, sm_errstring(errno));
564290792Sgshapiro			return false;
564364562Sgshapiro		}
564464562Sgshapiro	}
564590792Sgshapiro#endif /* HASLSTAT */
564664562Sgshapiro
564764562Sgshapiro	if (!S_ISDIR(statb.st_mode))
564864562Sgshapiro	{
564964562Sgshapiro		if (tTd(41, 2))
565090792Sgshapiro			sm_dprintf("chkqdir: \"%s\": Not a directory\n",
565164562Sgshapiro				name);
565290792Sgshapiro		return false;
565364562Sgshapiro	}
565464562Sgshapiro
565564562Sgshapiro	/* Print a warning if unsafe (but still use it) */
565690792Sgshapiro	/* XXX do this only if we want the warning? */
565764562Sgshapiro	i = safedirpath(name, RunAsUid, RunAsGid, NULL, sff, 0, 0);
565864562Sgshapiro	if (i != 0 && tTd(41, 2))
565990792Sgshapiro		sm_dprintf("chkqdir: \"%s\": Not safe: %s\n",
566090792Sgshapiro			   name, sm_errstring(i));
566190792Sgshapiro	return true;
566264562Sgshapiro}
566390792Sgshapiro/*
566464562Sgshapiro**  MULTIQUEUE_CACHE -- cache a list of paths to queues.
566564562Sgshapiro**
566664562Sgshapiro**	Each potential queue is checked as the cache is built.
566764562Sgshapiro**	Thereafter, each is blindly trusted.
566864562Sgshapiro**	Note that we can be called again after a timeout to rebuild
566964562Sgshapiro**	(although code for that is not ready yet).
567064562Sgshapiro**
567164562Sgshapiro**	Parameters:
567290792Sgshapiro**		basedir -- base of all queue directories.
567390792Sgshapiro**		blen -- strlen(basedir).
567490792Sgshapiro**		qg -- queue group.
567590792Sgshapiro**		qn -- number of queue directories already cached.
567690792Sgshapiro**		phash -- pointer to hash value over queue dirs.
567790792Sgshapiro#if SM_CONF_SHM
567890792Sgshapiro**			only used if shared memory is active.
567990792Sgshapiro#endif * SM_CONF_SHM *
568064562Sgshapiro**
568164562Sgshapiro**	Returns:
568290792Sgshapiro**		new number of queue directories.
568364562Sgshapiro*/
568464562Sgshapiro
568590792Sgshapiro#define INITIAL_SLOTS	20
568690792Sgshapiro#define ADD_SLOTS	10
568790792Sgshapiro
568890792Sgshapirostatic int
568990792Sgshapiromultiqueue_cache(basedir, blen, qg, qn, phash)
569090792Sgshapiro	char *basedir;
569190792Sgshapiro	int blen;
569290792Sgshapiro	QUEUEGRP *qg;
569390792Sgshapiro	int qn;
569490792Sgshapiro	unsigned int *phash;
569564562Sgshapiro{
569664562Sgshapiro	char *cp;
569764562Sgshapiro	int i, len;
569864562Sgshapiro	int slotsleft = 0;
569964562Sgshapiro	long sff = SFF_ANYFILE;
570064562Sgshapiro	char qpath[MAXPATHLEN];
570164562Sgshapiro	char subdir[MAXPATHLEN];
570290792Sgshapiro	char prefix[MAXPATHLEN];	/* dir relative to basedir */
570364562Sgshapiro
570464562Sgshapiro	if (tTd(41, 20))
570590792Sgshapiro		sm_dprintf("multiqueue_cache: called\n");
570664562Sgshapiro
570790792Sgshapiro	/* Initialize to current directory */
570890792Sgshapiro	prefix[0] = '.';
570990792Sgshapiro	prefix[1] = '\0';
571090792Sgshapiro	if (qg->qg_numqueues != 0 && qg->qg_qpaths != NULL)
571164562Sgshapiro	{
571290792Sgshapiro		for (i = 0; i < qg->qg_numqueues; i++)
571364562Sgshapiro		{
571490792Sgshapiro			if (qg->qg_qpaths[i].qp_name != NULL)
571590792Sgshapiro				(void) sm_free(qg->qg_qpaths[i].qp_name); /* XXX */
571664562Sgshapiro		}
571790792Sgshapiro		(void) sm_free((char *) qg->qg_qpaths); /* XXX */
571890792Sgshapiro		qg->qg_qpaths = NULL;
571990792Sgshapiro		qg->qg_numqueues = 0;
572064562Sgshapiro	}
572164562Sgshapiro
572264562Sgshapiro	/* If running as root, allow safedirpath() checks to use privs */
572364562Sgshapiro	if (RunAsUid == 0)
572464562Sgshapiro		sff |= SFF_ROOTOK;
572564562Sgshapiro
572690792Sgshapiro	if (!SM_IS_DIR_START(qg->qg_qdir))
572790792Sgshapiro	{
572890792Sgshapiro		/*
572990792Sgshapiro		**  XXX we could add basedir, but then we have to realloc()
573090792Sgshapiro		**  the string... Maybe another time.
573190792Sgshapiro		*/
573290792Sgshapiro
573390792Sgshapiro		syserr("QueuePath %s not absolute", qg->qg_qdir);
573490792Sgshapiro		ExitStat = EX_CONFIG;
573590792Sgshapiro		return qn;
573690792Sgshapiro	}
573790792Sgshapiro
573890792Sgshapiro	/* qpath: directory of current workgroup */
573990792Sgshapiro	len = sm_strlcpy(qpath, qg->qg_qdir, sizeof qpath);
574090792Sgshapiro	if (len >= sizeof qpath)
574190792Sgshapiro	{
574290792Sgshapiro		syserr("QueuePath %.256s too long (%d max)",
574390792Sgshapiro		       qg->qg_qdir, (int) sizeof qpath);
574490792Sgshapiro		ExitStat = EX_CONFIG;
574590792Sgshapiro		return qn;
574690792Sgshapiro	}
574790792Sgshapiro
574890792Sgshapiro	/* begin of qpath must be same as basedir */
574990792Sgshapiro	if (strncmp(basedir, qpath, blen) != 0 &&
575090792Sgshapiro	    (strncmp(basedir, qpath, blen - 1) != 0 || len != blen - 1))
575190792Sgshapiro	{
575290792Sgshapiro		syserr("QueuePath %s not subpath of QueueDirectory %s",
575390792Sgshapiro			qpath, basedir);
575490792Sgshapiro		ExitStat = EX_CONFIG;
575590792Sgshapiro		return qn;
575690792Sgshapiro	}
575790792Sgshapiro
575890792Sgshapiro	/* Do we have a nested subdirectory? */
575990792Sgshapiro	if (blen < len && SM_FIRST_DIR_DELIM(qg->qg_qdir + blen) != NULL)
576090792Sgshapiro	{
576190792Sgshapiro
576290792Sgshapiro		/* Copy subdirectory into prefix for later use */
576390792Sgshapiro		if (sm_strlcpy(prefix, qg->qg_qdir + blen, sizeof prefix) >=
576490792Sgshapiro		    sizeof prefix)
576590792Sgshapiro		{
576690792Sgshapiro			syserr("QueuePath %.256s too long (%d max)",
576790792Sgshapiro				qg->qg_qdir, (int) sizeof qpath);
576890792Sgshapiro			ExitStat = EX_CONFIG;
576990792Sgshapiro			return qn;
577090792Sgshapiro		}
577190792Sgshapiro		cp = SM_LAST_DIR_DELIM(prefix);
577290792Sgshapiro		SM_ASSERT(cp != NULL);
577390792Sgshapiro		*cp = '\0';	/* cut off trailing / */
577490792Sgshapiro	}
577590792Sgshapiro
577690792Sgshapiro	/* This is guaranteed by the basedir check above */
577790792Sgshapiro	SM_ASSERT(len >= blen - 1);
577890792Sgshapiro	cp = &qpath[len - 1];
577964562Sgshapiro	if (*cp == '*')
578064562Sgshapiro	{
578190792Sgshapiro		register DIR *dp;
578290792Sgshapiro		register struct dirent *d;
578390792Sgshapiro		int off;
578490792Sgshapiro		char *delim;
578590792Sgshapiro		char relpath[MAXPATHLEN];
578690792Sgshapiro
578790792Sgshapiro		*cp = '\0';	/* Overwrite wildcard */
578890792Sgshapiro		if ((cp = SM_LAST_DIR_DELIM(qpath)) == NULL)
578964562Sgshapiro		{
579064562Sgshapiro			syserr("QueueDirectory: can not wildcard relative path");
579164562Sgshapiro			if (tTd(41, 2))
579290792Sgshapiro				sm_dprintf("multiqueue_cache: \"%s*\": Can not wildcard relative path.\n",
579371345Sgshapiro					qpath);
579464562Sgshapiro			ExitStat = EX_CONFIG;
579590792Sgshapiro			return qn;
579664562Sgshapiro		}
579764562Sgshapiro		if (cp == qpath)
579864562Sgshapiro		{
579964562Sgshapiro			/*
580064562Sgshapiro			**  Special case of top level wildcard, like /foo*
580190792Sgshapiro			**	Change to //foo*
580264562Sgshapiro			*/
580364562Sgshapiro
580490792Sgshapiro			(void) sm_strlcpy(qpath + 1, qpath, sizeof qpath - 1);
580564562Sgshapiro			++cp;
580664562Sgshapiro		}
580790792Sgshapiro		delim = cp;
580890792Sgshapiro		*(cp++) = '\0';		/* Replace / with \0 */
580990792Sgshapiro		len = strlen(cp);	/* Last component of queue directory */
581064562Sgshapiro
581190792Sgshapiro		/*
581290792Sgshapiro		**  Path relative to basedir, with trailing /
581390792Sgshapiro		**  It will be modified below to specify the subdirectories
581490792Sgshapiro		**  so they can be opened without chdir().
581590792Sgshapiro		*/
581690792Sgshapiro
581790792Sgshapiro		off = sm_strlcpyn(relpath, sizeof relpath, 2, prefix, "/");
581890792Sgshapiro		SM_ASSERT(off < sizeof relpath);
581990792Sgshapiro
582064562Sgshapiro		if (tTd(41, 2))
582190792Sgshapiro			sm_dprintf("multiqueue_cache: prefix=\"%s%s\"\n",
582290792Sgshapiro				   relpath, cp);
582364562Sgshapiro
582490792Sgshapiro		/* It is always basedir: we don't need to store it per group */
582590792Sgshapiro		/* XXX: optimize this! -> one more global? */
582690792Sgshapiro		qg->qg_qdir = newstr(basedir);
582790792Sgshapiro		qg->qg_qdir[blen - 1] = '\0';	/* cut off trailing / */
582864562Sgshapiro
582964562Sgshapiro		/*
583064562Sgshapiro		**  XXX Should probably wrap this whole loop in a timeout
583164562Sgshapiro		**  in case some wag decides to NFS mount the queues.
583264562Sgshapiro		*/
583364562Sgshapiro
583490792Sgshapiro		/* Test path to get warning messages. */
583590792Sgshapiro		if (qn == 0)
583664562Sgshapiro		{
583790792Sgshapiro			/*  XXX qg_runasuid and qg_runasgid for specials? */
583890792Sgshapiro			i = safedirpath(basedir, RunAsUid, RunAsGid, NULL,
583990792Sgshapiro					sff, 0, 0);
584090792Sgshapiro			if (i != 0 && tTd(41, 2))
584190792Sgshapiro				sm_dprintf("multiqueue_cache: \"%s\": Not safe: %s\n",
584290792Sgshapiro					   basedir, sm_errstring(i));
584364562Sgshapiro		}
584464562Sgshapiro
584590792Sgshapiro		if ((dp = opendir(prefix)) == NULL)
584664562Sgshapiro		{
584790792Sgshapiro			syserr("can not opendir(%s/%s)", qg->qg_qdir, prefix);
584864562Sgshapiro			if (tTd(41, 2))
584990792Sgshapiro				sm_dprintf("multiqueue_cache: opendir(\"%s/%s\"): %s\n",
585090792Sgshapiro					   qg->qg_qdir, prefix,
585190792Sgshapiro					   sm_errstring(errno));
585264562Sgshapiro			ExitStat = EX_CONFIG;
585390792Sgshapiro			return qn;
585464562Sgshapiro		}
585564562Sgshapiro		while ((d = readdir(dp)) != NULL)
585664562Sgshapiro		{
585790792Sgshapiro			i = strlen(d->d_name);
585890792Sgshapiro			if (i < len || strncmp(d->d_name, cp, len) != 0)
585964562Sgshapiro			{
586064562Sgshapiro				if (tTd(41, 5))
586190792Sgshapiro					sm_dprintf("multiqueue_cache: \"%s\", skipped\n",
586264562Sgshapiro						d->d_name);
586364562Sgshapiro				continue;
586464562Sgshapiro			}
586590792Sgshapiro
586690792Sgshapiro			/* Create relative pathname: prefix + local directory */
586790792Sgshapiro			i = sizeof(relpath) - off;
586890792Sgshapiro			if (sm_strlcpy(relpath + off, d->d_name, i) >= i)
586990792Sgshapiro				continue;	/* way too long */
587090792Sgshapiro
587190792Sgshapiro			if (!chkqdir(relpath, sff))
587264562Sgshapiro				continue;
587364562Sgshapiro
587490792Sgshapiro			if (qg->qg_qpaths == NULL)
587564562Sgshapiro			{
587690792Sgshapiro				slotsleft = INITIAL_SLOTS;
587790792Sgshapiro				qg->qg_qpaths = (QPATHS *)xalloc((sizeof *qg->qg_qpaths) *
587890792Sgshapiro								slotsleft);
587990792Sgshapiro				qg->qg_numqueues = 0;
588064562Sgshapiro			}
588164562Sgshapiro			else if (slotsleft < 1)
588264562Sgshapiro			{
588390792Sgshapiro				qg->qg_qpaths = (QPATHS *)sm_realloc((char *)qg->qg_qpaths,
588490792Sgshapiro							  (sizeof *qg->qg_qpaths) *
588590792Sgshapiro							  (qg->qg_numqueues +
588690792Sgshapiro							   ADD_SLOTS));
588790792Sgshapiro				if (qg->qg_qpaths == NULL)
588864562Sgshapiro				{
588964562Sgshapiro					(void) closedir(dp);
589090792Sgshapiro					return qn;
589164562Sgshapiro				}
589290792Sgshapiro				slotsleft += ADD_SLOTS;
589364562Sgshapiro			}
589464562Sgshapiro
589564562Sgshapiro			/* check subdirs */
589690792Sgshapiro			qg->qg_qpaths[qg->qg_numqueues].qp_subdirs = QP_NOSUB;
589764562Sgshapiro
589890792Sgshapiro#define CHKRSUBDIR(name, flag)	\
589990792Sgshapiro	(void) sm_strlcpyn(subdir, sizeof subdir, 3, relpath, "/", name); \
590090792Sgshapiro	if (chkqdir(subdir, sff))	\
590190792Sgshapiro		qg->qg_qpaths[qg->qg_numqueues].qp_subdirs |= flag;	\
590290792Sgshapiro	else
590364562Sgshapiro
590464562Sgshapiro
590590792Sgshapiro			CHKRSUBDIR("qf", QP_SUBQF);
590690792Sgshapiro			CHKRSUBDIR("df", QP_SUBDF);
590790792Sgshapiro			CHKRSUBDIR("xf", QP_SUBXF);
590890792Sgshapiro
590964562Sgshapiro			/* assert(strlen(d->d_name) < MAXPATHLEN - 14) */
591064562Sgshapiro			/* maybe even - 17 (subdirs) */
591190792Sgshapiro
591290792Sgshapiro			if (prefix[0] != '.')
591390792Sgshapiro				qg->qg_qpaths[qg->qg_numqueues].qp_name =
591490792Sgshapiro					newstr(relpath);
591590792Sgshapiro			else
591690792Sgshapiro				qg->qg_qpaths[qg->qg_numqueues].qp_name =
591790792Sgshapiro					newstr(d->d_name);
591890792Sgshapiro
591964562Sgshapiro			if (tTd(41, 2))
592090792Sgshapiro				sm_dprintf("multiqueue_cache: %d: \"%s\" cached (%x).\n",
592190792Sgshapiro					qg->qg_numqueues, relpath,
592290792Sgshapiro					qg->qg_qpaths[qg->qg_numqueues].qp_subdirs);
592390792Sgshapiro#if SM_CONF_SHM
592490792Sgshapiro			qg->qg_qpaths[qg->qg_numqueues].qp_idx = qn;
592590792Sgshapiro			*phash = hash_q(relpath, *phash);
592690792Sgshapiro#endif /* SM_CONF_SHM */
592790792Sgshapiro			qg->qg_numqueues++;
592890792Sgshapiro			++qn;
592964562Sgshapiro			slotsleft--;
593064562Sgshapiro		}
593164562Sgshapiro		(void) closedir(dp);
593290792Sgshapiro
593390792Sgshapiro		/* undo damage */
593490792Sgshapiro		*delim = '/';
593564562Sgshapiro	}
593690792Sgshapiro	if (qg->qg_numqueues == 0)
593764562Sgshapiro	{
593890792Sgshapiro		qg->qg_qpaths = (QPATHS *) xalloc(sizeof *qg->qg_qpaths);
593964562Sgshapiro
594064562Sgshapiro		/* test path to get warning messages */
594190792Sgshapiro		i = safedirpath(qpath, RunAsUid, RunAsGid, NULL, sff, 0, 0);
594290792Sgshapiro		if (i == ENOENT)
594364562Sgshapiro		{
594490792Sgshapiro			syserr("can not opendir(%s)", qpath);
594564562Sgshapiro			if (tTd(41, 2))
594690792Sgshapiro				sm_dprintf("multiqueue_cache: opendir(\"%s\"): %s\n",
594790792Sgshapiro					   qpath, sm_errstring(i));
594864562Sgshapiro			ExitStat = EX_CONFIG;
594990792Sgshapiro			return qn;
595064562Sgshapiro		}
595164562Sgshapiro
595290792Sgshapiro		qg->qg_qpaths[0].qp_subdirs = QP_NOSUB;
595390792Sgshapiro		qg->qg_numqueues = 1;
595490792Sgshapiro
595564562Sgshapiro		/* check subdirs */
595690792Sgshapiro#define CHKSUBDIR(name, flag)	\
595790792Sgshapiro	(void) sm_strlcpyn(subdir, sizeof subdir, 3, qg->qg_qdir, "/", name); \
595890792Sgshapiro	if (chkqdir(subdir, sff))	\
595990792Sgshapiro		qg->qg_qpaths[0].qp_subdirs |= flag;	\
596090792Sgshapiro	else
596164562Sgshapiro
596290792Sgshapiro		CHKSUBDIR("qf", QP_SUBQF);
596390792Sgshapiro		CHKSUBDIR("df", QP_SUBDF);
596490792Sgshapiro		CHKSUBDIR("xf", QP_SUBXF);
596564562Sgshapiro
596690792Sgshapiro		if (qg->qg_qdir[blen - 1] != '\0' &&
596790792Sgshapiro		    qg->qg_qdir[blen] != '\0')
596890792Sgshapiro		{
596990792Sgshapiro			/*
597090792Sgshapiro			**  Copy the last component into qpaths and
597190792Sgshapiro			**  cut off qdir
597290792Sgshapiro			*/
597390792Sgshapiro
597490792Sgshapiro			qg->qg_qpaths[0].qp_name = newstr(qg->qg_qdir + blen);
597590792Sgshapiro			qg->qg_qdir[blen - 1] = '\0';
597690792Sgshapiro		}
597790792Sgshapiro		else
597890792Sgshapiro			qg->qg_qpaths[0].qp_name = newstr(".");
597990792Sgshapiro
598090792Sgshapiro#if SM_CONF_SHM
598190792Sgshapiro		qg->qg_qpaths[0].qp_idx = qn;
598290792Sgshapiro		*phash = hash_q(qg->qg_qpaths[0].qp_name, *phash);
598390792Sgshapiro#endif /* SM_CONF_SHM */
598490792Sgshapiro		++qn;
598564562Sgshapiro	}
598690792Sgshapiro	return qn;
598764562Sgshapiro}
598864562Sgshapiro
598990792Sgshapiro/*
599090792Sgshapiro**  FILESYS_FIND -- find entry in FileSys table, or add new one
599190792Sgshapiro**
599290792Sgshapiro**	Given the pathname of a directory, determine the file system
599390792Sgshapiro**	in which that directory resides, and return a pointer to the
599490792Sgshapiro**	entry in the FileSys table that describes the file system.
599590792Sgshapiro**	A new entry is added if necessary (and requested).
599690792Sgshapiro**	If the directory does not exist, -1 is returned.
599790792Sgshapiro**
599890792Sgshapiro**	Parameters:
599990792Sgshapiro**		path -- pathname of directory
600090792Sgshapiro**		add -- add to structure if not found.
600190792Sgshapiro**
600290792Sgshapiro**	Returns:
600390792Sgshapiro**		>=0: found: index in file system table
600490792Sgshapiro**		<0: some error, i.e.,
600590792Sgshapiro**		FSF_TOO_MANY: too many filesystems (-> syserr())
600690792Sgshapiro**		FSF_STAT_FAIL: can't stat() filesystem (-> syserr())
600790792Sgshapiro**		FSF_NOT_FOUND: not in list
600890792Sgshapiro*/
600990792Sgshapiro
601090792Sgshapirostatic short filesys_find __P((char *, bool));
601190792Sgshapiro
601290792Sgshapiro#define FSF_NOT_FOUND	(-1)
601390792Sgshapiro#define FSF_STAT_FAIL	(-2)
601490792Sgshapiro#define FSF_TOO_MANY	(-3)
601590792Sgshapiro
601690792Sgshapirostatic short
601790792Sgshapirofilesys_find(path, add)
601890792Sgshapiro	char *path;
601990792Sgshapiro	bool add;
602090792Sgshapiro{
602190792Sgshapiro	struct stat st;
602290792Sgshapiro	short i;
602390792Sgshapiro
602490792Sgshapiro	if (stat(path, &st) < 0)
602590792Sgshapiro	{
602690792Sgshapiro		syserr("cannot stat queue directory %s", path);
602790792Sgshapiro		return FSF_STAT_FAIL;
602890792Sgshapiro	}
602990792Sgshapiro	for (i = 0; i < NumFileSys; ++i)
603090792Sgshapiro	{
603190792Sgshapiro		if (FILE_SYS_DEV(i) == st.st_dev)
603290792Sgshapiro			return i;
603390792Sgshapiro	}
603490792Sgshapiro	if (i >= MAXFILESYS)
603590792Sgshapiro	{
603690792Sgshapiro		syserr("too many queue file systems (%d max)", MAXFILESYS);
603790792Sgshapiro		return FSF_TOO_MANY;
603890792Sgshapiro	}
603990792Sgshapiro	if (!add)
604090792Sgshapiro		return FSF_NOT_FOUND;
604190792Sgshapiro
604290792Sgshapiro	++NumFileSys;
604390792Sgshapiro	FILE_SYS_NAME(i) = path;
604490792Sgshapiro	FILE_SYS_DEV(i) = st.st_dev;
604590792Sgshapiro	FILE_SYS_AVAIL(i) = 0;
604690792Sgshapiro	FILE_SYS_BLKSIZE(i) = 1024; /* avoid divide by zero */
604790792Sgshapiro	return i;
604890792Sgshapiro}
604990792Sgshapiro
605090792Sgshapiro/*
605190792Sgshapiro**  FILESYS_SETUP -- set up mapping from queue directories to file systems
605290792Sgshapiro**
605390792Sgshapiro**	This data structure is used to efficiently check the amount of
605490792Sgshapiro**	free space available in a set of queue directories.
605590792Sgshapiro**
605690792Sgshapiro**	Parameters:
605790792Sgshapiro**		add -- initialize structure if necessary.
605890792Sgshapiro**
605990792Sgshapiro**	Returns:
606090792Sgshapiro**		0: success
606190792Sgshapiro**		<0: some error, i.e.,
606290792Sgshapiro**		FSF_NOT_FOUND: not in list
606390792Sgshapiro**		FSF_STAT_FAIL: can't stat() filesystem (-> syserr())
606490792Sgshapiro**		FSF_TOO_MANY: too many filesystems (-> syserr())
606590792Sgshapiro*/
606690792Sgshapiro
606790792Sgshapirostatic int filesys_setup __P((bool));
606890792Sgshapiro
606990792Sgshapirostatic int
607090792Sgshapirofilesys_setup(add)
607190792Sgshapiro	bool add;
607290792Sgshapiro{
607390792Sgshapiro	int i, j;
607490792Sgshapiro	short fs;
607590792Sgshapiro	int ret;
607690792Sgshapiro
607790792Sgshapiro	ret = 0;
607890792Sgshapiro	for (i = 0; i < NumQueue && Queue[i] != NULL; i++)
607990792Sgshapiro	{
608090792Sgshapiro		for (j = 0; j < Queue[i]->qg_numqueues; ++j)
608190792Sgshapiro		{
608290792Sgshapiro			QPATHS *qp = &Queue[i]->qg_qpaths[j];
608390792Sgshapiro
608490792Sgshapiro			fs = filesys_find(qp->qp_name, add);
608590792Sgshapiro			if (fs >= 0)
608690792Sgshapiro				qp->qp_fsysidx = fs;
608790792Sgshapiro			else
608890792Sgshapiro				qp->qp_fsysidx = 0;
608990792Sgshapiro			if (fs < ret)
609090792Sgshapiro				ret = fs;
609190792Sgshapiro		}
609290792Sgshapiro	}
609390792Sgshapiro	return ret;
609490792Sgshapiro}
609590792Sgshapiro
609690792Sgshapiro/*
609790792Sgshapiro**  FILESYS_UPDATE -- update amount of free space on all file systems
609890792Sgshapiro**
609990792Sgshapiro**	The FileSys table is used to cache the amount of free space
610090792Sgshapiro**	available on all queue directory file systems.
610190792Sgshapiro**	This function updates the cached information if it has expired.
610290792Sgshapiro**
610390792Sgshapiro**	Parameters:
610490792Sgshapiro**		none.
610590792Sgshapiro**
610690792Sgshapiro**	Returns:
610790792Sgshapiro**		none.
610890792Sgshapiro**
610990792Sgshapiro**	Side Effects:
611090792Sgshapiro**		Updates FileSys table.
611190792Sgshapiro*/
611290792Sgshapiro
611390792Sgshapirovoid
611490792Sgshapirofilesys_update()
611590792Sgshapiro{
611690792Sgshapiro	int i;
611790792Sgshapiro	long avail, blksize;
611890792Sgshapiro	time_t now;
611990792Sgshapiro	static time_t nextupdate = 0;
612090792Sgshapiro
612190792Sgshapiro#if SM_CONF_SHM
612290792Sgshapiro	/* only the daemon updates this structure */
612390792Sgshapiro	if (ShmId != SM_SHM_NO_ID && DaemonPid != CurrentPid)
612490792Sgshapiro		return;
612590792Sgshapiro#endif /* SM_CONF_SHM */
612690792Sgshapiro	now = curtime();
612790792Sgshapiro	if (now < nextupdate)
612890792Sgshapiro		return;
612990792Sgshapiro	nextupdate = now + FILESYS_UPDATE_INTERVAL;
613090792Sgshapiro	for (i = 0; i < NumFileSys; ++i)
613190792Sgshapiro	{
613290792Sgshapiro		FILESYS *fs = &FILE_SYS(i);
613390792Sgshapiro
613490792Sgshapiro		avail = freediskspace(FILE_SYS_NAME(i), &blksize);
613590792Sgshapiro		if (avail < 0 || blksize <= 0)
613690792Sgshapiro		{
613790792Sgshapiro			if (LogLevel > 5)
613890792Sgshapiro				sm_syslog(LOG_ERR, NOQID,
613990792Sgshapiro					"filesys_update failed: %s, fs=%s, avail=%ld, blocksize=%ld",
614090792Sgshapiro					sm_errstring(errno),
614190792Sgshapiro					FILE_SYS_NAME(i), avail, blksize);
614290792Sgshapiro			fs->fs_avail = 0;
614390792Sgshapiro			fs->fs_blksize = 1024; /* avoid divide by zero */
614490792Sgshapiro			nextupdate = now + 2; /* let's do this soon again */
614590792Sgshapiro		}
614690792Sgshapiro		else
614790792Sgshapiro		{
614890792Sgshapiro			fs->fs_avail = avail;
614990792Sgshapiro			fs->fs_blksize = blksize;
615090792Sgshapiro		}
615190792Sgshapiro	}
615290792Sgshapiro}
615390792Sgshapiro
615490792Sgshapiro#if _FFR_ANY_FREE_FS
615590792Sgshapiro/*
615690792Sgshapiro**  FILESYS_FREE -- check whether there is at least one fs with enough space.
615790792Sgshapiro**
615890792Sgshapiro**	Parameters:
615990792Sgshapiro**		fsize -- file size in bytes
616090792Sgshapiro**
616190792Sgshapiro**	Returns:
616290792Sgshapiro**		true iff there is one fs with more than fsize bytes free.
616390792Sgshapiro*/
616490792Sgshapiro
616590792Sgshapirobool
616690792Sgshapirofilesys_free(fsize)
616790792Sgshapiro	long fsize;
616890792Sgshapiro{
616990792Sgshapiro	int i;
617090792Sgshapiro
617190792Sgshapiro	if (fsize <= 0)
617290792Sgshapiro		return true;
617390792Sgshapiro	for (i = 0; i < NumFileSys; ++i)
617490792Sgshapiro	{
617590792Sgshapiro		long needed = 0;
617690792Sgshapiro
617790792Sgshapiro		if (FILE_SYS_AVAIL(i) < 0 || FILE_SYS_BLKSIZE(i) <= 0)
617890792Sgshapiro			continue;
617990792Sgshapiro		needed += fsize / FILE_SYS_BLKSIZE(i)
618090792Sgshapiro			  + ((fsize % FILE_SYS_BLKSIZE(i)
618190792Sgshapiro			      > 0) ? 1 : 0)
618290792Sgshapiro			  + MinBlocksFree;
618390792Sgshapiro		if (needed <= FILE_SYS_AVAIL(i))
618490792Sgshapiro			return true;
618590792Sgshapiro	}
618690792Sgshapiro	return false;
618790792Sgshapiro}
618890792Sgshapiro#endif /* _FFR_ANY_FREE_FS */
618990792Sgshapiro
619090792Sgshapiro#if _FFR_CONTROL_MSTAT
619190792Sgshapiro/*
619290792Sgshapiro**  DISK_STATUS -- show amount of free space in queue directories
619390792Sgshapiro**
619490792Sgshapiro**	Parameters:
619590792Sgshapiro**		out -- output file pointer.
619690792Sgshapiro**		prefix -- string to output in front of each line.
619790792Sgshapiro**
619890792Sgshapiro**	Returns:
619990792Sgshapiro**		none.
620090792Sgshapiro*/
620190792Sgshapiro
620290792Sgshapirovoid
620390792Sgshapirodisk_status(out, prefix)
620490792Sgshapiro	SM_FILE_T *out;
620590792Sgshapiro	char *prefix;
620690792Sgshapiro{
620790792Sgshapiro	int i;
620890792Sgshapiro	long avail, blksize;
620990792Sgshapiro	long free;
621090792Sgshapiro
621190792Sgshapiro	for (i = 0; i < NumFileSys; ++i)
621290792Sgshapiro	{
621390792Sgshapiro		avail = freediskspace(FILE_SYS_NAME(i), &blksize);
621490792Sgshapiro		if (avail >= 0 && blksize > 0)
621590792Sgshapiro		{
621690792Sgshapiro			free = (long)((double) avail *
621790792Sgshapiro				((double) blksize / 1024));
621890792Sgshapiro		}
621990792Sgshapiro		else
622090792Sgshapiro			free = -1;
622190792Sgshapiro		(void) sm_io_fprintf(out, SM_TIME_DEFAULT,
622290792Sgshapiro				"%s%d/%s/%ld\r\n",
622390792Sgshapiro				prefix, i,
622490792Sgshapiro				FILE_SYS_NAME(i),
622590792Sgshapiro					free);
622690792Sgshapiro	}
622790792Sgshapiro}
622890792Sgshapiro#endif /* _FFR_CONTROL_MSTAT */
622990792Sgshapiro
623090792Sgshapiro#if SM_CONF_SHM
623190792Sgshapiro/*
623290792Sgshapiro**  UPD_QS -- update information about queue when adding/deleting an entry
623390792Sgshapiro**
623490792Sgshapiro**	Parameters:
623590792Sgshapiro**		e -- envelope.
623690792Sgshapiro**		delete -- delete/add entry.
623790792Sgshapiro**		avail -- update the space available as well.
623890792Sgshapiro**
623990792Sgshapiro**	Returns:
624090792Sgshapiro**		none.
624190792Sgshapiro**
624290792Sgshapiro**	Side Effects:
624390792Sgshapiro**		Modifies available space in filesystem.
624490792Sgshapiro**		Changes number of entries in queue directory.
624590792Sgshapiro*/
624690792Sgshapiro
624790792Sgshapirovoid
624890792Sgshapiroupd_qs(e, delete, avail)
624990792Sgshapiro	ENVELOPE *e;
625090792Sgshapiro	bool delete;
625190792Sgshapiro	bool avail;
625290792Sgshapiro{
625390792Sgshapiro	short fidx;
625490792Sgshapiro	int idx;
625590792Sgshapiro	long s;
625690792Sgshapiro
625790792Sgshapiro	if (ShmId == SM_SHM_NO_ID || e == NULL)
625890792Sgshapiro		return;
625990792Sgshapiro	if (e->e_qgrp == NOQGRP || e->e_qdir == NOQDIR)
626090792Sgshapiro		return;
626190792Sgshapiro	idx = Queue[e->e_qgrp]->qg_qpaths[e->e_qdir].qp_idx;
626290792Sgshapiro
626390792Sgshapiro	/* XXX in theory this needs to be protected with a mutex */
626490792Sgshapiro	if (QSHM_ENTRIES(idx) >= 0)
626590792Sgshapiro	{
626690792Sgshapiro		if (delete)
626790792Sgshapiro			--QSHM_ENTRIES(idx);
626890792Sgshapiro		else
626990792Sgshapiro			++QSHM_ENTRIES(idx);
627090792Sgshapiro	}
627190792Sgshapiro
627290792Sgshapiro	fidx = Queue[e->e_qgrp]->qg_qpaths[e->e_qdir].qp_fsysidx;
627390792Sgshapiro	if (fidx < 0)
627490792Sgshapiro		return;
627590792Sgshapiro
627690792Sgshapiro	/* update available space also?  (might be loseqfile) */
627790792Sgshapiro	if (!avail)
627890792Sgshapiro		return;
627990792Sgshapiro
628090792Sgshapiro	/* convert size to blocks; this causes rounding errors */
628190792Sgshapiro	s = e->e_msgsize / FILE_SYS_BLKSIZE(fidx);
628290792Sgshapiro	if (s == 0)
628390792Sgshapiro		return;
628490792Sgshapiro
628590792Sgshapiro	/* XXX in theory this needs to be protected with a mutex */
628690792Sgshapiro	if (delete)
628790792Sgshapiro		FILE_SYS_AVAIL(fidx) += s;
628890792Sgshapiro	else
628990792Sgshapiro		FILE_SYS_AVAIL(fidx) -= s;
629090792Sgshapiro
629190792Sgshapiro}
629290792Sgshapiro/*
629390792Sgshapiro**  INIT_SHM -- initialize shared memory structure
629490792Sgshapiro**
629590792Sgshapiro**	Initialize or attach to shared memory segment.
629690792Sgshapiro**	Currently it is not a fatal error if this doesn't work.
629790792Sgshapiro**	However, it causes us to have a "fallback" storage location
629890792Sgshapiro**	for everything that is supposed to be in the shared memory,
629990792Sgshapiro**	which makes the code slightly ugly.
630090792Sgshapiro**
630190792Sgshapiro**	Parameters:
630290792Sgshapiro**		qn -- number of queue directories.
630390792Sgshapiro**		owner -- owner of shared memory.
630490792Sgshapiro**		hash -- identifies data that is stored in shared memory.
630590792Sgshapiro**
630690792Sgshapiro**	Returns:
630790792Sgshapiro**		none.
630890792Sgshapiro*/
630990792Sgshapiro
631090792Sgshapirostatic void init_shm __P((int, bool, unsigned int));
631190792Sgshapiro
631290792Sgshapirostatic void
631390792Sgshapiroinit_shm(qn, owner, hash)
631490792Sgshapiro	int qn;
631590792Sgshapiro	bool owner;
631690792Sgshapiro	unsigned int hash;
631790792Sgshapiro{
631890792Sgshapiro	int i;
631990792Sgshapiro
632090792Sgshapiro	PtrFileSys = &FileSys[0];
632190792Sgshapiro	PNumFileSys = &Numfilesys;
632290792Sgshapiro
632390792Sgshapiro	/* This allows us to disable shared memory at runtime. */
632490792Sgshapiro	if (ShmKey != 0)
632590792Sgshapiro	{
632690792Sgshapiro		int count;
632790792Sgshapiro		int save_errno;
632890792Sgshapiro		size_t shms;
632990792Sgshapiro
633090792Sgshapiro		count = 0;
633190792Sgshapiro		shms = SM_T_SIZE + qn * sizeof(QUEUE_SHM_T);
633290792Sgshapiro		for (;;)
633390792Sgshapiro		{
633490792Sgshapiro			/* XXX: maybe allow read access for group? */
633590792Sgshapiro			Pshm = sm_shmstart(ShmKey, shms, SHM_R|SHM_W, &ShmId,
633690792Sgshapiro					   owner);
633790792Sgshapiro			save_errno = errno;
633890792Sgshapiro			if (Pshm != NULL || save_errno != EEXIST)
633990792Sgshapiro				break;
634090792Sgshapiro			if (++count >= 3)
634190792Sgshapiro				break;
634290792Sgshapiro			sleep(count);
634390792Sgshapiro		}
634490792Sgshapiro		if (Pshm != NULL)
634590792Sgshapiro		{
634690792Sgshapiro			int *p;
634790792Sgshapiro
634890792Sgshapiro			p = (int *) Pshm;
634990792Sgshapiro			if (owner)
635090792Sgshapiro			{
635190792Sgshapiro				*p = (int) shms;
635290792Sgshapiro				*((pid_t *) SHM_OFF_PID(Pshm)) = CurrentPid;
635390792Sgshapiro				p = (int *) SHM_OFF_TAG(Pshm);
635490792Sgshapiro				*p = hash;
635590792Sgshapiro			}
635690792Sgshapiro			else
635790792Sgshapiro			{
635890792Sgshapiro				if (*p != (int) shms)
635990792Sgshapiro				{
636090792Sgshapiro					save_errno = EINVAL;
636190792Sgshapiro					cleanup_shm(false);
636290792Sgshapiro					goto error;
636390792Sgshapiro				}
636490792Sgshapiro				p = (int *) SHM_OFF_TAG(Pshm);
636590792Sgshapiro				if (*p != (int) hash)
636690792Sgshapiro				{
636790792Sgshapiro					save_errno = EINVAL;
636890792Sgshapiro					cleanup_shm(false);
636990792Sgshapiro					goto error;
637090792Sgshapiro				}
637190792Sgshapiro
637290792Sgshapiro				/*
637390792Sgshapiro				**  XXX how to check the pid?
637490792Sgshapiro				**  Read it from the pid-file? That does
637590792Sgshapiro				**  not need to exist.
637690792Sgshapiro				**  We could disable shm if we can't confirm
637790792Sgshapiro				**  that it is the right one.
637890792Sgshapiro				*/
637990792Sgshapiro			}
638090792Sgshapiro
638190792Sgshapiro			PtrFileSys = (FILESYS *) OFF_FILE_SYS(Pshm);
638290792Sgshapiro			PNumFileSys = (int *) OFF_NUM_FILE_SYS(Pshm);
638390792Sgshapiro			QShm = (QUEUE_SHM_T *) OFF_QUEUE_SHM(Pshm);
638490792Sgshapiro			PRSATmpCnt = (int *) OFF_RSA_TMP_CNT(Pshm);
638590792Sgshapiro			*PRSATmpCnt = 0;
638690792Sgshapiro			if (owner)
638790792Sgshapiro			{
638890792Sgshapiro				/* initialize values in shared memory */
638990792Sgshapiro				NumFileSys = 0;
639090792Sgshapiro				for (i = 0; i < qn; i++)
639190792Sgshapiro					QShm[i].qs_entries = -1;
639290792Sgshapiro			}
639390792Sgshapiro			return;
639490792Sgshapiro		}
639590792Sgshapiro  error:
639690792Sgshapiro		if (LogLevel > (owner ? 8 : 11))
639790792Sgshapiro		{
639890792Sgshapiro			sm_syslog(owner ? LOG_ERR : LOG_NOTICE, NOQID,
639990792Sgshapiro				  "can't %s shared memory, key=%ld: %s",
640090792Sgshapiro				  owner ? "initialize" : "attach to",
640190792Sgshapiro				  (long) ShmKey, sm_errstring(save_errno));
640290792Sgshapiro		}
640390792Sgshapiro	}
640490792Sgshapiro}
640590792Sgshapiro#endif /* SM_CONF_SHM */
640690792Sgshapiro
640790792Sgshapiro/*
640890792Sgshapiro**  SETUP_QUEUES -- setup all queue groups
640990792Sgshapiro**
641090792Sgshapiro**	Parameters:
641190792Sgshapiro**		owner -- owner of shared memory.
641290792Sgshapiro**
641390792Sgshapiro**	Returns:
641490792Sgshapiro**		none.
641590792Sgshapiro**
641690792Sgshapiro#if SM_CONF_SHM
641790792Sgshapiro**	Side Effects:
641890792Sgshapiro**		attaches shared memory.
641990792Sgshapiro#endif * SM_CONF_SHM *
642090792Sgshapiro*/
642190792Sgshapiro
642290792Sgshapirovoid
642390792Sgshapirosetup_queues(owner)
642490792Sgshapiro	bool owner;
642590792Sgshapiro{
642690792Sgshapiro	int i, qn, len;
642790792Sgshapiro	unsigned int hashval;
642890792Sgshapiro	char basedir[MAXPATHLEN];
642990792Sgshapiro	struct stat st;
643090792Sgshapiro
643190792Sgshapiro	/*
643290792Sgshapiro	**  Determine basedir for all queue directories.
643390792Sgshapiro	**  All queue directories must be (first level) subdirectories
643490792Sgshapiro	**  of the basedir.  The basedir is the QueueDir
643590792Sgshapiro	**  without wildcards, but with trailing /
643690792Sgshapiro	*/
643790792Sgshapiro
643890792Sgshapiro	hashval = 0;
643990792Sgshapiro	errno = 0;
644090792Sgshapiro	len = sm_strlcpy(basedir, QueueDir, sizeof basedir);
644190792Sgshapiro	if (len >= sizeof basedir)
644290792Sgshapiro	{
644390792Sgshapiro		syserr("QueueDirectory: path too long: %d,  max %d",
644490792Sgshapiro			len, (int) sizeof basedir);
644590792Sgshapiro		ExitStat = EX_CONFIG;
644690792Sgshapiro		return;
644790792Sgshapiro	}
644890792Sgshapiro	SM_ASSERT(len > 0);
644990792Sgshapiro	if (basedir[len - 1] == '*')
645090792Sgshapiro	{
645190792Sgshapiro		char *cp;
645290792Sgshapiro
645390792Sgshapiro		cp = SM_LAST_DIR_DELIM(basedir);
645490792Sgshapiro		if (cp == NULL)
645590792Sgshapiro		{
645690792Sgshapiro			syserr("QueueDirectory: can not wildcard relative path \"%s\"",
645790792Sgshapiro				QueueDir);
645890792Sgshapiro			if (tTd(41, 2))
645990792Sgshapiro				sm_dprintf("setup_queues: \"%s\": Can not wildcard relative path.\n",
646090792Sgshapiro					QueueDir);
646190792Sgshapiro			ExitStat = EX_CONFIG;
646290792Sgshapiro			return;
646390792Sgshapiro		}
646490792Sgshapiro
646590792Sgshapiro		/* cut off wildcard pattern */
646690792Sgshapiro		*++cp = '\0';
646790792Sgshapiro		len = cp - basedir;
646890792Sgshapiro	}
646990792Sgshapiro	else if (!SM_IS_DIR_DELIM(basedir[len - 1]))
647090792Sgshapiro	{
647190792Sgshapiro		/* append trailing slash since it is a directory */
647290792Sgshapiro		basedir[len] = '/';
647390792Sgshapiro		basedir[++len] = '\0';
647490792Sgshapiro	}
647590792Sgshapiro
647690792Sgshapiro	/* len counts up to the last directory delimiter */
647790792Sgshapiro	SM_ASSERT(basedir[len - 1] == '/');
647890792Sgshapiro
647990792Sgshapiro	if (chdir(basedir) < 0)
648090792Sgshapiro	{
648190792Sgshapiro		int save_errno = errno;
648290792Sgshapiro
648390792Sgshapiro		syserr("can not chdir(%s)", basedir);
648490792Sgshapiro		if (save_errno == EACCES)
648590792Sgshapiro			(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
648690792Sgshapiro				"Program mode requires special privileges, e.g., root or TrustedUser.\n");
648790792Sgshapiro		if (tTd(41, 2))
648890792Sgshapiro			sm_dprintf("setup_queues: \"%s\": %s\n",
648990792Sgshapiro				   basedir, sm_errstring(errno));
649090792Sgshapiro		ExitStat = EX_CONFIG;
649190792Sgshapiro		return;
649290792Sgshapiro	}
649390792Sgshapiro#if SM_CONF_SHM
649490792Sgshapiro	hashval = hash_q(basedir, hashval);
649590792Sgshapiro#endif /* SM_CONF_SHM */
649690792Sgshapiro
649790792Sgshapiro	/* initialize map for queue runs */
649890792Sgshapiro	clrbitmap(DoQueueRun);
649990792Sgshapiro
650090792Sgshapiro
650190792Sgshapiro	if (UseMSP && OpMode != MD_TEST)
650290792Sgshapiro	{
650390792Sgshapiro		long sff = SFF_CREAT;
650490792Sgshapiro
650590792Sgshapiro		if (stat(".", &st) < 0)
650690792Sgshapiro		{
650790792Sgshapiro			syserr("can not stat(%s)", basedir);
650890792Sgshapiro			if (tTd(41, 2))
650990792Sgshapiro				sm_dprintf("setup_queues: \"%s\": %s\n",
651090792Sgshapiro					   basedir, sm_errstring(errno));
651190792Sgshapiro			ExitStat = EX_CONFIG;
651290792Sgshapiro			return;
651390792Sgshapiro		}
651490792Sgshapiro		if (RunAsUid == 0)
651590792Sgshapiro			sff |= SFF_ROOTOK;
651690792Sgshapiro
651790792Sgshapiro		/*
651890792Sgshapiro		**  Check queue directory permissions.
651990792Sgshapiro		**	Can we write to a group writable queue directory?
652090792Sgshapiro		*/
652190792Sgshapiro
652290792Sgshapiro		if (bitset(S_IWGRP, QueueFileMode) &&
652390792Sgshapiro		    bitset(S_IWGRP, st.st_mode) &&
652490792Sgshapiro		    safefile(" ", RunAsUid, RunAsGid, RunAsUserName, sff,
652590792Sgshapiro			     QueueFileMode, NULL) != 0)
652690792Sgshapiro		{
652790792Sgshapiro			syserr("can not write to queue directory %s (RunAsGid=%d, required=%d)",
652890792Sgshapiro				basedir, (int) RunAsGid, (int) st.st_gid);
652990792Sgshapiro		}
653090792Sgshapiro		if (bitset(S_IWOTH|S_IXOTH, st.st_mode))
653190792Sgshapiro		{
653290792Sgshapiro#if _FFR_MSP_PARANOIA
653390792Sgshapiro			syserr("dangerous permissions=%o on queue directory %s",
653490792Sgshapiro				(int) st.st_mode, basedir);
653590792Sgshapiro#else /* _FFR_MSP_PARANOIA */
653690792Sgshapiro			if (LogLevel > 0)
653790792Sgshapiro				sm_syslog(LOG_ERR, NOQID,
653890792Sgshapiro					  "dangerous permissions=%o on queue directory %s",
653990792Sgshapiro					  (int) st.st_mode, basedir);
654090792Sgshapiro#endif /* _FFR_MSP_PARANOIA */
654190792Sgshapiro		}
654290792Sgshapiro#if _FFR_MSP_PARANOIA
654390792Sgshapiro		if (NumQueue > 1)
654490792Sgshapiro			syserr("can not use multiple queues for MSP");
654590792Sgshapiro#endif /* _FFR_MSP_PARANOIA */
654690792Sgshapiro	}
654790792Sgshapiro
654890792Sgshapiro	/* initial number of queue directories */
654990792Sgshapiro	qn = 0;
655090792Sgshapiro	for (i = 0; i < NumQueue && Queue[i] != NULL; i++)
655190792Sgshapiro		qn = multiqueue_cache(basedir, len, Queue[i], qn, &hashval);
655290792Sgshapiro
655390792Sgshapiro#if SM_CONF_SHM
655490792Sgshapiro	init_shm(qn, owner, hashval);
655590792Sgshapiro	i = filesys_setup(owner || ShmId == SM_SHM_NO_ID);
655690792Sgshapiro	if (i == FSF_NOT_FOUND)
655790792Sgshapiro	{
655890792Sgshapiro		/*
655990792Sgshapiro		**  We didn't get the right filesystem data
656090792Sgshapiro		**  This may happen if we don't have the right shared memory.
656190792Sgshapiro		**  So let's do this without shared memory.
656290792Sgshapiro		*/
656390792Sgshapiro
656490792Sgshapiro		SM_ASSERT(!owner);
656590792Sgshapiro		cleanup_shm(false);	/* release shared memory */
656690792Sgshapiro		i = filesys_setup(false);
656790792Sgshapiro		if (i < 0)
656890792Sgshapiro			syserr("filesys_setup failed twice, result=%d", i);
656990792Sgshapiro		else if (LogLevel > 8)
657090792Sgshapiro			sm_syslog(LOG_WARNING, NOQID,
657190792Sgshapiro				  "shared memory does not contain expected data, ignored");
657290792Sgshapiro	}
657390792Sgshapiro#else /* SM_CONF_SHM */
657490792Sgshapiro	i = filesys_setup(true);
657590792Sgshapiro#endif /* SM_CONF_SHM */
657690792Sgshapiro	if (i < 0)
657790792Sgshapiro		ExitStat = EX_CONFIG;
657890792Sgshapiro}
657990792Sgshapiro
658090792Sgshapiro#if SM_CONF_SHM
658190792Sgshapiro/*
658290792Sgshapiro**  CLEANUP_SHM -- do some cleanup work for shared memory etc
658390792Sgshapiro**
658490792Sgshapiro**	Parameters:
658590792Sgshapiro**		owner -- owner of shared memory?
658690792Sgshapiro**
658790792Sgshapiro**	Returns:
658890792Sgshapiro**		none.
658990792Sgshapiro**
659090792Sgshapiro**	Side Effects:
659190792Sgshapiro**		detaches shared memory.
659290792Sgshapiro*/
659390792Sgshapiro
659490792Sgshapirovoid
659590792Sgshapirocleanup_shm(owner)
659690792Sgshapiro	bool owner;
659790792Sgshapiro{
659890792Sgshapiro	if (ShmId != SM_SHM_NO_ID)
659990792Sgshapiro	{
660090792Sgshapiro		if (sm_shmstop(Pshm, ShmId, owner) < 0 && LogLevel > 8)
660190792Sgshapiro			sm_syslog(LOG_INFO, NOQID, "sh_shmstop failed=%s",
660290792Sgshapiro				  sm_errstring(errno));
660390792Sgshapiro		Pshm = NULL;
660490792Sgshapiro		ShmId = SM_SHM_NO_ID;
660590792Sgshapiro	}
660690792Sgshapiro}
660790792Sgshapiro#endif /* SM_CONF_SHM */
660890792Sgshapiro
660990792Sgshapiro/*
661090792Sgshapiro**  CLEANUP_QUEUES -- do some cleanup work for queues
661190792Sgshapiro**
661290792Sgshapiro**	Parameters:
661390792Sgshapiro**		none.
661490792Sgshapiro**
661590792Sgshapiro**	Returns:
661690792Sgshapiro**		none.
661790792Sgshapiro**
661890792Sgshapiro*/
661990792Sgshapiro
662090792Sgshapirovoid
662190792Sgshapirocleanup_queues()
662290792Sgshapiro{
662390792Sgshapiro	sync_queue_time();
662490792Sgshapiro}
662590792Sgshapiro/*
662690792Sgshapiro**  SET_DEF_QUEUEVAL -- set default values for a queue group.
662790792Sgshapiro**
662890792Sgshapiro**	Parameters:
662990792Sgshapiro**		qg -- queue group
663090792Sgshapiro**		all -- set all values (true for default group)?
663190792Sgshapiro**
663290792Sgshapiro**	Returns:
663390792Sgshapiro**		none.
663490792Sgshapiro**
663590792Sgshapiro**	Side Effects:
663690792Sgshapiro**		sets default values for the queue group.
663790792Sgshapiro*/
663890792Sgshapiro
663990792Sgshapirovoid
664090792Sgshapiroset_def_queueval(qg, all)
664190792Sgshapiro	QUEUEGRP *qg;
664290792Sgshapiro	bool all;
664390792Sgshapiro{
664490792Sgshapiro	if (bitnset(QD_DEFINED, qg->qg_flags))
664590792Sgshapiro		return;
664690792Sgshapiro	if (all)
664790792Sgshapiro		qg->qg_qdir = QueueDir;
664890792Sgshapiro#if 0
664990792Sgshapiro	qg->qg_sortorder = QueueSortOrder;
665090792Sgshapiro#endif /* 0 */
665190792Sgshapiro	qg->qg_maxqrun = all ? MaxRunnersPerQueue : -1;
665290792Sgshapiro	qg->qg_nice = NiceQueueRun;
665390792Sgshapiro}
665490792Sgshapiro/*
665590792Sgshapiro**  MAKEQUEUE -- define a new queue.
665690792Sgshapiro**
665790792Sgshapiro**	Parameters:
665890792Sgshapiro**		line -- description of queue.  This is in labeled fields.
665990792Sgshapiro**			The fields are:
666090792Sgshapiro**			   F -- the flags associated with the queue
666190792Sgshapiro**			   I -- the interval between running the queue
666290792Sgshapiro**			   J -- the maximum # of jobs in work list
666390792Sgshapiro**			   [M -- the maximum # of jobs in a queue run]
666490792Sgshapiro**			   N -- the niceness at which to run
666590792Sgshapiro**			   P -- the path to the queue
666690792Sgshapiro**			   S -- the queue sorting order
666790792Sgshapiro**			   R -- number of parallel queue runners
666890792Sgshapiro**			   r -- max recipients per envelope
666990792Sgshapiro**			The first word is the canonical name of the queue.
667090792Sgshapiro**		qdef -- this is a 'Q' definition from .cf
667190792Sgshapiro**
667290792Sgshapiro**	Returns:
667390792Sgshapiro**		none.
667490792Sgshapiro**
667590792Sgshapiro**	Side Effects:
667690792Sgshapiro**		enters the queue into the queue table.
667790792Sgshapiro*/
667890792Sgshapiro
667990792Sgshapirovoid
668090792Sgshapiromakequeue(line, qdef)
668190792Sgshapiro	char *line;
668290792Sgshapiro	bool qdef;
668390792Sgshapiro{
668490792Sgshapiro	register char *p;
668590792Sgshapiro	register QUEUEGRP *qg;
668690792Sgshapiro	register STAB *s;
668790792Sgshapiro	int i;
668890792Sgshapiro	char fcode;
668990792Sgshapiro
669090792Sgshapiro	/* allocate a queue and set up defaults */
669190792Sgshapiro	qg = (QUEUEGRP *) xalloc(sizeof *qg);
669290792Sgshapiro	memset((char *) qg, '\0', sizeof *qg);
669390792Sgshapiro
669490792Sgshapiro	if (line[0] == '\0')
669590792Sgshapiro	{
669690792Sgshapiro		syserr("name required for queue");
669790792Sgshapiro		return;
669890792Sgshapiro	}
669990792Sgshapiro
670090792Sgshapiro	/* collect the queue name */
670190792Sgshapiro	for (p = line;
670290792Sgshapiro	     *p != '\0' && *p != ',' && !(isascii(*p) && isspace(*p));
670390792Sgshapiro	     p++)
670490792Sgshapiro		continue;
670590792Sgshapiro	if (*p != '\0')
670690792Sgshapiro		*p++ = '\0';
670790792Sgshapiro	qg->qg_name = newstr(line);
670890792Sgshapiro
670990792Sgshapiro	/* set default values, can be overridden below */
671090792Sgshapiro	set_def_queueval(qg, false);
671190792Sgshapiro
671290792Sgshapiro	/* now scan through and assign info from the fields */
671390792Sgshapiro	while (*p != '\0')
671490792Sgshapiro	{
671590792Sgshapiro		auto char *delimptr;
671690792Sgshapiro
671790792Sgshapiro		while (*p != '\0' &&
671890792Sgshapiro		       (*p == ',' || (isascii(*p) && isspace(*p))))
671990792Sgshapiro			p++;
672090792Sgshapiro
672190792Sgshapiro		/* p now points to field code */
672290792Sgshapiro		fcode = *p;
672390792Sgshapiro		while (*p != '\0' && *p != '=' && *p != ',')
672490792Sgshapiro			p++;
672590792Sgshapiro		if (*p++ != '=')
672690792Sgshapiro		{
672790792Sgshapiro			syserr("queue %s: `=' expected", qg->qg_name);
672890792Sgshapiro			return;
672990792Sgshapiro		}
673090792Sgshapiro		while (isascii(*p) && isspace(*p))
673190792Sgshapiro			p++;
673290792Sgshapiro
673390792Sgshapiro		/* p now points to the field body */
673490792Sgshapiro		p = munchstring(p, &delimptr, ',');
673590792Sgshapiro
673690792Sgshapiro		/* install the field into the queue struct */
673790792Sgshapiro		switch (fcode)
673890792Sgshapiro		{
673990792Sgshapiro		  case 'P':		/* pathname */
674090792Sgshapiro			if (*p == '\0')
674190792Sgshapiro				syserr("queue %s: empty path name",
674290792Sgshapiro					qg->qg_name);
674390792Sgshapiro			else
674490792Sgshapiro				qg->qg_qdir = newstr(p);
674590792Sgshapiro			break;
674690792Sgshapiro
674790792Sgshapiro		  case 'F':		/* flags */
674890792Sgshapiro			for (; *p != '\0'; p++)
674990792Sgshapiro				if (!(isascii(*p) && isspace(*p)))
675090792Sgshapiro					setbitn(*p, qg->qg_flags);
675190792Sgshapiro			break;
675290792Sgshapiro
675390792Sgshapiro			/*
675490792Sgshapiro			**  Do we need two intervals here:
675590792Sgshapiro			**  One for persistent queue runners,
675690792Sgshapiro			**  one for "normal" queue runs?
675790792Sgshapiro			*/
675890792Sgshapiro
675990792Sgshapiro		  case 'I':	/* interval between running the queue */
676090792Sgshapiro			qg->qg_queueintvl = convtime(p, 'm');
676190792Sgshapiro			break;
676290792Sgshapiro
676390792Sgshapiro		  case 'N':		/* run niceness */
676490792Sgshapiro			qg->qg_nice = atoi(p);
676590792Sgshapiro			break;
676690792Sgshapiro
676790792Sgshapiro		  case 'R':		/* maximum # of runners for the group */
676890792Sgshapiro			i = atoi(p);
676990792Sgshapiro
677090792Sgshapiro			/* can't have more runners than allowed total */
677190792Sgshapiro			if (MaxQueueChildren > 0 && i > MaxQueueChildren)
677290792Sgshapiro			{
677390792Sgshapiro				qg->qg_maxqrun = MaxQueueChildren;
677490792Sgshapiro				(void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
677590792Sgshapiro						     "Q=%s: R=%d exceeds MaxQueueChildren=%d, set to MaxQueueChildren\n",
677690792Sgshapiro						     qg->qg_name, i,
677790792Sgshapiro						     MaxQueueChildren);
677890792Sgshapiro			}
677990792Sgshapiro			else
678090792Sgshapiro				qg->qg_maxqrun = i;
678190792Sgshapiro			break;
678290792Sgshapiro
678390792Sgshapiro		  case 'J':		/* maximum # of jobs in work list */
678490792Sgshapiro			qg->qg_maxlist = atoi(p);
678590792Sgshapiro			break;
678690792Sgshapiro
678790792Sgshapiro		  case 'r':		/* max recipients per envelope */
678890792Sgshapiro			qg->qg_maxrcpt = atoi(p);
678990792Sgshapiro			break;
679090792Sgshapiro
679190792Sgshapiro#if 0
679290792Sgshapiro		  case 'S':		/* queue sorting order */
679390792Sgshapiro			switch (*p)
679490792Sgshapiro			{
679590792Sgshapiro			  case 'h':	/* Host first */
679690792Sgshapiro			  case 'H':
679790792Sgshapiro				qg->qg_sortorder = QSO_BYHOST;
679890792Sgshapiro				break;
679990792Sgshapiro
680090792Sgshapiro			  case 'p':	/* Priority order */
680190792Sgshapiro			  case 'P':
680290792Sgshapiro				qg->qg_sortorder = QSO_BYPRIORITY;
680390792Sgshapiro				break;
680490792Sgshapiro
680590792Sgshapiro			  case 't':	/* Submission time */
680690792Sgshapiro			  case 'T':
680790792Sgshapiro				qg->qg_sortorder = QSO_BYTIME;
680890792Sgshapiro				break;
680990792Sgshapiro
681090792Sgshapiro			  case 'f':	/* File name */
681190792Sgshapiro			  case 'F':
681290792Sgshapiro				qg->qg_sortorder = QSO_BYFILENAME;
681390792Sgshapiro				break;
681490792Sgshapiro
681590792Sgshapiro			  case 'm':	/* Modification time */
681690792Sgshapiro			  case 'M':
681790792Sgshapiro				qgrp->qg_sortorder = QSO_BYMODTIME;
681890792Sgshapiro				break;
681990792Sgshapiro
682090792Sgshapiro			  default:
682190792Sgshapiro				syserr("Invalid queue sort order \"%s\"", p);
682290792Sgshapiro			}
682390792Sgshapiro			break;
682490792Sgshapiro#endif /* 0 */
682590792Sgshapiro
682690792Sgshapiro		  default:
682790792Sgshapiro			syserr("Q%s: unknown queue equate %c=",
682890792Sgshapiro			       qg->qg_name, fcode);
682990792Sgshapiro			break;
683090792Sgshapiro		}
683190792Sgshapiro
683290792Sgshapiro		p = delimptr;
683390792Sgshapiro	}
683490792Sgshapiro
683590792Sgshapiro#if !HASNICE
683690792Sgshapiro	if (qg->qg_nice != NiceQueueRun)
683790792Sgshapiro	{
683890792Sgshapiro		(void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
683990792Sgshapiro				     "Q%s: Warning: N= set on system that doesn't support nice()\n",
684090792Sgshapiro				     qg->qg_name);
684190792Sgshapiro	}
684290792Sgshapiro#endif /* !HASNICE */
684390792Sgshapiro
684490792Sgshapiro	/* do some rationality checking */
684590792Sgshapiro	if (NumQueue >= MAXQUEUEGROUPS)
684690792Sgshapiro	{
684790792Sgshapiro		syserr("too many queue groups defined (%d max)",
684890792Sgshapiro			MAXQUEUEGROUPS);
684990792Sgshapiro		return;
685090792Sgshapiro	}
685190792Sgshapiro
685290792Sgshapiro	if (qg->qg_qdir == NULL)
685390792Sgshapiro	{
685490792Sgshapiro		if (QueueDir == NULL || *QueueDir == '\0')
685590792Sgshapiro		{
685690792Sgshapiro			syserr("QueueDir must be defined before queue groups");
685790792Sgshapiro			return;
685890792Sgshapiro		}
685990792Sgshapiro		qg->qg_qdir = newstr(QueueDir);
686090792Sgshapiro	}
686190792Sgshapiro
686290792Sgshapiro	if (qg->qg_maxqrun > 1 && !bitnset(QD_FORK, qg->qg_flags))
686390792Sgshapiro	{
686490792Sgshapiro		(void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
686590792Sgshapiro				     "Warning: Q=%s: R=%d: multiple queue runners specified\n\tbut flag '%c' is not set\n",
686690792Sgshapiro				     qg->qg_name, qg->qg_maxqrun, QD_FORK);
686790792Sgshapiro	}
686890792Sgshapiro
686990792Sgshapiro	/* enter the queue into the symbol table */
687090792Sgshapiro	if (tTd(37, 8))
687190792Sgshapiro		sm_syslog(LOG_INFO, NOQID,
687290792Sgshapiro			  "Adding %s to stab, path: %s", qg->qg_name,
687390792Sgshapiro			  qg->qg_qdir);
687490792Sgshapiro	s = stab(qg->qg_name, ST_QUEUE, ST_ENTER);
687590792Sgshapiro	if (s->s_quegrp != NULL)
687690792Sgshapiro	{
687790792Sgshapiro		i = s->s_quegrp->qg_index;
687890792Sgshapiro
687990792Sgshapiro		/* XXX what about the pointers inside this struct? */
688090792Sgshapiro		sm_free(s->s_quegrp); /* XXX */
688190792Sgshapiro	}
688290792Sgshapiro	else
688390792Sgshapiro		i = NumQueue++;
688490792Sgshapiro	Queue[i] = s->s_quegrp = qg;
688590792Sgshapiro	qg->qg_index = i;
688690792Sgshapiro
688790792Sgshapiro	/* set default value for max queue runners */
688890792Sgshapiro	if (qg->qg_maxqrun < 0)
688990792Sgshapiro	{
689090792Sgshapiro		if (MaxRunnersPerQueue > 0)
689190792Sgshapiro			qg->qg_maxqrun = MaxRunnersPerQueue;
689290792Sgshapiro		else
689390792Sgshapiro			qg->qg_maxqrun = 1;
689490792Sgshapiro	}
689590792Sgshapiro	if (qdef)
689690792Sgshapiro		setbitn(QD_DEFINED, qg->qg_flags);
689790792Sgshapiro}
689890792Sgshapiro#if 0
689990792Sgshapiro/*
690064562Sgshapiro**  HASHFQN -- calculate a hash value for a fully qualified host name
690164562Sgshapiro**
690264562Sgshapiro**	Arguments:
690364562Sgshapiro**		fqn -- an all lower-case host.domain string
690464562Sgshapiro**		buckets -- the number of buckets (queue directories)
690564562Sgshapiro**
690664562Sgshapiro**	Returns:
690764562Sgshapiro**		a bucket number (signed integer)
690864562Sgshapiro**		-1 on error
690964562Sgshapiro**
691064562Sgshapiro**	Contributed by Exactis.com, Inc.
691164562Sgshapiro*/
691264562Sgshapiro
691364562Sgshapiroint
691464562Sgshapirohashfqn(fqn, buckets)
691564562Sgshapiro	register char *fqn;
691664562Sgshapiro	int buckets;
691764562Sgshapiro{
691864562Sgshapiro	register char *p;
691964562Sgshapiro	register int h = 0, hash, cnt;
692064562Sgshapiro
692164562Sgshapiro	if (fqn == NULL)
692264562Sgshapiro		return -1;
692364562Sgshapiro
692464562Sgshapiro	/*
692564562Sgshapiro	**  A variation on the gdb hash
692664562Sgshapiro	**  This is the best as of Feb 19, 1996 --bcx
692764562Sgshapiro	*/
692864562Sgshapiro
692964562Sgshapiro	p = fqn;
693064562Sgshapiro	h = 0x238F13AF * strlen(p);
693164562Sgshapiro	for (cnt = 0; *p != 0; ++p, cnt++)
693264562Sgshapiro	{
693364562Sgshapiro		h = (h + (*p << (cnt * 5 % 24))) & 0x7FFFFFFF;
693464562Sgshapiro	}
693564562Sgshapiro	h = (1103515243 * h + 12345) & 0x7FFFFFFF;
693664562Sgshapiro	if (buckets < 2)
693764562Sgshapiro		hash = 0;
693864562Sgshapiro	else
693964562Sgshapiro		hash = (h % buckets);
694064562Sgshapiro
694164562Sgshapiro	return hash;
694264562Sgshapiro}
694390792Sgshapiro#endif /* 0 */
694464562Sgshapiro
694590792Sgshapiro#if _FFR_QUEUEDELAY
694690792Sgshapiro/*
694764562Sgshapiro**  QUEUEDELAY -- compute queue delay time
694864562Sgshapiro**
694964562Sgshapiro**	Parameters:
695064562Sgshapiro**		e -- the envelope to queue up.
695164562Sgshapiro**
695264562Sgshapiro**	Returns:
695364562Sgshapiro**		queue delay time
695464562Sgshapiro**
695564562Sgshapiro**	Side Effects:
695664562Sgshapiro**		may change e_queuedelay
695764562Sgshapiro*/
695864562Sgshapiro
695964562Sgshapirostatic time_t
696064562Sgshapiroqueuedelay(e)
696164562Sgshapiro	ENVELOPE *e;
696264562Sgshapiro{
696364562Sgshapiro	time_t qd;
696464562Sgshapiro
696564562Sgshapiro	if (e->e_queuealg == QD_EXP)
696664562Sgshapiro	{
696764562Sgshapiro		if (e->e_queuedelay == 0)
696864562Sgshapiro			e->e_queuedelay = QueueInitDelay;
696964562Sgshapiro		else
697064562Sgshapiro		{
697164562Sgshapiro			e->e_queuedelay *= 2;
697264562Sgshapiro			if (e->e_queuedelay > QueueMaxDelay)
697364562Sgshapiro				e->e_queuedelay = QueueMaxDelay;
697464562Sgshapiro		}
697564562Sgshapiro		qd = e->e_queuedelay;
697664562Sgshapiro	}
697764562Sgshapiro	else
697864562Sgshapiro		qd = MinQueueAge;
697964562Sgshapiro	return qd;
698064562Sgshapiro}
698190792Sgshapiro#endif /* _FFR_QUEUEDELAY */
698290792Sgshapiro
698390792Sgshapiro/*
698490792Sgshapiro**  A structure for sorting Queue according to maxqrun without
698590792Sgshapiro**	screwing up Queue itself.
698690792Sgshapiro*/
698790792Sgshapiro
698890792Sgshapirostruct sortqgrp
698990792Sgshapiro{
699090792Sgshapiro	int sg_idx;		/* original index */
699190792Sgshapiro	int sg_maxqrun;		/* max queue runners */
699290792Sgshapiro};
699390792Sgshapirotypedef struct sortqgrp	SORTQGRP_T;
699490792Sgshapirostatic int cmpidx __P((const void *, const void *));
699590792Sgshapiro
699690792Sgshapirostatic int
699790792Sgshapirocmpidx(a, b)
699890792Sgshapiro	const void *a;
699990792Sgshapiro	const void *b;
700090792Sgshapiro{
700190792Sgshapiro	/* The sort is highest to lowest, so the comparison is reversed */
700290792Sgshapiro	if (((SORTQGRP_T *)a)->sg_maxqrun < ((SORTQGRP_T *)b)->sg_maxqrun)
700390792Sgshapiro		return 1;
700490792Sgshapiro	else if (((SORTQGRP_T *)a)->sg_maxqrun > ((SORTQGRP_T *)b)->sg_maxqrun)
700590792Sgshapiro		return -1;
700690792Sgshapiro	else
700790792Sgshapiro		return 0;
700890792Sgshapiro}
700990792Sgshapiro
701090792Sgshapiro/*
701190792Sgshapiro**  MAKEWORKGROUP -- balance queue groups into work groups per MaxQueueChildren
701290792Sgshapiro**
701390792Sgshapiro**  Take the now defined queue groups and assign them to work groups.
701490792Sgshapiro**  This is done to balance out the number of concurrently active
701590792Sgshapiro**  queue runners such that MaxQueueChildren is not exceeded. This may
701690792Sgshapiro**  result in more than one queue group per work group. In such a case
701790792Sgshapiro**  the number of running queue groups in that work group will have no
701890792Sgshapiro**  more than the work group maximum number of runners (a "fair" portion
701990792Sgshapiro**  of MaxQueueRunners). All queue groups within a work group will get a
702090792Sgshapiro**  chance at running.
702190792Sgshapiro**
702290792Sgshapiro**	Parameters:
702390792Sgshapiro**		none.
702490792Sgshapiro**
702590792Sgshapiro**	Returns:
702690792Sgshapiro**		nothing.
702790792Sgshapiro**
702890792Sgshapiro**	Side Effects:
702990792Sgshapiro**		Sets up WorkGrp structure.
703090792Sgshapiro*/
703190792Sgshapiro
703290792Sgshapirovoid
703390792Sgshapiromakeworkgroups()
703490792Sgshapiro{
703590792Sgshapiro	int i, j, total_runners = 0;
703690792Sgshapiro	int dir;
703790792Sgshapiro	SORTQGRP_T si[MAXQUEUEGROUPS + 1];
703890792Sgshapiro
703990792Sgshapiro	if (NumQueue == 1 && strcmp(Queue[0]->qg_name, "mqueue") == 0)
704090792Sgshapiro	{
704190792Sgshapiro		/*
704290792Sgshapiro		**  There is only the "mqueue" queue group (a default)
704390792Sgshapiro		**  containing all of the queues. We want to provide to
704490792Sgshapiro		**  this queue group the maximum allowable queue runners.
704590792Sgshapiro		**  To match older behavior (8.10/8.11) we'll try for
704690792Sgshapiro		**  1 runner per queue capping it at MaxQueueChildren.
704790792Sgshapiro		**  So if there are N queues, then there will be N runners
704890792Sgshapiro		**  for the "mqueue" queue group (where N is kept less than
704990792Sgshapiro		**  MaxQueueChildren).
705090792Sgshapiro		*/
705190792Sgshapiro
705290792Sgshapiro		NumWorkGroups = 1;
705390792Sgshapiro		WorkGrp[0].wg_numqgrp = 1;
705490792Sgshapiro		WorkGrp[0].wg_qgs = (QUEUEGRP **) xalloc(sizeof(QUEUEGRP *));
705590792Sgshapiro		WorkGrp[0].wg_qgs[0] = Queue[0];
705690792Sgshapiro		if (MaxQueueChildren > 0 &&
705790792Sgshapiro		    Queue[0]->qg_numqueues > MaxQueueChildren)
705890792Sgshapiro			WorkGrp[0].wg_runners = MaxQueueChildren;
705990792Sgshapiro		else
706090792Sgshapiro			WorkGrp[0].wg_runners = Queue[0]->qg_numqueues;
706190792Sgshapiro
706290792Sgshapiro		Queue[0]->qg_wgrp = 0;
706390792Sgshapiro
706490792Sgshapiro		/* can't have more runners than allowed total */
706590792Sgshapiro		if (MaxQueueChildren > 0 &&
706690792Sgshapiro		    Queue[0]->qg_maxqrun > MaxQueueChildren)
706790792Sgshapiro			Queue[0]->qg_maxqrun = MaxQueueChildren;
706890792Sgshapiro		WorkGrp[0].wg_maxact = Queue[0]->qg_maxqrun;
706990792Sgshapiro		WorkGrp[0].wg_lowqintvl = Queue[0]->qg_queueintvl;
707090792Sgshapiro		return;
707190792Sgshapiro	}
707290792Sgshapiro
707390792Sgshapiro	for (i = 0; i < NumQueue; i++)
707490792Sgshapiro	{
707590792Sgshapiro		si[i].sg_maxqrun = Queue[i]->qg_maxqrun;
707690792Sgshapiro		si[i].sg_idx = i;
707790792Sgshapiro	}
707890792Sgshapiro	qsort(si, NumQueue, sizeof(si[0]), cmpidx);
707990792Sgshapiro
708090792Sgshapiro	NumWorkGroups = 0;
708190792Sgshapiro	for (i = 0; i < NumQueue; i++)
708290792Sgshapiro	{
708390792Sgshapiro		total_runners += si[i].sg_maxqrun;
708490792Sgshapiro		if (MaxQueueChildren <= 0 || total_runners <= MaxQueueChildren)
708590792Sgshapiro			NumWorkGroups++;
708690792Sgshapiro		else
708790792Sgshapiro			break;
708890792Sgshapiro	}
708990792Sgshapiro
709090792Sgshapiro	if (NumWorkGroups < 1)
709190792Sgshapiro		NumWorkGroups = 1; /* gotta have one at least */
709290792Sgshapiro	else if (NumWorkGroups > MAXWORKGROUPS)
709390792Sgshapiro		NumWorkGroups = MAXWORKGROUPS; /* the limit */
709490792Sgshapiro
709590792Sgshapiro	/*
709690792Sgshapiro	**  We now know the number of work groups to pack the queue groups
709790792Sgshapiro	**  into. The queue groups in 'Queue' are sorted from highest
709890792Sgshapiro	**  to lowest for the number of runners per queue group.
709990792Sgshapiro	**  We put the queue groups with the largest number of runners
710090792Sgshapiro	**  into work groups first. Then the smaller ones are fitted in
710190792Sgshapiro	**  where it looks best.
710290792Sgshapiro	*/
710390792Sgshapiro
710490792Sgshapiro	j = 0;
710590792Sgshapiro	dir = 1;
710690792Sgshapiro	for (i = 0; i < NumQueue; i++)
710790792Sgshapiro	{
710890792Sgshapiro		/* a to-and-fro packing scheme, continue from last position */
710990792Sgshapiro		if (j >= NumWorkGroups)
711090792Sgshapiro		{
711190792Sgshapiro			dir = -1;
711290792Sgshapiro			j = NumWorkGroups - 1;
711390792Sgshapiro		}
711490792Sgshapiro		else if (j < 0)
711590792Sgshapiro		{
711690792Sgshapiro			j = 0;
711790792Sgshapiro			dir = 1;
711890792Sgshapiro		}
711990792Sgshapiro
712090792Sgshapiro		WorkGrp[j].wg_qgs = (QUEUEGRP **)sm_realloc(WorkGrp[j].wg_qgs,
712190792Sgshapiro						sizeof(QUEUEGRP *) *
712290792Sgshapiro						(WorkGrp[j].wg_numqgrp + 1));
712390792Sgshapiro		if (WorkGrp[j].wg_qgs == NULL)
712490792Sgshapiro		{
712590792Sgshapiro			syserr("@cannot allocate memory for work queues, need %d bytes",
712690792Sgshapiro			       (int) (sizeof(QUEUEGRP *) *
712790792Sgshapiro				      (WorkGrp[j].wg_numqgrp + 1)));
712890792Sgshapiro		}
712990792Sgshapiro
713090792Sgshapiro		WorkGrp[j].wg_qgs[WorkGrp[j].wg_numqgrp] = Queue[si[i].sg_idx];
713190792Sgshapiro		WorkGrp[j].wg_numqgrp++;
713290792Sgshapiro		WorkGrp[j].wg_runners += Queue[i]->qg_maxqrun;
713390792Sgshapiro		Queue[si[i].sg_idx]->qg_wgrp = j;
713490792Sgshapiro
713590792Sgshapiro		if (WorkGrp[j].wg_maxact == 0)
713690792Sgshapiro		{
713790792Sgshapiro			/* can't have more runners than allowed total */
713890792Sgshapiro			if (MaxQueueChildren > 0 &&
713990792Sgshapiro			    Queue[i]->qg_maxqrun > MaxQueueChildren)
714090792Sgshapiro				Queue[i]->qg_maxqrun = MaxQueueChildren;
714190792Sgshapiro			WorkGrp[j].wg_maxact = Queue[i]->qg_maxqrun;
714290792Sgshapiro		}
714390792Sgshapiro
714490792Sgshapiro		/*
714590792Sgshapiro		**  XXX: must wg_lowqintvl be the GCD?
714690792Sgshapiro		**  qg1: 2m, qg2: 3m, minimum: 2m, when do queue runs for
714790792Sgshapiro		**  qg2 occur?
714890792Sgshapiro		*/
714990792Sgshapiro
715090792Sgshapiro		/* keep track of the lowest interval for a persistent runner */
715190792Sgshapiro		if (Queue[si[i].sg_idx]->qg_queueintvl > 0 &&
715290792Sgshapiro		    WorkGrp[j].wg_lowqintvl < Queue[si[i].sg_idx]->qg_queueintvl)
715390792Sgshapiro			WorkGrp[j].wg_lowqintvl = Queue[si[i].sg_idx]->qg_queueintvl;
715490792Sgshapiro		j += dir;
715590792Sgshapiro	}
715690792Sgshapiro	if (tTd(41, 9))
715790792Sgshapiro	{
715890792Sgshapiro		for (i = 0; i < NumWorkGroups; i++)
715990792Sgshapiro		{
716090792Sgshapiro			sm_dprintf("Workgroup[%d]=", i);
716190792Sgshapiro			for (j = 0; j < WorkGrp[i].wg_numqgrp; j++)
716290792Sgshapiro			{
716390792Sgshapiro				sm_dprintf("%s, ",
716490792Sgshapiro					WorkGrp[i].wg_qgs[j]->qg_name);
716590792Sgshapiro			}
716690792Sgshapiro			sm_dprintf("\n");
716790792Sgshapiro		}
716890792Sgshapiro	}
716990792Sgshapiro}
717090792Sgshapiro
717190792Sgshapiro/*
717290792Sgshapiro**  DUP_DF -- duplicate envelope data file
717390792Sgshapiro**
717490792Sgshapiro**	Copy the data file from the 'old' envelope to the 'new' envelope
717590792Sgshapiro**	in the most efficient way possible.
717690792Sgshapiro**
717790792Sgshapiro**	Create a hard link from the 'old' data file to the 'new' data file.
717890792Sgshapiro**	If the old and new queue directories are on different file systems,
717990792Sgshapiro**	then the new data file link is created in the old queue directory,
718090792Sgshapiro**	and the new queue file will contain a 'd' record pointing to the
718190792Sgshapiro**	directory containing the new data file.
718290792Sgshapiro**
718390792Sgshapiro**	Parameters:
718490792Sgshapiro**		old -- old envelope.
718590792Sgshapiro**		new -- new envelope.
718690792Sgshapiro**
718790792Sgshapiro**	Results:
718890792Sgshapiro**		Returns true on success, false on failure.
718990792Sgshapiro**
719090792Sgshapiro**	Side Effects:
719190792Sgshapiro**		On success, the new data file is created.
719290792Sgshapiro**		On fatal failure, EF_FATALERRS is set in old->e_flags.
719390792Sgshapiro*/
719490792Sgshapiro
719590792Sgshapirostatic bool	dup_df __P((ENVELOPE *, ENVELOPE *));
719690792Sgshapiro
719790792Sgshapirostatic bool
719890792Sgshapirodup_df(old, new)
719990792Sgshapiro	ENVELOPE *old;
720090792Sgshapiro	ENVELOPE *new;
720190792Sgshapiro{
720290792Sgshapiro	int ofs, nfs, r;
720390792Sgshapiro	char opath[MAXPATHLEN];
720490792Sgshapiro	char npath[MAXPATHLEN];
720590792Sgshapiro
720690792Sgshapiro	SM_REQUIRE(bitset(EF_HAS_DF, old->e_flags));
720790792Sgshapiro	SM_REQUIRE(ISVALIDQGRP(old->e_qgrp) && ISVALIDQDIR(old->e_qdir));
720890792Sgshapiro	SM_REQUIRE(ISVALIDQGRP(new->e_qgrp) && ISVALIDQDIR(new->e_qdir));
720990792Sgshapiro
721090792Sgshapiro	(void) sm_strlcpy(opath, queuename(old, DATAFL_LETTER), sizeof opath);
721190792Sgshapiro	(void) sm_strlcpy(npath, queuename(new, DATAFL_LETTER), sizeof npath);
721290792Sgshapiro
721390792Sgshapiro	if (old->e_dfp != NULL)
721490792Sgshapiro	{
721590792Sgshapiro		r = sm_io_setinfo(old->e_dfp, SM_BF_COMMIT, NULL);
721690792Sgshapiro		if (r < 0 && errno != EINVAL)
721790792Sgshapiro		{
721890792Sgshapiro			syserr("@can't commit %s", opath);
721990792Sgshapiro			old->e_flags |= EF_FATALERRS;
722090792Sgshapiro			return false;
722190792Sgshapiro		}
722290792Sgshapiro	}
722390792Sgshapiro
722490792Sgshapiro	/*
722590792Sgshapiro	**  Attempt to create a hard link, if we think both old and new
722690792Sgshapiro	**  are on the same file system, otherwise copy the file.
722790792Sgshapiro	**
722890792Sgshapiro	**  Don't waste time attempting a hard link unless old and new
722990792Sgshapiro	**  are on the same file system.
723090792Sgshapiro	*/
723190792Sgshapiro
723290792Sgshapiro	ofs = Queue[old->e_qgrp]->qg_qpaths[old->e_qdir].qp_fsysidx;
723390792Sgshapiro	nfs = Queue[new->e_qgrp]->qg_qpaths[new->e_qdir].qp_fsysidx;
723490792Sgshapiro	if (FILE_SYS_DEV(ofs) == FILE_SYS_DEV(nfs))
723590792Sgshapiro	{
723690792Sgshapiro		if (link(opath, npath) == 0)
723790792Sgshapiro		{
723890792Sgshapiro			new->e_flags |= EF_HAS_DF;
723990792Sgshapiro			SYNC_DIR(npath, true);
724090792Sgshapiro			return true;
724190792Sgshapiro		}
724290792Sgshapiro		goto error;
724390792Sgshapiro	}
724490792Sgshapiro
724590792Sgshapiro	/*
724690792Sgshapiro	**  Can't link across queue directories, so try to create a hard
724790792Sgshapiro	**  link in the same queue directory as the old df file.
724890792Sgshapiro	**  The qf file will refer to the new df file using a 'd' record.
724990792Sgshapiro	*/
725090792Sgshapiro
725190792Sgshapiro	new->e_dfqgrp = old->e_dfqgrp;
725290792Sgshapiro	new->e_dfqdir = old->e_dfqdir;
725390792Sgshapiro	(void) sm_strlcpy(npath, queuename(new, DATAFL_LETTER), sizeof npath);
725490792Sgshapiro	if (link(opath, npath) == 0)
725590792Sgshapiro	{
725690792Sgshapiro		new->e_flags |= EF_HAS_DF;
725790792Sgshapiro		SYNC_DIR(npath, true);
725890792Sgshapiro		return true;
725990792Sgshapiro	}
726090792Sgshapiro
726190792Sgshapiro  error:
726290792Sgshapiro	if (LogLevel > 0)
726390792Sgshapiro		sm_syslog(LOG_ERR, old->e_id,
726490792Sgshapiro			  "dup_df: can't link %s to %s, error=%s, envelope splitting failed",
726590792Sgshapiro			  opath, npath, sm_errstring(errno));
726690792Sgshapiro	return false;
726790792Sgshapiro}
726890792Sgshapiro
726990792Sgshapiro/*
727090792Sgshapiro**  SPLIT_ENV -- Allocate a new envelope based on a given envelope.
727190792Sgshapiro**
727290792Sgshapiro**	Parameters:
727390792Sgshapiro**		e -- envelope.
727490792Sgshapiro**		sendqueue -- sendqueue for new envelope.
727590792Sgshapiro**		qgrp -- index of queue group.
727690792Sgshapiro**		qdir -- queue directory.
727790792Sgshapiro**
727890792Sgshapiro**	Results:
727990792Sgshapiro**		new envelope.
728090792Sgshapiro**
728190792Sgshapiro*/
728290792Sgshapiro
728390792Sgshapirostatic ENVELOPE	*split_env __P((ENVELOPE *, ADDRESS *, int, int));
728490792Sgshapiro
728590792Sgshapirostatic ENVELOPE *
728690792Sgshapirosplit_env(e, sendqueue, qgrp, qdir)
728790792Sgshapiro	ENVELOPE *e;
728890792Sgshapiro	ADDRESS *sendqueue;
728990792Sgshapiro	int qgrp;
729090792Sgshapiro	int qdir;
729190792Sgshapiro{
729290792Sgshapiro	ENVELOPE *ee;
729390792Sgshapiro
729490792Sgshapiro	ee = (ENVELOPE *) sm_rpool_malloc_x(e->e_rpool, sizeof *ee);
729590792Sgshapiro	STRUCTCOPY(*e, *ee);
729690792Sgshapiro	ee->e_message = NULL;	/* XXX use original message? */
729790792Sgshapiro	ee->e_id = NULL;
729890792Sgshapiro	assign_queueid(ee);
729990792Sgshapiro	ee->e_sendqueue = sendqueue;
730090792Sgshapiro	ee->e_flags &= ~(EF_INQUEUE|EF_CLRQUEUE|EF_FATALERRS
730190792Sgshapiro			 |EF_SENDRECEIPT|EF_RET_PARAM|EF_HAS_DF);
730290792Sgshapiro	ee->e_flags |= EF_NORECEIPT;	/* XXX really? */
730390792Sgshapiro	ee->e_from.q_state = QS_SENDER;
730490792Sgshapiro	ee->e_dfp = NULL;
730590792Sgshapiro	ee->e_lockfp = NULL;
730690792Sgshapiro	if (e->e_xfp != NULL)
730790792Sgshapiro		ee->e_xfp = sm_io_dup(e->e_xfp);
730890792Sgshapiro	ee->e_qgrp = ee->e_dfqgrp = qgrp;
730990792Sgshapiro	ee->e_qdir = ee->e_dfqdir = qdir;
731090792Sgshapiro	ee->e_errormode = EM_MAIL;
731190792Sgshapiro	ee->e_statmsg = NULL;
731290792Sgshapiro#if _FFR_QUARANTINE
731390792Sgshapiro	if (e->e_quarmsg != NULL)
731490792Sgshapiro		ee->e_quarmsg = sm_rpool_strdup_x(ee->e_rpool,
731590792Sgshapiro						  e->e_quarmsg);
731690792Sgshapiro#endif /* _FFR_QUARANTINE */
731790792Sgshapiro
731890792Sgshapiro	/*
731990792Sgshapiro	**  XXX Not sure if this copying is necessary.
732090792Sgshapiro	**  sendall() does this copying, but I don't know if that is
732190792Sgshapiro	**  because of the storage management discipline we were using
732290792Sgshapiro	**  before rpools were introduced, or if it is because these lists
732390792Sgshapiro	**  can be modified later.
732490792Sgshapiro	*/
732590792Sgshapiro
732690792Sgshapiro	ee->e_header = copyheader(e->e_header, ee->e_rpool);
732790792Sgshapiro	ee->e_errorqueue = copyqueue(e->e_errorqueue, ee->e_rpool);
732890792Sgshapiro
732990792Sgshapiro	return ee;
733090792Sgshapiro}
733190792Sgshapiro
733290792Sgshapiro/* return values from split functions, check also below! */
733390792Sgshapiro#define SM_SPLIT_FAIL	(0)
733490792Sgshapiro#define SM_SPLIT_NONE	(1)
733590792Sgshapiro#define SM_SPLIT_NEW(n)	(1 + (n))
733690792Sgshapiro
733790792Sgshapiro/*
733890792Sgshapiro**  SPLIT_ACROSS_QUEUE_GROUPS
733990792Sgshapiro**
734090792Sgshapiro**	This function splits an envelope across multiple queue groups
734190792Sgshapiro**	based on the queue group of each recipient.
734290792Sgshapiro**
734390792Sgshapiro**	Parameters:
734490792Sgshapiro**		e -- envelope.
734590792Sgshapiro**
734690792Sgshapiro**	Results:
734790792Sgshapiro**		SM_SPLIT_FAIL on failure
734890792Sgshapiro**		SM_SPLIT_NONE if no splitting occurred,
734990792Sgshapiro**		or 1 + the number of additional envelopes created.
735090792Sgshapiro**
735190792Sgshapiro**	Side Effects:
735290792Sgshapiro**		On success, e->e_sibling points to a list of zero or more
735390792Sgshapiro**		additional envelopes, and the associated data files exist
735490792Sgshapiro**		on disk.  But the queue files are not created.
735590792Sgshapiro**
735690792Sgshapiro**		On failure, e->e_sibling is not changed.
735790792Sgshapiro**		The order of recipients in e->e_sendqueue is permuted.
735890792Sgshapiro**		Abandoned data files for additional envelopes that failed
735990792Sgshapiro**		to be created may exist on disk.
736090792Sgshapiro*/
736190792Sgshapiro
736290792Sgshapirostatic int	q_qgrp_compare __P((const void *, const void *));
736390792Sgshapirostatic int	e_filesys_compare __P((const void *, const void *));
736490792Sgshapiro
736590792Sgshapirostatic int
736690792Sgshapiroq_qgrp_compare(p1, p2)
736790792Sgshapiro	const void *p1;
736890792Sgshapiro	const void *p2;
736990792Sgshapiro{
737090792Sgshapiro	ADDRESS **pq1 = (ADDRESS **) p1;
737190792Sgshapiro	ADDRESS **pq2 = (ADDRESS **) p2;
737290792Sgshapiro
737390792Sgshapiro	return (*pq1)->q_qgrp - (*pq2)->q_qgrp;
737490792Sgshapiro}
737590792Sgshapiro
737690792Sgshapirostatic int
737790792Sgshapiroe_filesys_compare(p1, p2)
737890792Sgshapiro	const void *p1;
737990792Sgshapiro	const void *p2;
738090792Sgshapiro{
738190792Sgshapiro	ENVELOPE **pe1 = (ENVELOPE **) p1;
738290792Sgshapiro	ENVELOPE **pe2 = (ENVELOPE **) p2;
738390792Sgshapiro	int fs1, fs2;
738490792Sgshapiro
738590792Sgshapiro	fs1 = Queue[(*pe1)->e_qgrp]->qg_qpaths[(*pe1)->e_qdir].qp_fsysidx;
738690792Sgshapiro	fs2 = Queue[(*pe2)->e_qgrp]->qg_qpaths[(*pe2)->e_qdir].qp_fsysidx;
738790792Sgshapiro	if (FILE_SYS_DEV(fs1) < FILE_SYS_DEV(fs2))
738890792Sgshapiro		return -1;
738990792Sgshapiro	if (FILE_SYS_DEV(fs1) > FILE_SYS_DEV(fs2))
739090792Sgshapiro		return 1;
739190792Sgshapiro	return 0;
739290792Sgshapiro}
739390792Sgshapiro
739490792Sgshapirostatic int
739590792Sgshapirosplit_across_queue_groups(e)
739690792Sgshapiro	ENVELOPE *e;
739790792Sgshapiro{
739890792Sgshapiro	int naddrs, nsplits, i;
739990792Sgshapiro	char **pvp;
740090792Sgshapiro	ADDRESS *q, **addrs;
740190792Sgshapiro	ENVELOPE *ee, *es;
740290792Sgshapiro	ENVELOPE *splits[MAXQUEUEGROUPS];
740390792Sgshapiro	char pvpbuf[PSBUFSIZE];
740490792Sgshapiro
740590792Sgshapiro	SM_REQUIRE(ISVALIDQGRP(e->e_qgrp));
740690792Sgshapiro
740790792Sgshapiro	/* Count addresses and assign queue groups. */
740890792Sgshapiro	naddrs = 0;
740990792Sgshapiro	for (q = e->e_sendqueue; q != NULL; q = q->q_next)
741090792Sgshapiro	{
741190792Sgshapiro		if (QS_IS_DEAD(q->q_state))
741290792Sgshapiro			continue;
741390792Sgshapiro		++naddrs;
741490792Sgshapiro
741590792Sgshapiro		/* bad addresses and those already sent stay put */
741690792Sgshapiro		if (QS_IS_BADADDR(q->q_state) ||
741790792Sgshapiro		    QS_IS_SENT(q->q_state))
741890792Sgshapiro			q->q_qgrp = e->e_qgrp;
741990792Sgshapiro		else if (!ISVALIDQGRP(q->q_qgrp))
742090792Sgshapiro		{
742190792Sgshapiro			/* call ruleset which should return a queue group */
742290792Sgshapiro			i = rscap(RS_QUEUEGROUP, q->q_user, NULL, e, &pvp,
742390792Sgshapiro				  pvpbuf, sizeof(pvpbuf));
742490792Sgshapiro			if (i == EX_OK &&
742590792Sgshapiro			    pvp != NULL && pvp[0] != NULL &&
742690792Sgshapiro			    (pvp[0][0] & 0377) == CANONNET &&
742790792Sgshapiro			    pvp[1] != NULL && pvp[1][0] != '\0')
742890792Sgshapiro			{
742990792Sgshapiro				i = name2qid(pvp[1]);
743090792Sgshapiro				if (ISVALIDQGRP(i))
743190792Sgshapiro				{
743290792Sgshapiro					q->q_qgrp = i;
743390792Sgshapiro					if (tTd(20, 4))
743490792Sgshapiro						sm_syslog(LOG_INFO, NOQID,
743590792Sgshapiro							"queue group name %s -> %d",
743690792Sgshapiro							pvp[1], i);
743790792Sgshapiro					continue;
743890792Sgshapiro				}
743990792Sgshapiro				else if (LogLevel > 10)
744090792Sgshapiro					sm_syslog(LOG_INFO, NOQID,
744190792Sgshapiro						"can't find queue group name %s, selection ignored",
744290792Sgshapiro						pvp[1]);
744390792Sgshapiro			}
744490792Sgshapiro			if (q->q_mailer != NULL &&
744590792Sgshapiro			    ISVALIDQGRP(q->q_mailer->m_qgrp))
744690792Sgshapiro				q->q_qgrp = q->q_mailer->m_qgrp;
744790792Sgshapiro			else
744890792Sgshapiro				q->q_qgrp = 0;
744990792Sgshapiro		}
745090792Sgshapiro	}
745190792Sgshapiro
745290792Sgshapiro	/* only one address? nothing to split. */
745390792Sgshapiro	if (naddrs <= 1)
745490792Sgshapiro		return SM_SPLIT_NONE;
745590792Sgshapiro
745690792Sgshapiro	/* sort the addresses by queue group */
745790792Sgshapiro	addrs = sm_rpool_malloc_x(e->e_rpool, naddrs * sizeof(ADDRESS *));
745890792Sgshapiro	for (i = 0, q = e->e_sendqueue; q != NULL; q = q->q_next)
745990792Sgshapiro	{
746090792Sgshapiro		if (QS_IS_DEAD(q->q_state))
746190792Sgshapiro			continue;
746290792Sgshapiro		addrs[i++] = q;
746390792Sgshapiro	}
746490792Sgshapiro	qsort(addrs, naddrs, sizeof(ADDRESS *), q_qgrp_compare);
746590792Sgshapiro
746690792Sgshapiro	/* split into multiple envelopes, by queue group */
746790792Sgshapiro	nsplits = 0;
746890792Sgshapiro	es = NULL;
746990792Sgshapiro	e->e_sendqueue = NULL;
747090792Sgshapiro	for (i = 0; i < naddrs; ++i)
747190792Sgshapiro	{
747290792Sgshapiro		if (i == naddrs - 1 || addrs[i]->q_qgrp != addrs[i + 1]->q_qgrp)
747390792Sgshapiro			addrs[i]->q_next = NULL;
747490792Sgshapiro		else
747590792Sgshapiro			addrs[i]->q_next = addrs[i + 1];
747690792Sgshapiro
747790792Sgshapiro		/* same queue group as original envelope? */
747890792Sgshapiro		if (addrs[i]->q_qgrp == e->e_qgrp)
747990792Sgshapiro		{
748090792Sgshapiro			if (e->e_sendqueue == NULL)
748190792Sgshapiro				e->e_sendqueue = addrs[i];
748290792Sgshapiro			continue;
748390792Sgshapiro		}
748490792Sgshapiro
748590792Sgshapiro		/* different queue group than original envelope */
748690792Sgshapiro		if (es == NULL || addrs[i]->q_qgrp != es->e_qgrp)
748790792Sgshapiro		{
748890792Sgshapiro			ee = split_env(e, addrs[i], addrs[i]->q_qgrp, NOQDIR);
748990792Sgshapiro			es = ee;
749090792Sgshapiro			splits[nsplits++] = ee;
749190792Sgshapiro		}
749290792Sgshapiro	}
749390792Sgshapiro
749490792Sgshapiro	/* no splits? return right now. */
749590792Sgshapiro	if (nsplits <= 0)
749690792Sgshapiro		return SM_SPLIT_NONE;
749790792Sgshapiro
749890792Sgshapiro	/* assign a queue directory to each additional envelope */
749990792Sgshapiro	for (i = 0; i < nsplits; ++i)
750090792Sgshapiro	{
750190792Sgshapiro		es = splits[i];
750290792Sgshapiro#if 0
750390792Sgshapiro		es->e_qdir = pickqdir(Queue[es->e_qgrp], es->e_msgsize, es);
750490792Sgshapiro#endif /* 0 */
750590792Sgshapiro		if (!setnewqueue(es))
750690792Sgshapiro			goto failure;
750790792Sgshapiro	}
750890792Sgshapiro
750990792Sgshapiro	/* sort the additional envelopes by queue file system */
751090792Sgshapiro	qsort(splits, nsplits, sizeof(ENVELOPE *), e_filesys_compare);
751190792Sgshapiro
751290792Sgshapiro	/* create data files for each additional envelope */
751390792Sgshapiro	if (!dup_df(e, splits[0]))
751490792Sgshapiro	{
751590792Sgshapiro		i = 0;
751690792Sgshapiro		goto failure;
751790792Sgshapiro	}
751890792Sgshapiro	for (i = 1; i < nsplits; ++i)
751990792Sgshapiro	{
752090792Sgshapiro		/* copy or link to the previous data file */
752190792Sgshapiro		if (!dup_df(splits[i - 1], splits[i]))
752290792Sgshapiro			goto failure;
752390792Sgshapiro	}
752490792Sgshapiro
752590792Sgshapiro	/* success: prepend the new envelopes to the e->e_sibling list */
752690792Sgshapiro	for (i = 0; i < nsplits; ++i)
752790792Sgshapiro	{
752890792Sgshapiro		es = splits[i];
752990792Sgshapiro		es->e_sibling = e->e_sibling;
753090792Sgshapiro		e->e_sibling = es;
753190792Sgshapiro	}
753290792Sgshapiro	return SM_SPLIT_NEW(nsplits);
753390792Sgshapiro
753490792Sgshapiro	/* failure: clean up */
753590792Sgshapiro  failure:
753690792Sgshapiro	if (i > 0)
753790792Sgshapiro	{
753890792Sgshapiro		int j;
753990792Sgshapiro
754090792Sgshapiro		for (j = 0; j < i; j++)
754190792Sgshapiro			(void) unlink(queuename(splits[j], DATAFL_LETTER));
754290792Sgshapiro	}
754390792Sgshapiro	e->e_sendqueue = addrs[0];
754490792Sgshapiro	for (i = 0; i < naddrs - 1; ++i)
754590792Sgshapiro		addrs[i]->q_next = addrs[i + 1];
754690792Sgshapiro	addrs[naddrs - 1]->q_next = NULL;
754790792Sgshapiro	return SM_SPLIT_FAIL;
754890792Sgshapiro}
754990792Sgshapiro
755090792Sgshapiro/*
755190792Sgshapiro**  SPLIT_WITHIN_QUEUE
755290792Sgshapiro**
755390792Sgshapiro**	Split an envelope with multiple recipients into several
755490792Sgshapiro**	envelopes within the same queue directory, if the number of
755590792Sgshapiro**	recipients exceeds the limit for the queue group.
755690792Sgshapiro**
755790792Sgshapiro**	Parameters:
755890792Sgshapiro**		e -- envelope.
755990792Sgshapiro**
756090792Sgshapiro**	Results:
756190792Sgshapiro**		SM_SPLIT_FAIL on failure
756290792Sgshapiro**		SM_SPLIT_NONE if no splitting occurred,
756390792Sgshapiro**		or 1 + the number of additional envelopes created.
756490792Sgshapiro*/
756590792Sgshapiro
756690792Sgshapiro#define SPLIT_LOG_LEVEL	8
756790792Sgshapiro
756890792Sgshapirostatic int	split_within_queue __P((ENVELOPE *));
756990792Sgshapiro
757090792Sgshapirostatic int
757190792Sgshapirosplit_within_queue(e)
757290792Sgshapiro	ENVELOPE *e;
757390792Sgshapiro{
757490792Sgshapiro	int maxrcpt, nrcpt, ndead, nsplit, i;
757590792Sgshapiro	int j, l;
757690792Sgshapiro	char *lsplits;
757790792Sgshapiro	ADDRESS *q, **addrs;
757890792Sgshapiro	ENVELOPE *ee, *firstsibling;
757990792Sgshapiro
758090792Sgshapiro	if (!ISVALIDQGRP(e->e_qgrp) || bitset(EF_SPLIT, e->e_flags))
758190792Sgshapiro		return SM_SPLIT_NONE;
758290792Sgshapiro
758390792Sgshapiro	/* don't bother if there is no recipient limit */
758490792Sgshapiro	maxrcpt = Queue[e->e_qgrp]->qg_maxrcpt;
758590792Sgshapiro	if (maxrcpt <= 0)
758690792Sgshapiro		return SM_SPLIT_NONE;
758790792Sgshapiro
758890792Sgshapiro	/* count recipients */
758990792Sgshapiro	nrcpt = 0;
759090792Sgshapiro	for (q = e->e_sendqueue; q != NULL; q = q->q_next)
759190792Sgshapiro	{
759290792Sgshapiro		if (QS_IS_DEAD(q->q_state))
759390792Sgshapiro			continue;
759490792Sgshapiro		++nrcpt;
759590792Sgshapiro	}
759690792Sgshapiro	if (nrcpt <= maxrcpt)
759790792Sgshapiro		return SM_SPLIT_NONE;
759890792Sgshapiro
759990792Sgshapiro	/*
760090792Sgshapiro	**  Preserve the recipient list
760190792Sgshapiro	**  so that we can restore it in case of error.
760290792Sgshapiro	**  (But we discard dead addresses.)
760390792Sgshapiro	*/
760490792Sgshapiro
760590792Sgshapiro	addrs = sm_rpool_malloc_x(e->e_rpool, nrcpt * sizeof(ADDRESS *));
760690792Sgshapiro	for (i = 0, q = e->e_sendqueue; q != NULL; q = q->q_next)
760790792Sgshapiro	{
760890792Sgshapiro		if (QS_IS_DEAD(q->q_state))
760990792Sgshapiro			continue;
761090792Sgshapiro		addrs[i++] = q;
761190792Sgshapiro	}
761290792Sgshapiro
761390792Sgshapiro	/*
761490792Sgshapiro	**  Partition the recipient list so that bad and sent addresses
761590792Sgshapiro	**  come first. These will go with the original envelope, and
761690792Sgshapiro	**  do not count towards the maxrcpt limit.
761790792Sgshapiro	**  addrs[] does not contain QS_IS_DEAD() addresses.
761890792Sgshapiro	*/
761990792Sgshapiro
762090792Sgshapiro	ndead = 0;
762190792Sgshapiro	for (i = 0; i < nrcpt; ++i)
762290792Sgshapiro	{
762390792Sgshapiro		if (QS_IS_BADADDR(addrs[i]->q_state) ||
762490792Sgshapiro		    QS_IS_SENT(addrs[i]->q_state) ||
762590792Sgshapiro		    QS_IS_DEAD(addrs[i]->q_state)) /* for paranoia's sake */
762690792Sgshapiro		{
762790792Sgshapiro			if (i > ndead)
762890792Sgshapiro			{
762990792Sgshapiro				ADDRESS *tmp = addrs[i];
763090792Sgshapiro
763190792Sgshapiro				addrs[i] = addrs[ndead];
763290792Sgshapiro				addrs[ndead] = tmp;
763390792Sgshapiro			}
763490792Sgshapiro			++ndead;
763590792Sgshapiro		}
763690792Sgshapiro	}
763790792Sgshapiro
763890792Sgshapiro	/* Check if no splitting required. */
763990792Sgshapiro	if (nrcpt - ndead <= maxrcpt)
764090792Sgshapiro		return SM_SPLIT_NONE;
764190792Sgshapiro
764290792Sgshapiro	/* fix links */
764390792Sgshapiro	for (i = 0; i < nrcpt - 1; ++i)
764490792Sgshapiro		addrs[i]->q_next = addrs[i + 1];
764590792Sgshapiro	addrs[nrcpt - 1]->q_next = NULL;
764690792Sgshapiro	e->e_sendqueue = addrs[0];
764790792Sgshapiro
764890792Sgshapiro	/* prepare buffer for logging */
764990792Sgshapiro	if (LogLevel > SPLIT_LOG_LEVEL)
765090792Sgshapiro	{
765190792Sgshapiro		l = MAXLINE;
765290792Sgshapiro		lsplits = sm_malloc(l);
765390792Sgshapiro		if (lsplits != NULL)
765490792Sgshapiro			*lsplits = '\0';
765590792Sgshapiro		j = 0;
765690792Sgshapiro	}
765790792Sgshapiro	else
765890792Sgshapiro	{
765990792Sgshapiro		/* get rid of stupid compiler warnings */
766090792Sgshapiro		lsplits = NULL;
766190792Sgshapiro		j = l = 0;
766290792Sgshapiro	}
766390792Sgshapiro
766490792Sgshapiro	/* split the envelope */
766590792Sgshapiro	firstsibling = e->e_sibling;
766690792Sgshapiro	i = maxrcpt + ndead;
766790792Sgshapiro	nsplit = 0;
766890792Sgshapiro	for (;;)
766990792Sgshapiro	{
767090792Sgshapiro		addrs[i - 1]->q_next = NULL;
767190792Sgshapiro		ee = split_env(e, addrs[i], e->e_qgrp, e->e_qdir);
767290792Sgshapiro		if (!dup_df(e, ee))
767390792Sgshapiro		{
767490792Sgshapiro
767590792Sgshapiro			ee = firstsibling;
767690792Sgshapiro			while (ee != NULL)
767790792Sgshapiro			{
767890792Sgshapiro				(void) unlink(queuename(ee, DATAFL_LETTER));
767990792Sgshapiro				ee = ee->e_sibling;
768090792Sgshapiro			}
768190792Sgshapiro
768290792Sgshapiro			/* Error.  Restore e's sibling & recipient lists. */
768390792Sgshapiro			e->e_sibling = firstsibling;
768490792Sgshapiro			for (i = 0; i < nrcpt - 1; ++i)
768590792Sgshapiro				addrs[i]->q_next = addrs[i + 1];
768690792Sgshapiro			return SM_SPLIT_FAIL;
768790792Sgshapiro		}
768890792Sgshapiro
768990792Sgshapiro		/* prepend the new envelope to e->e_sibling */
769090792Sgshapiro		ee->e_sibling = e->e_sibling;
769190792Sgshapiro		e->e_sibling = ee;
769290792Sgshapiro		++nsplit;
769390792Sgshapiro		if (LogLevel > SPLIT_LOG_LEVEL && lsplits != NULL)
769490792Sgshapiro		{
769590792Sgshapiro			if (j >= l - strlen(ee->e_id) - 3)
769690792Sgshapiro			{
769790792Sgshapiro				char *p;
769890792Sgshapiro
769990792Sgshapiro				l += MAXLINE;
770090792Sgshapiro				p = sm_realloc(lsplits, l);
770190792Sgshapiro				if (p == NULL)
770290792Sgshapiro				{
770390792Sgshapiro					/* let's try to get this done */
770490792Sgshapiro					sm_free(lsplits);
770590792Sgshapiro					lsplits = NULL;
770690792Sgshapiro				}
770790792Sgshapiro				else
770890792Sgshapiro					lsplits = p;
770990792Sgshapiro			}
771090792Sgshapiro			if (lsplits != NULL)
771190792Sgshapiro			{
771290792Sgshapiro				if (j == 0)
771390792Sgshapiro					j += sm_strlcat(lsplits + j,
771490792Sgshapiro							ee->e_id,
771590792Sgshapiro							l - j);
771690792Sgshapiro				else
771790792Sgshapiro					j += sm_strlcat2(lsplits + j,
771890792Sgshapiro							 "; ",
771990792Sgshapiro							 ee->e_id,
772090792Sgshapiro							 l - j);
772190792Sgshapiro				SM_ASSERT(j < l);
772290792Sgshapiro			}
772390792Sgshapiro		}
772490792Sgshapiro		if (nrcpt - i <= maxrcpt)
772590792Sgshapiro			break;
772690792Sgshapiro		i += maxrcpt;
772790792Sgshapiro	}
772890792Sgshapiro	if (LogLevel > SPLIT_LOG_LEVEL && lsplits != NULL && nsplit > 0)
772990792Sgshapiro	{
773090792Sgshapiro		sm_syslog(LOG_NOTICE, e->e_id,
773190792Sgshapiro			  "split: maxrcpts=%d, rcpts=%d, count=%d, id%s=%s",
773290792Sgshapiro			  maxrcpt, nrcpt - ndead, nsplit,
773390792Sgshapiro			  nsplit > 1 ? "s" : "", lsplits);
773490792Sgshapiro		sm_free(lsplits);
773590792Sgshapiro	}
773690792Sgshapiro	return SM_SPLIT_NEW(nsplit);
773790792Sgshapiro}
773890792Sgshapiro/*
773990792Sgshapiro**  SPLIT_BY_RECIPIENT
774090792Sgshapiro**
774190792Sgshapiro**	Split an envelope with multiple recipients into multiple
774290792Sgshapiro**	envelopes as required by the sendmail configuration.
774390792Sgshapiro**
774490792Sgshapiro**	Parameters:
774590792Sgshapiro**		e -- envelope.
774690792Sgshapiro**
774790792Sgshapiro**	Results:
774890792Sgshapiro**		Returns true on success, false on failure.
774990792Sgshapiro**
775090792Sgshapiro**	Side Effects:
775190792Sgshapiro**		see split_across_queue_groups(), split_within_queue(e)
775290792Sgshapiro*/
775390792Sgshapiro
775490792Sgshapirobool
775590792Sgshapirosplit_by_recipient(e)
775690792Sgshapiro	ENVELOPE *e;
775790792Sgshapiro{
775890792Sgshapiro	int split, n, i, j, l;
775990792Sgshapiro	char *lsplits;
776090792Sgshapiro	ENVELOPE *ee, *next, *firstsibling;
776190792Sgshapiro
776290792Sgshapiro	if (OpMode == SM_VERIFY || !ISVALIDQGRP(e->e_qgrp) ||
776390792Sgshapiro	    bitset(EF_SPLIT, e->e_flags))
776490792Sgshapiro		return true;
776590792Sgshapiro	n = split_across_queue_groups(e);
776690792Sgshapiro	if (n == SM_SPLIT_FAIL)
776790792Sgshapiro		return false;
776890792Sgshapiro	firstsibling = ee = e->e_sibling;
776990792Sgshapiro	if (n > 1 && LogLevel > SPLIT_LOG_LEVEL)
777090792Sgshapiro	{
777190792Sgshapiro		l = MAXLINE;
777290792Sgshapiro		lsplits = sm_malloc(l);
777390792Sgshapiro		if (lsplits != NULL)
777490792Sgshapiro			*lsplits = '\0';
777590792Sgshapiro		j = 0;
777690792Sgshapiro	}
777790792Sgshapiro	else
777890792Sgshapiro	{
777990792Sgshapiro		/* get rid of stupid compiler warnings */
778090792Sgshapiro		lsplits = NULL;
778190792Sgshapiro		j = l = 0;
778290792Sgshapiro	}
778390792Sgshapiro	for (i = 1; i < n; ++i)
778490792Sgshapiro	{
778590792Sgshapiro		next = ee->e_sibling;
778690792Sgshapiro		if (split_within_queue(ee) == SM_SPLIT_FAIL)
778790792Sgshapiro		{
778890792Sgshapiro			e->e_sibling = firstsibling;
778990792Sgshapiro			return false;
779090792Sgshapiro		}
779190792Sgshapiro		ee->e_flags |= EF_SPLIT;
779290792Sgshapiro		if (LogLevel > SPLIT_LOG_LEVEL && lsplits != NULL)
779390792Sgshapiro		{
779490792Sgshapiro			if (j >= l - strlen(ee->e_id) - 3)
779590792Sgshapiro			{
779690792Sgshapiro				char *p;
779790792Sgshapiro
779890792Sgshapiro				l += MAXLINE;
779990792Sgshapiro				p = sm_realloc(lsplits, l);
780090792Sgshapiro				if (p == NULL)
780190792Sgshapiro				{
780290792Sgshapiro					/* let's try to get this done */
780390792Sgshapiro					sm_free(lsplits);
780490792Sgshapiro					lsplits = NULL;
780590792Sgshapiro				}
780690792Sgshapiro				else
780790792Sgshapiro					lsplits = p;
780890792Sgshapiro			}
780990792Sgshapiro			if (lsplits != NULL)
781090792Sgshapiro			{
781190792Sgshapiro				if (j == 0)
781290792Sgshapiro					j += sm_strlcat(lsplits + j,
781390792Sgshapiro							ee->e_id, l - j);
781490792Sgshapiro				else
781590792Sgshapiro					j += sm_strlcat2(lsplits + j, "; ",
781690792Sgshapiro							 ee->e_id, l - j);
781790792Sgshapiro				SM_ASSERT(j < l);
781890792Sgshapiro			}
781990792Sgshapiro		}
782090792Sgshapiro		ee = next;
782190792Sgshapiro	}
782290792Sgshapiro	if (LogLevel > SPLIT_LOG_LEVEL && lsplits != NULL && n > 1)
782390792Sgshapiro	{
782490792Sgshapiro		sm_syslog(LOG_NOTICE, e->e_id, "split: count=%d, id%s=%s",
782590792Sgshapiro			  n - 1, n > 2 ? "s" : "", lsplits);
782690792Sgshapiro		sm_free(lsplits);
782790792Sgshapiro	}
782890792Sgshapiro	split = split_within_queue(e) != SM_SPLIT_FAIL;
782990792Sgshapiro	if (split)
783090792Sgshapiro		e->e_flags |= EF_SPLIT;
783190792Sgshapiro	return split;
783290792Sgshapiro}
783390792Sgshapiro
783490792Sgshapiro#if _FFR_QUARANTINE
783590792Sgshapiro/*
783690792Sgshapiro**  QUARANTINE_QUEUE_ITEM -- {un,}quarantine a single envelope
783790792Sgshapiro**
783890792Sgshapiro**	Add/remove quarantine reason and requeue appropriately.
783990792Sgshapiro**
784090792Sgshapiro**	Parameters:
784190792Sgshapiro**		qgrp -- queue group for the item
784290792Sgshapiro**		qdir -- queue directory in the given queue group
784390792Sgshapiro**		e -- envelope information for the item
784490792Sgshapiro**		reason -- quarantine reason, NULL means unquarantine.
784590792Sgshapiro**
784690792Sgshapiro**	Results:
784790792Sgshapiro**		true if item changed, false otherwise
784890792Sgshapiro**
784990792Sgshapiro**	Side Effects:
785090792Sgshapiro**		Changes quarantine tag in queue file and renames it.
785190792Sgshapiro*/
785290792Sgshapiro
785390792Sgshapirostatic bool
785490792Sgshapiroquarantine_queue_item(qgrp, qdir, e, reason)
785590792Sgshapiro	int qgrp;
785690792Sgshapiro	int qdir;
785790792Sgshapiro	ENVELOPE *e;
785890792Sgshapiro	char *reason;
785990792Sgshapiro{
786090792Sgshapiro	bool dirty = false;
786190792Sgshapiro	bool failing = false;
786290792Sgshapiro	bool foundq = false;
786390792Sgshapiro	bool finished = false;
786490792Sgshapiro	int fd;
786590792Sgshapiro	int flags;
786690792Sgshapiro	int oldtype;
786790792Sgshapiro	int newtype;
786890792Sgshapiro	int save_errno;
786990792Sgshapiro	MODE_T oldumask = 0;
787090792Sgshapiro	SM_FILE_T *oldqfp, *tempqfp;
787190792Sgshapiro	char *bp;
787290792Sgshapiro	char oldqf[MAXPATHLEN];
787390792Sgshapiro	char tempqf[MAXPATHLEN];
787490792Sgshapiro	char newqf[MAXPATHLEN];
787590792Sgshapiro	char buf[MAXLINE];
787690792Sgshapiro
787790792Sgshapiro	oldtype = queue_letter(e, ANYQFL_LETTER);
787890792Sgshapiro	(void) sm_strlcpy(oldqf, queuename(e, ANYQFL_LETTER), sizeof oldqf);
787990792Sgshapiro	(void) sm_strlcpy(tempqf, queuename(e, NEWQFL_LETTER), sizeof tempqf);
788090792Sgshapiro
788190792Sgshapiro	/*
788290792Sgshapiro	**  Instead of duplicating all the open
788390792Sgshapiro	**  and lock code here, tell readqf() to
788490792Sgshapiro	**  do that work and return the open
788590792Sgshapiro	**  file pointer in e_lockfp.  Note that
788690792Sgshapiro	**  we must release the locks properly when
788790792Sgshapiro	**  we are done.
788890792Sgshapiro	*/
788990792Sgshapiro
789090792Sgshapiro	if (!readqf(e, true))
789190792Sgshapiro	{
789290792Sgshapiro		(void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
789390792Sgshapiro				     "Skipping %s\n", qid_printname(e));
789490792Sgshapiro		return false;
789590792Sgshapiro	}
789690792Sgshapiro	oldqfp = e->e_lockfp;
789790792Sgshapiro
789890792Sgshapiro	/* open the new queue file */
789990792Sgshapiro	flags = O_CREAT|O_WRONLY|O_EXCL;
790090792Sgshapiro	if (bitset(S_IWGRP, QueueFileMode))
790190792Sgshapiro		oldumask = umask(002);
790290792Sgshapiro	fd = open(tempqf, flags, QueueFileMode);
790390792Sgshapiro	if (bitset(S_IWGRP, QueueFileMode))
790490792Sgshapiro		(void) umask(oldumask);
790590792Sgshapiro	RELEASE_QUEUE;
790690792Sgshapiro
790790792Sgshapiro	if (fd < 0)
790890792Sgshapiro	{
790990792Sgshapiro		save_errno = errno;
791090792Sgshapiro		(void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
791190792Sgshapiro				     "Skipping %s: Could not open %s: %s\n",
791290792Sgshapiro				     qid_printname(e), tempqf,
791390792Sgshapiro				     sm_errstring(save_errno));
791490792Sgshapiro		(void) sm_io_close(oldqfp, SM_TIME_DEFAULT);
791590792Sgshapiro		return false;
791690792Sgshapiro	}
791790792Sgshapiro	if (!lockfile(fd, tempqf, NULL, LOCK_EX|LOCK_NB))
791890792Sgshapiro	{
791990792Sgshapiro		(void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
792090792Sgshapiro				     "Skipping %s: Could not lock %s\n",
792190792Sgshapiro				     qid_printname(e), tempqf);
792290792Sgshapiro		(void) close(fd);
792390792Sgshapiro		(void) sm_io_close(oldqfp, SM_TIME_DEFAULT);
792490792Sgshapiro		return false;
792590792Sgshapiro	}
792690792Sgshapiro
792790792Sgshapiro	tempqfp = sm_io_open(SmFtStdiofd, SM_TIME_DEFAULT, (void *) &fd,
792890792Sgshapiro			     SM_IO_WRONLY, NULL);
792990792Sgshapiro	if (tempqfp == NULL)
793090792Sgshapiro	{
793190792Sgshapiro		(void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
793290792Sgshapiro				     "Skipping %s: Could not lock %s\n",
793390792Sgshapiro				     qid_printname(e), tempqf);
793490792Sgshapiro		(void) close(fd);
793590792Sgshapiro		(void) sm_io_close(oldqfp, SM_TIME_DEFAULT);
793690792Sgshapiro		return false;
793790792Sgshapiro	}
793890792Sgshapiro
793990792Sgshapiro	/* Copy the data over, changing the quarantine reason */
794090792Sgshapiro	while ((bp = fgetfolded(buf, sizeof buf, oldqfp)) != NULL)
794190792Sgshapiro	{
794290792Sgshapiro		if (tTd(40, 4))
794390792Sgshapiro			sm_dprintf("+++++ %s\n", bp);
794490792Sgshapiro		switch (bp[0])
794590792Sgshapiro		{
794690792Sgshapiro		  case 'q':		/* quarantine reason */
794790792Sgshapiro			foundq = true;
794890792Sgshapiro			if (reason == NULL)
794990792Sgshapiro			{
795090792Sgshapiro				if (Verbose)
795190792Sgshapiro				{
795290792Sgshapiro					(void) sm_io_fprintf(smioout,
795390792Sgshapiro							     SM_TIME_DEFAULT,
795490792Sgshapiro							     "%s: Removed quarantine of \"%s\"\n",
795590792Sgshapiro							     e->e_id, &bp[1]);
795690792Sgshapiro				}
795790792Sgshapiro				sm_syslog(LOG_INFO, e->e_id, "unquarantine");
795890792Sgshapiro				dirty = true;
795990792Sgshapiro				continue;
796090792Sgshapiro			}
796190792Sgshapiro			else if (strcmp(reason, &bp[1]) == 0)
796290792Sgshapiro			{
796390792Sgshapiro				if (Verbose)
796490792Sgshapiro				{
796590792Sgshapiro					(void) sm_io_fprintf(smioout,
796690792Sgshapiro							     SM_TIME_DEFAULT,
796790792Sgshapiro							     "%s: Already quarantined with \"%s\"\n",
796890792Sgshapiro							     e->e_id, reason);
796990792Sgshapiro				}
797090792Sgshapiro				(void) sm_io_fprintf(tempqfp, SM_TIME_DEFAULT,
797190792Sgshapiro						     "q%s\n", reason);
797290792Sgshapiro			}
797390792Sgshapiro			else
797490792Sgshapiro			{
797590792Sgshapiro				if (Verbose)
797690792Sgshapiro				{
797790792Sgshapiro					(void) sm_io_fprintf(smioout,
797890792Sgshapiro							     SM_TIME_DEFAULT,
797990792Sgshapiro							     "%s: Quarantine changed from \"%s\" to \"%s\"\n",
798090792Sgshapiro							     e->e_id, &bp[1],
798190792Sgshapiro							     reason);
798290792Sgshapiro				}
798390792Sgshapiro				(void) sm_io_fprintf(tempqfp, SM_TIME_DEFAULT,
798490792Sgshapiro						     "q%s\n", reason);
798590792Sgshapiro				sm_syslog(LOG_INFO, e->e_id, "quarantine=%s",
798690792Sgshapiro					  reason);
798790792Sgshapiro				dirty = true;
798890792Sgshapiro			}
798990792Sgshapiro			break;
799090792Sgshapiro
799190792Sgshapiro		  case 'R':
799290792Sgshapiro			/*
799390792Sgshapiro			**  If we are quarantining an unquarantined item,
799490792Sgshapiro			**  need to put in a new 'q' line before it's
799590792Sgshapiro			**  too late.
799690792Sgshapiro			*/
799790792Sgshapiro
799890792Sgshapiro			if (!foundq && reason != NULL)
799990792Sgshapiro			{
800090792Sgshapiro				if (Verbose)
800190792Sgshapiro				{
800290792Sgshapiro					(void) sm_io_fprintf(smioout,
800390792Sgshapiro							     SM_TIME_DEFAULT,
800490792Sgshapiro							     "%s: Quarantined with \"%s\"\n",
800590792Sgshapiro							     e->e_id, reason);
800690792Sgshapiro				}
800790792Sgshapiro				(void) sm_io_fprintf(tempqfp, SM_TIME_DEFAULT,
800890792Sgshapiro						     "q%s\n", reason);
800990792Sgshapiro				sm_syslog(LOG_INFO, e->e_id, "quarantine=%s",
801090792Sgshapiro					  reason);
801190792Sgshapiro				foundq = true;
801290792Sgshapiro				dirty = true;
801390792Sgshapiro			}
801490792Sgshapiro
801590792Sgshapiro			/* Copy the line to the new file */
801690792Sgshapiro			(void) sm_io_fprintf(tempqfp, SM_TIME_DEFAULT,
801790792Sgshapiro					     "%s\n", bp);
801890792Sgshapiro			break;
801990792Sgshapiro
802090792Sgshapiro		  case '.':
802190792Sgshapiro			finished = true;
802290792Sgshapiro			/* FALLTHROUGH */
802390792Sgshapiro
802490792Sgshapiro		  default:
802590792Sgshapiro			/* Copy the line to the new file */
802690792Sgshapiro			(void) sm_io_fprintf(tempqfp, SM_TIME_DEFAULT,
802790792Sgshapiro					     "%s\n", bp);
802890792Sgshapiro			break;
802990792Sgshapiro		}
803090792Sgshapiro	}
803190792Sgshapiro
803290792Sgshapiro	/* Make sure we read the whole old file */
803390792Sgshapiro	errno = sm_io_error(tempqfp);
803490792Sgshapiro	if (errno != 0 && errno != SM_IO_EOF)
803590792Sgshapiro	{
803690792Sgshapiro		save_errno = errno;
803790792Sgshapiro		(void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
803890792Sgshapiro				     "Skipping %s: Error reading %s: %s\n",
803990792Sgshapiro				     qid_printname(e), oldqf,
804090792Sgshapiro				     sm_errstring(save_errno));
804190792Sgshapiro		failing = true;
804290792Sgshapiro	}
804390792Sgshapiro
804490792Sgshapiro	if (!failing && !finished)
804590792Sgshapiro	{
804690792Sgshapiro		(void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
804790792Sgshapiro				     "Skipping %s: Incomplete file: %s\n",
804890792Sgshapiro				     qid_printname(e), oldqf);
804990792Sgshapiro		failing = true;
805090792Sgshapiro	}
805190792Sgshapiro
805290792Sgshapiro	/* Check if we actually changed anything or we can just bail now */
805390792Sgshapiro	if (!dirty)
805490792Sgshapiro	{
805590792Sgshapiro		/* pretend we failed, even though we technically didn't */
805690792Sgshapiro		failing = true;
805790792Sgshapiro	}
805890792Sgshapiro
805990792Sgshapiro	/* Make sure we wrote things out safely */
806090792Sgshapiro	if (!failing &&
806190792Sgshapiro	    (sm_io_flush(tempqfp, SM_TIME_DEFAULT) != 0 ||
806290792Sgshapiro	     ((SuperSafe == SAFE_REALLY || SuperSafe == SAFE_INTERACTIVE) &&
806390792Sgshapiro	      fsync(sm_io_getinfo(tempqfp, SM_IO_WHAT_FD, NULL)) < 0) ||
806490792Sgshapiro	     ((errno = sm_io_error(tempqfp)) != 0)))
806590792Sgshapiro	{
806690792Sgshapiro		save_errno = errno;
806790792Sgshapiro		(void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
806890792Sgshapiro				     "Skipping %s: Error writing %s: %s\n",
806990792Sgshapiro				     qid_printname(e), tempqf,
807090792Sgshapiro				     sm_errstring(save_errno));
807190792Sgshapiro		failing = true;
807290792Sgshapiro	}
807390792Sgshapiro
807490792Sgshapiro
807590792Sgshapiro	/* Figure out the new filename */
807690792Sgshapiro	newtype = (reason == NULL ? NORMQF_LETTER : QUARQF_LETTER);
807790792Sgshapiro	if (oldtype == newtype)
807890792Sgshapiro	{
807990792Sgshapiro		/* going to rename tempqf to oldqf */
808090792Sgshapiro		(void) sm_strlcpy(newqf, oldqf, sizeof newqf);
808190792Sgshapiro	}
808290792Sgshapiro	else
808390792Sgshapiro	{
808490792Sgshapiro		/* going to rename tempqf to new name based on newtype */
808590792Sgshapiro		(void) sm_strlcpy(newqf, queuename(e, newtype), sizeof newqf);
808690792Sgshapiro	}
808790792Sgshapiro
808890792Sgshapiro	save_errno = 0;
808990792Sgshapiro
809090792Sgshapiro	/* rename tempqf to newqf */
809190792Sgshapiro	if (!failing &&
809290792Sgshapiro	    rename(tempqf, newqf) < 0)
809390792Sgshapiro		save_errno = (errno == 0) ? EINVAL : errno;
809490792Sgshapiro
809590792Sgshapiro	/* Check rename() success */
809690792Sgshapiro	if (!failing && save_errno != 0)
809790792Sgshapiro	{
809890792Sgshapiro		sm_syslog(LOG_DEBUG, e->e_id,
809990792Sgshapiro			  "quarantine_queue_item: rename(%s, %s): %s",
810090792Sgshapiro			  tempqf, newqf, sm_errstring(save_errno));
810190792Sgshapiro
810290792Sgshapiro		(void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
810390792Sgshapiro				     "Error renaming %s to %s: %s\n",
810490792Sgshapiro				     tempqf, newqf,
810590792Sgshapiro				     sm_errstring(save_errno));
810690792Sgshapiro		if (oldtype == newtype)
810790792Sgshapiro		{
810890792Sgshapiro			/*
810990792Sgshapiro			**  Bail here since we don't know the state of
811090792Sgshapiro			**  the filesystem and may need to keep tempqf
811190792Sgshapiro			**  for the user to rescue us.
811290792Sgshapiro			*/
811390792Sgshapiro
811490792Sgshapiro			RELEASE_QUEUE;
811590792Sgshapiro			errno = save_errno;
811690792Sgshapiro			syserr("!452 Error renaming control file %s", tempqf);
811790792Sgshapiro			/* NOTREACHED */
811890792Sgshapiro		}
811990792Sgshapiro		else
812090792Sgshapiro		{
812190792Sgshapiro			/* remove new file (if rename() half completed) */
812290792Sgshapiro			if (xunlink(newqf) < 0)
812390792Sgshapiro			{
812490792Sgshapiro				save_errno = errno;
812590792Sgshapiro				(void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
812690792Sgshapiro						     "Error removing %s: %s\n",
812790792Sgshapiro						     newqf,
812890792Sgshapiro						     sm_errstring(save_errno));
812990792Sgshapiro			}
813090792Sgshapiro
813190792Sgshapiro			/* tempqf removed below */
813290792Sgshapiro			failing = true;
813390792Sgshapiro		}
813490792Sgshapiro
813590792Sgshapiro	}
813690792Sgshapiro
813790792Sgshapiro	/* If changing file types, need to remove old type */
813890792Sgshapiro	if (!failing && oldtype != newtype)
813990792Sgshapiro	{
814090792Sgshapiro		if (xunlink(oldqf) < 0)
814190792Sgshapiro		{
814290792Sgshapiro			save_errno = errno;
814390792Sgshapiro			(void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
814490792Sgshapiro					     "Error removing %s: %s\n",
814590792Sgshapiro					     oldqf, sm_errstring(save_errno));
814690792Sgshapiro		}
814790792Sgshapiro	}
814890792Sgshapiro
814990792Sgshapiro	/* see if anything above failed */
815090792Sgshapiro	if (failing)
815190792Sgshapiro	{
815290792Sgshapiro		/* Something failed: remove new file, old file still there */
815390792Sgshapiro		(void) xunlink(tempqf);
815490792Sgshapiro	}
815590792Sgshapiro
815690792Sgshapiro	/*
815790792Sgshapiro	**  fsync() after file operations to make sure metadata is
815890792Sgshapiro	**  written to disk on filesystems in which renames are
815990792Sgshapiro	**  not guaranteed.  It's ok if they fail, mail won't be lost.
816090792Sgshapiro	*/
816190792Sgshapiro
816290792Sgshapiro	if (SuperSafe != SAFE_NO)
816390792Sgshapiro	{
816490792Sgshapiro		/* for soft-updates */
816590792Sgshapiro		(void) fsync(sm_io_getinfo(tempqfp,
816690792Sgshapiro					   SM_IO_WHAT_FD, NULL));
816790792Sgshapiro
816890792Sgshapiro		if (!failing)
816990792Sgshapiro		{
817090792Sgshapiro			/* for soft-updates */
817190792Sgshapiro			(void) fsync(sm_io_getinfo(oldqfp,
817290792Sgshapiro						   SM_IO_WHAT_FD, NULL));
817390792Sgshapiro		}
817490792Sgshapiro
817590792Sgshapiro		/* for other odd filesystems */
817690792Sgshapiro		SYNC_DIR(tempqf, false);
817790792Sgshapiro	}
817890792Sgshapiro
817990792Sgshapiro	/* Close up shop */
818090792Sgshapiro	RELEASE_QUEUE;
818190792Sgshapiro	if (tempqfp != NULL)
818290792Sgshapiro		(void) sm_io_close(tempqfp, SM_TIME_DEFAULT);
818390792Sgshapiro	if (oldqfp != NULL)
818490792Sgshapiro		(void) sm_io_close(oldqfp, SM_TIME_DEFAULT);
818590792Sgshapiro
818690792Sgshapiro	/* All went well */
818790792Sgshapiro	return !failing;
818890792Sgshapiro}
818990792Sgshapiro
819090792Sgshapiro/*
819190792Sgshapiro**  QUARANTINE_QUEUE -- {un,}quarantine matching items in the queue
819290792Sgshapiro**
819390792Sgshapiro**	Read all matching queue items, add/remove quarantine
819490792Sgshapiro**	reason, and requeue appropriately.
819590792Sgshapiro**
819690792Sgshapiro**	Parameters:
819790792Sgshapiro**		reason -- quarantine reason, "." means unquarantine.
819890792Sgshapiro**		qgrplimit -- limit to single queue group unless NOQGRP
819990792Sgshapiro**
820090792Sgshapiro**	Results:
820190792Sgshapiro**		none.
820290792Sgshapiro**
820390792Sgshapiro**	Side Effects:
820490792Sgshapiro**		Lots of changes to the queue.
820590792Sgshapiro*/
820690792Sgshapiro
820790792Sgshapirovoid
820890792Sgshapiroquarantine_queue(reason, qgrplimit)
820990792Sgshapiro	char *reason;
821090792Sgshapiro	int qgrplimit;
821190792Sgshapiro{
821290792Sgshapiro	int changed = 0;
821390792Sgshapiro	int qgrp;
821490792Sgshapiro
821590792Sgshapiro	/* Convert internal representation of unquarantine */
821690792Sgshapiro	if (reason != NULL && reason[0] == '.' && reason[1] == '\0')
821790792Sgshapiro		reason = NULL;
821890792Sgshapiro
821990792Sgshapiro	if (reason != NULL)
822090792Sgshapiro	{
822190792Sgshapiro		/* clean it */
822290792Sgshapiro		reason = newstr(denlstring(reason, true, true));
822390792Sgshapiro	}
822490792Sgshapiro
822590792Sgshapiro	for (qgrp = 0; qgrp < NumQueue && Queue[qgrp] != NULL; qgrp++)
822690792Sgshapiro	{
822790792Sgshapiro		int qdir;
822890792Sgshapiro
822990792Sgshapiro		if (qgrplimit != NOQGRP && qgrplimit != qgrp)
823090792Sgshapiro			continue;
823190792Sgshapiro
823290792Sgshapiro		for (qdir = 0; qdir < Queue[qgrp]->qg_numqueues; qdir++)
823390792Sgshapiro		{
823490792Sgshapiro			int i;
823590792Sgshapiro			int nrequests;
823690792Sgshapiro
823790792Sgshapiro			if (StopRequest)
823890792Sgshapiro				stop_sendmail();
823990792Sgshapiro
824090792Sgshapiro			nrequests = gatherq(qgrp, qdir, true, NULL, NULL);
824190792Sgshapiro
824290792Sgshapiro			/* first see if there is anything */
824390792Sgshapiro			if (nrequests <= 0)
824490792Sgshapiro			{
824590792Sgshapiro				if (Verbose)
824690792Sgshapiro				{
824790792Sgshapiro					(void) sm_io_fprintf(smioout,
824890792Sgshapiro							     SM_TIME_DEFAULT, "%s: no matches\n",
824990792Sgshapiro							     qid_printqueue(qgrp, qdir));
825090792Sgshapiro				}
825190792Sgshapiro				continue;
825290792Sgshapiro			}
825390792Sgshapiro
825490792Sgshapiro			if (Verbose)
825590792Sgshapiro			{
825690792Sgshapiro				(void) sm_io_fprintf(smioout,
825790792Sgshapiro						     SM_TIME_DEFAULT, "Processing %s:\n",
825890792Sgshapiro						     qid_printqueue(qgrp, qdir));
825990792Sgshapiro			}
826090792Sgshapiro
826190792Sgshapiro			for (i = 0; i < WorkListCount; i++)
826290792Sgshapiro			{
826390792Sgshapiro				ENVELOPE e;
826490792Sgshapiro
826590792Sgshapiro				if (StopRequest)
826690792Sgshapiro					stop_sendmail();
826790792Sgshapiro
826890792Sgshapiro				/* setup envelope */
826990792Sgshapiro				clearenvelope(&e, true, sm_rpool_new_x(NULL));
827090792Sgshapiro				e.e_id = WorkList[i].w_name + 2;
827190792Sgshapiro				e.e_qgrp = qgrp;
827290792Sgshapiro				e.e_qdir = qdir;
827390792Sgshapiro
827490792Sgshapiro				if (tTd(70, 101))
827590792Sgshapiro				{
827690792Sgshapiro					sm_io_fprintf(smioout, SM_TIME_DEFAULT,
827790792Sgshapiro						      "Would do %s\n", e.e_id);
827890792Sgshapiro					changed++;
827990792Sgshapiro				}
828090792Sgshapiro				else if (quarantine_queue_item(qgrp, qdir,
828190792Sgshapiro							       &e, reason))
828290792Sgshapiro					changed++;
828390792Sgshapiro
828490792Sgshapiro				/* clean up */
828590792Sgshapiro				sm_rpool_free(e.e_rpool);
828690792Sgshapiro				e.e_rpool = NULL;
828790792Sgshapiro			}
828890792Sgshapiro			if (WorkList != NULL)
828990792Sgshapiro				sm_free(WorkList); /* XXX */
829090792Sgshapiro			WorkList = NULL;
829190792Sgshapiro			WorkListSize = 0;
829290792Sgshapiro			WorkListCount = 0;
829390792Sgshapiro		}
829490792Sgshapiro	}
829590792Sgshapiro	if (Verbose)
829690792Sgshapiro	{
829790792Sgshapiro		if (changed == 0)
829890792Sgshapiro			(void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
829990792Sgshapiro					     "No changes\n");
830090792Sgshapiro		else
830190792Sgshapiro			(void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
830290792Sgshapiro					     "%d change%s\n",
830390792Sgshapiro					     changed,
830490792Sgshapiro					     changed == 1 ? "" : "s");
830590792Sgshapiro	}
830690792Sgshapiro}
830790792Sgshapiro#endif /* _FFR_QUARANTINE */
8308