File Coverage

blib/lib/LockFile/NetLock.pm
Criterion Covered Total %
statement 26 107 24.3
branch 1 48 2.0
condition 0 24 0.0
subroutine 9 15 60.0
pod 4 5 80.0
total 40 199 20.1


line stmt bran cond sub pod time code
1             ######################################################################
2             # LockFile::NetLock
3             #
4             # Use ftp and directory creation to create mutual exclusion/locking
5             # available cross platform and on a network. Based on an article
6             # by Sean M. Burke in the Summer 2002 Perl Journal.
7             #
8             # Basic idea is based on mutually exclusive property of creating
9             # directories via FTP. The first process that asks to create the
10             # directory succeeds and later attempts are notified of failure
11             # because directory already exists. FTP session is maintained
12             # by separate program called 'netlock' that automatically removes
13             # the directory if the creating program dies or requests removal.
14             # Communication between this module and netlock program is via an
15             # interprocess pipe. On win32 systems some communication is also
16             # done via a mutex because pipes block too quickly and problems
17             # were encountered when trying other solutions.
18             #
19             # Implemented by Ronald Schmidt.
20             ######################################################################
21              
22             package LockFile::NetLock;
23              
24 1     1   496994 use strict;
  1         4  
  1         34  
25 1     1   5 use warnings;
  1         1  
  1         34  
26             require 5.006; # goes back a ways but does not seem to like 5.005
27 1     1   4 use Exporter;
  1         6  
  1         29  
28 1     1   4 use Config;
  1         2  
  1         29  
29 1     1   4 use Carp;
  1         2  
  1         55  
30 1     1   962 use FileHandle;
  1         3728  
  1         5  
31 1     1   1048 use POSIX qw(sys_wait_h signal_h);
  1         7558  
  1         6  
32              
33             our @ISA = qw/ Exporter /;
34             our @EXPORT_OK = qw(lock unlock);
35              
36             our $VERSION = '0.32';
37              
38             our $errstr;
39              
40             my %named_lock;
41             my $mx_id = 0;
42             my $is_win32;
43              
44             BEGIN {
45 1     1   1471 $is_win32 = ($^O =~ /win32/i);
46 1 50       68 require Win32::Mutex if ($is_win32);
47             }
48              
49             ######################################################################
50             # new - Constructor method.
51             #
52             # Not much going on here - just setup of parameters that will be
53             # used by lock process. Caller is allowed to pass up to four initial
54             # un-named parameters that are interpreted as ftp host, lock directory
55             # ftp user and password respectively.
56             ######################################################################
57             sub new {
58 0     0 1   my $self = shift;
59 0   0       my $class = ref($self) || $self;
60              
61 1         1151 use constant KNOWN_OPT => {
62             map( ($_ => 1) , qw(
63             -dir -disconnect -ftp_heartbeat -heartbeat -host -idle
64             -password -sleep -timeout -user
65             ))
66 1     1   6 };
  1         2  
67              
68 0           my %ivar = (
69             -host => 'localhost',
70             -dir => 'lockdir'
71             );
72              
73             # allow host, directory user and password to be passed unlabeled
74 0           foreach (qw (-host -dir -user -password)) {
75 0 0 0       last if ($_[0] && ($_[0] =~ /^\-/));
76 0           $ivar{$_} = shift;
77             }
78 0           %ivar = (%ivar, @_);
79              
80             # empty parameters may cause trouble with ./netlock program
81 0   0       grep( $_ ne '-disconnect' && (! $ivar{$_}) && delete($ivar{$_}),
82             (keys %ivar));
83              
84 0           foreach my $opt (keys %ivar) {
85 0 0         carp("Unknown option: $opt") unless (KNOWN_OPT->{$opt});
86             }
87            
88 0           return bless \%ivar, $class;
89             }
90              
91             ######################################################################
92             ######################################################################
93             sub set_error {
94 0     0 0   my $self = shift;
95 0           $errstr = $self->{error} = shift;
96             }
97              
98             ######################################################################
99             # lock
100             #
101             # Call netlock program to use FTP to create a directory on a
102             # mutually exclusive basis if one has not been created. Can
103             # be called using an existing LockFile::NetLock object reference
104             # or the parameters needed to create a new object can be passed
105             # to this method.
106             ######################################################################
107             sub lock {
108 0   0 0 1   my $self = ref($_[0]) && shift;
109 0           my $lock_key;
110              
111 0 0         unless ($self) {
112 0           $self = LockFile::NetLock->new(@_);
113 0           $lock_key = "$self->{-host},$self->{-dir}";
114 0 0         if ($named_lock{$lock_key}) {
115 0           $errstr = "Already locking $lock_key";
116 0           return;
117             }
118 0           $named_lock{$lock_key} = $self;
119             }
120              
121 0 0         my ($cmd) = grep { -r && ! -d }
  0            
122             ( './netlock', "$Config{bin}/netlock",
123             "$Config{installsitescript}/netlock"
124             );
125 0           my $cmd_line = $Config{perlpath};
126 0 0         $cmd_line =~ s!/!\\!g if ($is_win32);
127 0           $cmd_line .= " $cmd";
128              
129 0 0         $cmd_line .= ' -d' if $self->{-disconnect};
130              
131             # first character after - in package option is Getopt::Std prog option
132 0           foreach my $opt (grep($_ !~ /^(-d|-host|-reader)/, keys %$self)) {
133 0           $cmd_line .= ' ' . substr($opt, 0, 2) . $self->{$opt};
134             }
135              
136 0 0         if ($is_win32) {
137 0           $self->{mutex_name} = "netlock:$$:" . $mx_id++;
138 0 0         unless ($self->{mutex} =
139             Win32::Mutex->new(1, $self->{mutex_name})) {
140 0           $self->set_error("Could not create mutex: $^E");
141 0           return;
142             }
143 0           $cmd_line .= " -m $self->{mutex_name}";
144             }
145              
146 0           $cmd_line .= " $self->{-host} $self->{-dir}";
147 0           my $fh = new FileHandle "$cmd_line |";
148              
149 0 0         unless ($fh) {
150 0           $self->set_error("Could not start netlock process: $!");
151 0 0         delete $named_lock{$lock_key} if ($lock_key);
152 0           return;
153             }
154              
155 0           $self->{filehandle} = $fh;
156 0           my $from_netlock = <$fh>;
157 0 0         if ($from_netlock !~ /^\.*OK/) {
158 0           $self->set_error("Failed to acquire net lock: $from_netlock");
159 0           return;
160             }
161 0           $self->{is_locked} = 1;
162              
163 0           return $self;
164             }
165              
166              
167             ######################################################################
168             # unlock
169             #
170             # Close the handle to the running netlock process holding the lock
171             # uniquely identified by the host and directory or object containing
172             # those unique identifiers. If we are not currently the locker, or
173             # an error happens on close, or we get a child exist code of -1
174             # indicating a netlock lock removal error we return an error code.
175             ######################################################################
176             sub unlock {
177 0   0 0 1   my $self = ref($_[0]) && shift;
178 0           my $lock_key;
179              
180 0 0         unless ($self) {
181             # bit of a hack - just want a standard way to get host and dir
182 0           $self = LockFile::NetLock->new(@_);
183 0           $lock_key = "$self->{-host},$self->{-dir}";
184 0           $self = $named_lock{$lock_key};
185              
186             # once you try to unlock you can always try to lock again
187 0           delete $named_lock{$lock_key};
188             }
189              
190 0 0 0       unless ($self && $self->{filehandle}) {
191 0           $errstr = "Cannot unlock: not currently locking";
192 0 0         if ($lock_key) {
193 0           $errstr .= " $lock_key";
194             }
195             else {
196 0           $errstr .= ' object';
197             }
198 0 0         $self->{error} = $errstr if ($self);
199 0           return;
200             }
201              
202 0 0         if ($is_win32) {
203 0 0         unless ($self->{mutex}->release) {
204 0           $self->set_error("Could not release mutex: $^E");
205             }
206             }
207              
208 0           my $close_rc;
209 0 0         unless ($close_rc = close($self->{filehandle})) {
210 0           $self->set_error("Failed to close lock process: $!");
211             }
212              
213 0 0         if ($?) {
214 0           my $reaped_rc = $? >> 8;
215 0 0         if ($reaped_rc & 255 == 255) {
216 0           $self->set_error("Failed to remove lock directory.");
217             }
218             else {
219 0           $self->set_error("Unlock failure code: $reaped_rc");
220             }
221             }
222 0 0         return unless($close_rc);
223 0           $self->{is_locked} = 0;
224              
225 0           return 1;
226             }
227              
228             ######################################################################
229             # Return most recent error on object if passed an object reference
230             # otherwise most recent error against module.
231             ######################################################################
232             sub errstr {
233 0     0 1   my $self = shift;
234              
235 0   0       return ($self && ref($self) && $self->{error}) || $errstr;
236             }
237              
238             ######################################################################
239             # Needed for win32. Without this exiting scope could
240             # attempt to close the process handle without releasing the mutex
241             # leading to deadlock.
242             ######################################################################
243             sub DESTROY {
244 0     0     my $self = shift;
245              
246 0 0 0       $self->{mutex}->release if (
247             $is_win32 && $self->{is_locked}
248             );
249             }
250              
251             1;
252             __END__