]> git.pld-linux.org Git - packages/eximq.git/blob - eximq.pl
- drop obsolete and outdated manual inclusion of rpm macros
[packages/eximq.git] / eximq.pl
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
11 eximq - Supervising process for Exim's queue runners.
12
13 =head1 SYNOPSIS
14
15 B<eximq> B<-h>|B<--help>
16
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>]
20
21 =cut
22
23 use 5.006;
24 use strict;
25
26 use Getopt::Long qw(:config require_order no_auto_abbrev);
27 use POSIX qw(:sys_wait_h :locale_h setsid);
28 use Pod::Usage;
29 use Unix::Syslog qw(:macros :subs);
30
31
32 ##############################################################################
33
34 ## Constant variables
35 ##
36
37 ## Program name
38 my $NAME = "eximq";
39
40 ## Program version
41 my $VERSION = 0.4;
42
43 ## Global variables
44 ##
45
46 ## Spawned command
47 my @eximq_cmd = qw(/usr/sbin/exim -q);
48
49
50 ##############################################################################
51
52 ## Private variables
53 ##
54
55 ## Count for running spawned processes
56 my $running = 0;
57
58 ## Process group ID
59 my $pgrp = 0;
60
61 ## Getopt::Long handler
62 my %opt = (
63     'pidfile' => "/var/run/$NAME.pid",
64 );
65
66 ## Old value for LC_TIME
67 my $old_lc_time = setlocale(LC_TIME);
68
69
70 ##############################################################################
71
72 ## debug($msg)
73 ##
74 ## Dumps message if debug mode is turned on.
75 ##
76 sub 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 ##
96 sub 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 ##
112 sub 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 ##
130 sub 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 ##
151 sub 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)
159 sub 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 ##
170 sub 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 ##
194 sub 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 ##
219 sub 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 ##
232 sub 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 ##
246 sub 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 ##
271 sub 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 ##
285 sub 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 ##
326 sub 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
362 END: {
363     cleanup();
364 }
365
366
367 main();
368
369
370 __END__
371
372 =head1 DESCRIPTION
373
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.
377
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.
381
382 The 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
391 Also, 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
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
406 each 5th minute.
407
408 The queue runner is spawned as B</usr/sbin/exim -q> command as default.
409 Different command can be specified as last argument.
410
411 For best result you should put
412
413  queue_only = yes
414  queue_smtp_domains = *
415
416 in your F</etc/exim/exim.conf> configuration file.
417
418 =head1 OPTIONS
419
420 =over 8
421
422 =item I<minage>
423
424 Minimal age of message.  The queue runner will be process messages not newer
425 than I<minage>.  B<0s> means no limits.
426
427 =item I<maxage>
428
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
431 I<minage>.
432
433 =item I<interval>
434
435 Interval time between spawning another queue runner.  The new runner will be
436 not spawned if the I<interval> time was not reached.
437
438 =item I<processmax>
439
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>.
442
443 =item I<exim_q_command>
444
445 The queue runner command.  The default is B</usr/sbin/exim -q>.
446
447 =item B<--debug-stderr>
448
449 Turn on debug mode on stderr.
450
451 =item B<--debug-syslog>
452
453 Turn on debug mode on syslog (MAIL|INFO).
454
455 =item B<--daemon>
456
457 Runs the B<eximq> as daemon. In this mode it creates pidfile and
458 detaches from terminal.
459
460 =item B<--pidfile> I<path>
461
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.
465
466 =item B<-h>|B<--help>
467
468 This help.
469
470 =back
471
472 =head1 SIGNALS
473
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
476 signal.
477
478 =head1 SEE ALSO
479
480 B<exim>(8)
481
482 =head1 AUTHOR
483
484 (c) 2003-2004 Piotr Roszatycki E<lt>dexter@debian.orgE<gt>
485
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
488 latest version.
This page took 0.096802 seconds and 3 git commands to generate.