Robustness: make_dirs() now continues when a missing
directory is created by another process.
+
+20000726
+
+ Robustness: added watchdog_pat() routine to keep the watchdog
+ quiet if a client stays connected for a lot of time. Files:
+ util/watchdog.[hc], smtpd/smtpd.c.
+
+20000729
+
+ Robustness: if relayhost is specified but the host does
+ not exist, defer mail instead of bouncing it (which would
+ lose the mail if the bounce would have to be delivered to
+ that same non-existent relayhost). Problem reported by
+ Chris Cooper @ maths.ox.ac.uk.
+
+20000821
+
+ Feature: added -r (replace) option to postalias and postmap.
+
+ Cleanup: smtpd now replies with 555 when the client sends
+ unrecognized RCPT TO parameters, as required by RFC 1869
+ (problem report by Robert Norris @ its.monash.edu.au).
* Version of this program.
*/
#define VAR_MAIL_VERSION "mail_version"
-#define DEF_MAIL_VERSION "Snapshot-20000718"
+#define DEF_MAIL_VERSION "Snapshot-20000821"
extern char *var_mail_version;
/* LICENSE
/* SYNOPSIS
/* #include <opened.h>
/*
-/* void opened(queue_id, sender, size, format, ...)
+/* void opened(queue_id, sender, size, nrcpt, format, ...)
/* const char *queue_id;
/* const char *sender;
/* long size;
+/* int nrcpt;
/* const char *format;
/* DESCRIPTION
/* opened() logs that a message was successfully delivered.
/* Sender address.
/* .IP size
/* Message content size.
+/* .IP nrcpt
+/* Number of recipients.
/* .IP format
/* Format of optional text.
/* DIAGNOSTICS
/* opened - log that a message was opened */
-void opened(const char *queue_id, const char *sender, long size, const char *fmt,...)
+void opened(const char *queue_id, const char *sender, long size, int nrcpt,
+ const char *fmt,...)
{
va_list ap;
va_start(ap, fmt);
- vopened(queue_id, sender, size, fmt, ap);
+ vopened(queue_id, sender, size, nrcpt, fmt, ap);
va_end(ap);
}
-/* opened - log that a message was opened */
+/* vopened - log that a message was opened */
-void vopened(const char *queue_id, const char *sender, long size, const char *fmt, va_list ap)
+void vopened(const char *queue_id, const char *sender, long size, int nrcpt,
+ const char *fmt, va_list ap)
{
VSTRING *text = vstring_alloc(100);
#define TEXT (vstring_str(text))
vstring_vsprintf(text, fmt, ap);
- msg_info("%s: from=<%s>, size=%ld%s%s%s",
- queue_id, sender, size, *TEXT ? " (" : "", TEXT, *TEXT ? ")" : "");
+ msg_info("%s: from=<%s>, size=%ld, nrcpt=%d%s%s%s",
+ queue_id, sender, size, nrcpt,
+ *TEXT ? " (" : "", TEXT, *TEXT ? ")" : "");
vstring_free(text);
}
/*
* External interface.
*/
-extern void PRINTFLIKE(4, 5) opened(const char *, const char *, long,
+extern void PRINTFLIKE(5, 6) opened(const char *, const char *, long, int,
const char *,...);
-extern void vopened(const char *, const char *, long, const char *, va_list);
+extern void vopened(const char *, const char *, long, int,
+ const char *, va_list);
/* LICENSE
/* .ad
DAEMONS = bounce.8.html cleanup.8.html defer.8.html error.8.html local.8.html \
lmtp.8.html master.8.html pickup.8.html pipe.8.html qmgr.8.html \
- showq.8.html smtp.8.html smtpd.8.html trivial-rewrite.8.html
+ showq.8.html smtp.8.html smtpd.8.html trivial-rewrite.8.html \
+ nqmgr.8.html
COMMANDS= mailq.1.html newaliases.1.html postalias.1.html postcat.1.html \
postconf.1.html postfix.1.html postkick.1.html postlock.1.html \
postlog.1.html postdrop.1.html postmap.1.html sendmail.1.html \
master.8.html: ../master/master.c
srctoman $? | nroff -man | man2html | postlink >$@
+nqmgr.8.html: ../nqmgr/qmgr.c
+ srctoman $? | sed -e 's/qmgr[^_]/n&/' -e 's/QMGR[^_]/N&/' | \
+ nroff -man | man2html | postlink >$@
+
pickup.8.html: ../pickup/pickup.c
srctoman $? | nroff -man | man2html | postlink >$@
/etc/postfix/virtual_regexp:
/^virtual\.domain$/ whatever
- /^(.*\)@virtual\.domain$/ joe+$1
+ /^(.*)@virtual\.domain$/ joe+$1
</pre>
<p>
--- /dev/null
+<html> <head> </head> <body> <pre>
+
+
+
+NQMGR(8) NQMGR(8)
+
+
+<b>NAME</b>
+ qmgr - Postfix queue manager
+
+<b>SYNOPSIS</b>
+ <b>nqmgr</b> [generic Postfix daemon options]
+
+<b>DESCRIPTION</b>
+ The <b>nqmgr</b> daemon awaits the arrival of incoming mail and
+ arranges for its delivery via Postfix delivery processes.
+ The actual mail routing strategy is delegated to the <a href="trivial-rewrite.8.html"><b>triv-</b>
+ <b>ial-rewrite</b>(8)</a> daemon. This program expects to be run
+ from the <a href="master.8.html"><b>master</b>(8)</a> process manager.
+
+ Mail addressed to the local <b>double-bounce</b> address is
+ silently discarded. This stops potential loops caused by
+ undeliverable bounce notifications.
+
+ Mail addressed to a user listed in the optional <b>relocated</b>
+ database is bounced with a "user has moved to <i>new_loca-</i>
+ <i>tion</i>" message. See <a href="relocated.5.html"><b>relocated</b>(5)</a> for a precise description.
+
+<b>MAIL</b> <b>QUEUES</b>
+ The <b>nqmgr</b> daemon maintains the following queues:
+
+ <b>incoming</b>
+ Inbound mail from the network, or mail picked up by
+ the local <b>pickup</b> agent from the <b>maildrop</b> directory.
+
+ <b>active</b> Messages that the queue manager has opened for
+ delivery. Only a limited number of messages is
+ allowed to enter the <b>active</b> queue (leaky bucket
+ strategy, for a fixed delivery rate).
+
+ <b>deferred</b>
+ Mail that could not be delivered upon the first
+ attempt. The queue manager implements exponential
+ backoff by doubling the time between delivery
+ attempts.
+
+ <b>corrupt</b>
+ Unreadable or damaged queue files are moved here
+ for inspection.
+
+<b>DELIVERY</b> <b>STATUS</b> <b>REPORTS</b>
+ The <b>nqmgr</b> daemon keeps an eye on per-message delivery sta-
+ tus reports in the following directories. Each status
+ report file has the same name as the corresponding message
+ file:
+
+ <b>bounce</b> Per-recipient status information about why mail is
+ bounced. These files are maintained by the
+ <a href="bounce.8.html"><b>bounce</b>(8)</a> daemon.
+
+ <b>defer</b> Per-recipient status information about why mail is
+
+
+
+ 1
+
+
+
+
+
+NQMGR(8) NQMGR(8)
+
+
+ delayed. These files are maintained by the
+ <a href="defer.8.html"><b>defer</b>(8)</a> daemon.
+
+ The <b>nqmgr</b> daemon is responsible for asking the <a href="bounce.8.html"><b>bounce</b>(8)</a>
+ or <a href="defer.8.html"><b>defer</b>(8)</a> daemons to send non-delivery reports.
+
+<b>STRATEGIES</b>
+ The queue manager implements a variety of strategies for
+ either opening queue files (input) or for message delivery
+ (output).
+
+ <b>leaky</b> <b>bucket</b>
+ This strategy limits the number of messages in the
+ <b>active</b> queue and prevents the queue manager from
+ running out of memory under heavy load.
+
+ <b>fairness</b>
+ When the <b>active</b> queue has room, the queue manager
+ takes one message from the <b>incoming</b> queue and one
+ from the <b>deferred</b> queue. This prevents a large mail
+ backlog from blocking the delivery of new mail.
+
+ <b>slow</b> <b>start</b>
+ This strategy eliminates "thundering herd" problems
+ by slowly adjusting the number of parallel deliver-
+ ies to the same destination.
+
+ <b>round</b> <b>robin</b>
+ The queue manager sorts delivery requests by desti-
+ nation. Round-robin selection prevents one desti-
+ nation from dominating deliveries to other destina-
+ tions.
+
+ <b>exponential</b> <b>backoff</b>
+ Mail that cannot be delivered upon the first
+ attempt is deferred. The time interval between
+ delivery attempts is doubled after each attempt.
+
+ <b>destination</b> <b>status</b> <b>cache</b>
+ The queue manager avoids unnecessary delivery
+ attempts by maintaining a short-term, in-memory
+ list of unreachable destinations.
+
+ <b>preemptive</b> <b>message</b> <b>scheduling</b>
+ The queue manager attempts to minimize the average
+ per-recipient delay while still preserving the cor-
+ rect per-message delays, using a sophisticated pre-
+ emptive message scheduling.
+
+<b>TRIGGERS</b>
+ On an idle system, the queue manager waits for the arrival
+ of trigger events, or it waits for a timer to go off. A
+ trigger is a one-byte message. Depending on the message
+ received, the queue manager performs one of the following
+
+
+
+ 2
+
+
+
+
+
+NQMGR(8) NQMGR(8)
+
+
+ actions (the message is followed by the symbolic constant
+ used internally by the software):
+
+ <b>D</b> <b>(QMGR</b><i>_</i><b>REQ</b><i>_</i><b>SCAN</b><i>_</i><b>DEFERRED)</b>
+ Start a deferred queue scan. If a deferred queue
+ scan is already in progress, that scan will be
+ restarted as soon as it finishes.
+
+ <b>I</b> <b>(QMGR</b><i>_</i><b>REQ</b><i>_</i><b>SCAN</b><i>_</i><b>INCOMING)</b>
+ Start an incoming queue scan. If an incoming queue
+ scan is already in progress, that scan will be
+ restarted as soon as it finishes.
+
+ <b>A</b> <b>(QMGR</b><i>_</i><b>REQ</b><i>_</i><b>SCAN</b><i>_</i><b>ALL)</b>
+ Ignore deferred queue file time stamps. The request
+ affects the next deferred queue scan.
+
+ <b>F</b> <b>(QMGR</b><i>_</i><b>REQ</b><i>_</i><b>FLUSH</b><i>_</i><b>DEAD)</b>
+ Purge all information about dead transports and
+ destinations.
+
+ <b>W</b> <b>(TRIGGER</b><i>_</i><b>REQ</b><i>_</i><b>WAKEUP)</b>
+ Wakeup call, This is used by the master server to
+ instantiate servers that should not go away for-
+ ever. The action is to start an incoming queue
+ scan.
+
+ The <b>nqmgr</b> daemon reads an entire buffer worth of triggers.
+ Multiple identical trigger requests are collapsed into
+ one, and trigger requests are sorted so that <b>A</b> and <b>F</b> pre-
+ cede <b>D</b> and <b>I</b>. Thus, in order to force a deferred queue
+ run, one would request <b>A</b> <b>F</b> <b>D</b>; in order to notify the queue
+ manager of the arrival of new mail one would request <b>I</b>.
+
+<b>STANDARDS</b>
+ None. The <b>nqmgr</b> daemon does not interact with the outside
+ world.
+
+<b>SECURITY</b>
+ The <b>nqmgr</b> daemon is not security sensitive. It reads sin-
+ gle-character messages from untrusted local users, and
+ thus may be susceptible to denial of service attacks. The
+ <b>nqmgr</b> daemon does not talk to the outside world, and it
+ can be run at fixed low privilege in a chrooted environ-
+ ment.
+
+<b>DIAGNOSTICS</b>
+ Problems and transactions are logged to the syslog daemon.
+ Corrupted message files are saved to the <b>corrupt</b> queue for
+ further inspection.
+
+ Depending on the setting of the <b>notify</b><i>_</i><b>classes</b> parameter,
+ the postmaster is notified of bounces and of other trou-
+ ble.
+
+
+
+ 3
+
+
+
+
+
+NQMGR(8) NQMGR(8)
+
+
+<b>BUGS</b>
+ A single queue manager process has to compete for disk
+ access with multiple front-end processes such as <b>smtpd</b>. A
+ sudden burst of inbound mail can negatively impact out-
+ bound delivery rates.
+
+<b>CONFIGURATION</b> <b>PARAMETERS</b>
+ The following <b>main.cf</b> parameters are especially relevant
+ to this program. See the Postfix <b>main.cf</b> file for syntax
+ details and for default values. Use the <b>postfix</b> <b>reload</b>
+ command after a configuration change.
+
+<b>Miscellaneous</b>
+ <b>allow</b><i>_</i><b>min</b><i>_</i><b>user</b>
+ Do not bounce recipient addresses that begin with
+ '-'.
+
+ <b>relocated</b><i>_</i><b>maps</b>
+ Tables with contact information for users, hosts or
+ domains that no longer exist. See <a href="relocated.5.html"><b>relocated</b>(5)</a>.
+
+ <b>queue</b><i>_</i><b>directory</b>
+ Top-level directory of the Postfix queue.
+
+<b>Active</b> <b>queue</b> <b>controls</b>
+ In the text below, <i>transport</i> is the first field in a <b>mas-</b>
+ <b>ter.cf</b> entry.
+
+ <b>qmgr</b><i>_</i><b>message</b><i>_</i><b>active</b><i>_</i><b>limit</b>
+ Limit the number of messages in the active queue.
+
+ <b>qmgr</b><i>_</i><b>message</b><i>_</i><b>recipient</b><i>_</i><b>limit</b>
+ Limit the number of in-memory recipients.
+
+ This parameter also limits the size of the short-
+ term, in-memory destination cache.
+
+ <b>qmgr</b><i>_</i><b>message</b><i>_</i><b>recipient</b><i>_</i><b>minimum</b>
+ Per message minimum of in-memory recipients.
+
+ <b>default</b><i>_</i><b>recipient</b><i>_</i><b>limit</b>
+ Default limit on the number of in-memory recipients
+ per transport.
+
+ <i>transport_</i><b>recipient</b><i>_</i><b>limit</b>
+ Limit on the number of in-memory recipients, for
+ the named message <i>transport</i>.
+
+ <b>default</b><i>_</i><b>extra</b><i>_</i><b>recipient</b><i>_</i><b>limit</b>
+ Default limit on the total number of per transport
+ in-memory recipients that the preempting messages
+ can have.
+
+
+
+
+
+ 4
+
+
+
+
+
+NQMGR(8) NQMGR(8)
+
+
+ <i>transport_</i><b>extra</b><i>_</i><b>recipient</b><i>_</i><b>limit</b>
+ Limit on the number of in-memory recipients which
+ all preempting messages delivered by the transport
+ <i>transport</i> can have.
+
+<b>Timing</b> <b>controls</b>
+ <b>min</b><i>_</i><b>backoff</b>
+ Minimal time in seconds between delivery attempts
+ of a deferred message.
+
+ This parameter also limits the time an unreachable
+ destination is kept in the short-term, in-memory
+ destination status cache.
+
+ <b>max</b><i>_</i><b>backoff</b>
+ Maximal time in seconds between delivery attempts
+ of a deferred message.
+
+ <b>maximal</b><i>_</i><b>queue</b><i>_</i><b>lifetime</b>
+ Maximal time in days a message is queued before it
+ is sent back as undeliverable.
+
+ <b>queue</b><i>_</i><b>run</b><i>_</i><b>delay</b>
+ Time in seconds between deferred queue scans. Queue
+ scans do not overlap.
+
+ <b>transport</b><i>_</i><b>retry</b><i>_</i><b>time</b>
+ Time in seconds between attempts to contact a bro-
+ ken delivery transport.
+
+<b>Concurrency</b> <b>controls</b>
+ <b>initial</b><i>_</i><b>destination</b><i>_</i><b>concurrency</b>
+ Initial per-destination concurrency level for par-
+ allel delivery to the same destination.
+
+ <b>default</b><i>_</i><b>destination</b><i>_</i><b>concurrency</b><i>_</i><b>limit</b>
+ Default limit on the number of parallel deliveries
+ to the same destination.
+
+ <i>transport_</i><b>destination</b><i>_</i><b>concurrency</b><i>_</i><b>limit</b>
+ Limit on the number of parallel deliveries to the
+ same destination, for delivery via the named mes-
+ sage <i>transport</i>.
+
+<b>Recipient</b> <b>controls</b>
+ <b>default</b><i>_</i><b>destination</b><i>_</i><b>recipient</b><i>_</i><b>limit</b>
+ Default limit on the number of recipients per mes-
+ sage transfer.
+
+ <i>transport_</i><b>destination</b><i>_</i><b>recipient</b><i>_</i><b>limit</b>
+ Limit on the number of recipients per message
+ transfer, for the named message <i>transport</i>.
+
+
+
+
+
+ 5
+
+
+
+
+
+NQMGR(8) NQMGR(8)
+
+
+<b>Message</b> <b>scheduling</b>
+ <i>transport_</i><b>delivery</b><i>_</i><b>slot</b><i>_</i><b>cost</b> (valid range: 0,2,3...)
+ This parameter basically controls how often a mes-
+ sage delivered by <i>transport</i> <b>can</b> <b>be</b> <b>preempted</b> <b>by</b>
+ <b>another</b> <b>message.</b> <b>An</b> <b>internal</b> <b>per-message/transport</b>
+ <b>counter</b> <b>is</b> <b>incremented</b> <b>by</b> <b>one</b> <b>for</b> <b>each</b> <i>trans-</i>
+ <i>port_</i><b>delivery</b><i>_</i><b>slot</b><i>_</i><b>cost</b> deliveries handled by
+ <i>transport</i>. This counter represents the number of
+ "available delivery slots" for use by other mes-
+ sages. Current message can be preempted by another
+ message when that other message can be delivered
+ using less <i>tranpsort</i> agents than the value of the
+ "available delivery slots" counter.
+
+ Value equal to 0 disables the message preemption
+ for <i>transport</i>.
+
+ <i>transport_</i><b>minimum</b><i>_</i><b>delivery</b><i>_</i><b>slots</b>
+ Message preemption is not attempted at all whenever
+ a message that can't ever accumulate at least
+ <i>transport_</i><b>minimum</b><i>_</i><b>delivery</b><i>_</i><b>slots</b> available delivery
+ slots is being delivered by <i>transport</i>.
+
+ <i>transport_</i><b>delivery</b><i>_</i><b>slot</b><i>_</i><b>discount</b> (valid range: 0..100)
+
+ <i>transport_</i><b>delivery</b><i>_</i><b>slot</b><i>_</i><b>loan</b>
+ These parameters speed up the moment when a message
+ preemption can happen. Instead of waiting until
+ the full amount of delivery slots required is
+ available, the preemption can happen when <i>trans-</i>
+ <i>port_</i><b>delivery</b><i>_</i><b>slot</b><i>_</i><b>discount</b> percent of the required
+ amount plus <i>transport_</i><b>delivery</b><i>_</i><b>slot</b><i>_</i><b>loan</b> still
+ remains to be accumulated. Note that the full
+ amount will still have to be accumulated before
+ another preemption can take place later.
+
+ <b>default</b><i>_</i><b>delivery</b><i>_</i><b>slot</b><i>_</i><b>cost</b>
+
+ <b>default</b><i>_</i><b>minimum</b><i>_</i><b>delivery</b><i>_</i><b>slots</b>
+
+ <b>default</b><i>_</i><b>delivery</b><i>_</i><b>slot</b><i>_</i><b>discount</b>
+
+ <b>default</b><i>_</i><b>delivery</b><i>_</i><b>slot</b><i>_</i><b>loan</b>
+ Default values for the transport specific parame-
+ ters described above.
+
+<b>SEE</b> <b>ALSO</b>
+ <a href="master.8.html">master(8)</a>, process manager
+ <a href="relocated.5.html">relocated(5)</a>, format of the "user has moved" table
+ syslogd(8) system logging
+ <a href="trivial-rewrite.8.html">trivial-rewrite(8)</a>, address routing
+
+<b>LICENSE</b>
+ The Secure Mailer license must be distributed with this
+
+
+
+ 6
+
+
+
+
+
+NQMGR(8) NQMGR(8)
+
+
+ software.
+
+<b>AUTHOR(S)</b>
+ Wietse Venema
+ IBM T.J. Watson Research
+ P.O. Box 704
+ Yorktown Heights, NY 10598, USA
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 7
+
+
+</pre> </body> </html>
postalias - Postfix alias database maintenance
<b>SYNOPSIS</b>
- <b>postalias</b> [<b>-Ninvw</b>] [<b>-c</b> <i>config_dir</i>] [<b>-d</b> <i>key</i>] [<b>-q</b> <i>key</i>]
+ <b>postalias</b> [<b>-Ninrvw</b>] [<b>-c</b> <i>config_dir</i>] [<b>-d</b> <i>key</i>] [<b>-q</b> <i>key</i>]
[<i>file_type</i>:]<i>file_name</i> ...
<b>DESCRIPTION</b>
The exit status is non-zero if the requested infor-
mation was not found.
- <b>-v</b> Enable verbose logging for debugging purposes. Mul-
- tiple <b>-v</b> options make the software increasingly
- verbose.
+ <b>-r</b> When updating a table, do not warn about duplicate
+ entries; silently replace them.
+ <b>-v</b> Enable verbose logging for debugging purposes.
POSTALIAS(1) POSTALIAS(1)
- <b>-w</b> When updating a table, do not warn about duplicate
+ Multiple <b>-v</b> options make the software increasingly
+ verbose.
+
+ <b>-w</b> When updating a table, do not warn about duplicate
entries; silently ignore them.
Arguments:
<i>file_type</i>
The type of database to be produced.
- <b>btree</b> The output is a btree file, named
- <i>file_name</i><b>.db</b>. This is available only on
+ <b>btree</b> The output is a btree file, named
+ <i>file_name</i><b>.db</b>. This is available only on
systems with support for <b>db</b> databases.
- <b>dbm</b> The output consists of two files, named
- <i>file_name</i><b>.pag</b> and <i>file_name</i><b>.dir</b>. This is
- available only on systems with support for
+ <b>dbm</b> The output consists of two files, named
+ <i>file_name</i><b>.pag</b> and <i>file_name</i><b>.dir</b>. This is
+ available only on systems with support for
<b>dbm</b> databases.
- <b>hash</b> The output is a hashed file, named
- <i>file_name</i><b>.db</b>. This is available only on
+ <b>hash</b> The output is a hashed file, named
+ <i>file_name</i><b>.db</b>. This is available only on
systems with support for <b>db</b> databases.
- When no <i>file_type</i> is specified, the software uses
- the database type specified via the <b>database</b><i>_</i><b>type</b>
- configuration parameter. The default value for
+ When no <i>file_type</i> is specified, the software uses
+ the database type specified via the <b>database</b><i>_</i><b>type</b>
+ configuration parameter. The default value for
this parameter depends on the host environment.
<i>file_name</i>
- The name of the alias database source file when
+ The name of the alias database source file when
rebuilding a database.
<b>DIAGNOSTICS</b>
- Problems are logged to the standard error stream. No out-
+ Problems are logged to the standard error stream. No out-
put means no problems were detected. Duplicate entries are
skipped and are flagged with a warning.
<b>BUGS</b>
- The "delete key" support is limited to one delete opera-
+ The "delete key" support is limited to one delete opera-
tion per command invocation.
<b>ENVIRONMENT</b>
Enable verbose logging for debugging purposes.
<b>CONFIGURATION</b> <b>PARAMETERS</b>
- The following <b>main.cf</b> parameters are especially relevant
- to this program. See the Postfix <b>main.cf</b> file for syntax
+ The following <b>main.cf</b> parameters are especially relevant
+ to this program. See the Postfix <b>main.cf</b> file for syntax
details and for default values.
- <b>database</b><i>_</i><b>type</b>
- Default alias database type. On many UNIX systems,
- the default type is either <b>dbm</b> or <b>hash</b>.
POSTALIAS(1) POSTALIAS(1)
+ <b>database</b><i>_</i><b>type</b>
+ Default alias database type. On many UNIX systems,
+ the default type is either <b>dbm</b> or <b>hash</b>.
+
<b>STANDARDS</b>
- RFC 822 (ARPA Internet Text Messages)
+ <a href="http://www.faqs.org/rfcs/rfc822.html">RFC 822</a> (ARPA Internet Text Messages)
<b>SEE</b> <b>ALSO</b>
<a href="aliases.5.html">aliases(5)</a> format of alias database input file.
<a href="sendmail.1.html">sendmail(1)</a> mail posting and compatibility interface.
<b>LICENSE</b>
- The Secure Mailer license must be distributed with this
+ The Secure Mailer license must be distributed with this
software.
<b>AUTHOR(S)</b>
-
-
-
-
postmap - Postfix lookup table management
<b>SYNOPSIS</b>
- <b>postmap</b> [<b>-Ninvw</b>] [<b>-c</b> <i>config_dir</i>] [<b>-d</b> <i>key</i>] [<b>-q</b> <i>key</i>]
+ <b>postmap</b> [<b>-Ninrvw</b>] [<b>-c</b> <i>config_dir</i>] [<b>-d</b> <i>key</i>] [<b>-q</b> <i>key</i>]
[<i>file_type</i>:]<i>file_name</i> ...
<b>DESCRIPTION</b>
The exit status is non-zero if the requested infor-
mation was not found.
+ <b>-r</b> When updating a table, do not warn about duplicate
+ entries; silently replace them.
+
<b>-v</b> Enable verbose logging for debugging purposes. Mul-
- tiple <b>-v</b> options make the software increasingly
+ tiple <b>-v</b> options make the software increasingly
verbose.
- <b>-w</b> When updating a table, do not warn about duplicate
+ <b>-w</b> When updating a table, do not warn about duplicate
entries; silently ignore them.
Arguments:
<i>file_type</i>
The type of database to be produced.
- <b>btree</b> The output file is a btree file, named
- <i>file_name</i><b>.db</b>. This is available only on
+ <b>btree</b> The output file is a btree file, named
+ <i>file_name</i><b>.db</b>. This is available only on
systems with support for <b>db</b> databases.
- <b>dbm</b> The output consists of two files, named
- <i>file_name</i><b>.pag</b> and <i>file_name</i><b>.dir</b>. This is
- available only on systems with support for
+ <b>dbm</b> The output consists of two files, named
+ <i>file_name</i><b>.pag</b> and <i>file_name</i><b>.dir</b>. This is
+ available only on systems with support for
<b>dbm</b> databases.
- <b>hash</b> The output file is a hashed file, named
- <i>file_name</i><b>.db</b>. This is available only on
+ <b>hash</b> The output file is a hashed file, named
+ <i>file_name</i><b>.db</b>. This is available only on
systems with support for <b>db</b> databases.
- When no <i>file_type</i> is specified, the software uses
- the database type specified via the <b>database</b><i>_</i><b>type</b>
+ When no <i>file_type</i> is specified, the software uses
+ the database type specified via the <b>database</b><i>_</i><b>type</b>
configuration parameter.
<i>file_name</i>
- The name of the lookup table source file when
+ The name of the lookup table source file when
rebuilding a database.
<b>DIAGNOSTICS</b>
Problems and transactions are logged to the standard error
stream. No output means no problems. Duplicate entries are
- skipped and are flagged with a warning.
-
-
POSTMAP(1) POSTMAP(1)
+ skipped and are flagged with a warning.
+
<b>BUGS</b>
- The "delete key" support is limited to one delete opera-
+ The "delete key" support is limited to one delete opera-
tion per command invocation.
<b>ENVIRONMENT</b>
<b>CONFIGURATION</b> <b>PARAMETERS</b>
<b>database</b><i>_</i><b>type</b>
- Default output database type. On many UNIX sys-
- tems, the default database type is either <b>hash</b> or
+ Default output database type. On many UNIX sys-
+ tems, the default database type is either <b>hash</b> or
<b>dbm</b>.
<b>LICENSE</b>
- The Secure Mailer license must be distributed with this
+ The Secure Mailer license must be distributed with this
software.
<b>AUTHOR(S)</b>
-
-
<b>.foo.org</b> <b>error:mail</b> <b>for</b> <b>*.foo.org</b> <b>is</b> <b>not</b> <b>deliverable</b>
- This causes all mail for <i>user</i>@<i>anything</i><b>foo.org</b>
+ This causes all mail for <i>user</i>@<i>anything</i><b>.foo.org</b>
to be bounced.
DAEMONS = man8/bounce.8 man8/defer.8 man8/cleanup.8 man8/error.8 man8/local.8 \
man8/lmtp.8 man8/master.8 man8/pickup.8 man8/pipe.8 man8/qmgr.8 \
- man8/showq.8 man8/smtp.8 man8/smtpd.8 man8/trivial-rewrite.8
+ man8/showq.8 man8/smtp.8 man8/smtpd.8 man8/trivial-rewrite.8 \
+ man8/nqmgr.8
COMMANDS= man1/postalias.1 man1/postcat.1 man1/postconf.1 man1/postfix.1 \
man1/postkick.1 man1/postlock.1 man1/postlog.1 man1/postdrop.1 \
man1/postmap.1 man1/sendmail.1 man1/mailq.1 man1/newaliases.1 \
man8/master.8: ../master/master.c
../mantools/srctoman $? >$@
+man8/nqmgr.8: ../nqmgr/qmgr.c
+ ../mantools/srctoman $? | \
+ sed -e 's/qmgr[^_]/n&/' -e 's/QMGR[^_]/N&/' >$@
+
man8/pickup.8: ../pickup/pickup.c
../mantools/srctoman $? >$@
.na
.nf
.fi
-\fBpostalias\fR [\fB-Ninvw\fR] [\fB-c \fIconfig_dir\fR]
+\fBpostalias\fR [\fB-Ninrvw\fR] [\fB-c \fIconfig_dir\fR]
[\fB-d \fIkey\fR] [\fB-q \fIkey\fR]
[\fIfile_type\fR:]\fIfile_name\fR ...
.SH DESCRIPTION
Search the specified maps for \fIkey\fR and print the first value
found on the standard output stream. The exit status is non-zero
if the requested information was not found.
+.IP \fB-r\fR
+When updating a table, do not warn about duplicate entries; silently
+replace them.
.IP \fB-v\fR
Enable verbose logging for debugging purposes. Multiple \fB-v\fR
options make the software increasingly verbose.
.na
.nf
.fi
-\fBpostmap\fR [\fB-Ninvw\fR] [\fB-c \fIconfig_dir\fR] [\fB-d \fIkey\fR]
+\fBpostmap\fR [\fB-Ninrvw\fR] [\fB-c \fIconfig_dir\fR] [\fB-d \fIkey\fR]
[\fB-q \fIkey\fR] [\fIfile_type\fR:]\fIfile_name\fR ...
.SH DESCRIPTION
.ad
Search the specified maps for \fIkey\fR and print the first value
found on the standard output stream. The exit status is non-zero
if the requested information was not found.
+.IP \fB-r\fR
+When updating a table, do not warn about duplicate entries; silently
+replace them.
.IP \fB-v\fR
Enable verbose logging for debugging purposes. Multiple \fB-v\fR
options make the software increasingly verbose.
.ti +5
\fB\&.foo.org error:mail for *.foo.org is not deliverable\fR
-This causes all mail for \fIuser\fR@\fIanything\fBfoo.org\fR
+This causes all mail for \fIuser\fR@\fIanything\fB.foo.org\fR
to be bounced.
.SH REGULAR EXPRESSION TABLES
.na
--- /dev/null
+.TH NQMGR 8
+.ad
+.fi
+.SH NAME
+qmgr
+\-
+Postfix queue manager
+.SH SYNOPSIS
+.na
+.nf
+\fBnqmgr\fR [generic Postfix daemon options]
+.SH DESCRIPTION
+.ad
+.fi
+The \fBnqmgr\fR daemon awaits the arrival of incoming mail
+and arranges for its delivery via Postfix delivery processes.
+The actual mail routing strategy is delegated to the
+\fBtrivial-rewrite\fR(8) daemon.
+This program expects to be run from the \fBmaster\fR(8) process
+manager.
+
+Mail addressed to the local \fBdouble-bounce\fR address is silently
+discarded. This stops potential loops caused by undeliverable
+bounce notifications.
+
+Mail addressed to a user listed in the optional \fBrelocated\fR
+database is bounced with a "user has moved to \fInew_location\fR"
+message. See \fBrelocated\fR(5) for a precise description.
+.SH MAIL QUEUES
+.na
+.nf
+.ad
+.fi
+The \fBnqmgr\fR daemon maintains the following queues:
+.IP \fBincoming\fR
+Inbound mail from the network, or mail picked up by the
+local \fBpickup\fR agent from the \fBmaildrop\fR directory.
+.IP \fBactive\fR
+Messages that the queue manager has opened for delivery. Only
+a limited number of messages is allowed to enter the \fBactive\fR
+queue (leaky bucket strategy, for a fixed delivery rate).
+.IP \fBdeferred\fR
+Mail that could not be delivered upon the first attempt. The queue
+manager implements exponential backoff by doubling the time between
+delivery attempts.
+.IP \fBcorrupt\fR
+Unreadable or damaged queue files are moved here for inspection.
+.SH DELIVERY STATUS REPORTS
+.na
+.nf
+.ad
+.fi
+The \fBnqmgr\fR daemon keeps an eye on per-message delivery status
+reports in the following directories. Each status report file has
+the same name as the corresponding message file:
+.IP \fBbounce\fR
+Per-recipient status information about why mail is bounced.
+These files are maintained by the \fBbounce\fR(8) daemon.
+.IP \fBdefer\fR
+Per-recipient status information about why mail is delayed.
+These files are maintained by the \fBdefer\fR(8) daemon.
+.PP
+The \fBnqmgr\fR daemon is responsible for asking the
+\fBbounce\fR(8) or \fBdefer\fR(8) daemons to send non-delivery
+reports.
+.SH STRATEGIES
+.na
+.nf
+.ad
+.fi
+The queue manager implements a variety of strategies for
+either opening queue files (input) or for message delivery (output).
+.IP "\fBleaky bucket\fR"
+This strategy limits the number of messages in the \fBactive\fR queue
+and prevents the queue manager from running out of memory under
+heavy load.
+.IP \fBfairness\fR
+When the \fBactive\fR queue has room, the queue manager takes one
+message from the \fBincoming\fR queue and one from the \fBdeferred\fR
+queue. This prevents a large mail backlog from blocking the delivery
+of new mail.
+.IP "\fBslow start\fR"
+This strategy eliminates "thundering herd" problems by slowly
+adjusting the number of parallel deliveries to the same destination.
+.IP "\fBround robin\fR
+The queue manager sorts delivery requests by destination.
+Round-robin selection prevents one destination from dominating
+deliveries to other destinations.
+.IP "\fBexponential backoff\fR"
+Mail that cannot be delivered upon the first attempt is deferred.
+The time interval between delivery attempts is doubled after each
+attempt.
+.IP "\fBdestination status cache\fR"
+The queue manager avoids unnecessary delivery attempts by
+maintaining a short-term, in-memory list of unreachable destinations.
+.IP "\fBpreemptive message scheduling\fR"
+The queue manager attempts to minimize the average per-recipient delay
+while still preserving the correct per-message delays, using
+a sophisticated preemptive message scheduling.
+.SH TRIGGERS
+.na
+.nf
+.ad
+.fi
+On an idle system, the queue manager waits for the arrival of
+trigger events, or it waits for a timer to go off. A trigger
+is a one-byte message.
+Depending on the message received, the queue manager performs
+one of the following actions (the message is followed by the
+symbolic constant used internally by the software):
+.IP "\fBD (QMGR_REQ_SCAN_DEFERRED)\fR"
+Start a deferred queue scan. If a deferred queue scan is already
+in progress, that scan will be restarted as soon as it finishes.
+.IP "\fBI (QMGR_REQ_SCAN_INCOMING)\fR"
+Start an incoming queue scan. If an incoming queue scan is already
+in progress, that scan will be restarted as soon as it finishes.
+.IP "\fBA (QMGR_REQ_SCAN_ALL)\fR"
+Ignore deferred queue file time stamps. The request affects
+the next deferred queue scan.
+.IP "\fBF (QMGR_REQ_FLUSH_DEAD)\fR"
+Purge all information about dead transports and destinations.
+.IP "\fBW (TRIGGER_REQ_WAKEUP)\fR"
+Wakeup call, This is used by the master server to instantiate
+servers that should not go away forever. The action is to start
+an incoming queue scan.
+.PP
+The \fBnqmgr\fR daemon reads an entire buffer worth of triggers.
+Multiple identical trigger requests are collapsed into one, and
+trigger requests are sorted so that \fBA\fR and \fBF\fR precede
+\fBD\fR and \fBI\fR. Thus, in order to force a deferred queue run,
+one would request \fBA F D\fR; in order to notify the queue manager
+of the arrival of new mail one would request \fBI\fR.
+.SH STANDARDS
+.na
+.nf
+.ad
+.fi
+None. The \fBnqmgr\fR daemon does not interact with the outside world.
+.SH SECURITY
+.na
+.nf
+.ad
+.fi
+The \fBnqmgr\fR daemon is not security sensitive. It reads
+single-character messages from untrusted local users, and thus may
+be susceptible to denial of service attacks. The \fBnqmgr\fR daemon
+does not talk to the outside world, and it can be run at fixed low
+privilege in a chrooted environment.
+.SH DIAGNOSTICS
+.ad
+.fi
+Problems and transactions are logged to the syslog daemon.
+Corrupted message files are saved to the \fBcorrupt\fR queue
+for further inspection.
+
+Depending on the setting of the \fBnotify_classes\fR parameter,
+the postmaster is notified of bounces and of other trouble.
+.SH BUGS
+.ad
+.fi
+A single queue manager process has to compete for disk access with
+multiple front-end processes such as \fBsmtpd\fR. A sudden burst of
+inbound mail can negatively impact outbound delivery rates.
+.SH CONFIGURATION PARAMETERS
+.na
+.nf
+.ad
+.fi
+The following \fBmain.cf\fR parameters are especially relevant to
+this program. See the Postfix \fBmain.cf\fR file for syntax details
+and for default values. Use the \fBpostfix reload\fR command after
+a configuration change.
+.SH Miscellaneous
+.ad
+.fi
+.IP \fBallow_min_user\fR
+Do not bounce recipient addresses that begin with '-'.
+.IP \fBrelocated_maps\fR
+Tables with contact information for users, hosts or domains
+that no longer exist. See \fBrelocated\fR(5).
+.IP \fBqueue_directory\fR
+Top-level directory of the Postfix queue.
+.SH "Active queue controls"
+.ad
+.fi
+In the text below, \fItransport\fR is the first field in a
+\fBmaster.cf\fR entry.
+.IP \fBqmgr_message_active_limit\fR
+Limit the number of messages in the active queue.
+.IP \fBqmgr_message_recipient_limit\fR
+Limit the number of in-memory recipients.
+.sp
+This parameter also limits the size of the short-term, in-memory
+destination cache.
+.IP \fBqmgr_message_recipient_minimum\fR
+Per message minimum of in-memory recipients.
+.IP \fBdefault_recipient_limit\fR
+Default limit on the number of in-memory recipients per transport.
+.IP \fItransport\fB_recipient_limit\fR
+Limit on the number of in-memory recipients, for the named
+message \fItransport\fR.
+.IP \fBdefault_extra_recipient_limit\fR
+Default limit on the total number of per transport in-memory
+recipients that the preempting messages can have.
+.IP \fItransport\fB_extra_recipient_limit\fR
+Limit on the number of in-memory recipients which all preempting
+messages delivered by the transport \fItransport\fR can have.
+.SH "Timing controls"
+.ad
+.fi
+.IP \fBmin_backoff\fR
+Minimal time in seconds between delivery attempts
+of a deferred message.
+.sp
+This parameter also limits the time an unreachable destination
+is kept in the short-term, in-memory destination status cache.
+.IP \fBmax_backoff\fR
+Maximal time in seconds between delivery attempts
+of a deferred message.
+.IP \fBmaximal_queue_lifetime\fR
+Maximal time in days a message is queued
+before it is sent back as undeliverable.
+.IP \fBqueue_run_delay\fR
+Time in seconds between deferred queue scans. Queue scans do
+not overlap.
+.IP \fBtransport_retry_time\fR
+Time in seconds between attempts to contact a broken
+delivery transport.
+.SH "Concurrency controls"
+.ad
+.fi
+.IP \fBinitial_destination_concurrency\fR
+Initial per-destination concurrency level for parallel delivery
+to the same destination.
+.IP \fBdefault_destination_concurrency_limit\fR
+Default limit on the number of parallel deliveries to the same
+destination.
+.IP \fItransport\fB_destination_concurrency_limit\fR
+Limit on the number of parallel deliveries to the same destination,
+for delivery via the named message \fItransport\fR.
+.SH "Recipient controls"
+.ad
+.fi
+.IP \fBdefault_destination_recipient_limit\fR
+Default limit on the number of recipients per message transfer.
+.IP \fItransport\fB_destination_recipient_limit\fR
+Limit on the number of recipients per message transfer, for the
+named message \fItransport\fR.
+.SH "Message scheduling"
+.ad
+.fi
+.IP "\fItransport\fB_delivery_slot_cost\fR (valid range: 0,2,3...)
+This parameter basically controls how often a message
+delivered by \fItransport\fB can be preempted by another
+message.
+An internal per-message/transport counter is incremented by one
+for each \fItransport\fB_delivery_slot_cost\fR
+deliveries handled by \fItransport\fR. This counter represents
+the number of "available delivery slots" for use by other messages.
+Current message can be preempted by another message when that
+other message can be delivered using less \fItranpsort\fR agents
+than the value of the "available delivery slots" counter.
+.sp
+Value equal to 0 disables the message preemption for \fItransport\fR.
+.IP \fItransport\fB_minimum_delivery_slots\fR
+Message preemption is not attempted at all whenever a message
+that can't ever accumulate at least \fItransport\fB_minimum_delivery_slots\fR
+available delivery slots is being delivered by \fItransport\fR.
+.IP "\fItransport\fB_delivery_slot_discount\fR (valid range: 0..100)"
+.IP \fItransport\fB_delivery_slot_loan\fR
+These parameters speed up the moment when a message preemption can happen.
+Instead of waiting until the full amount of delivery slots
+required is available, the preemption can happen when
+\fItransport\fB_delivery_slot_discount\fR percent of the required
+amount plus \fItransport\fB_delivery_slot_loan\fR still remains to
+be accumulated. Note that the full amount will still have to be
+accumulated before another preemption can take place later.
+.IP \fBdefault_delivery_slot_cost\fR
+.IP \fBdefault_minimum_delivery_slots\fR
+.IP \fBdefault_delivery_slot_discount\fR
+.IP \fBdefault_delivery_slot_loan\fR
+Default values for the transport specific parameters described above.
+.SH SEE ALSO
+.na
+.nf
+master(8), process manager
+relocated(5), format of the "user has moved" table
+syslogd(8) system logging
+trivial-rewrite(8), address routing
+.SH LICENSE
+.na
+.nf
+.ad
+.fi
+The Secure Mailer license must be distributed with this software.
+.SH AUTHOR(S)
+.na
+.nf
+Wietse Venema
+IBM T.J. Watson Research
+P.O. Box 704
+Yorktown Heights, NY 10598, USA
/* maintaining a short-term, in-memory list of unreachable destinations.
/* .IP "\fBpreemptive message scheduling\fR"
/* The queue manager attempts to minimize the average per-recipient delay
-/* while still assuring equality of average per-message delays, using
-/* sophisticated preemptive message scheduling.
+/* while still preserving the correct per-message delays, using
+/* a sophisticated preemptive message scheduling.
/* TRIGGERS
/* .ad
/* .fi
/* recipients that the preempting messages can have.
/* .IP \fItransport\fB_extra_recipient_limit\fR
/* Limit on the number of in-memory recipients which all preempting
-/* messages delivered by transport \fItransport\fR can have.
+/* messages delivered by the transport \fItransport\fR can have.
/* .SH "Timing controls"
/* .ad
/* .fi
/* .IP "\fItransport\fB_delivery_slot_discount\fR (valid range: 0..100)"
/* .IP \fItransport\fB_delivery_slot_loan\fR
/* These parameters speed up the moment when a message preemption can happen.
-/* Instead of waiting till the full amount of delivery slots
+/* Instead of waiting until the full amount of delivery slots
/* required is available, the preemption can happen when
/* \fItransport\fB_delivery_slot_discount\fR percent of the required
/* amount plus \fItransport\fB_delivery_slot_loan\fR still remains to
char *var_virtual_maps;
char *var_defer_xports;
bool var_allow_min_user;
-int var_qmgr_hog; /* XXX */
-int var_qmgr_fudge; /* XXX */
-int var_local_rcpt_lim; /* XXX */
static QMGR_SCAN *qmgr_incoming;
static QMGR_SCAN *qmgr_deferred;
/*
* Let some new blood into the active queue when the queue size is
- * smaller than some configurable limit, and when the number of in-core
- * recipients does not exceed some configurable limit. When the system is
- * under heavy load, favor new mail over old mail.
+ * smaller than some configurable limit. When the system is under heavy
+ * load, favor new mail over old mail.
*/
if (qmgr_message_count < var_qmgr_active_limit)
if ((in_path = qmgr_scan_next(qmgr_incoming)) != 0)
VAR_QUEUE_RUN_DELAY, DEF_QUEUE_RUN_DELAY, &var_queue_run_delay, 1, 0,
VAR_MIN_BACKOFF_TIME, DEF_MIN_BACKOFF_TIME, &var_min_backoff_time, 1, 0,
VAR_MAX_BACKOFF_TIME, DEF_MAX_BACKOFF_TIME, &var_max_backoff_time, 1, 0,
- VAR_MAX_QUEUE_TIME, DEF_MAX_QUEUE_TIME, &var_max_queue_time, 1, 0,
+ VAR_MAX_QUEUE_TIME, DEF_MAX_QUEUE_TIME, &var_max_queue_time, 1, 1000,
VAR_QMGR_ACT_LIMIT, DEF_QMGR_ACT_LIMIT, &var_qmgr_active_limit, 1, 0,
VAR_QMGR_RCPT_LIMIT, DEF_QMGR_RCPT_LIMIT, &var_qmgr_rcpt_limit, 0, 0,
VAR_QMGR_MSG_RCPT_LIMIT, DEF_QMGR_MSG_RCPT_LIMIT, &var_qmgr_msg_rcpt_limit, 1, 0,
VAR_XPORT_RETRY_TIME, DEF_XPORT_RETRY_TIME, &var_transport_retry_time, 1, 0,
VAR_DEST_CON_LIMIT, DEF_DEST_CON_LIMIT, &var_dest_con_limit, 0, 0,
VAR_DEST_RCPT_LIMIT, DEF_DEST_RCPT_LIMIT, &var_dest_rcpt_limit, 0, 0,
- VAR_QMGR_HOG, DEF_QMGR_HOG, &var_qmgr_hog, 10, 100,
- VAR_QMGR_FUDGE, DEF_QMGR_FUDGE, &var_qmgr_fudge, 10, 100,
- VAR_LOCAL_RCPT_LIMIT, DEF_LOCAL_RCPT_LIMIT, &var_local_rcpt_lim, 0, 0,
0,
};
static CONFIG_BOOL_TABLE bool_table[] = {
typedef struct QMGR_ENTRY QMGR_ENTRY;
typedef struct QMGR_MESSAGE QMGR_MESSAGE;
typedef struct QMGR_JOB QMGR_JOB;
-typedef struct QMGR_PEER QMGR_PEER ;
+typedef struct QMGR_PEER QMGR_PEER;
typedef struct QMGR_TRANSPORT_LIST QMGR_TRANSPORT_LIST;
typedef struct QMGR_QUEUE_LIST QMGR_QUEUE_LIST;
typedef struct QMGR_ENTRY_LIST QMGR_ENTRY_LIST;
int dest_concurrency_limit; /* concurrency per domain */
int init_dest_concurrency; /* init. per-domain concurrency */
int recipient_limit; /* recipients per transaction */
- int rcpt_per_stack; /* slots reserved for job on 1st stack level */
- int rcpt_unused; /* available in-core slots */
- int slot_cost; /* cost of new preemption slot (# selected entries) */
- int slot_loan; /* preemption boost offset and factor, see */
- int slot_loan_factor; /* qmgr_job_preempt() for more info */
- int min_slots; /* when preemption can take effect at all */
+ int rcpt_per_stack; /* extra slots reserved for jobs on
+ * the job stack */
+ int rcpt_unused; /* available in-core recipient slots */
+ int slot_cost; /* cost of new preemption slot (#
+ * selected entries) */
+ int slot_loan; /* preemption boost offset and
+ * factor, see */
+ int slot_loan_factor; /* qmgr_job_preempt() for more info */
+ int min_slots; /* when preemption can take effect at
+ * all */
struct HTABLE *queue_byname; /* queues indexed by domain */
QMGR_QUEUE_LIST queue_list; /* queues, round robin order */
- struct HTABLE *job_byname; /* jobs indexed by queue id */
- QMGR_JOB_LIST job_list; /* list of message jobs (1 per message) */
- QMGR_JOB_LIST job_stack; /* job stack for tracking preemption */
- QMGR_JOB *job_next_unread; /* next job with unread recipients */
- QMGR_JOB *candidate_cache; /* cached result from qmgr_job_candidate() */
- time_t candidate_cache_time; /* when candidate_cache was last updated */
+ struct HTABLE *job_byname; /* jobs indexed by queue id */
+ QMGR_JOB_LIST job_list; /* list of message jobs (1 per
+ * message) */
+ QMGR_JOB_LIST job_stack; /* job stack for tracking preemption */
+ QMGR_JOB *job_next_unread; /* next job with unread recipients */
+ QMGR_JOB *candidate_cache; /* cached result from
+ * qmgr_job_candidate() */
+ time_t candidate_cache_time; /* when candidate_cache was last
+ * updated */
QMGR_TRANSPORT_LIST peers; /* linkage */
char *reason; /* why unavailable */
};
QMGR_MESSAGE *message; /* message info */
QMGR_RCPT_LIST rcpt_list; /* as many as it takes */
QMGR_QUEUE *queue; /* parent linkage */
- QMGR_PEER *peer; /* parent linkage */
+ QMGR_PEER *peer; /* parent linkage */
QMGR_ENTRY_LIST queue_peers; /* per queue neighbor entries */
QMGR_ENTRY_LIST peer_peers; /* per peer neighbor entries */
};
int refcount; /* queue entries */
int single_rcpt; /* send one rcpt at a time */
long arrival_time; /* time when queued */
- time_t queued_time; /* time when moved to the active queue */
+ time_t queued_time; /* time when moved to the active
+ * queue */
long warn_offset; /* warning bounce flag offset */
time_t warn_time; /* time next warning to be sent */
long data_offset; /* data seek offset */
long rcpt_offset; /* more recipients here */
long unread_offset; /* more unread recipients here */
QMGR_RCPT_LIST rcpt_list; /* complete addresses */
- int rcpt_count; /* used recipient slots */
- int rcpt_limit; /* maximum read in-core */
- int rcpt_unread; /* # of recipients left in queue file */
- QMGR_JOB_LIST job_list; /* jobs delivering this message (1 per transport) */
+ int rcpt_count; /* used recipient slots */
+ int rcpt_limit; /* maximum read in-core */
+ int rcpt_unread; /* # of recipients left in queue file */
+ QMGR_JOB_LIST job_list; /* jobs delivering this message (1
+ * per transport) */
};
#define QMGR_MESSAGE_LOCKED ((QMGR_MESSAGE *) 1)
/*
* Sometimes it's required to access the transport queues and entries on per
- * message basis. That's what the QMGR_JOB structure is for - it groups all per
- * message information within each transport using a list of QMGR_PEER structures.
- * These structures in turn correspond with per message QMGR_QUEUE structure
- * and list all per message QMGR_ENTRY structures.
+ * message basis. That's what the QMGR_JOB structure is for - it groups all
+ * per message information within each transport using a list of QMGR_PEER
+ * structures. These structures in turn correspond with per message
+ * QMGR_QUEUE structure and list all per message QMGR_ENTRY structures.
*/
struct QMGR_PEER_LIST {
- QMGR_PEER *next;
- QMGR_PEER *prev;
+ QMGR_PEER *next;
+ QMGR_PEER *prev;
};
struct QMGR_JOB {
- QMGR_MESSAGE *message; /* message delivered by this job */
- QMGR_TRANSPORT *transport; /* transport this job belongs to */
- QMGR_JOB_LIST message_peers; /* per message neighbor linkage */
- QMGR_JOB_LIST transport_peers; /* per transport neighbor linkage */
- QMGR_JOB_LIST stack_peers; /* transport stack linkage */
- int stack_level; /* job stack nesting level (-1 -> retired) */
- struct HTABLE *peer_byname; /* message job peers, indexed by domain */
- QMGR_PEER_LIST peer_list; /* list of message job peers */
- int slots_used; /* slots used during preemption */
- int slots_available; /* slots available for preemption (* slot_cost) */
- int selected_entries; /* # of entries selected for delivery so far */
- int read_entries; /* # of entries read in-core so far */
- int rcpt_count; /* used recipient slots */
- int rcpt_limit; /* available recipient slots */
+ QMGR_MESSAGE *message; /* message delivered by this job */
+ QMGR_TRANSPORT *transport; /* transport this job belongs to */
+ QMGR_JOB_LIST message_peers; /* per message neighbor linkage */
+ QMGR_JOB_LIST transport_peers; /* per transport neighbor linkage */
+ QMGR_JOB_LIST stack_peers; /* transport stack linkage */
+ int stack_level; /* job stack nesting level (-1 ->
+ * retired) */
+ struct HTABLE *peer_byname; /* message job peers, indexed by
+ * domain */
+ QMGR_PEER_LIST peer_list; /* list of message job peers */
+ int slots_used; /* slots used during preemption */
+ int slots_available; /* slots available for preemption (*
+ * slot_cost) */
+ int selected_entries; /* # of entries selected for delivery
+ * so far */
+ int read_entries; /* # of entries read in-core so far */
+ int rcpt_count; /* used recipient slots */
+ int rcpt_limit; /* available recipient slots */
};
struct QMGR_PEER {
- QMGR_JOB *job; /* job handling this peer */
- QMGR_QUEUE *queue; /* queue corresponding with this peer */
- int refcount; /* peer entries */
- QMGR_ENTRY_LIST entry_list; /* todo message entries queued for this peer */
- QMGR_PEER_LIST peers; /* neighbor linkage */
+ QMGR_JOB *job; /* job handling this peer */
+ QMGR_QUEUE *queue; /* queue corresponding with this peer */
+ int refcount; /* peer entries */
+ QMGR_ENTRY_LIST entry_list; /* todo message entries queued for
+ * this peer */
+ QMGR_PEER_LIST peers; /* neighbor linkage */
};
extern QMGR_ENTRY *qmgr_job_entry_select(QMGR_TRANSPORT *);
/* qmgr_entry_select - select queue entry for delivery */
-QMGR_ENTRY *qmgr_entry_select(QMGR_PEER *peer)
+QMGR_ENTRY *qmgr_entry_select(QMGR_PEER * peer)
{
QMGR_ENTRY *entry;
QMGR_QUEUE *queue;
if ((entry = peer->entry_list.next) != 0) {
- queue = entry->queue;
+ queue = entry->queue;
QMGR_LIST_UNLINK(queue->todo, QMGR_ENTRY *, entry, queue_peers);
queue->todo_refcount--;
QMGR_LIST_APPEND(queue->busy, entry, queue_peers);
queue->busy_refcount++;
QMGR_LIST_UNLINK(peer->entry_list, QMGR_ENTRY *, entry, peer_peers);
- peer->job->selected_entries++;
+ peer->job->selected_entries++;
}
return (entry);
}
QMGR_QUEUE *queue = entry->queue;
QMGR_MESSAGE *message = entry->message;
QMGR_PEER *peer = entry->peer;
- QMGR_JOB *sponsor, *job = peer->job;
+ QMGR_JOB *sponsor,
+ *job = peer->job;
/*
* Take this entry off the in-core queue.
queue->busy_refcount--;
} else if (which == QMGR_QUEUE_TODO) {
QMGR_LIST_UNLINK(peer->entry_list, QMGR_ENTRY *, entry, peer_peers);
+ job->selected_entries++;
QMGR_LIST_UNLINK(queue->todo, QMGR_ENTRY *, entry, queue_peers);
queue->todo_refcount--;
} else {
myfree((char *) entry);
/*
- * Make sure that transport of any retired or finishing jobs that
- * donated recipient slots to this job's message gets them back first.
- * Then, if possible, pass the remaining unused recipient slots to the
- * next job in the queue.
+ * Make sure that the transport of any retired or finishing job that
+ * donated recipient slots to this message gets them back first. Then, if
+ * possible, pass the remaining unused recipient slots to the next job in
+ * the job list.
*/
for (sponsor = message->job_list.next; sponsor; sponsor = sponsor->message_peers.next) {
- if (sponsor->rcpt_count >= sponsor->rcpt_limit || sponsor == job)
- continue;
- if (sponsor->stack_level < 0 || message->rcpt_offset == 0)
- qmgr_job_move_limits(sponsor);
+ if (sponsor->rcpt_count >= sponsor->rcpt_limit || sponsor == job)
+ continue;
+ if (sponsor->stack_level < 0 || message->rcpt_offset == 0)
+ qmgr_job_move_limits(sponsor);
}
if (message->rcpt_offset == 0) {
- qmgr_job_move_limits(job);
+ qmgr_job_move_limits(job);
}
-
+
/*
- * When there are no more entries for this peer, discard the peer structure.
+ * When there are no more entries for this peer, discard the peer
+ * structure.
*/
- peer->refcount--;
- if (peer->refcount == 0)
- qmgr_peer_free(peer);
+ peer->refcount--;
+ if (peer->refcount == 0)
+ qmgr_peer_free(peer);
/*
* When the in-core queue for this site is empty and when this site is
/* qmgr_entry_create - create queue todo entry */
-QMGR_ENTRY *qmgr_entry_create(QMGR_PEER *peer, QMGR_MESSAGE *message)
+QMGR_ENTRY *qmgr_entry_create(QMGR_PEER * peer, QMGR_MESSAGE *message)
{
QMGR_ENTRY *entry;
QMGR_QUEUE *queue = peer->queue;
message->refcount++;
entry->peer = peer;
QMGR_LIST_APPEND(peer->entry_list, entry, peer_peers);
- peer->refcount++ ;
+ peer->refcount++;
entry->queue = queue;
QMGR_LIST_APPEND(queue->todo, entry, queue_peers);
queue->todo_refcount++;
/*
/* qmgr_job_obtain() finds an existing job for named message and
/* transport combination. New empty job is created if no existing can
-/* be found.In either case, the job is prepared for assignement of
-/* (more) message recipients
+/* be found. In either case, the job is prepared for assignement of
+/* (more) message recipients.
/*
/* qmgr_job_free() disposes of a per-transport job after all
/* its entries have been taken care of. It is an error to dispose
static QMGR_JOB *qmgr_job_create(QMGR_MESSAGE *message, QMGR_TRANSPORT *transport)
{
QMGR_JOB *job;
-
+
job = (QMGR_JOB *) mymalloc(sizeof(QMGR_JOB));
job->message = message;
QMGR_LIST_APPEND(message->job_list, job, message_peers);
/* qmgr_job_link - append the job to the job list, according to the time it was queued */
-static void qmgr_job_link(QMGR_JOB *job)
+static void qmgr_job_link(QMGR_JOB * job)
{
QMGR_TRANSPORT *transport = job->transport;
QMGR_MESSAGE *message = job->message;
- QMGR_JOB *prev,*next,*unread;
- int delay;
-
+ QMGR_JOB *prev,
+ *next,
+ *unread;
+ int delay;
+
unread = transport->job_next_unread;
-
+
/*
* This may look inefficient but under normal operation it is expected
- * that the loop will stop right away, resulting in normal list append below.
- * However, this code is necessary for reviving retired jobs and for jobs
- * which are created long after the first chunk of recipients was read in-core
- * (either of these can happen only for multi-transport messages).
- *
- * In case this is found unsatisfactory one day, it's possible to deploy some
- * smarter technique (using some form of lookup trees perhaps).
+ * that the loop will stop right away, resulting in normal list append
+ * below. However, this code is necessary for reviving retired jobs and
+ * for jobs which are created long after the first chunk of recipients
+ * was read in-core (either of these can happen only for multi-transport
+ * messages).
+ *
+ * In case this is found unsatisfactory one day, it's possible to deploy
+ * some smarter technique (using some form of lookup trees perhaps).
*/
for (next = 0, prev = transport->job_list.prev; prev;
- next = prev, prev = prev->transport_peers.prev)
- {
- delay = message->queued_time - prev->message->queued_time;
- if (delay >= 0)
- break;
- if (unread == prev)
- unread = 0;
+ next = prev, prev = prev->transport_peers.prev) {
+ delay = message->queued_time - prev->message->queued_time;
+ if (delay >= 0)
+ break;
+ if (unread == prev)
+ unread = 0;
}
-
+
/*
- * Don't link the new job in front of the first job on the job list
- * if that job was already used for the regular delivery.
- * This seems like a subtle difference but it helps many invariants
- * used at various other places to remain true.
+ * Don't link the new job in front of the first job on the job list if
+ * that job was already used for the regular delivery. This seems like a
+ * subtle difference but it helps many invariants used at various other
+ * places to remain true.
*/
if (prev == 0 && next != 0 && next->slots_used != 0) {
- prev = next;
- next = next->transport_peers.next;
- /*
- * The following is not currently necessary but is done anyway
- * for the sake of consistency.
- */
- if (prev == transport->job_next_unread)
- unread = prev;
+ prev = next;
+ next = next->transport_peers.next;
+
+ /*
+ * The following is not currently necessary but is done anyway for
+ * the sake of consistency.
+ */
+ if (prev == transport->job_next_unread)
+ unread = prev;
}
-
+
/*
* Link the job into the proper place on the job list.
*/
job->transport_peers.prev = prev;
job->transport_peers.next = next;
if (prev != 0)
- prev->transport_peers.next = job;
+ prev->transport_peers.next = job;
else
- transport->job_list.next = job;
+ transport->job_list.next = job;
if (next != 0)
- next->transport_peers.prev = job;
+ next->transport_peers.prev = job;
else
- transport->job_list.prev = job;
+ transport->job_list.prev = job;
/*
- * Update the pointer to the first unread job on the job list
- * and steal the unused recipient slots from the old one.
+ * Update the pointer to the first unread job on the job list and steal
+ * the unused recipient slots from the old one.
*/
if (unread == 0) {
- unread = transport->job_next_unread;
- transport->job_next_unread = job;
- if (unread != 0)
- qmgr_job_move_limits(unread);
+ unread = transport->job_next_unread;
+ transport->job_next_unread = job;
+ if (unread != 0)
+ qmgr_job_move_limits(unread);
}
/*
* (which is usually after all recipients have been read in core).
*/
if (transport->rcpt_unused > 0) {
- job->rcpt_limit += transport->rcpt_unused;
- message->rcpt_limit += transport->rcpt_unused;
- transport->rcpt_unused = 0;
+ job->rcpt_limit += transport->rcpt_unused;
+ message->rcpt_limit += transport->rcpt_unused;
+ transport->rcpt_unused = 0;
}
}
static QMGR_JOB *qmgr_job_find(QMGR_MESSAGE *message, QMGR_TRANSPORT *transport)
{
+
/*
- * Instead of traversing the message job list, we use single per transport
- * hash table. This is better (at least with respect to memory usage)
- * than having single hash table (usually almost empty) for each message.
+ * Instead of traversing the message job list, we use single per
+ * transport hash table. This is better (at least with respect to memory
+ * usage) than having single hash table (usually almost empty) for each
+ * message.
*/
return ((QMGR_JOB *) htable_find(transport->job_byname, message->queue_id));
}
/* qmgr_job_obtain - find/create the appropriate job and make it ready for new recipients */
-QMGR_JOB * qmgr_job_obtain(QMGR_MESSAGE *message, QMGR_TRANSPORT *transport)
+QMGR_JOB *qmgr_job_obtain(QMGR_MESSAGE *message, QMGR_TRANSPORT *transport)
{
QMGR_JOB *job;
-
+
/*
- * Try finding the existing job and revive it if it was already retired.
- * Create the new job for this transport/message combination otherwise.
+ * Try finding an existing job and revive it if it was already retired.
+ * Create a new job for this transport/message combination otherwise.
*/
if ((job = qmgr_job_find(message, transport)) != 0) {
- if (job->stack_level < 0) {
- job->stack_level = 0;
- qmgr_job_link(job);
- }
- }
- else {
- job = qmgr_job_create(message, transport);
- qmgr_job_link(job);
+ if (job->stack_level < 0) {
+ job->stack_level = 0;
+ qmgr_job_link(job);
+ }
+ } else {
+ job = qmgr_job_create(message, transport);
+ qmgr_job_link(job);
}
/*
* Reset the candidate cache because of the new expected recipients.
- */
+ */
RESET_CANDIDATE_CACHE(transport);
- return(job);
+ return (job);
}
/* qmgr_job_move_limits - move unused recipient slots to the next job */
-void qmgr_job_move_limits(QMGR_JOB *job)
+void qmgr_job_move_limits(QMGR_JOB * job)
{
QMGR_TRANSPORT *transport = job->transport;
QMGR_MESSAGE *message = job->message;
QMGR_JOB *next = transport->job_next_unread;
- int rcpt_unused, msg_rcpt_unused;
+ int rcpt_unused,
+ msg_rcpt_unused;
/*
* Find next unread job on the job list if necessary. Cache it for later.
* This makes the amortized efficiency of this routine O(1) per job.
*/
if (job == next) {
- for (next = next->transport_peers.next; next; next = next->transport_peers.next)
- if (next->message->rcpt_offset != 0)
- break;
- transport->job_next_unread = next;
+ for (next = next->transport_peers.next; next; next = next->transport_peers.next)
+ if (next->message->rcpt_offset != 0)
+ break;
+ transport->job_next_unread = next;
}
/*
*/
rcpt_unused = job->rcpt_limit - job->rcpt_count;
msg_rcpt_unused = message->rcpt_limit - message->rcpt_count;
- if( msg_rcpt_unused < rcpt_unused )
- rcpt_unused = msg_rcpt_unused;
+ if (msg_rcpt_unused < rcpt_unused)
+ rcpt_unused = msg_rcpt_unused;
/*
- * Transfer the unused recipient slots back to the transport pool and
- * to the next not-fully-read job. Job's message limits are adjusted accordingly.
+ * Transfer the unused recipient slots back to the transport pool and to
+ * the next not-fully-read job. Job's message limits are adjusted
+ * accordingly.
*/
if (rcpt_unused > 0) {
- job->rcpt_limit -= rcpt_unused;
- message->rcpt_limit -= rcpt_unused;
- transport->rcpt_unused += rcpt_unused;
- if (next != 0 && (rcpt_unused = transport->rcpt_unused) > 0) {
- next->rcpt_limit += rcpt_unused;
- next->message->rcpt_limit += rcpt_unused;
- transport->rcpt_unused = 0;
- }
+ job->rcpt_limit -= rcpt_unused;
+ message->rcpt_limit -= rcpt_unused;
+ transport->rcpt_unused += rcpt_unused;
+ if (next != 0 && (rcpt_unused = transport->rcpt_unused) > 0) {
+ next->rcpt_limit += rcpt_unused;
+ next->message->rcpt_limit += rcpt_unused;
+ transport->rcpt_unused = 0;
+ }
}
}
/* qmgr_job_retire - remove the job from the job list while waiting for recipients to deliver */
-static void qmgr_job_retire(QMGR_JOB *job)
+static void qmgr_job_retire(QMGR_JOB * job)
{
- char *myname = "qmgr_job_retire";
+ char *myname = "qmgr_job_retire";
QMGR_TRANSPORT *transport = job->transport;
if (msg_verbose)
- msg_info("%s: %s", myname, job->message->queue_id);
+ msg_info("%s: %s", myname, job->message->queue_id);
/*
* Sanity checks.
*/
if (job->stack_level != 0)
- msg_panic("%s: non-zero stack level (%d)", myname, job->stack_level);
+ msg_panic("%s: non-zero stack level (%d)", myname, job->stack_level);
/*
- * Make sure this job is not cached as the next unread job for this transport.
- * The qmgr_entry_done() will make sure that the slots donated by this job
- * are moved back to the transport pool as soon as possible.
+ * Make sure this job is not cached as the next unread job for this
+ * transport. The qmgr_entry_done() will make sure that the slots donated
+ * by this job are moved back to the transport pool as soon as possible.
*/
qmgr_job_move_limits(job);
-
+
/*
* Invalidate the candidate selection cache if necessary.
*/
if (job == transport->candidate_cache
- || (transport->job_stack.next == 0 && job == transport->job_list.next))
- RESET_CANDIDATE_CACHE(transport);
+ || (transport->job_stack.next == 0 && job == transport->job_list.next))
+ RESET_CANDIDATE_CACHE(transport);
/*
* Remove the job from the job list and mark it as retired.
/* qmgr_job_free - release the job structure */
-void qmgr_job_free(QMGR_JOB *job)
+void qmgr_job_free(QMGR_JOB * job)
{
- char *myname = "qmgr_job_free";
+ char *myname = "qmgr_job_free";
QMGR_MESSAGE *message = job->message;
QMGR_TRANSPORT *transport = job->transport;
if (msg_verbose)
- msg_info("%s: %s %s", myname, message->queue_id, transport->name);
+ msg_info("%s: %s %s", myname, message->queue_id, transport->name);
/*
* Sanity checks.
*/
if (job->rcpt_count)
- msg_panic("%s: non-zero recipient count (%d)", myname, job->rcpt_count);
+ msg_panic("%s: non-zero recipient count (%d)", myname, job->rcpt_count);
/*
* Remove the job from the job stack if necessary.
*/
if (job->stack_level > 0)
- qmgr_job_pop(job);
+ qmgr_job_pop(job);
/*
* Return any remaining recipient slots back to the recipient slots pool.
*/
qmgr_job_move_limits(job);
if (job->rcpt_limit)
- msg_panic("%s: recipient slots leak (%d)", myname, job->rcpt_limit);
-
+ msg_panic("%s: recipient slots leak (%d)", myname, job->rcpt_limit);
+
/*
* Invalidate the candidate selection cache if necessary.
*/
if (job == transport->candidate_cache
- || (transport->job_stack.next == 0 && job == transport->job_list.next))
- RESET_CANDIDATE_CACHE(transport);
+ || (transport->job_stack.next == 0 && job == transport->job_list.next))
+ RESET_CANDIDATE_CACHE(transport);
/*
- * Unlink and discard the structure. Check if the job is still on the transport
- * job list or if it was already retired before unlinking it.
+ * Unlink and discard the structure. Check if the job is still on the
+ * transport job list or if it was already retired before unlinking it.
*/
if (job->stack_level >= 0)
- QMGR_LIST_UNLINK(transport->job_list, QMGR_JOB *, job, transport_peers);
+ QMGR_LIST_UNLINK(transport->job_list, QMGR_JOB *, job, transport_peers);
QMGR_LIST_UNLINK(message->job_list, QMGR_JOB *, job, message_peers);
- htable_delete(transport->job_byname, message->queue_id, (void (*) (char *)) 0) ;
+ htable_delete(transport->job_byname, message->queue_id, (void (*) (char *)) 0);
htable_free(job->peer_byname, (void (*) (char *)) 0);
myfree((char *) job);
}
-/* qmgr_job_count_slots - maintain the delivery slots' counters */
+/* qmgr_job_count_slots - maintain the delivery slot counters */
-static void qmgr_job_count_slots(QMGR_JOB *current, QMGR_JOB *job)
+static void qmgr_job_count_slots(QMGR_JOB * current, QMGR_JOB * job)
{
+
/*
- * Count the number of delivery slots used during the delivery
- * of the selected job and also the number of delivery slots
- * available for preemption.
- *
+ * Count the number of delivery slots used during the delivery of the
+ * selected job. Also count the number of delivery slots available for
+ * preemption.
+ *
* However, suppress any slot counting if we didn't start regular delivery
* of the selected job yet.
*/
if (job == current || job->slots_used > 0) {
- job->slots_used++;
- job->slots_available++;
+ job->slots_used++;
+ job->slots_available++;
}
-
+
/*
* If the selected job is not the current job, its chance to be chosen by
* qmgr_job_candidate() has slightly changed. If we would like to make
- * the candidate cache completely transparent, we should invalidate it now.
- *
+ * the candidate cache completely transparent, we should invalidate it
+ * now.
+ *
* However, this case should usually happen only at "end of current job"
* phase, when it's unlikely that the current job can be preempted
* anyway. And because it's likely to happen quite often then, we
- * intentionally don't reset the cache, to safe some cycles.
- * Furthermore, the cache times out every second anyway.
+ * intentionally don't reset the cache, to safe some cycles. Furthermore,
+ * the cache times out every second anyway.
*/
#if 0
if (job != current)
- RESET_CANDIDATE_CACHE(job->transport);
+ RESET_CANDIDATE_CACHE(job->transport);
#endif
}
/* qmgr_job_candidate - find best job candidate for preempting given job */
-static QMGR_JOB *qmgr_job_candidate(QMGR_JOB *current)
+static QMGR_JOB *qmgr_job_candidate(QMGR_JOB * current)
{
QMGR_TRANSPORT *transport = current->transport;
- QMGR_JOB *job, *best_job = 0;
- float score, best_score = 0.0;
- int max_slots, max_needed_entries, max_total_entries;
- int delay;
- time_t now = event_time();
-
+ QMGR_JOB *job,
+ *best_job = 0;
+ float score,
+ best_score = 0.0;
+ int max_slots,
+ max_needed_entries,
+ max_total_entries;
+ int delay;
+ time_t now = event_time();
+
/*
* Fetch the result directly from the cache if the cache is still valid.
- *
- * Note that we cache negative results too, so the cache must be
- * invalidated by resetting the cache time, not the candidate pointer itself.
+ *
+ * Note that we cache negative results too, so the cache must be invalidated
+ * by resetting the cache time, not the candidate pointer itself.
*/
if (transport->candidate_cache_time == now)
- return (transport->candidate_cache);
-
+ return (transport->candidate_cache);
+
/*
- * Estimate the minimum amount of delivery slots that can ever be accumulated
- * for the given job. All jobs that won't fit into these slots are excluded
- * from the candidate selection.
+ * Estimate the minimum amount of delivery slots that can ever be
+ * accumulated for the given job. All jobs that won't fit into these
+ * slots are excluded from the candidate selection.
*/
max_slots = (MIN_ENTRIES(current) - current->selected_entries
- + current->slots_available) / transport->slot_cost;
+ + current->slots_available) / transport->slot_cost;
/*
- * Select the candidate with best time_since_queued/total_recipients score.
- * In addition to jobs which don't meet the max_slots limit, skip also jobs
- * which don't have any selectable entries at the moment.
- *
- * By the way, the selection is reasonably resistant to OS time warping, too.
- *
- * However, don't bother searching if we can't find anything suitable anyway.
+ * Select the candidate with best time_since_queued/total_recipients
+ * score. In addition to jobs which don't meet the max_slots limit, skip
+ * also jobs which don't have any selectable entries at the moment.
+ *
+ * By the way, the selection is reasonably resistant to OS time warping,
+ * too.
+ *
+ * However, don't bother searching if we can't find anything suitable
+ * anyway.
*/
if (max_slots > 0) {
- for (job = transport->job_list.next; job; job = job->transport_peers.next) {
- if (job->stack_level != 0 || job == current)
- continue;
- max_total_entries = MAX_ENTRIES(job);
- max_needed_entries = max_total_entries - job->selected_entries;
- delay = now - job->message->queued_time + 1;
- if (max_needed_entries > 0 && max_needed_entries <= max_slots) {
- score = (float) delay / max_total_entries;
- if (score > best_score) {
- best_score = score;
- best_job = job;
- }
- }
- /*
- * Stop early if the best score is as good as it can get.
- */
- if (delay <= best_score)
- break;
- }
+ for (job = transport->job_list.next; job; job = job->transport_peers.next) {
+ if (job->stack_level != 0 || job == current)
+ continue;
+ max_total_entries = MAX_ENTRIES(job);
+ max_needed_entries = max_total_entries - job->selected_entries;
+ delay = now - job->message->queued_time + 1;
+ if (max_needed_entries > 0 && max_needed_entries <= max_slots) {
+ score = (float) delay / max_total_entries;
+ if (score > best_score) {
+ best_score = score;
+ best_job = job;
+ }
+ }
+
+ /*
+ * Stop early if the best score is as good as it can get.
+ */
+ if (delay <= best_score)
+ break;
+ }
}
/*
/* qmgr_job_preempt - preempt large message with smaller one */
-static QMGR_JOB * qmgr_job_preempt(QMGR_JOB *current)
+static QMGR_JOB *qmgr_job_preempt(QMGR_JOB * current)
{
- char *myname = "qmgr_job_preempt";
+ char *myname = "qmgr_job_preempt";
QMGR_TRANSPORT *transport = current->transport;
QMGR_JOB *job;
- int rcpt_slots;
-
+ int rcpt_slots;
+
/*
- * Suppress preempting completely if the current job is not big enough
- * to accumulate even the mimimal number of slots required.
- *
- * Also, don't look for better job candidate if there are no available
- * slots yet (the count can get negative due to the slot loans below).
+ * Suppress preempting completely if the current job is not big enough to
+ * accumulate even the mimimal number of slots required.
+ *
+ * Also, don't look for better job candidate if there are no available slots
+ * yet (the count can get negative due to the slot loans below).
*/
if (current->slots_available <= 0
- || MAX_ENTRIES(current) < transport->min_slots * transport->slot_cost)
- return (current);
+ || MAX_ENTRIES(current) < transport->min_slots * transport->slot_cost)
+ return (current);
/*
* Find best candidate for preempting the current job.
- *
- * Note that the function also takes care that the candidate fits within
- * the number of delivery slots the current job can ever accumulate yet.
- */
+ *
+ * Note that the function also takes care that the candidate fits within the
+ * number of delivery slots which the current job is still able to
+ * accumulate.
+ */
if ((job = qmgr_job_candidate(current)) == 0)
- return (current);
-
+ return (current);
+
/*
* Sanity checks.
*/
msg_panic("%s: already on the job stack (%d)", myname, job->stack_level);
/*
- * Check if there is enough available delivery slots accumulated
- * to preempt the current job.
- *
- * The slot loaning scheme improves the average message response time.
- * Note that the loan only allows the preemption happen earlier, though.
- * It doesn't affect how many slots have to be "paid" - the full number
- * of slots required will have to accumulate later before next
- * preemption on the same stack level can happen anyway.
+ * Check if there is enough available delivery slots accumulated to
+ * preempt the current job.
+ *
+ * The slot loaning scheme improves the average message response time. Note
+ * that the loan only allows the preemption happen earlier, though. It
+ * doesn't affect how many slots have to be "paid" - the full number of
+ * slots required has to be accumulated later before next preemption on
+ * the same stack level can happen in either case.
*/
- if (current->slots_available / transport->slot_cost
- + transport->slot_loan
- < (MAX_ENTRIES(job) - job->selected_entries)
- * transport->slot_loan_factor / 100.0)
- return (current);
+ if (current->slots_available / transport->slot_cost
+ + transport->slot_loan
+ < (MAX_ENTRIES(job) - job->selected_entries)
+ * transport->slot_loan_factor / 100.0)
+ return (current);
/*
* Preempt the current job.
*/
QMGR_LIST_PREPEND(transport->job_stack, job, stack_peers);
job->stack_level = current->stack_level + 1;
-
+
/*
- * Add part of extra recipient slots reserved for preempting jobs
- * to the new current job if necessary.
- *
- * Note that transport->rcpt_unused is within <-rcpt_per_stack,0> in such case.
+ * Add part of extra recipient slots reserved for preempting jobs to the
+ * new current job if necessary.
+ *
+ * Note that transport->rcpt_unused is within <-rcpt_per_stack,0> in such
+ * case.
*/
if (job->message->rcpt_offset != 0) {
- rcpt_slots = (transport->rcpt_per_stack + transport->rcpt_unused + 1) / 2;
- job->rcpt_limit += rcpt_slots;
- job->message->rcpt_limit += rcpt_slots;
- transport->rcpt_unused -= rcpt_slots;
+ rcpt_slots = (transport->rcpt_per_stack + transport->rcpt_unused + 1) / 2;
+ job->rcpt_limit += rcpt_slots;
+ job->message->rcpt_limit += rcpt_slots;
+ transport->rcpt_unused -= rcpt_slots;
}
/*
- * Candidate cache must be reset because the current job has changed completely.
+ * Candidate cache must be reset because the current job has changed
+ * completely.
*/
RESET_CANDIDATE_CACHE(transport);
if (msg_verbose)
- msg_info("%s: %s by %s", myname, current->message->queue_id,
- job->message->queue_id);
+ msg_info("%s: %s by %s", myname, current->message->queue_id,
+ job->message->queue_id);
return (job);
}
/* qmgr_job_pop - remove the job from the job preemption stack */
-static void qmgr_job_pop(QMGR_JOB *job)
+static void qmgr_job_pop(QMGR_JOB * job)
{
QMGR_TRANSPORT *transport = job->transport;
QMGR_JOB *parent;
-
+
if (msg_verbose)
- msg_info("qmgr_job_pop: %s", job->message->queue_id);
+ msg_info("qmgr_job_pop: %s", job->message->queue_id);
/*
- * Adjust the number of delivery slots available to preempt
- * job's parent.
+ * Adjust the number of delivery slots available to preempt job's parent.
+ *
+ * Note that we intentionally do not adjust slots_used of the parent. Doing
+ * so would decrease the maximum per message inflation factor if the
+ * preemption appeared near the end of parent delivery.
+ *
+ * For the same reason we do not adjust parent's slots_available if the
+ * parent is not the original parent preempted by the selected job (i.e.,
+ * the original parent job has already completed).
*
- * Note that we intentionally do not adjust slots_used of the parent.
- * Doing so would decrease the maximum per message inflation factor
- * if the preemption appeared near the end of parent delivery.
- *
- * For the same reason we do not adjust parent's slots_available
- * if the parent is not the original parent preempted by the
- * selected job (i.e., the original parent job has already completed).
- *
- * The special case when the head of the job list was preempted and
- * then delivered before the preempting job itself is taken care of too.
+ * The special case when the head of the job list was preempted and then
+ * delivered before the preempting job itself is taken care of too.
* Otherwise we would decrease available slot counter of some job that
* was not in fact preempted yet.
*/
if (((parent = job->stack_peers.next) != 0
- || ((parent = transport->job_list.next) != 0 && parent->slots_used > 0))
- && job->stack_level == parent->stack_level + 1)
- parent->slots_available -= job->slots_used * transport->slot_cost;
+ || ((parent = transport->job_list.next) != 0 && parent->slots_used > 0))
+ && job->stack_level == parent->stack_level + 1)
+ parent->slots_available -= job->slots_used * transport->slot_cost;
/*
* Invalidate the candidate selection cache if necessary.
*/
if (job == transport->job_stack.next)
- RESET_CANDIDATE_CACHE(transport);
+ RESET_CANDIDATE_CACHE(transport);
/*
* Remove the job from the job stack and reinitialize the slot counters.
/* qmgr_job_peer_select - select next peer suitable for delivery */
-static QMGR_PEER *qmgr_job_peer_select(QMGR_JOB *job)
+static QMGR_PEER *qmgr_job_peer_select(QMGR_JOB * job)
{
QMGR_PEER *peer;
QMGR_MESSAGE *message = job->message;
-
+
if (HAS_ENTRIES(job) && (peer = qmgr_peer_select(job)) != 0)
- return (peer);
+ return (peer);
/*
* Try reading in more recipients. Note that we do not try to read them
* recipient grouping. We waited until reading more is really necessary.
*/
if (message->rcpt_offset != 0 && message->rcpt_limit > message->rcpt_count) {
- qmgr_message_realloc(message);
- if (HAS_ENTRIES(job))
- return (qmgr_peer_select(job));
+ qmgr_message_realloc(message);
+ if (HAS_ENTRIES(job))
+ return (qmgr_peer_select(job));
}
return (0);
}
QMGR_ENTRY *qmgr_job_entry_select(QMGR_TRANSPORT *transport)
{
- QMGR_JOB *job, *current, *next;
+ QMGR_JOB *job,
+ *current,
+ *next;
QMGR_PEER *peer;
QMGR_ENTRY *entry;
-
+
/*
* Select the "current" job.
*/
if ((current = transport->job_stack.next) == 0
- && (current = transport->job_list.next) == 0)
- return (0);
-
+ && (current = transport->job_list.next) == 0)
+ return (0);
+
/*
* Exercise the preempting algorithm if enabled.
- *
+ *
* The slot_cost equal to 1 causes the algorithm to degenerate and is
* therefore disabled too.
*/
if (transport->slot_cost >= 2)
- current = qmgr_job_preempt(current);
+ current = qmgr_job_preempt(current);
/*
* Select next entry suitable for delivery. First check the stack of
* preempting jobs, then the list of all remaining jobs in FIFO order.
- *
- * Note that although the loops may look inefficient, they only serve as
- * a recovery mechanism when an entry of the current job itself can't be
+ *
+ * Note that although the loops may look inefficient, they only serve as a
+ * recovery mechanism when an entry of the current job itself can't be
* selected due peer concurrency restrictions. In most cases some entry
* of the current job itself is selected.
- *
+ *
* Note that both loops also take care of getting the "stall" current job
* (job with no entries currently available) out of the way if necessary.
- * Stall jobs can appear in case of multi-transport messages whose recipients
- * don't fit in-core at once. Some jobs created by such message may have
- * only few recipients and would block the job queue until all other
- * jobs of the message are delivered. Trying to read in more recipients
- * of such jobs each selection would also break the per peer recipient
- * grouping of the other jobs. That's why we retire such jobs below.
+ * Stall jobs can appear in case of multi-transport messages whose
+ * recipients don't fit in-core at once. Some jobs created by such
+ * message may have only few recipients and would block the job queue
+ * until all other jobs of the message are delivered. Trying to read in
+ * more recipients of such jobs each selection would also break the per
+ * peer recipient grouping of the other jobs. That's why we retire such
+ * jobs below.
*/
for (job = transport->job_stack.next; job; job = next) {
- next = job->stack_peers.next;
- if ((peer = qmgr_job_peer_select(job)) != 0) {
- entry = qmgr_entry_select(peer);
- qmgr_job_count_slots(current, job);
-
- /*
- * In case we selected the very last job entry, remove the job
- * from the job stack and the job list right now.
- *
- * This action uses the assumption that once the job entry
- * has been selected, it can be unselected only before the
- * message ifself is deferred. Thus the job with all entries
- * selected can't re-appear with more entries available for
- * selection again (without reading in more entries from
- * the queue file, which in turn invokes qmgr_job_obtain()
- * which re-links the job back on the list if necessary).
- *
- * Note that qmgr_job_move_limits() transfers the recipients slots
- * correctly even if the job is unlinked from the job list
- * thanks to the job_next_unread caching.
- */
- if (!HAS_ENTRIES(job) && job->message->rcpt_offset == 0) {
- qmgr_job_pop(job);
- qmgr_job_retire(job);
- }
- return (entry);
- }
- else if (job == current && !HAS_ENTRIES(job)) {
- qmgr_job_pop(job);
- qmgr_job_retire(job);
- current = next ? next : transport->job_list.next;
- }
+ next = job->stack_peers.next;
+ if ((peer = qmgr_job_peer_select(job)) != 0) {
+ entry = qmgr_entry_select(peer);
+ qmgr_job_count_slots(current, job);
+
+ /*
+ * In case we selected the very last job entry, remove the job
+ * from the job stack and the job list right now.
+ *
+ * This action uses the assumption that once the job entry has been
+ * selected, it can be unselected only before the message ifself
+ * is deferred. Thus the job with all entries selected can't
+ * re-appear with more entries available for selection again
+ * (without reading in more entries from the queue file, which in
+ * turn invokes qmgr_job_obtain() which re-links the job back on
+ * the list if necessary).
+ *
+ * Note that qmgr_job_move_limits() transfers the recipients slots
+ * correctly even if the job is unlinked from the job list thanks
+ * to the job_next_unread caching.
+ */
+ if (!HAS_ENTRIES(job) && job->message->rcpt_offset == 0) {
+ qmgr_job_pop(job);
+ qmgr_job_retire(job);
+ }
+ return (entry);
+ } else if (job == current && !HAS_ENTRIES(job)) {
+ qmgr_job_pop(job);
+ qmgr_job_retire(job);
+ current = next ? next : transport->job_list.next;
+ }
}
-
+
/*
- * Try the regular job list if there is nothing (suitable) on the job stack.
+ * Try the regular job list if there is nothing (suitable) on the job
+ * stack.
*/
for (job = transport->job_list.next; job; job = next) {
- next = job->transport_peers.next;
- if (job->stack_level != 0)
- continue;
- if ((peer = qmgr_job_peer_select(job)) != 0) {
- entry = qmgr_entry_select(peer);
- qmgr_job_count_slots(current, job);
-
- /*
- * In case we selected the very last job entry, remove the job
- * from the job list right away.
- */
- if (!HAS_ENTRIES(job) && job->message->rcpt_offset == 0)
- qmgr_job_retire(job);
- return (entry);
- }
- else if (job == current && !HAS_ENTRIES(job)) {
- qmgr_job_retire(job);
- current = next;
- }
+ next = job->transport_peers.next;
+ if (job->stack_level != 0)
+ continue;
+ if ((peer = qmgr_job_peer_select(job)) != 0) {
+ entry = qmgr_entry_select(peer);
+ qmgr_job_count_slots(current, job);
+
+ /*
+ * In case we selected the very last job entry, remove the job
+ * from the job list right away.
+ */
+ if (!HAS_ENTRIES(job) && job->message->rcpt_offset == 0)
+ qmgr_job_retire(job);
+ return (entry);
+ } else if (job == current && !HAS_ENTRIES(job)) {
+ qmgr_job_retire(job);
+ current = next;
+ }
}
return (0);
}
/* structure members. A null result means that the file could not be
/* read or that the file contained incorrect information. Recipient
/* limit imposed this time is based on the position of the message
-/* job(s) on corresponding job list(s).
+/* job(s) on corresponding transport job list(s).
/*
/* qmgr_message_free() destroys an in-core message structure and makes
/* the resources available for reuse. It is an error to destroy
#include <errno.h>
#include <unistd.h>
#include <string.h>
-#include <stdio.h> /* sscanf() */
+#include <stdio.h> /* sscanf() */
#ifdef STRCASECMP_IN_STRINGS_H
#include <strings.h>
return (0);
}
-/* qmgr_message_oldstyle_scan - extract required information from old style queue file */
+/* qmgr_message_oldstyle_scan - extract required information from an old style queue file */
static void qmgr_message_oldstyle_scan(QMGR_MESSAGE *message)
{
VSTRING *buf;
- long orig_offset, curr_offset, extra_offset;
+ long orig_offset,
+ curr_offset,
+ extra_offset;
int rec_type;
- char *start;
+ char *start;
/*
* Initialize. No early returns or we have a memory leak.
*/
buf = vstring_alloc(100);
if ((orig_offset = vstream_ftell(message->fp)) < 0)
- msg_fatal("vstream_ftell %s: %m", VSTREAM_PATH(message->fp));
+ msg_fatal("vstream_ftell %s: %m", VSTREAM_PATH(message->fp));
/*
- * Rewind to the very begining to make sure we see all records.
+ * Rewind to the very beginning to make sure we see all records.
*/
if (vstream_fseek(message->fp, 0, SEEK_SET) < 0)
- msg_fatal("seek file %s: %m", VSTREAM_PATH(message->fp));
+ msg_fatal("seek file %s: %m", VSTREAM_PATH(message->fp));
/*
- * Scan through the old style queue file. Count the total number
- * of recipients and find the data/extra sections offsets.
- * Note that the new queue files require that data_size equals
- * extra_offset - data_offset, so we set data_size to this as well
- * and ignore the size record itself completely.
+ * Scan through the old style queue file. Count the total number of
+ * recipients and find the data/extra sections offsets. Note that the new
+ * queue files require that data_size equals extra_offset - data_offset,
+ * so we set data_size to this as well and ignore the size record itself
+ * completely.
*/
message->rcpt_unread = 0;
do {
msg_fatal("vstream_ftell %s: %m", VSTREAM_PATH(message->fp));
if ((extra_offset = atol(start)) <= curr_offset)
msg_fatal("bad extra offset %s file %s",
- start, VSTREAM_PATH(message->fp));
+ start, VSTREAM_PATH(message->fp));
if (vstream_fseek(message->fp, extra_offset, SEEK_SET) < 0)
msg_fatal("seek file %s: %m", VSTREAM_PATH(message->fp));
- message->data_size = extra_offset - message->data_offset;
+ message->data_size = extra_offset - message->data_offset;
}
} while (rec_type > 0 && rec_type != REC_TYPE_END);
-
+
/*
* Clean up.
*/
if (vstream_fseek(message->fp, orig_offset, SEEK_SET) < 0)
- msg_fatal("seek file %s: %m", VSTREAM_PATH(message->fp));
+ msg_fatal("seek file %s: %m", VSTREAM_PATH(message->fp));
vstring_free(buf);
/*
/*
* If we re-open this file, skip over on-file recipient records that we
* already looked at, and reset the in-core recipient address list.
- *
- * For the first time, the message recipient limit is calculated from
- * the global recipient limit. This is to avoid reading little recipients
+ *
+ * For the first time, the message recipient limit is calculated from the
+ * global recipient limit. This is to avoid reading little recipients
* when the active queue is near empty. When the queue becomes full, only
* the necessary amount is read in core. Such priming is necessary
* because there are no message jobs yet.
- *
+ *
* For the next time, the recipient limit is based solely on the message
- * jobs' positions in the job queues and/or job stacks.
+ * jobs' positions in the job lists and/or job stacks.
*/
if (message->rcpt_offset) {
if (message->rcpt_list.len)
msg_fatal("seek file %s: %m", VSTREAM_PATH(message->fp));
message->rcpt_offset = 0;
recipient_limit = message->rcpt_limit - message->rcpt_count;
- }
- else {
- recipient_limit = var_qmgr_rcpt_limit - qmgr_recipient_count;
- if (recipient_limit < message->rcpt_limit)
- recipient_limit = message->rcpt_limit;
+ } else {
+ recipient_limit = var_qmgr_rcpt_limit - qmgr_recipient_count;
+ if (recipient_limit < message->rcpt_limit)
+ recipient_limit = message->rcpt_limit;
}
/*
* may appear before or after the message content, so we keep reading
* from the queue file until we have enough recipients (rcpt_offset != 0)
* and until we know where the message content starts (data_offset != 0).
- *
+ *
* Note that the total recipient count record is accurate only for fresh
- * queue files. After some of the recipients are marked as done and
- * the queue file is deferred, it can be used as upper bound estimate
- * only. Fortunately, this poses no major problem on the scheduling
- * algorithm, as the only impact is that the already deferred messages
- * are not chosen by qmgr_job_candidate() as often as they could.
+ * queue files. After some of the recipients are marked as done and the
+ * queue file is deferred, it can be used as upper bound estimate only.
+ * Fortunately, this poses no major problem on the scheduling algorithm,
+ * as the only impact is that the already deferred messages are not
+ * chosen by qmgr_job_candidate() as often as they could.
*/
do {
if ((curr_offset = vstream_ftell(message->fp)) < 0)
msg_fatal("vstream_ftell %s: %m", VSTREAM_PATH(message->fp));
- if (curr_offset == message->data_offset && curr_offset > 0) {
- extra_offset = curr_offset + message->data_size;
- if (extra_offset <= curr_offset)
+ if (curr_offset == message->data_offset && curr_offset > 0) {
+ extra_offset = curr_offset + message->data_size;
+ if (extra_offset <= curr_offset)
msg_fatal("bad extra offset %ld file %s",
- extra_offset, VSTREAM_PATH(message->fp));
- if (vstream_fseek(message->fp, extra_offset, SEEK_SET) < 0)
- msg_fatal("seek file %s: %m", VSTREAM_PATH(message->fp));
- curr_offset = extra_offset;
- }
+ extra_offset, VSTREAM_PATH(message->fp));
+ if (vstream_fseek(message->fp, extra_offset, SEEK_SET) < 0)
+ msg_fatal("seek file %s: %m", VSTREAM_PATH(message->fp));
+ curr_offset = extra_offset;
+ }
rec_type = rec_get(message->fp, buf, 0);
start = vstring_str(buf);
if (rec_type == REC_TYPE_SIZE) {
if (message->data_size == 0) {
- switch (sscanf(start, "%ld %ld %d", &message->data_size,
- &message->data_offset, &message->rcpt_unread))
- {
- case 1:
- /*
- * Gather data_size, data_offset and rcpt_unread values
- * from the old style queue file.
- */
- qmgr_message_oldstyle_scan(message);
- break;
- case 3:
- /*
- * No extra work for new style queue files.
- */
- break;
- default:
- msg_fatal("%s: weird size record", message->queue_id);
- break;
- }
+ switch (sscanf(start, "%ld %ld %d", &message->data_size,
+ &message->data_offset, &message->rcpt_unread)) {
+ case 1:
+
+ /*
+ * Gather data_size, data_offset and rcpt_unread values
+ * from the old style queue file.
+ */
+ qmgr_message_oldstyle_scan(message);
+ break;
+ case 3:
+
+ /*
+ * No extra work for new style queue files.
+ */
+ break;
+ default:
+ msg_fatal("%s: weird size record", message->queue_id);
+ break;
+ }
}
} else if (rec_type == REC_TYPE_TIME) {
if (message->arrival_time == 0)
if (message->sender == 0) {
message->sender = mystrdup(start);
opened(message->queue_id, message->sender,
- message->data_size, "queue %s", message->queue_name);
+ message->data_size, message->rcpt_unread,
+ "queue %s", message->queue_name);
}
} else if (rec_type == REC_TYPE_DONE) {
if (curr_offset > message->unread_offset) {
- message->unread_offset = curr_offset;
- message->rcpt_unread--;
+ message->unread_offset = curr_offset;
+ message->rcpt_unread--;
}
} else if (rec_type == REC_TYPE_RCPT) {
if (message->rcpt_list.len < recipient_limit) {
*/
if (message->rcpt_unread < 0
|| (message->rcpt_offset == 0 && message->rcpt_unread != 0)) {
- msg_warn("%s: rcpt count mismatch (%d)",
- message->queue_id, message->rcpt_unread);
- message->rcpt_unread = 0;
+ msg_warn("%s: rcpt count mismatch (%d)",
+ message->queue_id, message->rcpt_unread);
+ message->rcpt_unread = 0;
}
if (message->arrival_time == 0
|| message->sender == 0
|| !LIMIT_OK(queue->transport->recipient_limit,
entry->rcpt_list.len)) {
if (job == 0 || queue->transport != job->transport) {
- /* FIXME: randomly rotate the peer list of previous job */
job = qmgr_job_obtain(message, queue->transport);
peer = 0;
}
if (peer == 0 || queue != peer->queue) {
if ((peer = qmgr_peer_find(job, queue)) == 0)
- peer = qmgr_peer_create(job, queue);
+ peer = qmgr_peer_create(job, queue);
}
entry = qmgr_entry_create(peer, message);
job->read_entries++;
static void qmgr_message_move_limits(QMGR_MESSAGE *message)
{
QMGR_JOB *job;
-
+
for (job = message->job_list.next; job; job = job->message_peers.next)
- qmgr_job_move_limits(job);
+ qmgr_job_move_limits(job);
}
/* qmgr_message_free - release memory for in-core message structure */
void qmgr_message_free(QMGR_MESSAGE *message)
{
QMGR_JOB *job;
+
if (message->refcount != 0)
msg_panic("qmgr_message_free: reference len: %d", message->refcount);
if (message->fp)
msg_panic("qmgr_message_free: queue file is open");
while ((job = message->job_list.next) != 0)
- qmgr_job_free(job);
+ qmgr_job_free(job);
myfree(message->queue_id);
myfree(message->queue_name);
if (message->sender)
qmgr_message_sort(message);
qmgr_message_assign(message);
qmgr_message_close(message);
- if(message->rcpt_offset == 0)
- qmgr_message_move_limits(message);
+ if (message->rcpt_offset == 0)
+ qmgr_message_move_limits(message);
return (message);
}
}
qmgr_message_sort(message);
qmgr_message_assign(message);
qmgr_message_close(message);
- if(message->rcpt_offset == 0)
- qmgr_message_move_limits(message);
+ if (message->rcpt_offset == 0)
+ qmgr_message_move_limits(message);
return (message);
}
}
/*
/* qmgr_peer_create() creates an empty peer structure for the named
/* job and destination. It is an error to call this function
-/* if an peer for given combination already exists.
+/* if a peer for given combination already exists.
/*
/* qmgr_peer_find() looks up the peer for the named destination
-/* for the named job. A null result means that the queue
+/* for the named job. A null result means that the peer
/* was not found.
/*
-/* qmgr_peer_free() disposes of a per-job queue after all
+/* qmgr_peer_free() disposes of a per-job peer after all
/* its entries have been taken care of. It is an error to dispose
/* of a peer still in use.
/*
/* has messages pending delivery. This routine implements
/* round-robin search among job's peers.
/* DIAGNOSTICS
-/* Panic: consistency check failure.
+/* None
/* LICENSE
/* .ad
/* .fi
/* qmgr_peer_create - create and initialize message peer structure */
-QMGR_PEER *qmgr_peer_create(QMGR_JOB *job, QMGR_QUEUE *queue)
+QMGR_PEER *qmgr_peer_create(QMGR_JOB * job, QMGR_QUEUE *queue)
{
QMGR_PEER *peer;
/* qmgr_peer_free - release peer structure */
-void qmgr_peer_free(QMGR_PEER *peer)
+void qmgr_peer_free(QMGR_PEER * peer)
{
QMGR_JOB *job = peer->job;
QMGR_LIST_UNLINK(job->peer_list, QMGR_PEER *, peer, peers);
- htable_delete(job->peer_byname, peer->queue->name, (void (*) (char *)) 0) ;
+ htable_delete(job->peer_byname, peer->queue->name, (void (*) (char *)) 0);
myfree((char *) peer);
}
/* qmgr_peer_find - lookup peer associated with given job and queue */
-QMGR_PEER *qmgr_peer_find(QMGR_JOB *job, QMGR_QUEUE *queue)
+QMGR_PEER *qmgr_peer_find(QMGR_JOB * job, QMGR_QUEUE *queue)
{
return ((QMGR_PEER *) htable_find(job->peer_byname, queue->name));
}
/* qmgr_peer_select - select next peer suitable for delivery within given job */
-QMGR_PEER *qmgr_peer_select(QMGR_JOB *job)
+QMGR_PEER *qmgr_peer_select(QMGR_JOB * job)
{
QMGR_PEER *peer;
QMGR_QUEUE *queue;
-
+
/*
* If we find a suitable site, rotate the list to enforce round-robin
* selection. See similar selection code in qmgr_transport_select().
*/
for (peer = job->peer_list.next; peer; peer = peer->peers.next) {
- queue = peer->queue;
- if (queue->window > queue->busy_refcount && peer->entry_list.next != 0) {
- QMGR_LIST_ROTATE(job->peer_list, peer, peers);
- if (msg_verbose)
- msg_info("qmgr_peer_select: %s %s", job->message->queue_id, queue->name);
+ queue = peer->queue;
+ if (queue->window > queue->busy_refcount && peer->entry_list.next != 0) {
+ QMGR_LIST_ROTATE(job->peer_list, peer, peers);
+ if (msg_verbose)
+ msg_info("qmgr_peer_select: %s %s", job->message->queue_id, queue->name);
return (peer);
- }
+ }
}
return (0);
}
*/
transport->dest_concurrency_limit =
get_mail_conf_int2(name, _DEST_CON_LIMIT,
- var_dest_con_limit, 0, 0);
+ var_dest_con_limit, 0, 0);
transport->recipient_limit =
get_mail_conf_int2(name, _DEST_RCPT_LIMIT,
- var_dest_rcpt_limit, 0, 0);
+ var_dest_rcpt_limit, 0, 0);
if (transport->dest_concurrency_limit == 0
|| transport->dest_concurrency_limit >= var_init_dest_concurrency)
transport->init_dest_concurrency = transport->dest_concurrency_limit;
transport->slot_cost = get_mail_conf_int2(name, _DELIVERY_SLOT_COST,
- var_delivery_slot_cost, 0, 0);
+ var_delivery_slot_cost, 0, 0);
transport->slot_loan = get_mail_conf_int2(name, _DELIVERY_SLOT_LOAN,
- var_delivery_slot_loan, 0, 0);
+ var_delivery_slot_loan, 0, 0);
transport->slot_loan_factor =
- 100 - get_mail_conf_int2(name, _DELIVERY_SLOT_DISCOUNT,
- var_delivery_slot_discount, 0, 100);
+ 100 - get_mail_conf_int2(name, _DELIVERY_SLOT_DISCOUNT,
+ var_delivery_slot_discount, 0, 100);
transport->min_slots = get_mail_conf_int2(name, _MIN_DELIVERY_SLOTS,
- var_min_delivery_slots, 0, 0);
+ var_min_delivery_slots, 0, 0);
transport->rcpt_unused = get_mail_conf_int2(name, _XPORT_RCPT_LIMIT,
- var_xport_rcpt_limit, 0, 0);
+ var_xport_rcpt_limit, 0, 0);
transport->rcpt_per_stack = get_mail_conf_int2(name, _STACK_RCPT_LIMIT,
- var_stack_rcpt_limit, 0, 0);
+ var_stack_rcpt_limit, 0, 0);
transport->queue_byname = htable_create(0);
QMGR_LIST_INIT(transport->queue_list);
/* Postfix alias database maintenance
/* SYNOPSIS
/* .fi
-/* \fBpostalias\fR [\fB-Ninvw\fR] [\fB-c \fIconfig_dir\fR]
+/* \fBpostalias\fR [\fB-Ninrvw\fR] [\fB-c \fIconfig_dir\fR]
/* [\fB-d \fIkey\fR] [\fB-q \fIkey\fR]
/* [\fIfile_type\fR:]\fIfile_name\fR ...
/* DESCRIPTION
/* Search the specified maps for \fIkey\fR and print the first value
/* found on the standard output stream. The exit status is non-zero
/* if the requested information was not found.
+/* .IP \fB-r\fR
+/* When updating a table, do not warn about duplicate entries; silently
+/* replace them.
/* .IP \fB-v\fR
/* Enable verbose logging for debugging purposes. Multiple \fB-v\fR
/* options make the software increasingly verbose.
static NORETURN usage(char *myname)
{
- msg_fatal("usage: %s [-Ninvw] [-c config_dir] [-d key] [-q key] [map_type:]file...",
+ msg_fatal("usage: %s [-Ninrvw] [-c config_dir] [-d key] [-q key] [map_type:]file...",
myname);
}
/*
* Parse JCL.
*/
- while ((ch = GETOPT(argc, argv, "Nc:d:inq:vw")) > 0) {
+ while ((ch = GETOPT(argc, argv, "Nc:d:inq:rvw")) > 0) {
switch (ch) {
default:
usage(argv[0]);
msg_fatal("specify only one of -q or -d");
query = optarg;
break;
+ case 'r':
+ dict_flags &= ~(DICT_FLAG_DUP_WARN | DICT_FLAG_DUP_IGNORE);
+ dict_flags |= DICT_FLAG_DUP_REPLACE;
+ break;
case 'v':
msg_verbose++;
break;
case 'w':
- dict_flags &= ~DICT_FLAG_DUP_WARN;
+ dict_flags &= ~(DICT_FLAG_DUP_WARN | DICT_FLAG_DUP_REPLACE);
dict_flags |= DICT_FLAG_DUP_IGNORE;
break;
}
/* Postfix lookup table management
/* SYNOPSIS
/* .fi
-/* \fBpostmap\fR [\fB-Ninvw\fR] [\fB-c \fIconfig_dir\fR] [\fB-d \fIkey\fR]
+/* \fBpostmap\fR [\fB-Ninrvw\fR] [\fB-c \fIconfig_dir\fR] [\fB-d \fIkey\fR]
/* [\fB-q \fIkey\fR] [\fIfile_type\fR:]\fIfile_name\fR ...
/* DESCRIPTION
/* The \fBpostmap\fR command creates or queries one or more Postfix
/* Search the specified maps for \fIkey\fR and print the first value
/* found on the standard output stream. The exit status is non-zero
/* if the requested information was not found.
+/* .IP \fB-r\fR
+/* When updating a table, do not warn about duplicate entries; silently
+/* replace them.
/* .IP \fB-v\fR
/* Enable verbose logging for debugging purposes. Multiple \fB-v\fR
/* options make the software increasingly verbose.
static NORETURN usage(char *myname)
{
- msg_fatal("usage: %s [-Ninvw] [-c config_dir] [-d key] [-q key] [map_type:]file...",
+ msg_fatal("usage: %s [-Ninrvw] [-c config_dir] [-d key] [-q key] [map_type:]file...",
myname);
}
/*
* Parse JCL.
*/
- while ((ch = GETOPT(argc, argv, "Nc:d:inq:vw")) > 0) {
+ while ((ch = GETOPT(argc, argv, "Nc:d:inq:rvw")) > 0) {
switch (ch) {
default:
usage(argv[0]);
msg_fatal("specify only one of -q or -d");
query = optarg;
break;
+ case 'r':
+ dict_flags &= ~(DICT_FLAG_DUP_WARN | DICT_FLAG_DUP_IGNORE);
+ dict_flags |= DICT_FLAG_DUP_REPLACE;
+ break;
case 'v':
msg_verbose++;
break;
case 'w':
- dict_flags &= ~DICT_FLAG_DUP_WARN;
+ dict_flags &= ~(DICT_FLAG_DUP_WARN | DICT_FLAG_DUP_REPLACE);
dict_flags |= DICT_FLAG_DUP_IGNORE;
break;
}
# .ti +5
# \fB\&.foo.org error:mail for *.foo.org is not deliverable\fR
#
-# This causes all mail for \fIuser\fR@\fIanything\fBfoo.org\fR
+# This causes all mail for \fIuser\fR@\fIanything\fB.foo.org\fR
# to be bounced.
# REGULAR EXPRESSION TABLES
# .ad
VAR_QUEUE_RUN_DELAY, DEF_QUEUE_RUN_DELAY, &var_queue_run_delay, 1, 0,
VAR_MIN_BACKOFF_TIME, DEF_MIN_BACKOFF_TIME, &var_min_backoff_time, 1, 0,
VAR_MAX_BACKOFF_TIME, DEF_MAX_BACKOFF_TIME, &var_max_backoff_time, 1, 0,
- VAR_MAX_QUEUE_TIME, DEF_MAX_QUEUE_TIME, &var_max_queue_time, 1, 0,
+ VAR_MAX_QUEUE_TIME, DEF_MAX_QUEUE_TIME, &var_max_queue_time, 1, 1000,
VAR_QMGR_ACT_LIMIT, DEF_QMGR_ACT_LIMIT, &var_qmgr_active_limit, 1, 0,
VAR_QMGR_RCPT_LIMIT, DEF_QMGR_RCPT_LIMIT, &var_qmgr_rcpt_limit, 1, 0,
VAR_INIT_DEST_CON, DEF_INIT_DEST_CON, &var_init_dest_concurrency, 1, 0,
long save_offset = message->rcpt_offset; /* save a flag */
char *start;
struct stat st;
+ int nrcpt;
/*
* Initialize. No early returns or we have a memory leak.
start = vstring_str(buf);
if (rec_type == REC_TYPE_SIZE) {
if (message->data_size == 0)
- message->data_size = atol(start);
+ sscanf(start, "%ld %ld %d",
+ &message->data_size, &message->data_offset, &nrcpt);
} else if (rec_type == REC_TYPE_TIME) {
if (message->arrival_time == 0)
message->arrival_time = atol(start);
if (message->sender == 0) {
message->sender = mystrdup(start);
opened(message->queue_id, message->sender,
- message->data_size, "queue %s", message->queue_name);
+ message->data_size, nrcpt,
+ "queue %s", message->queue_name);
}
} else if (rec_type == REC_TYPE_RCPT) {
#define FUDGE(x) ((x) * (var_qmgr_fudge / 100.0))
} else {
session = smtp_connect_domain(host, port, why);
}
+ if (session == 0
+ && smtp_errno == SMTP_FAIL
+ && strcmp(host, var_relayhost) == 0) {
+ msg_warn("relayhost configuration problem: %s", var_relayhost);
+ smtp_errno = SMTP_RETRY;
+ }
myfree(dest_buf);
return (session);
}
#include <smtp_stream.h>
#include <valid_hostname.h>
#include <dict.h>
+#include <watchdog.h>
/* Global library. */
static int rcpt_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv)
{
char *err;
+ int narg;
+ char *arg;
/*
* Sanity checks.
smtpd_chat_reply(state, "503 Error: need MAIL command");
return (-1);
}
- if (argc != 3
+ if (argc < 3
|| strcasecmp(argv[1].strval, "to:") != 0) {
state->error_mask |= MAIL_ERROR_PROTOCOL;
smtpd_chat_reply(state, "501 Syntax: RCPT TO: <address>");
smtpd_chat_reply(state, "%s", err);
return (-1);
}
+ for (narg = 3; narg < argc; narg++) {
+ arg = argv[narg].strval;
+ if (1) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "555 Unsupported option: %s", arg);
+ return (-1);
+ }
+ }
if (var_smtpd_rcpt_limit && state->rcpt_count >= var_smtpd_rcpt_limit) {
state->error_mask |= MAIL_ERROR_POLICY;
smtpd_chat_reply(state, "452 Error: too many recipients");
smtpd_chat_reply(state, "421 Error: too many errors");
break;
}
+ watchdog_pat();
smtpd_chat_query(state);
if ((argc = smtpd_token(vstring_str(state->buffer), &argv)) == 0) {
state->error_mask |= MAIL_ERROR_PROTOCOL;
-/*
+/*++
/* NAME
/* vstring 3
/* SUMMARY
/* The function takes a VSTRING pointer and a list of zero
/* or more (name,value) pairs. The expected valye type of the
/* value depends on the specified name. The name codes are:
-/* .IP VSTRING_CTL_MAXLEN (int)
+/* .IP "VSTRING_CTL_MAXLEN (int)"
/* Specifies a hard upper limit on a string's length. When the
/* length would be exceeded, the program simulates a memory
/* allocation problem (i.e. it terminates through msg_fatal()).
-/* .IP VSTRING_CTL_END (no value)
+/* .IP "VSTRING_CTL_END (no value)"
/* Specifies the end of the argument list. Forgetting to terminate
/* the argument list may cause the program to crash.
/* .PP
/*
/* void watchdog_destroy(watchdog)
/* WATCHDOG *watchdog;
+/*
+/* void watchdog_pat()
/* DESCRIPTION
/* This module implements watchdog timers that are based on ugly
/* UNIX alarm timers. The module is designed to survive systems
/* watchdog_destroy() stops the watchdog timer, and resumes the
/* watchdog timer instance that was suspended by watchdog_create().
/*
+/* watchdog_pat() pats the watchdog, so it stays quiet.
+/*
/* Arguments:
/* .IP timeout
/* The watchdog time limit. When the watchdog timer runs, the
msg_info("%s: %p", myname, (char *) wp);
}
+/* watchdog_pat - pat the dog so it stays quiet */
+
+void watchdog_pat(void)
+{
+ char *myname = "watchdog_pat";
+
+ if (watchdog_curr)
+ watchdog_curr->trip_run = 0;
+ if (msg_verbose)
+ msg_info("%s: %p", myname, (char *) watchdog_curr);
+}
+
#ifdef TEST
#include <vstream.h>
msg_verbose = 1;
wp = watchdog_create(10, (WATCHDOG_FN) 0, (char *) 0);
+ watchdog_start(wp);
do {
- watchdog_start(wp);
+ watchdog_pat();
} while (VSTREAM_GETCHAR() != VSTREAM_EOF);
watchdog_destroy(wp);
}
extern void watchdog_start(WATCHDOG *);
extern void watchdog_stop(WATCHDOG *);
extern void watchdog_destroy(WATCHDOG *);
+extern void watchdog_pat(void);
/* LICENSE
/* .ad