File Coverage

blib/script/plockf
Criterion Covered Total %
statement 116 147 78.9
branch 47 74 63.5
condition 30 63 47.6
subroutine 23 26 88.4
pod n/a
total 216 310 69.6


line stmt bran cond sub pod time code
1             #!/usr/local/bin/perl -w
2             # -*- perl -*-
3              
4             #
5             # Author: Slaven Rezic
6             #
7             # Copyright (C) 2016,2017,2026 Slaven Rezic. All rights reserved.
8             # This program is free software; you can redistribute it and/or
9             # modify it under the same terms as Perl itself.
10             #
11             # WWW: https://github.com/eserte/app-plockf
12             #
13              
14 22     22   81575 use strict;
  22         32  
  22         823  
15 22     22   8149 use Errno;
  22         28499  
  22         941  
16 22     22   176 use Fcntl ();
  22         34  
  22         418  
17 22     22   13153 use Getopt::Long;
  22         265723  
  22         116  
18              
19 22     22   3310 use vars qw($VERSION);
  22         37  
  22         1239  
20 22         2567830 $VERSION = "0.05";
21              
22             # sysexits(3) constants
23 22     22   216 use constant EX_USAGE => 64;
  22         38  
  22         1070  
24 22     22   75 use constant EX_UNAVAILABLE => 69;
  22         29  
  22         697  
25 22     22   88 use constant EX_SOFTWARE => 70;
  22         51  
  22         636  
26 22     22   78 use constant EX_OSERR => 71;
  22         27  
  22         561  
27 22     22   68 use constant EX_CANTCREAT => 73;
  22         24  
  22         525  
28 22     22   71 use constant EX_TEMPFAIL => 75;
  22         26  
  22         18189  
29              
30             sub set_impl ();
31             sub handle_error ($);
32             sub progname ();
33              
34             sub usage (;$) {
35 4     4   8 my $msg = shift;
36 4 100       12 if ($msg) {
37 3         97 warn $msg, "\n";
38             }
39 4         32 warn "usage: " . progname . " [-kns] [-t seconds] file command [arguments]\n";
40 4         362 exit EX_USAGE;
41             }
42              
43 22         108 my $timeout;
44             my $keep;
45 22         0 my $silent;
46 22         0 my $nocreat;
47              
48 22         155 Getopt::Long::Configure('require_order');
49             GetOptions(
50 1     1   785 'help|h|?' => sub { usage },
51             'k' => \$keep,
52             'n' => \$nocreat,
53             's' => \$silent,
54             't=f' => \$timeout,
55             'v|version' => sub {
56 1     1   936 print "plockf version $VERSION\n";
57 1         86 exit 0;
58             },
59 22         971 );
60              
61 20 100 100     17331 if (defined $timeout && $timeout < 0) {
62 1         5 usage "Timeout must be positive";
63             }
64              
65 19 100       72 my $lock_file = shift
66             or usage "Lock file is not specified";
67 18 100       65 my $cmd = shift
68             or usage "Command is not specified";
69              
70 17         66 set_impl;
71              
72 17         34 my $timed_out;
73             my $alarm;
74 17 100 100     63 if (defined $timeout && $timeout > 0) {
75 3     1   54 $SIG{ALRM} = sub { $timed_out = 1 };
  1         14  
76 3 100 66     24 if ($timeout !~ m{^\d+$} && eval { require Time::HiRes; Time::HiRes->VERSION(1.9716); Time::HiRes->import('ualarm'); 1 }) { # do we need and can we do floating point timeouts?
  1         478  
  1         1112  
  1         5  
  1         88  
77 1         2 $alarm = \&Time::HiRes::alarm;
78             } else {
79 2 50       7 if ($timeout < 1) { $timeout = 1 }
  0         0  
80 2     4   7 $alarm = sub { alarm($_[0]) };
  4         32  
81             }
82 3         16 $alarm->($timeout);
83             }
84              
85 17         58 my $lock_fh = acquire_lock(0);
86 16   100     119 while (!$lock_fh && !$timed_out && (!defined $timeout || $timeout > 0)) {
      100        
      100        
87 4 50       28 if ($^O eq 'MSWin32') {
    100          
88             # systems where alarm() does not work with blocking syscalls
89 0         0 wait_for_lock_nonblocking();
90 0         0 $lock_fh = acquire_lock(0);
91             } elsif ($keep) {
92 1         2 $lock_fh = acquire_lock(1);
93             } else {
94 3         13 wait_for_lock();
95 3         54 $lock_fh = acquire_lock(0);
96             }
97             }
98              
99 16 100       53 if ($alarm) {
100 3         23 $alarm->(0);
101             }
102              
103 16 100       47 if (!$lock_fh) {
104 3         14 handle_error EX_TEMPFAIL;
105             }
106              
107 13         31 my $do_cleanup = !$keep;
108              
109 13     3   204 $SIG{TERM} = sub { exit }; # would run END block
  3         574  
110              
111 13         25 system { $cmd } $cmd, @ARGV;
  13         9285611  
112 10 50       881 if ($? == -1) {
    100          
113 0         0 warn progname . ": calling '$cmd @ARGV' failed: $!\n";
114 0         0 exit EX_OSERR;
115             } elsif ($? & 127) {
116 1         194 exit EX_SOFTWARE;
117             } else {
118 9         65 my $exit_code = $? >> 8;
119 9         1719 exit $exit_code;
120             }
121              
122             END {
123 22 100   22   87 if ($do_cleanup) {
124 10         0 unlink $lock_file;
125             }
126             }
127              
128             sub set_impl () {
129 17     17   41 my($block) = @_;
130 17 50 33     477 if ( ($] < 5.010 && eval { &Fcntl::O_EXLOCK } && eval { &Fcntl::O_NONBLOCK })
  0 50 33     0  
  0   33     0  
      33        
      33        
      33        
      33        
      33        
      33        
      33        
131             || ($] >= 5.010 && defined &Fcntl::O_EXLOCK && defined &Fcntl::O_NONBLOCK)) {
132 0         0 *acquire_lock = \&acquire_lock_bsd;
133 0         0 *wait_for_lock = \&wait_for_lock_bsd;
134 0         0 } elsif ( ($] < 5.010 && eval { &Fcntl::LOCK_EX } && eval { &Fcntl::LOCK_NB })
  0         0  
135             || ($] >= 5.010 && defined &Fcntl::LOCK_EX && defined &Fcntl::LOCK_NB)) {
136 17         77 *acquire_lock = \&acquire_lock_other;
137 17         70 *wait_for_lock = \&wait_for_lock_other;
138             } else {
139 0         0 die "Can't lock on this operating system";
140             }
141             }
142              
143             sub acquire_lock_bsd {
144 0     0   0 my($block) = @_;
145 0         0 my $lock_fh;
146 0 0       0 if (!sysopen $lock_fh, $lock_file, &Fcntl::O_RDONLY|($block ? 0 : &Fcntl::O_NONBLOCK)|&Fcntl::O_EXLOCK|($nocreat ? 0 : &Fcntl::O_CREAT), 0666) {
    0          
    0          
147 0 0 0     0 if ($!{EAGAIN} || $!{EINTR}) {
148 0         0 return undef;
149             }
150 0 0 0     0 if ($nocreat && $!{ENOENT}) {
151 0         0 handle_error EX_UNAVAILABLE;
152             } else {
153 0         0 handle_error EX_CANTCREAT;
154             }
155             }
156 0         0 return $lock_fh;
157             }
158              
159             sub wait_for_lock_bsd {
160 0     0   0 sysopen my $fh, $lock_file, &Fcntl::O_EXLOCK;
161             # no error handling needed; probably same failure will happen in acquire_lock_bsd
162             }
163              
164             sub acquire_lock_other {
165 21     21   85 my($block) = @_;
166 21         32 my $lock_fh;
167 21 100       2829 if (!sysopen $lock_fh, $lock_file, &Fcntl::O_RDONLY|($nocreat ? 0 : &Fcntl::O_CREAT), 0666) {
    100          
168 1 50 33     8 if ($!{EAGAIN} || $!{EINTR}) {
169 0         0 return undef;
170             }
171 1 50 33     28 if ($nocreat && $!{ENOENT}) {
172 1         11 handle_error EX_UNAVAILABLE;
173             } else {
174 0         0 handle_error EX_CANTCREAT;
175             }
176             }
177 20 100       670279 if (!flock $lock_fh, ($block ? 0 : &Fcntl::LOCK_NB)|&Fcntl::LOCK_EX) {
    100          
178 7         101 return undef;
179             }
180 13 100       62 if (!$keep) {
181 10         150 my @stat_file = stat $lock_file;
182 10 50       31 if (!@stat_file) {
183             # file was unlinked in the meantime
184 0         0 return undef;
185             }
186 10 50       77 if ($^O ne 'MSWin32') { # ino+dev are not meaningful on Windows systems
187 10         64 my @stat_fh = stat $lock_fh;
188 10 50 33     83 if ($stat_fh[1] != $stat_file[1] || $stat_fh[0] != $stat_file[0]) {
189             # file was unlinked in the meantime and another plockf process was faster
190 0         0 return undef;
191             }
192             }
193             }
194 13         58 return $lock_fh;
195             }
196              
197             sub wait_for_lock_other {
198 3 50   3   103 if (sysopen my $fh, $lock_file, &Fcntl::O_RDONLY) {
199 3         1515074 flock $fh, &Fcntl::LOCK_EX;
200             }
201             }
202              
203             sub wait_for_lock_nonblocking {
204 0     0   0 while () {
205 0 0       0 last if $timed_out;
206 0 0       0 if (sysopen my $fh, $lock_file, &Fcntl::O_RDONLY) {
207 0 0       0 return if flock $fh, &Fcntl::LOCK_EX|&Fcntl::LOCK_NB;
208             }
209 0         0 sleep 1;
210             }
211             }
212              
213             sub handle_error ($) {
214 4     4   8 my $exit = shift;
215 4 100       11 unless ($silent) {
216 3 100       9 if ($exit == EX_UNAVAILABLE) {
217 1         3 warn progname . ": cannot open $lock_file: $!\n";
218             } else {
219 2         7 warn progname . ": $lock_file: already locked\n";
220             }
221             }
222 4         299 exit $exit;
223             }
224              
225             sub progname () {
226 7     7   60 require File::Basename;
227 7         713 File::Basename::basename($0);
228             }
229              
230             __END__