File Coverage

blib/lib/Tie/DB_Lock.pm
Criterion Covered Total %
statement 10 12 83.3
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 14 16 87.5


line stmt bran cond sub pod time code
1             package Tie::DB_Lock;
2              
3 1     1   599 use strict;
  1         2  
  1         27  
4 1     1   5 use Carp;
  1         1  
  1         113  
5 1     1   4 use Fcntl(':flock');
  1         4  
  1         84  
6 1     1   1235 use DB_File;
  0            
  0            
7             use FileHandle;
8              
9             use vars qw($VERSION $WAIT_TIME $RETRIES $TEMPDIR $VERBOSE $TIEPACK);
10             $VERSION = '0.07';
11              
12             $TEMPDIR = '/tmp' unless defined $TEMPDIR;
13             $TIEPACK = 'DB_File' unless defined $TIEPACK;
14             $WAIT_TIME = 1 unless defined $WAIT_TIME;
15             $RETRIES = 15 unless defined $RETRIES;
16             $VERBOSE = 0 unless defined $VERBOSE; # Print out diagnostics on STDERR?
17              
18             # For internal use only:
19             my $READ_WRITE = O_CREAT|O_RDWR;
20             my $READ_ONLY = O_RDONLY;
21              
22             # Values acceptable to lock_file:
23             my %LOCKS = (
24             "ex" => LOCK_EX|LOCK_NB, # Exclusive
25             "sh" => LOCK_SH|LOCK_NB, # Shared
26             "un" => LOCK_UN|LOCK_NB, # Unlock
27             );
28              
29             sub jabber ($) { carp("$$: $_[0]") if $VERBOSE }
30              
31             sub TIEHASH {
32             my $class = shift;
33             my $filename = shift;
34             my $mode = shift() . '';
35             my $self = {'db' => {},
36             'fh' => undef,
37             'mode' => $mode,
38             };
39            
40             if ($mode eq 'rw') {
41             # Open a hashfile and put an exclusive lock on it.
42            
43             jabber "Attempting to gain read/write access to $filename";
44            
45             if (not -e $filename) {
46             jabber "$filename doesn't exist, creating it";
47             # Create it in the proper format. This is necessary for locking:
48             unless (defined ($self->{'db'} = $TIEPACK->TIEHASH($filename, $READ_WRITE, 0660)) ) {
49             jabber "Couldn't create $filename: $!";
50             return;
51             }
52             delete $self->{'db'};
53             }
54            
55             # Try to lock the file:
56             if ($self->{'fh'} = new FileHandle($filename,$READ_WRITE)) {
57             jabber "Opened $filename";
58             } else {
59             jabber "Couldn't open $filename for locking: $!";
60             return;
61             }
62             unless (lock_file($self->{'fh'}, 'ex')) {
63             jabber "Couldn't lock $filename";
64             close $self->{'fh'};
65             return;
66             }
67            
68             # Tie a hash to the file:
69             $self->{'db'} = $TIEPACK->TIEHASH($filename, $READ_WRITE, 0660);
70            
71             } else {
72             jabber "Attempting to gain read-only access to $filename";
73            
74            
75             my $tempfile = "$TEMPDIR/" . _random_string();
76            
77             # Try to lock the file:
78             my $temp_fh = new FileHandle($filename);
79             unless (defined $temp_fh) {
80             jabber "Couldn't open $filename for reading: $!";
81             return;
82             }
83             unless (lock_file($temp_fh, 'sh')) {
84             jabber "Couldn't lock $filename, aborting dbm_open_ro";
85             close $temp_fh;
86             return;
87             }
88            
89             # Copy to a tempfile:
90            
91             unless (system ("cp $filename $tempfile") == 0) {
92             jabber "cp of $filename to $tempfile failed: $!";
93             return;
94             }
95             jabber "Copied $filename to $tempfile";
96            
97            
98             # Unlock & close:
99             unless ( lock_file($temp_fh, 'un')) {
100             jabber "Couldn't unlock $filename: $!";
101             close $temp_fh;
102             unless (unlink $tempfile) {
103             jabber "IMPORTANT! Couldn't unlink tempfile $tempfile: $!";
104             }
105             return;
106             }
107             close $temp_fh;
108            
109            
110             # Tie the tempfile:
111             $self->{'db'} = $TIEPACK->TIEHASH($tempfile, $READ_ONLY, 0660);
112             $self->{tempfile} = $tempfile;
113            
114             # Delete the tempfile (don't worry, it will appear to remain
115             # around until you close it, at least on UNIX):
116             unless (unlink $tempfile) {
117             warn("Couldn't remove tempfile $tempfile: $!");
118             return;
119             }
120             }
121            
122             unless ($self->{'db'}) {
123             carp "$TIEPACK->TIEHASH failed: $!";
124             return;
125             }
126            
127             return bless $self, $class;
128             }
129              
130             sub tempfile { $_[0]->{tempfile} }
131              
132             sub DESTROY {
133             # Called to close, unlock, and untie a hashfile:
134            
135             my $self = shift;
136            
137             jabber "Database closing process begun";
138            
139             delete $self->{'db'};
140            
141             if ($self->{'mode'} eq 'rw') {
142             jabber "Closing read/write file";
143            
144             # Close the file - this removes the lock too
145             close $self->{'fh'} or croak "Couldn't unlock & close database: $!";
146             }
147            
148             jabber "dbm_close completed";
149            
150             return 1;
151             }
152              
153             #line 161
154             sub FETCH { my $self=shift; $self->{'db'}->FETCH(@_) }
155             sub STORE { my $self=shift; $self->{'db'}->STORE(@_) }
156             sub DELETE { my $self=shift; $self->{'db'}->DELETE(@_) }
157             sub FIRSTKEY { my $self=shift; $self->{'db'}->FIRSTKEY(@_) }
158             sub NEXTKEY { my $self=shift; $self->{'db'}->NEXTKEY(@_) }
159             sub EXISTS { my $self=shift; $self->{'db'}->EXISTS(@_) }
160             sub CLEAR { my $self=shift; $self->{'db'}->CLEAR(@_) }
161              
162              
163             sub _random_string {
164             # Usage: $filehandle = _random_string($n_chars);
165             #
166             # Returns a string made of $n_chars (default 9) random letters.
167            
168             my $chars = @_ ? shift() : 9;
169             my $string_chars = "QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm";
170            
171             my $string_out = '';
172             my $i;
173             for($i=0; $i<$chars; $i++) {
174             $string_out .= substr($string_chars, int(rand length $string_chars), 1);
175             }
176             return $string_out;
177             }
178              
179             sub lock_file($$) {
180             # Usage: lock_file FILEHANDLE, lock_type;
181             # where FILEHANDLE is an _open_ filehandle,
182             # and lock_type is "ex" or "sh" for exclusive or shared locks.
183            
184             my $filehandle = shift;
185             my $type = shift;
186             my ($i, $didlock) = (0,0);
187            
188             unless (exists $LOCKS{$type}) { croak("Invalid lock type: '$type'"); }
189            
190             # Try to apply the lock:
191             while (1) {
192             if (flock ($filehandle, $LOCKS{$type})) {
193             jabber "Lock successfully obtained";
194             $didlock = 1;
195             last;
196             } else {
197             jabber "Lock attempt $i failed: $!";
198             }
199              
200             last if ++$i > $RETRIES;
201             jabber "Sleeping for $WAIT_TIME seconds";
202             sleep $WAIT_TIME;
203             }
204            
205             unless ($didlock) {
206             jabber "Lock ($type) attempt $i (final) failed ($!), aborting";
207             return 0;
208             }
209            
210             return 1;
211             }
212              
213             1;
214             __END__