* Copyright (c) 1983 Eric P. Allman
* Copyright (c) 1988 Regents of the University of California.
* Redistribution and use in source and binary forms are permitted
* provided that the above copyright notice and this paragraph are
* duplicated in all such forms and that any documentation,
* advertising materials, and other materials related to such
* distribution and use acknowledge that the software was developed
* by the University of California, Berkeley. The name of the
* University may not be used to endorse or promote products derived
* from this software without specific prior written permission.
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
* WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
static char sccsid
[] = "@(#)queue.c 5.24 (Berkeley) 6/30/88 (with queueing)";
static char sccsid
[] = "@(#)queue.c 5.24 (Berkeley) 6/30/88 (without queueing)";
char *w_name
; /* name of control file */
long w_pri
; /* priority of message, see below */
time_t w_ctime
; /* creation time of message */
struct work
*w_next
; /* next in queue */
typedef struct work WORK
;
WORK
*WorkQ
; /* queue of things to be done */
** QUEUEUP -- queue a message up for future transmission.
** e -- the envelope to queue up.
** queueall -- if TRUE, queue all addresses, rather than
** just those with the QQUEUEUP flag set.
** announce -- if TRUE, tell when you are queueing up.
** The current request are saved in a control file.
queueup(e
, queueall
, announce
)
tf
= newstr(queuename(e
, 't'));
syserr("queueup: cannot create temp file %s", tf
);
(void) chmod(tf
, FileMode
);
printf("queueing %s\n", e
->e_id
);
** If there is no data file yet, create one.
e
->e_df
= newstr(queuename(e
, 'd'));
dfp
= fopen(e
->e_df
, "w");
syserr("queueup: cannot create %s", e
->e_df
);
(void) chmod(e
->e_df
, FileMode
);
(*e
->e_putbody
)(dfp
, ProgMailer
, e
);
** Output future work requests.
** Priority and creation time should be first, since
** they are required by orderq.
/* output message priority */
fprintf(tfp
, "P%ld\n", e
->e_msgpriority
);
/* output creation time */
fprintf(tfp
, "T%ld\n", e
->e_ctime
);
/* output name of data file */
fprintf(tfp
, "D%s\n", e
->e_df
);
/* message from envelope, if it exists */
if (e
->e_message
!= NULL
)
fprintf(tfp
, "M%s\n", e
->e_message
);
/* output name of sender */
fprintf(tfp
, "S%s\n", e
->e_from
.q_paddr
);
/* output list of recipient addresses */
for (q
= e
->e_sendqueue
; q
!= NULL
; q
= q
->q_next
)
if (queueall
? !bitset(QDONTSEND
, q
->q_flags
) :
bitset(QQUEUEUP
, q
->q_flags
))
fprintf(tfp
, "R%s\n", q
->q_paddr
);
message(Arpa_Info
, "queued");
/* output list of error recipients */
for (q
= e
->e_errorqueue
; q
!= NULL
; q
= q
->q_next
)
if (!bitset(QDONTSEND
, q
->q_flags
))
fprintf(tfp
, "E%s\n", q
->q_paddr
);
** Output headers for this message.
** Expand macros completely here. Queue run will deal with
** everything as absolute headers.
** All headers that must be relative to the recipient
** We set up a "null mailer" -- i.e., a mailer that will have
** no effect on the addresses as they are output.
bzero((char *) &nullmailer
, sizeof nullmailer
);
nullmailer
.m_r_rwset
= nullmailer
.m_s_rwset
= -1;
for (h
= e
->e_header
; h
!= NULL
; h
= h
->h_link
)
/* don't output null headers */
if (h
->h_value
== NULL
|| h
->h_value
[0] == '\0')
/* don't output resent headers on non-resent messages */
if (bitset(H_RESENT
, h
->h_flags
) && !bitset(EF_RESENT
, e
->e_flags
))
/* if conditional, output the set of conditions */
if (!bitzerop(h
->h_mflags
) && bitset(H_CHECK
|H_ACHECK
, h
->h_flags
))
for (j
= '\0'; j
<= '\177'; j
++)
if (bitnset(j
, h
->h_mflags
))
/* output the header: expand macros, convert addresses */
if (bitset(H_DEFAULT
, h
->h_flags
))
(void) expand(h
->h_value
, buf
, &buf
[sizeof buf
], e
);
fprintf(tfp
, "%s: %s\n", h
->h_field
, buf
);
else if (bitset(H_FROM
|H_RCPT
, h
->h_flags
))
commaize(h
, h
->h_value
, tfp
, bitset(EF_OLDSTYLE
, e
->e_flags
),
fprintf(tfp
, "%s: %s\n", h
->h_field
, h
->h_value
);
syserr("cannot unlink(%s, %s), df=%s", tf
, qf
, e
->e_df
);
syslog(LOG_DEBUG
, "%s: queueup, qf=%s, df=%s\n", e
->e_id
, qf
, e
->e_df
);
** RUNQUEUE -- run the jobs in the queue.
** Gets the stuff out of the queue in some presumably logical
** order and processes them.
** forkflag -- TRUE if the queue scanning should be done in
** a child process. We double-fork so it is not our
** child and we don't have to clean up after it.
** runs things in the mail queue.
extern bool shouldqueue();
** If no work will ever be selected, don't even bother reading
if (shouldqueue(-100000000L))
printf("Skipping queue run -- load average too high\n");
** See if we want to go off and do other useful work.
/* parent -- pick up intermediate zombie */
(void) signal(SIGCHLD
, reapchild
);
(void) setevent(QueueIntvl
, runqueue
, TRUE
);
/* child -- double fork */
(void) signal(SIGCHLD
, SIG_DFL
);
setproctitle("running queue");
syslog(LOG_DEBUG
, "runqueue %s, pid=%d", QueueDir
, getpid());
** Release any resources used by the daemon code.
** Make sure the alias database is open.
initaliases(AliasFile
, FALSE
);
** Start making passes through the queue.
** First, read and sort the entire queue.
** Then, process the work in that order.
** But if you take too long, start over.
/* order the existing work requests */
/* process them once at a time */
/* exit without the usual cleanup */
** ORDERQ -- order the work queue.
** doall -- if set, include everything in the queue (even
** the jobs that cannot be run because the load
** average is too high). Otherwise, exclude those
** The number of request in the queue (not necessarily
** the number of requests in WorkQ however).
** Sets WorkQ to the queue of available work, in order.
register struct direct
*d
;
/* clear out old WorkQ */
for (w
= WorkQ
; w
!= NULL
; )
register WORK
*nw
= w
->w_next
;
/* open the queue directory */
syserr("orderq: cannot open \"%s\" as \".\"", QueueDir
);
** Read the work directory.
while ((d
= readdir(f
)) != NULL
)
/* is this an interesting entry? */
if (d
->d_name
[0] != 'q' || d
->d_name
[1] != 'f')
/* yes -- open control file (if not too many files) */
cf
= fopen(d
->d_name
, "r");
/* this may be some random person sending hir msgs */
/* syserr("orderq: cannot open %s", cbuf); */
printf("orderq: cannot open %s (%d)\n",
w
->w_name
= newstr(d
->d_name
);
/* make sure jobs in creation don't clog queue */
/* extract useful information */
while (i
!= 0 && fgets(lbuf
, sizeof lbuf
, cf
) != NULL
)
w
->w_pri
= atol(&lbuf
[1]);
w
->w_ctime
= atol(&lbuf
[1]);
if (!doall
&& shouldqueue(w
->w_pri
))
/* don't even bother sorting this job in */
** Sort the work directory.
qsort((char *) wlist
, min(wn
, QUEUESIZE
), sizeof *wlist
, workcmpf
);
** Convert the work list into canonical form.
** Should be turning it into a list of envelopes here perhaps.
for (i
= min(wn
, QUEUESIZE
); --i
>= 0; )
w
= (WORK
*) xalloc(sizeof *w
);
w
->w_name
= wlist
[i
].w_name
;
w
->w_pri
= wlist
[i
].w_pri
;
w
->w_ctime
= wlist
[i
].w_ctime
;
for (w
= WorkQ
; w
!= NULL
; w
= w
->w_next
)
printf("%32s: pri=%ld\n", w
->w_name
, w
->w_pri
);
** WORKCMPF -- compare function for ordering work.
** a -- the first argument.
** b -- the second argument.
long pa
= a
->w_pri
+ a
->w_ctime
;
long pb
= b
->w_pri
+ b
->w_ctime
;
** DOWORK -- do a work request.
** w -- the work request to be satisfied.
** The work request is satisfied if possible.
extern bool shouldqueue();
printf("dowork: %s pri %ld\n", w
->w_name
, w
->w_pri
);
** Ignore jobs that are too expensive for the moment.
if (shouldqueue(w
->w_pri
))
printf("\nSkipping %s\n", w
->w_name
+ 2);
syserr("dowork: cannot fork");
** Lock the control file to avoid duplicate deliveries.
** Then run the file as though we had just read it.
** We save an idea of the temporary name so we
** can recover on interrupt.
/* set basic modes, etc. */
clearenvelope(CurEnv
, FALSE
);
CurEnv
->e_id
= &w
->w_name
[2];
syslog(LOG_DEBUG
, "%s: dowork, pid=%d", CurEnv
->e_id
,
/* don't use the headers from sendmail.cf... */
/* lock the control file during processing */
if (link(w
->w_name
, queuename(CurEnv
, 'l')) < 0)
/* being processed by another queuer */
syslog(LOG_DEBUG
, "%s: locked", CurEnv
->e_id
);
/* do basic system initialization */
/* read the queue control file */
CurEnv
->e_flags
|= EF_INQUEUE
;
if (!bitset(EF_FATALERRS
, CurEnv
->e_flags
))
sendall(CurEnv
, SM_DELIVER
);
** Parent -- pick up results.
** READQF -- read queue file and set up environment.
** e -- the envelope of the job to run.
** full -- if set, read in all information. Otherwise just
** read in info needed for a queue print.
** cf is read and created as the current job, as though
** we had been invoked by argument.
extern char *fgetfolded();
** Read and process the file.
syserr("readqf: no control file %s", qf
);
printf("\nRunning %s\n", e
->e_id
);
while (fgetfolded(buf
, sizeof buf
, qfp
) != NULL
)
printf("+++++ %s\n", buf
);
case 'R': /* specify recipient */
sendtolist(&buf
[1], (ADDRESS
*) NULL
, &e
->e_sendqueue
);
case 'E': /* specify error recipient */
sendtolist(&buf
[1], (ADDRESS
*) NULL
, &e
->e_errorqueue
);
(void) chompheader(&buf
[1], FALSE
);
e
->e_message
= newstr(&buf
[1]);
setsender(newstr(&buf
[1]));
case 'D': /* data file name */
e
->e_df
= newstr(&buf
[1]);
e
->e_dfp
= fopen(e
->e_df
, "r");
syserr("readqf: cannot open %s", e
->e_df
);
case 'T': /* init time */
e
->e_ctime
= atol(&buf
[1]);
case 'P': /* message priority */
e
->e_msgpriority
= atol(&buf
[1]) + WkTimeFact
;
case '\0': /* blank line; ignore */
syserr("readqf(%s:%d): bad line \"%s\"", e
->e_id
,
** If we haven't read any lines, this queue file is empty.
** Arrange to remove it without referencing any null pointers.
e
->e_flags
|= EF_CLRQUEUE
| EF_FATALERRS
| EF_RESPONSE
;
** PRINTQUEUE -- print out a representation of the mail queue
** Prints a listing of the mail queue on the standard output.
** Read and order the queue.
nrequests
= orderq(TRUE
);
** Print the work list that we have read.
/* first see if there is anything */
printf("Mail queue is empty\n");
printf("\t\tMail Queue (%d request%s", nrequests
, nrequests
== 1 ? "" : "s");
if (nrequests
> QUEUESIZE
)
printf(", only %d printed", QUEUESIZE
);
printf(")\n--QID-- --Size-- -Priority- ---Q-Time--- -----------Sender/Recipient-----------\n");
printf(")\n--QID-- --Size-- -----Q-Time----- ------------Sender/Recipient------------\n");
for (w
= WorkQ
; w
!= NULL
; w
= w
->w_next
)
auto time_t submittime
= 0;
extern bool shouldqueue();
f
= fopen(w
->w_name
, "r");
printf("%7s", w
->w_name
+ 2);
(void) strcpy(lf
, w
->w_name
);
else if (shouldqueue(w
->w_pri
))
while (fgets(buf
, sizeof buf
, f
) != NULL
)
case 'M': /* error message */
(void) strcpy(message
, &buf
[1]);
case 'S': /* sender name */
printf("%8ld %10ld %.12s %.38s", dfsize
,
w
->w_pri
, ctime(&submittime
) + 4,
printf("%8ld %.16s %.45s", dfsize
,
ctime(&submittime
), &buf
[1]);
printf("\n\t\t (%.60s)", message
);
case 'R': /* recipient name */
printf("\n\t\t\t\t\t %.38s", &buf
[1]);
printf("\n\t\t\t\t %.45s", &buf
[1]);
case 'T': /* creation time */
submittime
= atol(&buf
[1]);
case 'D': /* data file name */
if (stat(&buf
[1], &st
) >= 0)
if (submittime
== (time_t) 0)
printf(" (no control file)");
** QUEUENAME -- build a file name in the queue directory for this envelope.
** Assigns an id code if one does not already exist.
** This code is very careful to avoid trashing existing files
** under any circumstances.
** We first create an nf file that is only used when
** assigning an id. This file is always empty, so that
** we can never accidently truncate an lf file.
** e -- envelope to build it in/from.
** type -- the file type, used as the first character
** a pointer to the new file name (in a static buffer).
** Will create the lf and qf files if no id code is
** already assigned. This will cause the envelope
static char buf
[MAXNAME
];
/* new process -- start back at "AA" */
(void) sprintf(qf
, "qfAA%05d", pid
);
while (c1
< '~' || c2
< 'Z')
lf
[2] = nf
[2] = qf
[2] = c1
;
lf
[3] = nf
[3] = qf
[3] = ++c2
;
printf("queuename: trying \"%s\"\n", nf
);
if (access(lf
, 0) >= 0 || access(qf
, 0) >= 0)
(void) unlink(nf
); /* kernel bug */
if (close(creat(qf
, FileMode
)) >= 0)
if (c1
>= '~' && c2
>= 'Z')
syserr("queuename: Cannot create \"%s\" in \"%s\"",
e
->e_id
= newstr(&qf
[2]);
printf("queuename: assigned id %s, env=%x\n", e
->e_id
, e
);
syslog(LOG_DEBUG
, "%s: assigned id", e
->e_id
);
(void) sprintf(buf
, "%cf%s", type
, e
->e_id
);
printf("queuename: %s\n", buf
);
** UNLOCKQUEUE -- unlock the queue entry for a specified envelope
** e -- the envelope to unlock.
** unlocks the queue for `e'.
/* remove the transcript */
syslog(LOG_DEBUG
, "%s: unlock", e
->e_id
);
xunlink(queuename(e
, 'x'));
/* last but not least, remove the lock */
xunlink(queuename(e
, 'l'));