5 ## (c) 2003-2004 Piotr Roszatycki <dexter@debian.org>, GPL
11 eximq - Supervising process for Exim's queue runners.
15 B<eximq> B<-h>|B<--help>
17 B<eximq> [B<--debug-stderr>] [B<--debug-syslog>]
18 [B<--daemon>] S<[B<--pidfile> I<path>]>
19 I<agemin> I<agemax> I<interval> I<processmax> [I<exim_q_command>]
26 use Getopt::Long qw(:config require_order no_auto_abbrev);
27 use POSIX qw(:sys_wait_h :locale_h setsid);
29 use Unix::Syslog qw(:macros :subs);
32 ##############################################################################
47 my @eximq_cmd = qw(/usr/sbin/exim -q);
50 ##############################################################################
55 ## Count for running spawned processes
61 ## Getopt::Long handler
63 'pidfile' => "/var/run/$NAME.pid",
66 ## Old value for LC_TIME
67 my $old_lc_time = setlocale(LC_TIME);
70 ##############################################################################
74 ## Dumps message if debug mode is turned on.
79 if (${opt{'debug-syslog'}}) {
80 setlocale(LC_TIME, "C");
81 openlog($NAME, LOG_PID, LOG_MAIL);
82 syslog(LOG_INFO, "%s", join('', @msg));
84 setlocale(LC_TIME, $old_lc_time);
86 if (${opt{'debug-stderr'}}) {
87 print STDERR "*** @msg\n";
94 ## Dumps error message
99 setlocale(LC_TIME, "C");
100 openlog($NAME, LOG_PID, LOG_MAIL);
101 syslog(LOG_ERR, "%s", join('', @_));
103 setlocale(LC_TIME, $old_lc_time);
104 print STDERR "*** @_\n";
110 ## Handler for process signal
115 return unless defined $sig;
116 debug("Got a SIG$sig");
118 debug("Killing spawned processes for session $pgrp");
119 $SIG{'TERM'} = 'IGNORE';
122 die "Die for SIG$sig\n";
128 ## Daemonize main process
131 chdir '/' or die "Can't chdir to /: $!";
132 open STDIN, '/dev/null' or die "Can't read /dev/null: $!";
133 open STDOUT, '>/dev/null'
134 or die "Can't write to /dev/null: $!";
135 defined(my $pid = fork) or die "Can't fork: $!";
137 open PIDFILE, ">$opt{pidfile}" or die "Can't open pidfile $opt{pidfile}: $!";
138 print PIDFILE "$pid\n" or die "Can't write pidfile $opt{pidfile}: $!";
142 $pgrp = setsid or die "Can't start a new session: $!";
143 open STDERR, '>&STDOUT' or die "Can't dup stdout: $!";
149 ## Clean up before die
152 unlink $opt{'pidfile'} if $opt{'daemon'} and -f $opt{'pidfile'};
158 ## Sleep $usec seconds (can be factorized)
162 select(undef, undef, undef, $sec);
166 ## $n62 = base62($n10);
168 ## Convert decimal number to b62 string (part of Exim msgid)
174 my @CHAR = qw(0 1 2 3 4 5 6 7 8 9
175 A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
176 a b c d e f g h i j k l m n o p q r s t u v w x y z);
181 my $d = $n10 % $BASE;
182 $n62 = $CHAR[$d] . $n62;
183 $n10 = int $n10 / $BASE;
190 ## $seconds = seconds($time);
192 ## Converts time with modifiers [smhd] to seconds
197 return $time if $time eq "inf";
201 my $modifier = "[smhd]";
202 while ($time =~ s/^([\d.]+)($modifier)//) {
203 if ($2 eq "s") { $seconds += $1; $modifier = ""; }
204 if ($2 eq "m") { $seconds += $1 * 60; $modifier = "s"; }
205 if ($2 eq "h") { $seconds += $1 * 60 * 60; $modifier = "[sm]"; }
206 if ($2 eq "d") { $seconds += $1 * 60 * 60 * 24; $modifier = "[smh]"; }
209 return undef if $time ne "";
222 $SIG{'__DIE__'} = 'DEFAULT';
235 debug("exec " . join(' ', @cmd));
238 die "Cannot exec: $!";
242 ## eximq_spawn($msgid)
244 ## Spawn neq eximq process
246 sub eximq_spawn(;$$) {
247 my ($msgid_min, $msgid_max) = @_;
250 if (!defined($pid = fork)) {
251 error("Cannot fork: $!");
256 if (defined $msgid_max) {
257 eximq_exec(@eximq_cmd, "$msgid_min-000000-00", "$msgid_max-zzzzzz-zz");
258 } elsif (defined $msgid_min) {
259 eximq_exec(@eximq_cmd, "$msgid_min-000000-00");
261 eximq_exec(@eximq_cmd);
269 ## Harvest spawned eximq processes
273 while ((my $pid = waitpid(-1,WNOHANG)) > 0) {
277 $SIG{CHLD} = \&eximq_reaper;
281 ## eximq($agemin, $agemax, $interval, $processmax);
283 ## Start Exim queue processing
286 my ($agemin, $agemax, $interval, $processmax) = @_;
287 my $time_last = time;
289 $SIG{'__DIE__'} = \&eximq_die;
290 $SIG{$_} = 'IGNORE' foreach (qw(HUP PIPE USR1 USR2));
291 $SIG{$_} = \&sig_handler foreach (qw(INT QUIT TERM));
292 $SIG{'CHLD'} = \&eximq_reaper;
295 if ($time_last + $interval < time && $running < $processmax) {
297 if ($agemax > 0 || $agemax eq "inf") {
298 my $msgid_min = "000000";
300 $msgid_min .= base62($time_last - $agemax);
301 $msgid_min =~ s/^.*(.{6})$/$1/;
303 if ($agemin > 0 || $agemin eq "inf") {
304 my $msgid_max = "000000";
306 $msgid_max .= base62($time_last - $agemin);
307 $msgid_max =~ s/^.*(.{6})$/$1/;
309 eximq_spawn($msgid_min, $msgid_max);
311 eximq_spawn($msgid_min);
329 my $opt = GetOptions(\%opt,
337 pod2usage(2) unless $opt;
339 pod2usage(-verbose=>1, -message=>"$NAME $VERSION\n") if $opt{'help'};
341 pod2usage(2) if $#ARGV < 3;
343 ## set the process name
344 $0 = join(' ', $0, @ARGV);
346 my ($agemin, $agemax, $interval, $processmax);
347 defined ($agemin = seconds(shift @ARGV)) or die "Bad format for agemin argument\n";
348 defined ($agemax = seconds(shift @ARGV)) or die "Bad format for agemax argument\n";
349 ($agemin <= $agemax || $agemax eq "inf") or die "agemin argument cannot be greater than agemax argument\n";
350 defined ($interval = seconds(shift @ARGV)) or die "Bad format for interval argument\n";
351 ($processmax = shift @ARGV) =~ /^\d+$/ or die "Bad format for max argument\n";
357 daemonize() if $opt{'daemon'};
358 eximq($agemin, $agemax, $interval, $processmax);
374 The B<eximq> controlls the count of queue runner processes based on
375 messages age. This allows to keep low system load and maximum number of
376 queue runner processes.
378 The B<eximq> can be started as daemon or foregound process. The example
379 init script is available as separate file and it requires F<eximq.args>
380 file which contains arguments for each B<eximq> instances.
382 The example F<eximq.args> file:
387 5m 15m 1m 5 /usr/sbin/exim -qq
388 15m 2h 2m 5 /usr/sbin/exim -qq
389 2h inf 5m 5 /usr/sbin/exim -qq
391 Also, the B<eximq> can be started from F</etc/inittab> file. I.e.:
394 ex01:23:respawn:+/usr/sbin/eximq 0s 1m 5s 10
395 ex02:23:respawn:+/usr/sbin/eximq 1m 2m 15s 10
396 ex03:23:respawn:+/usr/sbin/eximq 2m 5m 30s 10
397 ex04:23:respawn:+/usr/sbin/eximq 5m 15m 1m 5 /usr/sbin/exim -qq
398 ex05:23:respawn:+/usr/sbin/eximq 15m 2h 2m 5 /usr/sbin/exim -qq
399 ex06:23:respawn:+/usr/sbin/eximq 2h inf 5m 5 /usr/sbin/exim -qq
401 In this example the B<eximq> is started three times with different message
402 ages. The first process spawn max 10 queue runners each 5th second for
403 messages not older than 1 minute. The second process spawn max 10 runners
404 each minute for messages not older than 5 minutes and nor newer than 1 minute.
405 The last queue runners work for messages older than 2 hours and are spawned
408 The queue runner is spawned as B</usr/sbin/exim -q> command as default.
409 Different command can be specified as last argument.
411 For best result you should put
414 queue_smtp_domains = *
416 in your F</etc/exim/exim.conf> configuration file.
424 Minimal age of message. The queue runner will be process messages not newer
425 than I<minage>. B<0s> means no limits.
429 Maximal age of message. The queue runner will be process messages not older
430 than I<maxage>. B<inf> means no limits. I<maxage> can not be lower than
435 Interval time between spawning another queue runner. The new runner will be
436 not spawned if the I<interval> time was not reached.
440 Maximal number of spawned processes. The new runner will be not spawned if
441 whole number of running processes will be greater than I<processmax>.
443 =item I<exim_q_command>
445 The queue runner command. The default is B</usr/sbin/exim -q>.
447 =item B<--debug-stderr>
449 Turn on debug mode on stderr.
451 =item B<--debug-syslog>
453 Turn on debug mode on syslog (MAIL|INFO).
457 Runs the B<eximq> as daemon. In this mode it creates pidfile and
458 detaches from terminal.
460 =item B<--pidfile> I<path>
462 The I<path> for pidfile which is created if B<eximq> is started in
463 daemon mode. The default I<path> is F</var/run/eximq.pid>. The file is
464 removed after daemon dies.
466 =item B<-h>|B<--help>
474 The B<eximq> ignores B<HUP>, B<PIPE>, B<USR1> and B<USR2> and dies for B<INT>,
475 B<QUIT> and B<TERM>. It kills all spawned processes with B<TERM> after die
484 (c) 2003-2004 Piotr Roszatycki E<lt>dexter@debian.orgE<gt>
486 All rights reserved. This program is free software; you can redistribute it
487 and/or modify it under the terms of the GNU General Public License, the