]> git.pld-linux.org Git - packages/eximq.git/blame - eximq.pl
- drop obsolete and outdated manual inclusion of rpm macros
[packages/eximq.git] / eximq.pl
CommitLineData
cae70075
AM
1#!/usr/bin/perl -w
2
3## eximq
4##
5## (c) 2003-2004 Piotr Roszatycki <dexter@debian.org>, GPL
6##
7## $Id$
8
9=head1 NAME
10
11eximq - Supervising process for Exim's queue runners.
12
13=head1 SYNOPSIS
14
15B<eximq> B<-h>|B<--help>
16
17B<eximq> [B<--debug-stderr>] [B<--debug-syslog>]
18[B<--daemon>] S<[B<--pidfile> I<path>]>
19I<agemin> I<agemax> I<interval> I<processmax> [I<exim_q_command>]
20
21=cut
22
23use 5.006;
24use strict;
25
26use Getopt::Long qw(:config require_order no_auto_abbrev);
27use POSIX qw(:sys_wait_h :locale_h setsid);
28use Pod::Usage;
29use Unix::Syslog qw(:macros :subs);
30
31
32##############################################################################
33
34## Constant variables
35##
36
37## Program name
38my $NAME = "eximq";
39
40## Program version
41my $VERSION = 0.4;
42
43## Global variables
44##
45
46## Spawned command
47my @eximq_cmd = qw(/usr/sbin/exim -q);
48
49
50##############################################################################
51
52## Private variables
53##
54
55## Count for running spawned processes
56my $running = 0;
57
58## Process group ID
59my $pgrp = 0;
60
61## Getopt::Long handler
62my %opt = (
63 'pidfile' => "/var/run/$NAME.pid",
64);
65
66## Old value for LC_TIME
67my $old_lc_time = setlocale(LC_TIME);
68
69
70##############################################################################
71
72## debug($msg)
73##
74## Dumps message if debug mode is turned on.
75##
76sub debug(@) {
77 my (@msg) = @_;
78
79 if (${opt{'debug-syslog'}}) {
80 setlocale(LC_TIME, "C");
81 openlog($NAME, LOG_PID, LOG_MAIL);
82 syslog(LOG_INFO, "%s", join('', @msg));
83 closelog;
84 setlocale(LC_TIME, $old_lc_time);
85 }
86 if (${opt{'debug-stderr'}}) {
87 print STDERR "*** @msg\n";
88 }
89}
90
91
92## error($msg)
93##
94## Dumps error message
95##
96sub error(@) {
97 my (@msg) = @_;
98
99 setlocale(LC_TIME, "C");
100 openlog($NAME, LOG_PID, LOG_MAIL);
101 syslog(LOG_ERR, "%s", join('', @_));
102 closelog;
103 setlocale(LC_TIME, $old_lc_time);
104 print STDERR "*** @_\n";
105}
106
107
108## sig_handler($)
109##
110## Handler for process signal
111##
112sub sig_handler($) {
113 my($sig) = @_;
114 cleanup();
115 return unless defined $sig;
116 debug("Got a SIG$sig");
117 if ($pgrp) {
118 debug("Killing spawned processes for session $pgrp");
119 $SIG{'TERM'} = 'IGNORE';
120 kill 'TERM', -$pgrp;
121 }
122 die "Die for SIG$sig\n";
123}
124
125
126## daemonize()
127##
128## Daemonize main process
129##
130sub daemonize() {
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: $!";
136 if ($pid) {
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}: $!";
139 close PIDFILE;
140 exit;
141 }
142 $pgrp = setsid or die "Can't start a new session: $!";
143 open STDERR, '>&STDOUT' or die "Can't dup stdout: $!";
144}
145
146
147## cleanup()
148##
149## Clean up before die
150##
151sub cleanup() {
152 unlink $opt{'pidfile'} if $opt{'daemon'} and -f $opt{'pidfile'};
153}
154
155
156## usleep($sec);
157##
158## Sleep $usec seconds (can be factorized)
159sub usleep($) {
160 my ($sec) = @_;
161
162 select(undef, undef, undef, $sec);
163}
164
165
166## $n62 = base62($n10);
167##
168## Convert decimal number to b62 string (part of Exim msgid)
169##
170sub base62($) {
171 my ($n10) = @_;
172
173 my $BASE = 62;
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);
177
178 my $n62 = "";
179
180 while ($n10 > 0) {
181 my $d = $n10 % $BASE;
182 $n62 = $CHAR[$d] . $n62;
183 $n10 = int $n10 / $BASE;
184 }
185
186 return $n62;
187}
188
189
190## $seconds = seconds($time);
191##
192## Converts time with modifiers [smhd] to seconds
193##
194sub seconds($) {
195 my ($time) = @_;
196
197 return $time if $time eq "inf";
198
199 my $seconds = 0;
200
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]"; }
207 }
208
209 return undef if $time ne "";
210
211 return $seconds;
212}
213
214
215## eximq_die(@msg)
216##
217## Die with message
218##
219sub eximq_die(@) {
220 my (@msg) = @_;
221
222 $SIG{'__DIE__'} = 'DEFAULT';
223 debug(@msg);
224 die(@msg, "\n");
225}
226
227
228## eximq_exec(@cmd)
229##
230## Execute command
231##
232sub eximq_exec(@) {
233 my (@cmd) = @_;
234
235 debug("exec " . join(' ', @cmd));
236 exec(@cmd);
237
238 die "Cannot exec: $!";
239}
240
241
242## eximq_spawn($msgid)
243##
244## Spawn neq eximq process
245##
246sub eximq_spawn(;$$) {
247 my ($msgid_min, $msgid_max) = @_;
248
249 my $pid;
250 if (!defined($pid = fork)) {
251 error("Cannot fork: $!");
252 } elsif ($pid) {
253 debug("spawn $pid");
254 $running++;
255 } else {
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");
260 } else {
261 eximq_exec(@eximq_cmd);
262 }
263 }
264}
265
266
267## eximq_reaper()
268##
269## Harvest spawned eximq processes
270##
271sub eximq_reaper() {
272
273 while ((my $pid = waitpid(-1,WNOHANG)) > 0) {
274 debug("reap $pid");
275 $running--;
276 }
277 $SIG{CHLD} = \&eximq_reaper;
278}
279
280
281## eximq($agemin, $agemax, $interval, $processmax);
282##
283## Start Exim queue processing
284##
285sub eximq($$$$) {
286 my ($agemin, $agemax, $interval, $processmax) = @_;
287 my $time_last = time;
288
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;
293
294 for (;;) {
295 if ($time_last + $interval < time && $running < $processmax) {
296 $time_last = time;
297 if ($agemax > 0 || $agemax eq "inf") {
298 my $msgid_min = "000000";
299 if ($agemax > 0) {
300 $msgid_min .= base62($time_last - $agemax);
301 $msgid_min =~ s/^.*(.{6})$/$1/;
302 }
303 if ($agemin > 0 || $agemin eq "inf") {
304 my $msgid_max = "000000";
305 if ($agemin > 0) {
306 $msgid_max .= base62($time_last - $agemin);
307 $msgid_max =~ s/^.*(.{6})$/$1/;
308 }
309 eximq_spawn($msgid_min, $msgid_max);
310 } else {
311 eximq_spawn($msgid_min);
312 }
313 } else {
314 eximq_spawn();
315 }
316 }
317 sleep(1);
318 }
319}
320
321
322## main()
323##
324## Main subroutine
325##
326sub main() {
327
328 ## get options
329 my $opt = GetOptions(\%opt,
330 'help|h|?',
331 'debug-stderr',
332 'debug-syslog',
333 'daemon|d',
334 'pidfile|p=s',
335 );
336
337 pod2usage(2) unless $opt;
338
339 pod2usage(-verbose=>1, -message=>"$NAME $VERSION\n") if $opt{'help'};
340
341 pod2usage(2) if $#ARGV < 3;
342
343 ## set the process name
344 $0 = join(' ', $0, @ARGV);
345
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";
352
353 if (@ARGV) {
354 @eximq_cmd = @ARGV;
355 }
356
357 daemonize() if $opt{'daemon'};
358 eximq($agemin, $agemax, $interval, $processmax);
359}
360
361
362END: {
363 cleanup();
364}
365
366
367main();
368
369
370__END__
371
372=head1 DESCRIPTION
373
374The B<eximq> controlls the count of queue runner processes based on
375messages age. This allows to keep low system load and maximum number of
376queue runner processes.
377
378The B<eximq> can be started as daemon or foregound process. The example
379init script is available as separate file and it requires F<eximq.args>
380file which contains arguments for each B<eximq> instances.
381
382The example F<eximq.args> file:
383
384 0s 1m 5s 10
385 1m 2m 15s 10
386 2m 5m 30s 10
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
390
391Also, the B<eximq> can be started from F</etc/inittab> file. I.e.:
392
393 # Exim queue
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
400
401In this example the B<eximq> is started three times with different message
402ages. The first process spawn max 10 queue runners each 5th second for
403messages not older than 1 minute. The second process spawn max 10 runners
404each minute for messages not older than 5 minutes and nor newer than 1 minute.
405The last queue runners work for messages older than 2 hours and are spawned
406each 5th minute.
407
408The queue runner is spawned as B</usr/sbin/exim -q> command as default.
409Different command can be specified as last argument.
410
411For best result you should put
412
413 queue_only = yes
414 queue_smtp_domains = *
415
416in your F</etc/exim/exim.conf> configuration file.
417
418=head1 OPTIONS
419
420=over 8
421
422=item I<minage>
423
424Minimal age of message. The queue runner will be process messages not newer
425than I<minage>. B<0s> means no limits.
426
427=item I<maxage>
428
429Maximal age of message. The queue runner will be process messages not older
430than I<maxage>. B<inf> means no limits. I<maxage> can not be lower than
431I<minage>.
432
433=item I<interval>
434
435Interval time between spawning another queue runner. The new runner will be
436not spawned if the I<interval> time was not reached.
437
438=item I<processmax>
439
440Maximal number of spawned processes. The new runner will be not spawned if
441whole number of running processes will be greater than I<processmax>.
442
443=item I<exim_q_command>
444
445The queue runner command. The default is B</usr/sbin/exim -q>.
446
447=item B<--debug-stderr>
448
449Turn on debug mode on stderr.
450
451=item B<--debug-syslog>
452
453Turn on debug mode on syslog (MAIL|INFO).
454
455=item B<--daemon>
456
457Runs the B<eximq> as daemon. In this mode it creates pidfile and
458detaches from terminal.
459
460=item B<--pidfile> I<path>
461
462The I<path> for pidfile which is created if B<eximq> is started in
463daemon mode. The default I<path> is F</var/run/eximq.pid>. The file is
464removed after daemon dies.
465
466=item B<-h>|B<--help>
467
468This help.
469
470=back
471
472=head1 SIGNALS
473
474The B<eximq> ignores B<HUP>, B<PIPE>, B<USR1> and B<USR2> and dies for B<INT>,
475B<QUIT> and B<TERM>. It kills all spawned processes with B<TERM> after die
476signal.
477
478=head1 SEE ALSO
479
480B<exim>(8)
481
482=head1 AUTHOR
483
484(c) 2003-2004 Piotr Roszatycki E<lt>dexter@debian.orgE<gt>
485
486All rights reserved. This program is free software; you can redistribute it
487and/or modify it under the terms of the GNU General Public License, the
488latest version.
This page took 0.088772 seconds and 4 git commands to generate.