Skip Menu |

This queue is for tickets about the Sendmail-Queue CPAN distribution.

Report information
The Basics
Id: 78119
Status: resolved
Priority: 0/
Queue: Sendmail-Queue

People
Owner: Nobody in particular
Requestors: dfs+pause [...] roaringpenguin.com
Cc:
AdminCc:

Bug Information
Severity: Critical
Broken in: 0.400
Fixed in: 0.600



Subject: Lock failure
Sendmail::Queue uses flock locking, whereas sendmail (on Debian Linux) uses fcntl. I have observed problems whereby sendmail processes a qf that Sendmail::Queue believes is locked. Sendmail::Queue needs to (somehow?) know or to be told which form of locking sendmail is using so it can use the same type of lock.
I suppose I'm to blame for this one, so here's some ideas on fixing it. The Bat Book says "if HASFLOCK is defined when Sendmail is compiled, the file is locked with flock(2). Otherwise, it's locked with fcntl(2) F_SETLK". So... to find out what Sendmail was built with, you can run sendmail -d0.10 </dev/null and look for HASFLOCK. On my Debian Squeeze system, HASFLOCK isn't there. calling sendmail to check HASFLOCK every time isn't a workable solution, but I just checked qtool.pl on my system and its lock_file sub will do both flock and fcntl locking just in case -- maybe that's the way to go?
Subject: Re: [rt.cpan.org #78119] Lock failure
Date: Thu, 5 Jul 2012 02:43:41 -0400
To: bug-Sendmail-Queue [...] rt.cpan.org
From: "David F. Skoll" <dfs [...] roaringpenguin.com>
Hey, Dave, Show quoted text
> calling sendmail to check HASFLOCK every time isn't a workable > solution, but I just checked qtool.pl on my system and its lock_file > sub will do both flock and fcntl locking just in case -- maybe that's > the way to go?
I think so. I only care about Linux, so if it lets you hold both a flock-style and fcntl-style lock, I'd go with that. It might fail on some systems that emulate flock with fcntl, but we can worry about that later. Regards, David.
Hi, Dave, Attached is a proposed (slightly hacky) patch. The Sendmail::Queue constructor tests whether or not both fcntl and flock-style locking work at the same time. If they do, then we use them both whenever we lock a Qf. It would be better to have a feature test that tests this once instead of at run-time, but this is better than nothing. Regards, David.
Subject: sendmail-queue.diff
diff -u -r Sendmail-Queue-0.400/MANIFEST Sendmail-Queue-0.600/MANIFEST --- Sendmail-Queue-0.400/MANIFEST 2010-06-28 14:21:41.000000000 -0400 +++ Sendmail-Queue-0.600/MANIFEST 2012-07-17 08:55:14.000000000 -0400 @@ -17,6 +17,7 @@ META.yml # Will be created by "make dist" README t/00-load.t +t/lock.t t/pod-coverage.t t/pod.t t/sendmail-queue-df.t diff -u -r Sendmail-Queue-0.400/lib/Sendmail/Queue/Qf.pm Sendmail-Queue-0.600/lib/Sendmail/Queue/Qf.pm --- Sendmail-Queue-0.400/lib/Sendmail/Queue/Qf.pm 2010-07-20 10:39:49.000000000 -0400 +++ Sendmail-Queue-0.600/lib/Sendmail/Queue/Qf.pm 2012-07-17 08:27:55.000000000 -0400 @@ -12,6 +12,12 @@ use Mail::Header::Generator (); use Storable (); +my $fcntl_struct = 's H60'; +my $fcntl_structlockp = pack($fcntl_struct, Fcntl::F_WRLCK, + "000000000000000000000000000000000000000000000000000000000000"); +my $fcntl_structunlockp = pack($fcntl_struct, Fcntl::F_UNLCK, + "000000000000000000000000000000000000000000000000000000000000"); + ## no critic 'ProhibitMagicNumbers' # TODO: should we fail if total size of headers > 32768 bytes, or let sendmail die? @@ -143,10 +149,11 @@ } } -=head2 create_and_lock ( ) +=head2 create_and_lock ( [$lock_both] ) Generate a Sendmail 8.12-compatible queue ID, and create a locked qf -file with that name. +file with that name. If $lock_both is true, we lock the file using +both fcntl and flock-style locking. See Bat Book 3rd edition, section 11.2.1 for information on how the queue file name is generated. @@ -164,7 +171,7 @@ sub create_and_lock { - my ($self) = @_; + my ($self, $lock_both) = @_; if( ! -d $self->get_queue_directory ) { die q{Cannot create queue file without queue directory!}; @@ -194,6 +201,13 @@ close($fh); unlink($path); $seq = ($seq + 1) % 3600; + next; + } + if ($lock_both && !fcntl($fh, Fcntl::F_SETLK, $fcntl_structlockp)) { + # See above... couldn't lock with fcntl + close($fh); + unlink($path); + $seq = ($seq + 1) % 3600; next; } $self->set_queue_id( $qid ); diff -u -r Sendmail-Queue-0.400/lib/Sendmail/Queue.pm Sendmail-Queue-0.600/lib/Sendmail/Queue.pm --- Sendmail-Queue-0.400/lib/Sendmail/Queue.pm 2010-12-30 14:39:15.000000000 -0500 +++ Sendmail-Queue-0.600/lib/Sendmail/Queue.pm 2012-07-17 08:30:41.000000000 -0400 @@ -4,13 +4,19 @@ use Carp; use 5.8.0; -our $VERSION = '0.400'; +our $VERSION = '0.600'; use Sendmail::Queue::Qf; use Sendmail::Queue::Df; use File::Spec; use IO::Handle; -use Fcntl; +use Fcntl qw( :flock ); +use File::Temp qw(tempfile); +my $fcntl_struct = 's H60'; +my $fcntl_structlockp = pack($fcntl_struct, Fcntl::F_WRLCK, + "000000000000000000000000000000000000000000000000000000000000"); +my $fcntl_structunlockp = pack($fcntl_struct, Fcntl::F_UNLCK, + "000000000000000000000000000000000000000000000000000000000000"); use Sendmail::Queue::Base; our @ISA = qw( Sendmail::Queue::Base ); @@ -130,6 +136,8 @@ my $self = { queue_directory => $args->{queue_directory}, }; + $self->{lock_both} = 0; + bless $self, $class; if(!-d $self->{queue_directory}) { @@ -148,6 +156,17 @@ $self->set_df_directory(File::Spec->catfile($self->{queue_directory})); } + # Check if both fcntl-style and flock-style locking is available + my ($fh, $filename) = tempfile(); + if ($fh) { + my $flock_status = flock($fh, LOCK_EX | LOCK_NB); + my $fcntl_status = fcntl($fh, Fcntl::F_SETLK, $fcntl_structlockp); + if ($flock_status && $fcntl_status) { + $self->{lock_both} = 1; + } + $fh->close(); + } + unlink($filename) if $filename; return $self; } @@ -413,7 +432,7 @@ $cur_qf->set_sender( $sender ); $cur_qf->add_recipient( @{ $env_data->{recipients} } ); - $cur_qf->create_and_lock(); + $cur_qf->create_and_lock($self->{lock_both}); # As soon as it's created, put it on the list so it can # be cleaned up later if necessary. @@ -435,7 +454,21 @@ $first_df->write(); } else { # Otherwise, link to the first df - $cur_df->hardlink_to( $first_df->get_data_filename() ); + eval { $cur_df->hardlink_to( $first_df->get_data_filename() ); }; + if ($@) { + if ($@ =~ /Path .* does not exist/) { + # This should NEVER happen... + # but it was observed to happen! + # Sorry to spew to STDERR, but there's no + # feasible way to log this + print STDERR 'Sendmail::Queue warning: ' . $first_df->get_data_filename() . ' has disappeared! Writing new file as ' . $cur_df->get_data_filename() . "\n"; + $first_df = $cur_df; + $first_df->set_data($data); + $first_df->write(); + } else { + die($@); + } + } } $results{ $env_name } = $cur_qf->get_queue_id; @@ -481,7 +514,7 @@ # TODO: this needs testing on solaris and bsd my $directory = $self->get_df_directory(); - sysopen(DIR_FH, $directory, O_RDONLY) or die qq{Couldn't sysopen $directory: $!}; + sysopen(DIR_FH, $directory, Fcntl::O_RDONLY) or die qq{Couldn't sysopen $directory: $!}; my $handle = IO::Handle->new(); $handle->fdopen(fileno(DIR_FH), 'w') or die qq{Couldn't fdopen the directory handle: $!}; diff -N -u -r Sendmail-Queue-0.400/t/lock.t Sendmail-Queue-0.600/t/lock.t --- Sendmail-Queue-0.400/t/lock.t 1969-12-31 19:00:00.000000000 -0500 +++ Sendmail-Queue-0.600/t/lock.t 2012-07-17 08:55:01.000000000 -0400 @@ -0,0 +1,55 @@ +package test_lock; +use strict; +use warnings; + +use base qw(Test::Class); + +use Test::Most; +use File::Temp; + + +use Sendmail::Queue; + +my $USER = getpwuid($>); + +sub make_tmpdir : Test(setup) +{ + my ($self) = @_; + $self->{tmpdir} = File::Temp::tempdir( CLEANUP => 1 ); +} + +sub del_tmpdir : Test(teardown) +{ + my ($self) = @_; + + delete $self->{tmpdir} +} + +sub test_locking : Test(1) +{ + my ($self) = @_; + my $q = Sendmail::Queue->new({queue_directory => $self->{tmpdir}}); + my $qf = Sendmail::Queue::Qf->new({ + queue_directory => $self->{tmpdir} + }); + $qf->set_headers("From: foo\nTo: bar\nSubject: Blech\n"); + $qf->set_sender('devnull@roaringpenguin.com'); + $qf->add_recipient('devnull@roaringpenguin.com'); + $qf->create_and_lock($q->{lock_both}); + $qf->write(); + $qf->sync(); + my $df = Sendmail::Queue::Df->new({ + queue_directory => $self->{tmpdir}, + queue_id => $qf->get_queue_id()}); + $df->set_data("Wookie!\n"); + $df->write(); + + # Now try running sendmail + $ENV{'PATH'} .= ":/sbin:/usr/sbin"; + my $qid = $qf->get_queue_id(); + my $tmp = $self->{tmpdir}; + my $output = `sendmail -v -qI$qid -OQueueDirectory=$tmp 2>&1`; + like($output, qr/$qid: locked/, 'Queue file was successfully locked according to sendmail'); +} + +__PACKAGE__->runtests unless caller();
Fixed in 0.600. I forgot to update Changelog, though. :( Also only tested on Linux.