re-mqueue.pl revision 182352
1#!/usr/bin/perl
2#
3# re-mqueue -- requeue messages from queueA to queueB based on age.
4#
5#	Contributed by Paul Pomes <ppomes@Qualcomm.COM>.
6#		http://www.qualcomm.com/~ppomes/
7#
8# Usage: re-mqueue [-d] queueA queueB seconds
9#
10#  -d		enable debugging
11#  queueA	source directory
12#  queueB	destination directory
13#  seconds	select files older than this number of seconds
14#
15# Example: re-mqueue /var/spool/mqueue /var/spool/mqueue2 2700
16#
17# Moves the qf* and df* files for a message from /var/spool/mqueue to
18# /var/spool/mqueue2 if the df* file is over 2700 seconds old.
19#
20# The qf* file can't be used for age checking as it's partially re-written
21# with the results of the last queue run.
22#
23# Rationale: With a limited number of sendmail processes allowed to run,
24# messages that can't be delivered immediately slow down the ones that can.
25# This becomes especially important when messages are being queued instead
26# of delivered right away, or when the queue becomes excessively deep.
27# By putting messages that have already failed one or more delivery attempts
28# into another queue, the primary queue can be kept small and fast.
29#
30# On postoffice.cso.uiuc.edu, the primary sendmail daemon runs the queue
31# every thirty minutes.  Messages over 45 minutues old are moved to
32# /var/spool/mqueue2 where sendmail runs every hour.  Messages more than
33# 3.25 hours old are moved to /var/spool/mqueue3 where sendmail runs every
34# four hours.  Messages more than a day old are moved to /var/spool/mqueue4
35# where sendmail runs three times a day.  The idea is that a message is
36# tried at least twice in the first three queues before being moved to the
37# old-age ghetto.
38#
39# (Each must be re-formed into a single line before using in crontab)
40#
41# 08 * * * *	/usr/local/libexec/re-mqueue /var/spool/mqueue ##						/var/spool/mqueue2 2700
42# 11 * * * *	/usr/lib/sendmail -oQ/var/spool/mqueue2 -q > ##							> /var/log/mqueue2 2>&1
43# 38 * * * *	/usr/local/libexec/re-mqueue /var/spool/mqueue2
44#					/var/spool/mqueue3 11700
45# 41 1,5,9,13,17,21 * * * /usr/lib/sendmail -oQ/var/spool/mqueue3 -q ##							> /var/log/mqueue3 2>&1
46# 48 * * * *	/usr/local/libexec/re-mqueue /var/spool/mqueue3
47#					/var/spool/mqueue4 100000
48#53 3,11,19 * * * /usr/lib/sendmail -oQ/var/spool/mqueue4 -q > ##							> /var/log/mqueue4 2>&1
49#
50#
51# N.B., the moves are done with link().  This has two effects: 1) the mqueue*
52# directories must all be on the same filesystem, and 2) the file modification
53# times are not changed.  All times must be cumulative from when the df*
54# file was created.
55#
56# Copyright (c) 1995 University of Illinois Board of Trustees and Paul Pomes
57# All rights reserved.
58#
59# Redistribution and use in source and binary forms, with or without
60# modification, are permitted provided that the following conditions
61# are met:
62# 1. Redistributions of source code must retain the above copyright
63#    notice, this list of conditions and the following disclaimer.
64# 2. Redistributions in binary form must reproduce the above copyright
65#    notice, this list of conditions and the following disclaimer in the
66#    documentation and/or other materials provided with the distribution.
67# 3. All advertising materials mentioning features or use of this software
68#    must display the following acknowledgement:
69#       This product includes software developed by the University of
70#       Illinois at Urbana and their contributors.
71# 4. Neither the name of the University nor the names of their contributors
72#    may be used to endorse or promote products derived from this software
73#    without specific prior written permission.
74#
75# THIS SOFTWARE IS PROVIDED BY THE TRUSTEES AND CONTRIBUTORS ``AS IS'' AND
76# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
77# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
78# ARE DISCLAIMED.  IN NO EVENT SHALL THE TRUSTEES OR CONTRIBUTORS BE LIABLE
79# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
80# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
81# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
82# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
83# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
84# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
85# SUCH DAMAGE.
86#
87# @(#)$OrigId: re-mqueue,v 1.3 1995/05/25 18:14:53 p-pomes Exp $
88#
89# Updated by Graeme Hewson <ghewson@uk.oracle.com> May 1999
90#
91#	'use Sys::Syslog' for Perl 5
92#	Move transcript (xf) files if they exist
93#	Allow zero-length df files (empty message body)
94#	Preserve $! for error messages
95#
96# Updated by Graeme Hewson <ghewson@uk.oracle.com> April 2000
97#
98#	Improve handling of race between re-mqueue and sendmail
99#
100# Updated by Graeme Hewson <graeme.hewson@oracle.com> June 2000
101#
102#	Don't exit(0) at end so can be called as subroutine
103#
104# NB This program can't handle separate qf/df/xf subdirectories
105# as introduced in sendmail 8.10.0.
106#
107
108use Sys::Syslog;
109
110$LOCK_EX = 2;
111$LOCK_NB = 4;
112$LOCK_UN = 8;
113
114# Count arguments, exit if wrong in any way.
115die "Usage: $0 [-d] queueA queueB seconds\n" if ($#ARGV < 2);
116
117while ($_ = $ARGV[0], /^-/) {
118    shift;
119    last if /^--$/;
120    /^-d/ && $debug++;
121}
122
123$queueA = shift;
124$queueB = shift;
125$age = shift;
126
127die "$0: $queueA not a directory\n" if (! -d $queueA);
128die "$0: $queueB not a directory\n" if (! -d $queueB);
129die "$0: $age isn't a valid number of seconds for age\n" if ($age =~ /\D/);
130
131# chdir to $queueA and read the directory.  When a df* file is found, stat it.
132# If it's older than $age, lock the corresponding qf* file.  If the lock
133# fails, give up and move on.  Once the lock is obtained, verify that files
134# of the same name *don't* already exist in $queueB and move on if they do.
135# Otherwise re-link the qf* and df* files into $queueB then release the lock.
136
137chdir "$queueA" || die "$0: can't cd to $queueA: $!\n";
138opendir (QA, ".") || die "$0: can't open directory $queueA for reading: $!\n";
139@dfiles = grep(/^df/, readdir(QA));
140$now = time();
141($program = $0) =~ s,.*/,,;
142&openlog($program, 'pid', 'mail');
143
144# Loop through the dfiles
145while ($dfile = pop(@dfiles)) {
146    print "Checking $dfile\n" if ($debug);
147    ($qfile = $dfile) =~ s/^d/q/;
148    ($xfile = $dfile) =~ s/^d/x/;
149    ($mfile = $dfile) =~ s/^df//;
150    if (! -e $qfile || -z $qfile) {
151	print "$qfile is gone or zero bytes - skipping\n" if ($debug);
152	next;
153    }
154
155    ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,
156     $atime,$mtime,$ctime,$blksize,$blocks) = stat($dfile);
157    if (! defined $mtime) {
158	print "$dfile is gone - skipping\n" if ($debug);
159	next;
160    }
161
162    # Compare timestamps
163    if (($mtime + $age) > $now) {
164	printf ("%s is %d seconds old - skipping\n", $dfile, $now-$mtime) if ($debug);
165	next;
166    }
167
168    # See if files of the same name already exist in $queueB
169    if (-e "$queueB/$dfile") {
170	print "$queueb/$dfile already exists - skipping\n" if ($debug);
171	next;
172    }
173    if (-e "$queueB/$qfile") {
174	print "$queueb/$qfile already exists - skipping\n" if ($debug);
175	next;
176    }
177    if (-e "$queueB/$xfile") {
178	print "$queueb/$xfile already exists - skipping\n" if ($debug);
179	next;
180    }
181
182    # Try and lock qf* file
183    unless (open(QF, ">>$qfile")) {
184	print "$qfile: $!\n" if ($debug);
185	next;
186    }
187    $retval = flock(QF, $LOCK_EX|$LOCK_NB) || ($retval = -1);
188    if ($retval == -1) {
189	print "$qfile already flock()ed - skipping\n" if ($debug);
190	close(QF);
191	next;
192    }
193    print "$qfile now flock()ed\n" if ($debug);
194
195    # Check df* file again in case sendmail got in
196    if (! -e $dfile) {
197	print "$mfile sent - skipping\n" if ($debug);
198	# qf* file created by ourselves at open? (Almost certainly)
199	if (-z $qfile) {
200	   unlink($qfile);
201	}
202	close(QF);
203	next;
204    }
205
206    # Show time!  Do the link()s
207    if (link("$dfile", "$queueB/$dfile") == 0) {
208	$bang = $!;
209	&syslog('err', 'link(%s, %s/%s): %s', $dfile, $queueB, $dfile, $bang);
210	print STDERR "$0: link($dfile, $queueB/$dfile): $bang\n";
211	exit (1);
212    }
213    if (link("$qfile", "$queueB/$qfile") == 0) {
214	$bang = $!;
215	&syslog('err', 'link(%s, %s/%s): %s', $qfile, $queueB, $qfile, $bang);
216	print STDERR "$0: link($qfile, $queueB/$qfile): $bang\n";
217	unlink("$queueB/$dfile");
218	exit (1);
219    }
220    if (-e "$xfile") {
221	if (link("$xfile", "$queueB/$xfile") == 0) {
222	    $bang = $!;
223	    &syslog('err', 'link(%s, %s/%s): %s', $xfile, $queueB, $xfile, $bang);
224	    print STDERR "$0: link($xfile, $queueB/$xfile): $bang\n";
225	    unlink("$queueB/$dfile");
226	    unlink("$queueB/$qfile");
227	    exit (1);
228	}
229    }
230
231    # Links created successfully.  Unlink the original files, release the
232    # lock, and close the file.
233    print "links ok\n" if ($debug);
234    if (unlink($qfile) == 0) {
235	$bang = $!;
236	&syslog('err', 'unlink(%s): %s', $qfile, $bang);
237	print STDERR "$0: unlink($qfile): $bang\n";
238	exit (1);
239    }
240    if (unlink($dfile) == 0) {
241	$bang = $!;
242	&syslog('err', 'unlink(%s): %s', $dfile, $bang);
243	print STDERR "$0: unlink($dfile): $bang\n";
244	exit (1);
245    }
246    if (-e "$xfile") {
247	if (unlink($xfile) == 0) {
248	    $bang = $!;
249	    &syslog('err', 'unlink(%s): %s', $xfile, $bang);
250	    print STDERR "$0: unlink($xfile): $bang\n";
251	    exit (1);
252	}
253    }
254    flock(QF, $LOCK_UN);
255    close(QF);
256    &syslog('info', '%s moved to %s', $mfile, $queueB);
257    print "Done with $dfile $qfile\n\n" if ($debug);
258}
259