File Coverage

blib/lib/NL/File/Lock.pm
Criterion Covered Total %
statement 47 125 37.6
branch 18 74 24.3
condition 5 30 16.6
subroutine 9 17 52.9
pod 0 10 0.0
total 79 256 30.8


line stmt bran cond sub pod time code
1             #!/usr/bin/perl
2             # NL::File::Lock - mostNeeded Libs :: File locking (based on lockfiles)
3             # (C) 2007-2008 Nickolay Kovalev, http://resume.nickola.ru
4             # E-mail: nickola_code@nickola.ru
5            
6             package NL::File::Lock;
7 1     1   561 use strict;
  1         1  
  1         1908  
8            
9             our $VERSION = '0.3';
10             sub LOCK_SH() {1} # multi-lock
11             sub LOCK_EX() {2} # mono-lock
12             sub LOCK_NB() {4} # don't wait lock result
13             sub LOCK_UN() {8} # unlock
14            
15             # OS SETTING
16             $NL::File::Lock::OS_SETTINGS = {
17             'IS_SOLARIS' => 0,
18             'USE_FCNTL' => 0,
19             'FCNTL_ERROR' => ''
20             };
21             if ($^O =~ /^(solaris|sunos)$/i) {
22             $NL::File::Lock::OS_SETTINGS->{'IS_SOLARIS'} = 1;
23             $NL::File::Lock::OS_SETTINGS->{'USE_FCNTL'} = 1;
24             eval { require Fcntl; }; # If we can - we will use 'Fcntl'
25             if ($@) {
26             $NL::File::Lock::OS_SETTINGS->{'USE_FCNTL'} = 0;
27             $NL::File::Lock::OS_SETTINGS->{'FCNTL_ERROR'} = $@;
28             }
29             else { Fcntl->import(); }
30             }
31            
32             # Internal DATA
33             $NL::File::Lock::DATA = {
34             'SETTINGS' => {
35             'SECONDS_TO_REMOVE_OLD_LOCKS' => 3600*5, # 3600 = 1 hour
36             'LOCK_FILE_POSTFIX' => '.lck',
37             'dir_for_locks' => '',
38             'dir_splitter' => '/',
39             'dir_splitters_extra' => []
40             },
41             'LOCKED_FILES' => {}
42             };
43             # Path processing
44             sub _path_get_file {
45 0     0   0 my ($str) = @_;
46            
47 0         0 foreach my $spl ($NL::File::Lock::DATA->{'SETTINGS'}->{'dir_splitter'}, @{ $NL::File::Lock::DATA->{'SETTINGS'}->{'dir_splitters_extra'} }) {
  0         0  
48 0         0 my $splitter = quotemeta($spl);
49 0         0 $str =~ s/^.*$splitter([^$splitter]{0,})$/$1/;
50             }
51 0         0 return $str;
52             }
53             sub _path_dir_chomp {
54 0     0   0 my ($ref_str) = @_;
55            
56 0         0 foreach my $spl ($NL::File::Lock::DATA->{'SETTINGS'}->{'dir_splitter'}, @{ $NL::File::Lock::DATA->{'SETTINGS'}->{'dir_splitters_extra'} }) {
  0         0  
57 0         0 my $splitter = quotemeta($spl);
58 0         0 ${ $ref_str } =~ s/[$splitter]{1,}$//;
  0         0  
59             }
60             }
61             sub _make_lock_file_name {
62 2     2   4 my ($file_name) = @_;
63            
64 2 50       10 if ($NL::File::Lock::DATA->{'SETTINGS'}->{'dir_for_locks'} ne '') {
65 0         0 my $fn = &_path_get_file($file_name);
66 0 0       0 if ($fn ne '') {
67 0         0 return $NL::File::Lock::DATA->{'SETTINGS'}->{'dir_for_locks'}.$NL::File::Lock::DATA->{'SETTINGS'}->{'dir_splitter'}.$fn.$NL::File::Lock::DATA->{'SETTINGS'}->{'LOCK_FILE_POSTFIX'};
68             }
69             }
70 2         9 return $file_name.$NL::File::Lock::DATA->{'SETTINGS'}->{'LOCK_FILE_POSTFIX'};
71             }
72             # Initialization
73             sub init {
74 0     0 0 0 my ($dir_for_locks, $in_SETTINGS) = @_;
75 0 0       0 $in_SETTINGS = {} if (!$in_SETTINGS);
76            
77 0 0       0 if ($^O eq 'MacOS') { $NL::File::Lock::DATA->{'SETTINGS'}->{'dir_splitter'} = ':'; }
  0 0       0  
78             elsif ($^O eq 'MSWin32') {
79 0         0 $NL::File::Lock::DATA->{'SETTINGS'}->{'dir_splitter'} = '/';
80 0         0 $NL::File::Lock::DATA->{'SETTINGS'}->{'dir_splitters_extra'} = ['\\'];
81             }
82 0 0 0     0 if (defined $dir_for_locks && $dir_for_locks ne '') {
83 0         0 &_path_dir_chomp(\$dir_for_locks);
84 0 0 0     0 if ($dir_for_locks ne '' && -d $dir_for_locks) {
85 0         0 $NL::File::Lock::DATA->{'SETTINGS'}->{'dir_for_locks'} = $dir_for_locks;
86             # Removing old LOCKS
87 0 0 0     0 if (defined $in_SETTINGS->{'REMOVE_OLD'} && $in_SETTINGS->{'REMOVE_OLD'}) {
88             # Getting listing
89 0         0 my @arr_listing;
90 0 0       0 if (opendir(DIR, $NL::File::Lock::DATA->{'SETTINGS'}->{'dir_for_locks'})) {
91 0         0 my $pf_QM = quotemeta($NL::File::Lock::DATA->{'SETTINGS'}->{'LOCK_FILE_POSTFIX'});
92 0         0 @arr_listing = grep( /${pf_QM}$/, grep(!/^\.{1,2}$/, readdir (DIR)) );
93 0         0 closedir (DIR);
94             }
95 0         0 my $splitter = $NL::File::Lock::DATA->{'SETTINGS'}->{'dir_splitter'};
96 0 0       0 my $dir = ($NL::File::Lock::DATA->{'SETTINGS'}->{'dir_for_locks'} =~ /$splitter$/) ? $NL::File::Lock::DATA->{'SETTINGS'}->{'dir_for_locks'} : $NL::File::Lock::DATA->{'SETTINGS'}->{'dir_for_locks'}.$splitter;
97 0         0 my $time = time();
98 0         0 foreach (@arr_listing) {
99 0         0 my $file = $dir.$_;
100 0 0       0 if (-f $file) {
101 0         0 my @arr_stat = stat($file);
102 0 0       0 if (defined $arr_stat[9]) {
103 0 0       0 unlink $file if ($time - $arr_stat[9] >= $NL::File::Lock::DATA->{'SETTINGS'}->{'SECONDS_TO_REMOVE_OLD_LOCKS'});
104             }
105             }
106             }
107             }
108 0         0 return 1;
109             }
110             }
111 0         0 return 0;
112            
113             }
114             # Locking
115 1 50   1 0 3 sub lock_read { my ($file_name, $in_ref_hash_EXT) = @_; return &lf_lock($file_name, &LOCK_SH(), defined $in_ref_hash_EXT ? $in_ref_hash_EXT : {}); }
  1         7  
116 1 50   1 0 15 sub lock_write { my ($file_name, $in_ref_hash_EXT) = @_; return &lf_lock($file_name, &LOCK_EX(), defined $in_ref_hash_EXT ? $in_ref_hash_EXT : {}); }
  1         11  
117             sub lf_lock {
118 2     2 0 5 my ($file_name, $lock_type, $in_ref_hash_EXT) = @_;
119 2 50 33     15 $lock_type = &LOCK_EX() if (!defined $lock_type || $lock_type <= 0);
120 2 50 33     14 $in_ref_hash_EXT = {} if (!defined $in_ref_hash_EXT || ref $in_ref_hash_EXT ne 'HASH');
121            
122 2         3 my $lock_file_name = '';
123 2         4 my ($time_stop, $time_sleep) = (0, 0);
124 2 50       8 if (defined $in_ref_hash_EXT->{'timeout'}) {
125 2 50 33     16 $time_sleep = (defined $in_ref_hash_EXT->{'time_sleep'} && $in_ref_hash_EXT->{'time_sleep'} > 0) ? $in_ref_hash_EXT->{'time_sleep'} : 0;
126 2         11 $time_stop = time() + $in_ref_hash_EXT->{'timeout'};
127             }
128 2 50       9 if (defined $NL::File::Lock::DATA->{'LOCKED_FILES'}->{$file_name}) {
129 0 0       0 if ($NL::File::Lock::DATA->{'LOCKED_FILES'}->{$file_name}->{'IS_LOCKED'}) { return 2; } # already locked
  0         0  
130             else {
131 0 0       0 if (&_lf_lock_MAKE_LOCK($NL::File::Lock::DATA->{'LOCKED_FILES'}->{$file_name}->{'lock_handle'}, $lock_type, $time_stop, $time_sleep)) {
132             # Locked
133 0         0 $NL::File::Lock::DATA->{'LOCKED_FILES'}->{$file_name}->{'IS_LOCKED'} = 1;
134 0         0 return 1;
135             }
136 0         0 else { return 0; }
137             }
138             }
139 2         7 else { $lock_file_name = &_make_lock_file_name($file_name); }
140            
141 2         4 my $is_locked = 0;
142 2   0     4 do {
143 2         3 my $FILE_OPENED;
144 2 50       6 if ($NL::File::Lock::OS_SETTINGS->{'USE_FCNTL'}) {
145             # eval '$FILE_OPENED = sysopen(LFH, $lock_file_name, O_WRONLY|O_CREAT)';
146 0         0 eval '$FILE_OPENED = sysopen(LFH, $lock_file_name, O_RDWR|O_CREAT)';
147             }
148 2         213 else { $FILE_OPENED = open(LFH, ">>$lock_file_name"); }
149            
150 2 50       11 if ($FILE_OPENED) {
151 2 50       9 if (&_lf_lock_MAKE_LOCK(\*LFH, $lock_type, $time_stop, $time_sleep)) {
152             # Locked
153 2         13 $NL::File::Lock::DATA->{'LOCKED_FILES'}->{$file_name} = { 'IS_LOCKED' => 1, 'lock_file' => $lock_file_name, 'lock_handle' => \*LFH };
154 2         14 return 1;
155             }
156             else {
157 0         0 close(LFH);
158 0         0 return 0;
159             }
160             }
161             else {
162             # Sleeping
163             # sleep($time_sleep) if ($time_sleep > 0);
164 0 0       0 if ($time_sleep > 0) { select(undef, undef, undef, $time_sleep); }
  0         0  
165             }
166             } while (!$is_locked && time() < $time_stop);
167 0         0 return 0;
168             }
169             sub _lf_lock_MAKE_LOCK {
170 2     2   12 my ($lock_file_handle, $lock_type, $time_stop, $time_sleep) = @_;
171            
172             # Solaris workaround
173 2 0 33     11 $lock_type = &LOCK_EX() if ($NL::File::Lock::OS_SETTINGS->{'IS_SOLARIS'} && !$NL::File::Lock::OS_SETTINGS->{'USE_FCNTL'} && $lock_type == &LOCK_SH());
      33        
174 2         3 do {
175 2 50       22 if (flock($lock_file_handle, $lock_type | &LOCK_NB())) { return 1; }
  2         7  
176             else {
177             # Sleeping
178             # sleep($time_sleep) if ($time_sleep > 0);
179 0 0       0 if ($time_sleep > 0) { select(undef, undef, undef, $time_sleep); }
  0         0  
180             }
181             } while (time() < $time_stop);
182 0         0 return 0;
183             }
184             # Ulocking
185             sub unlock {
186 2     2 0 5 my ($file_name, $not_unlink) = @_;
187 2 100       10 $not_unlink = 0 if (!defined $not_unlink);
188            
189 2 50       8 if (defined $NL::File::Lock::DATA->{'LOCKED_FILES'}->{$file_name})
190             {
191 2 50       10 if ($NL::File::Lock::DATA->{'LOCKED_FILES'}->{$file_name}->{'IS_LOCKED'}) {
192 2         19 flock($NL::File::Lock::DATA->{'LOCKED_FILES'}->{$file_name}->{'lock_handle'}, &LOCK_UN());
193             }
194 2         31 close $NL::File::Lock::DATA->{'LOCKED_FILES'}->{$file_name}->{'lock_handle'};
195 2 100       104 unlink $NL::File::Lock::DATA->{'LOCKED_FILES'}->{$file_name}->{'lock_file'} if (!$not_unlink); # If file is opened it will not be removed on some OS
196 2         8 delete $NL::File::Lock::DATA->{'LOCKED_FILES'}->{$file_name};
197 2         12 return 1;
198             }
199 0         0 return 0;
200             }
201             sub unlock_not_unlink {
202 1     1 0 2 my ($file_name) = @_;
203 1         4 return &unlock($file_name, 1);
204             }
205             # DO NOT USE 'unlock_not_close' - USE 'unlock_not_unlink'
206             # 'unlock_not_close' is not good because, proccess A can make 'unlock_not_close' and proccess B
207             # can remove lock file on some OS then, when proccess A will make lock again via FILE_HANDLE - can be error
208             sub unlock_not_close {
209 0     0 0   my ($file_name) = @_;
210            
211 0 0         if (defined $NL::File::Lock::DATA->{'LOCKED_FILES'}->{$file_name})
212             {
213 0 0         if ($NL::File::Lock::DATA->{'LOCKED_FILES'}->{$file_name}->{'IS_LOCKED'}) {
214 0 0         if ($] < 5.004) {
215             # Fix for old Perl
216 0           my $old_fh = select($NL::File::Lock::DATA->{'LOCKED_FILES'}->{$file_name}->{'lock_handle'});
217 0           local $|=1; # Enable commands bufferization
218 0           local $\ = ''; # Make empty splitter of output records
219 0           print ''; # Call buffer cleaning
220 0           select($old_fh); # Restore old HANDLER
221             }
222 0           flock($NL::File::Lock::DATA->{'LOCKED_FILES'}->{$file_name}->{'lock_handle'}, &LOCK_UN()); # LOCK_UN = 8
223 0           $NL::File::Lock::DATA->{'LOCKED_FILES'}->{$file_name}->{'status'} = 'unlocked';
224 0           return 1;
225             }
226             }
227 0           return 0;
228             }
229             # Removing all LOCKS
230             sub END
231             {
232 1     1   617 foreach (keys %{ $NL::File::Lock::DATA->{'LOCKED_FILES'} }) { &unlock($_); }
  1         0  
  0         0  
233             }
234             # Simple 'flock' based locks
235 0     0 0   sub flock_read { return &_flock($_[0], &LOCK_SH()); }
236 0     0 0   sub flock_write { return &_flock($_[0], &LOCK_EX()); }
237             sub _flock {
238 0     0     my ($file_handle, $lock_type) = @_;
239 0 0 0       $lock_type = &LOCK_EX() if (!defined $lock_type || $lock_type <= 0);
240 0           return flock($file_handle, $lock_type);
241             }
242 0     0 0   sub unflock { return flock($_[0], &LOCK_UN()); }
243             1;
244             __END__