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__
|