| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | # -*- perl -*- | 
| 2 |  |  |  |  |  |  | # | 
| 3 |  |  |  |  |  |  | #  File::NFSLock - bdpO - NFS compatible (safe) locking utility | 
| 4 |  |  |  |  |  |  | # | 
| 5 |  |  |  |  |  |  | #  $Id: NFSLock.pm,v 1.29 2018/11/01 14:00:00 bbb Exp $ | 
| 6 |  |  |  |  |  |  | # | 
| 7 |  |  |  |  |  |  | #  Copyright (C) 2002, Paul T Seamons | 
| 8 |  |  |  |  |  |  | #                      paul@seamons.com | 
| 9 |  |  |  |  |  |  | #                      http://seamons.com/ | 
| 10 |  |  |  |  |  |  | # | 
| 11 |  |  |  |  |  |  | #                      Rob B Brown | 
| 12 |  |  |  |  |  |  | #                      bbb@cpan.org | 
| 13 |  |  |  |  |  |  | # | 
| 14 |  |  |  |  |  |  | #  This package may be distributed under the terms of either the | 
| 15 |  |  |  |  |  |  | #  GNU General Public License | 
| 16 |  |  |  |  |  |  | #    or the | 
| 17 |  |  |  |  |  |  | #  Perl Artistic License | 
| 18 |  |  |  |  |  |  | # | 
| 19 |  |  |  |  |  |  | #  All rights reserved. | 
| 20 |  |  |  |  |  |  | # | 
| 21 |  |  |  |  |  |  | #  Please read the perldoc File::NFSLock | 
| 22 |  |  |  |  |  |  | # | 
| 23 |  |  |  |  |  |  | ################################################################ | 
| 24 |  |  |  |  |  |  |  | 
| 25 |  |  |  |  |  |  | package File::NFSLock; | 
| 26 |  |  |  |  |  |  |  | 
| 27 | 78 |  |  | 78 |  | 6747738 | use strict; | 
|  | 78 |  |  |  |  | 1022 |  | 
|  | 78 |  |  |  |  | 2262 |  | 
| 28 | 78 |  |  | 78 |  | 422 | use warnings; | 
|  | 78 |  |  |  |  | 130 |  | 
|  | 78 |  |  |  |  | 2247 |  | 
| 29 |  |  |  |  |  |  |  | 
| 30 | 78 |  |  | 78 |  | 411 | use Carp qw(croak confess); | 
|  | 78 |  |  |  |  | 155 |  | 
|  | 78 |  |  |  |  | 4855 |  | 
| 31 |  |  |  |  |  |  | our $errstr; | 
| 32 | 78 |  |  | 78 |  | 492 | use base 'Exporter'; | 
|  | 78 |  |  |  |  | 171 |  | 
|  | 78 |  |  |  |  | 14084 |  | 
| 33 |  |  |  |  |  |  | our @EXPORT_OK = qw(uncache); | 
| 34 |  |  |  |  |  |  |  | 
| 35 |  |  |  |  |  |  | our $VERSION = '1.29'; | 
| 36 |  |  |  |  |  |  |  | 
| 37 |  |  |  |  |  |  | #Get constants, but without the bloat of | 
| 38 |  |  |  |  |  |  | #use Fcntl qw(LOCK_SH LOCK_EX LOCK_NB); | 
| 39 |  |  |  |  |  |  | use constant { | 
| 40 | 78 |  |  |  |  | 193793 | LOCK_SH => 1, | 
| 41 |  |  |  |  |  |  | LOCK_EX => 2, | 
| 42 |  |  |  |  |  |  | LOCK_NB => 4, | 
| 43 | 78 |  |  | 78 |  | 570 | }; | 
|  | 78 |  |  |  |  | 158 |  | 
| 44 |  |  |  |  |  |  |  | 
| 45 |  |  |  |  |  |  | ### Convert lock_type to a number | 
| 46 |  |  |  |  |  |  | our $TYPES = { | 
| 47 |  |  |  |  |  |  | BLOCKING    => LOCK_EX, | 
| 48 |  |  |  |  |  |  | BL          => LOCK_EX, | 
| 49 |  |  |  |  |  |  | EXCLUSIVE   => LOCK_EX, | 
| 50 |  |  |  |  |  |  | EX          => LOCK_EX, | 
| 51 |  |  |  |  |  |  | NONBLOCKING => LOCK_EX | LOCK_NB, | 
| 52 |  |  |  |  |  |  | NB          => LOCK_EX | LOCK_NB, | 
| 53 |  |  |  |  |  |  | SHARED      => LOCK_SH, | 
| 54 |  |  |  |  |  |  | SH          => LOCK_SH, | 
| 55 |  |  |  |  |  |  | }; | 
| 56 |  |  |  |  |  |  | our $LOCK_EXTENSION = '.NFSLock'; # customizable extension | 
| 57 |  |  |  |  |  |  | our $HOSTNAME = undef; | 
| 58 |  |  |  |  |  |  | our $SHARE_BIT = 1; | 
| 59 |  |  |  |  |  |  |  | 
| 60 |  |  |  |  |  |  | ###----------------------------------------------------------------### | 
| 61 |  |  |  |  |  |  |  | 
| 62 |  |  |  |  |  |  | my $graceful_sig = sub { | 
| 63 |  |  |  |  |  |  | print STDERR "Received SIG$_[0]\n" if @_; | 
| 64 |  |  |  |  |  |  | # Perl's exit should safely DESTROY any objects | 
| 65 |  |  |  |  |  |  | # still "alive" before calling the real _exit(). | 
| 66 |  |  |  |  |  |  | exit 1; | 
| 67 |  |  |  |  |  |  | }; | 
| 68 |  |  |  |  |  |  |  | 
| 69 |  |  |  |  |  |  | our @CATCH_SIGS = qw(TERM INT); | 
| 70 |  |  |  |  |  |  |  | 
| 71 |  |  |  |  |  |  | sub new { | 
| 72 | 1167 |  |  | 1167 | 0 | 82523906 | $errstr = undef; | 
| 73 |  |  |  |  |  |  |  | 
| 74 | 1167 |  |  |  |  | 3855 | my $type  = shift; | 
| 75 | 1167 |  | 50 |  |  | 9753 | my $class = ref($type) || $type || __PACKAGE__; | 
| 76 | 1167 |  |  |  |  | 3211 | my $self  = {}; | 
| 77 |  |  |  |  |  |  |  | 
| 78 |  |  |  |  |  |  | ### allow for arguments by hash ref or serially | 
| 79 | 1167 | 100 | 66 |  |  | 7406 | if( @_ && ref $_[0] ){ | 
| 80 | 1134 |  |  |  |  | 2708 | $self = shift; | 
| 81 |  |  |  |  |  |  | }else{ | 
| 82 | 33 |  |  |  |  | 287 | $self->{file}      = shift; | 
| 83 | 33 |  |  |  |  | 252 | $self->{lock_type} = shift; | 
| 84 | 33 |  |  |  |  | 196 | $self->{blocking_timeout}   = shift; | 
| 85 | 33 |  |  |  |  | 185 | $self->{stale_lock_timeout} = shift; | 
| 86 |  |  |  |  |  |  | } | 
| 87 | 1167 |  | 50 |  |  | 3755 | $self->{file}       ||= ""; | 
| 88 | 1167 |  | 50 |  |  | 3723 | $self->{lock_type}  ||= 0; | 
| 89 | 1167 |  | 100 |  |  | 5502 | $self->{blocking_timeout}   ||= 0; | 
| 90 | 1167 |  | 100 |  |  | 6019 | $self->{stale_lock_timeout} ||= 0; | 
| 91 | 1167 |  |  |  |  | 4745 | $self->{lock_pid} = $$; | 
| 92 | 1167 |  |  |  |  | 5443 | $self->{unlocked} = 1; | 
| 93 | 1167 |  |  |  |  | 4398 | foreach my $signal (@CATCH_SIGS) { | 
| 94 | 2334 | 100 | 66 |  |  | 10171 | if (!$SIG{$signal} || | 
| 95 |  |  |  |  |  |  | $SIG{$signal} eq "DEFAULT") { | 
| 96 | 2246 |  |  |  |  | 34310 | $SIG{$signal} = $graceful_sig; | 
| 97 |  |  |  |  |  |  | } | 
| 98 |  |  |  |  |  |  | } | 
| 99 |  |  |  |  |  |  |  | 
| 100 |  |  |  |  |  |  | ### force lock_type to be numerical | 
| 101 | 1167 | 50 | 33 |  |  | 13992 | if( $self->{lock_type} && | 
|  |  |  | 33 |  |  |  |  | 
| 102 |  |  |  |  |  |  | $self->{lock_type} !~ /^\d+/ && | 
| 103 |  |  |  |  |  |  | exists $TYPES->{$self->{lock_type}} ){ | 
| 104 | 0 |  |  |  |  | 0 | $self->{lock_type} = $TYPES->{$self->{lock_type}}; | 
| 105 |  |  |  |  |  |  | } | 
| 106 |  |  |  |  |  |  |  | 
| 107 |  |  |  |  |  |  | ### need the hostname | 
| 108 | 1167 | 100 |  |  |  | 3173 | if( !$HOSTNAME ){ | 
| 109 | 68 |  |  |  |  | 74569 | require Sys::Hostname; | 
| 110 | 68 |  |  |  |  | 118772 | $HOSTNAME = Sys::Hostname::hostname(); | 
| 111 |  |  |  |  |  |  | } | 
| 112 |  |  |  |  |  |  |  | 
| 113 |  |  |  |  |  |  | ### quick usage check | 
| 114 |  |  |  |  |  |  | croak ($errstr = "Usage: my \$f = $class->new('/pathtofile/file',\n" | 
| 115 |  |  |  |  |  |  | ."'BLOCKING|EXCLUSIVE|NONBLOCKING|SHARED', [blocking_timeout, stale_lock_timeout]);\n" | 
| 116 |  |  |  |  |  |  | ."(You passed \"$self->{file}\" and \"$self->{lock_type}\")") | 
| 117 | 1167 | 50 |  |  |  | 4462 | unless length($self->{file}); | 
| 118 |  |  |  |  |  |  |  | 
| 119 |  |  |  |  |  |  | croak ($errstr = "Unrecognized lock_type operation setting [$self->{lock_type}]") | 
| 120 | 1167 | 50 | 33 |  |  | 8829 | unless $self->{lock_type} && $self->{lock_type} =~ /^\d+$/; | 
| 121 |  |  |  |  |  |  |  | 
| 122 |  |  |  |  |  |  | ### Input syntax checking passed, ready to bless | 
| 123 | 1167 |  |  |  |  | 3037 | bless $self, $class; | 
| 124 |  |  |  |  |  |  |  | 
| 125 |  |  |  |  |  |  | ### choose a random filename | 
| 126 | 1167 |  |  |  |  | 3511 | $self->{rand_file} = rand_file( $self->{file} ); | 
| 127 |  |  |  |  |  |  |  | 
| 128 |  |  |  |  |  |  | ### choose the lock filename | 
| 129 | 1167 |  |  |  |  | 4640 | $self->{lock_file} = $self->{file} . $LOCK_EXTENSION; | 
| 130 |  |  |  |  |  |  |  | 
| 131 |  |  |  |  |  |  | my $quit_time = $self->{blocking_timeout} && | 
| 132 |  |  |  |  |  |  | !($self->{lock_type} & LOCK_NB) ? | 
| 133 | 1167 | 100 | 66 |  |  | 4522 | time() + $self->{blocking_timeout} : 0; | 
| 134 |  |  |  |  |  |  |  | 
| 135 |  |  |  |  |  |  | ### remove an old lockfile if it is older than the stale_timeout | 
| 136 | 1167 | 50 | 100 |  |  | 22996 | if( -e $self->{lock_file} && | 
|  |  |  | 66 |  |  |  |  | 
| 137 |  |  |  |  |  |  | $self->{stale_lock_timeout} > 0 && | 
| 138 |  |  |  |  |  |  | time() - (stat _)[9] > $self->{stale_lock_timeout} ){ | 
| 139 | 0 |  |  |  |  | 0 | unlink $self->{lock_file}; | 
| 140 |  |  |  |  |  |  | } | 
| 141 |  |  |  |  |  |  |  | 
| 142 | 1167 |  |  |  |  | 2909 | while (1) { | 
| 143 |  |  |  |  |  |  | ### open the temporary file | 
| 144 | 1373 | 50 |  |  |  | 6039 | $self->create_magic | 
| 145 |  |  |  |  |  |  | or return undef; | 
| 146 |  |  |  |  |  |  |  | 
| 147 | 1373 | 100 |  |  |  | 5331 | if ( $self->{lock_type} & LOCK_EX ) { | 
|  |  | 50 |  |  |  |  |  | 
| 148 | 1344 | 100 |  |  |  | 4291 | last if $self->do_lock; | 
| 149 |  |  |  |  |  |  | } elsif ( $self->{lock_type} & LOCK_SH ) { | 
| 150 | 29 | 100 |  |  |  | 125 | last if $self->do_lock_shared; | 
| 151 |  |  |  |  |  |  | } else { | 
| 152 | 0 |  |  |  |  | 0 | $errstr = "Unknown lock_type [$self->{lock_type}]"; | 
| 153 | 0 |  |  |  |  | 0 | return undef; | 
| 154 |  |  |  |  |  |  | } | 
| 155 |  |  |  |  |  |  |  | 
| 156 |  |  |  |  |  |  | ### Lock failed! | 
| 157 |  |  |  |  |  |  |  | 
| 158 |  |  |  |  |  |  | ### I know this may be a race condition, but it's okay.  It is just a | 
| 159 |  |  |  |  |  |  | ### stab in the dark to possibly find long dead processes. | 
| 160 |  |  |  |  |  |  |  | 
| 161 |  |  |  |  |  |  | ### If lock exists and is readable, see who is mooching on the lock | 
| 162 |  |  |  |  |  |  |  | 
| 163 | 218 |  |  |  |  | 572 | my $fh; | 
| 164 | 218 | 100 | 100 |  |  | 10711 | if ( -e $self->{lock_file} && | 
| 165 |  |  |  |  |  |  | open ($fh,'+<', $self->{lock_file}) ){ | 
| 166 |  |  |  |  |  |  |  | 
| 167 | 166 |  |  |  |  | 736 | my @mine = (); | 
| 168 | 166 |  |  |  |  | 407 | my @them = (); | 
| 169 | 166 |  |  |  |  | 385 | my @dead = (); | 
| 170 |  |  |  |  |  |  |  | 
| 171 | 166 |  |  |  |  | 858 | my $has_lock_exclusive = !((stat _)[2] & $SHARE_BIT); | 
| 172 | 166 |  |  |  |  | 584 | my $try_lock_exclusive = !($self->{lock_type} & LOCK_SH); | 
| 173 |  |  |  |  |  |  |  | 
| 174 | 166 |  |  |  |  | 2850 | while(defined(my $line=<$fh>)){ | 
| 175 | 166 | 50 |  |  |  | 4476 | if ($line =~ /^\Q$HOSTNAME\E (-?\d+) /) { | 
| 176 | 166 |  |  |  |  | 1373 | my $pid = $1; | 
| 177 | 166 | 100 |  |  |  | 2770 | if ($pid == $$) {       # This is me. | 
|  |  | 100 |  |  |  |  |  | 
| 178 | 1 |  |  |  |  | 13 | push @mine, $line; | 
| 179 |  |  |  |  |  |  | }elsif(kill 0, $pid) {  # Still running on this host. | 
| 180 | 163 |  |  |  |  | 2328 | push @them, $line; | 
| 181 |  |  |  |  |  |  | }else{                  # Finished running on this host. | 
| 182 | 2 |  |  |  |  | 29 | push @dead, $line; | 
| 183 |  |  |  |  |  |  | } | 
| 184 |  |  |  |  |  |  | } else {                  # Running on another host, so | 
| 185 | 0 |  |  |  |  | 0 | push @them, $line;      #  assume it is still running. | 
| 186 |  |  |  |  |  |  | } | 
| 187 |  |  |  |  |  |  | } | 
| 188 |  |  |  |  |  |  |  | 
| 189 |  |  |  |  |  |  | ### If there was at least one stale lock discovered... | 
| 190 | 166 | 100 |  |  |  | 881 | if (@dead) { | 
| 191 |  |  |  |  |  |  | # Lock lock_file to avoid a race condition. | 
| 192 | 2 |  |  |  |  | 18 | local $LOCK_EXTENSION = ".shared"; | 
| 193 |  |  |  |  |  |  | my $lock = new File::NFSLock { | 
| 194 |  |  |  |  |  |  | file => $self->{lock_file}, | 
| 195 | 2 |  |  |  |  | 66 | lock_type => LOCK_EX, | 
| 196 |  |  |  |  |  |  | blocking_timeout => 62, | 
| 197 |  |  |  |  |  |  | stale_lock_timeout => 60, | 
| 198 |  |  |  |  |  |  | }; | 
| 199 |  |  |  |  |  |  |  | 
| 200 |  |  |  |  |  |  | ### Rescan in case lock contents were modified between time stale lock | 
| 201 |  |  |  |  |  |  | ###  was discovered and lockfile lock was acquired. | 
| 202 | 2 |  |  |  |  | 23 | seek ($fh, 0, 0); | 
| 203 | 2 |  |  |  |  | 14 | my $content = ''; | 
| 204 | 2 |  |  |  |  | 26 | while(defined(my $line=<$fh>)){ | 
| 205 | 2 | 50 |  |  |  | 73 | if ($line =~ /^\Q$HOSTNAME\E (-?\d+) /) { | 
| 206 | 2 |  |  |  |  | 10 | my $pid = $1; | 
| 207 | 2 | 50 |  |  |  | 45 | next if (!kill 0, $pid);  # Skip dead locks from this host | 
| 208 |  |  |  |  |  |  | } | 
| 209 | 0 |  |  |  |  | 0 | $content .= $line;          # Save valid locks | 
| 210 |  |  |  |  |  |  | } | 
| 211 |  |  |  |  |  |  |  | 
| 212 |  |  |  |  |  |  | ### Save any valid locks or wipe file. | 
| 213 | 2 | 50 |  |  |  | 12 | if( length($content) ){ | 
| 214 | 0 |  |  |  |  | 0 | seek     $fh, 0, 0; | 
| 215 | 0 |  |  |  |  | 0 | print    $fh $content; | 
| 216 | 0 |  |  |  |  | 0 | truncate $fh, length($content); | 
| 217 | 0 |  |  |  |  | 0 | close    $fh; | 
| 218 |  |  |  |  |  |  | }else{ | 
| 219 | 2 |  |  |  |  | 18 | close $fh; | 
| 220 | 2 |  |  |  |  | 96 | unlink $self->{lock_file}; | 
| 221 |  |  |  |  |  |  | } | 
| 222 |  |  |  |  |  |  |  | 
| 223 |  |  |  |  |  |  | ### No "dead" or stale locks found. | 
| 224 |  |  |  |  |  |  | } else { | 
| 225 | 164 |  |  |  |  | 2903 | close $fh; | 
| 226 |  |  |  |  |  |  | } | 
| 227 |  |  |  |  |  |  |  | 
| 228 |  |  |  |  |  |  | ### If attempting to acquire the same type of lock | 
| 229 |  |  |  |  |  |  | ###  that it is already locked with, and I've already | 
| 230 |  |  |  |  |  |  | ###  locked it myself, then it is safe to lock again. | 
| 231 |  |  |  |  |  |  | ### Just kick out successfully without really locking. | 
| 232 |  |  |  |  |  |  | ### Assumes locks will be released in the reverse | 
| 233 |  |  |  |  |  |  | ###  order from how they were established. | 
| 234 | 166 | 100 | 100 |  |  | 1695 | if ($try_lock_exclusive eq $has_lock_exclusive && @mine){ | 
| 235 | 1 |  |  |  |  | 8 | return $self; | 
| 236 |  |  |  |  |  |  | } | 
| 237 |  |  |  |  |  |  | } | 
| 238 |  |  |  |  |  |  |  | 
| 239 |  |  |  |  |  |  | ### If non-blocking, then kick out now. | 
| 240 |  |  |  |  |  |  | ### ($errstr might already be set to the reason.) | 
| 241 | 217 | 100 |  |  |  | 1056 | if ($self->{lock_type} & LOCK_NB) { | 
| 242 | 11 |  | 50 |  |  | 139 | $errstr ||= "NONBLOCKING lock failed!"; | 
| 243 | 11 |  |  |  |  | 85 | return undef; | 
| 244 |  |  |  |  |  |  | } | 
| 245 |  |  |  |  |  |  |  | 
| 246 |  |  |  |  |  |  | ### wait a moment | 
| 247 | 206 |  |  |  |  | 206047494 | sleep(1); | 
| 248 |  |  |  |  |  |  |  | 
| 249 |  |  |  |  |  |  | ### but don't wait past the time out | 
| 250 | 206 | 50 | 66 |  |  | 5596 | if( $quit_time && (time > $quit_time) ){ | 
| 251 | 0 |  |  |  |  | 0 | $errstr = "Timed out waiting for blocking lock"; | 
| 252 | 0 |  |  |  |  | 0 | return undef; | 
| 253 |  |  |  |  |  |  | } | 
| 254 |  |  |  |  |  |  |  | 
| 255 |  |  |  |  |  |  | # BLOCKING Lock, So Keep Trying | 
| 256 |  |  |  |  |  |  | } | 
| 257 |  |  |  |  |  |  |  | 
| 258 |  |  |  |  |  |  | ### clear up the NFS cache | 
| 259 | 1155 |  |  |  |  | 5762 | $self->uncache; | 
| 260 |  |  |  |  |  |  |  | 
| 261 |  |  |  |  |  |  | ### Yes, the lock has been acquired. | 
| 262 | 1155 |  |  |  |  | 4827 | delete $self->{unlocked}; | 
| 263 |  |  |  |  |  |  |  | 
| 264 | 1155 |  |  |  |  | 4130 | return $self; | 
| 265 |  |  |  |  |  |  | } | 
| 266 |  |  |  |  |  |  |  | 
| 267 |  |  |  |  |  |  | sub DESTROY { | 
| 268 | 1167 |  |  | 1167 |  | 36093930 | shift()->unlock(); | 
| 269 |  |  |  |  |  |  | } | 
| 270 |  |  |  |  |  |  |  | 
| 271 |  |  |  |  |  |  | sub unlock ($) { | 
| 272 | 1187 |  |  | 1187 | 1 | 100015469 | my $self = shift; | 
| 273 | 1187 | 100 |  |  |  | 3656 | if (!$self->{unlocked}) { | 
| 274 | 1155 | 50 |  |  |  | 19555 | unlink( $self->{rand_file} ) if -e $self->{rand_file}; | 
| 275 | 1155 | 100 |  |  |  | 4797 | if( $self->{lock_type} & LOCK_SH ){ | 
| 276 | 33 |  |  |  |  | 355 | $self->do_unlock_shared; | 
| 277 |  |  |  |  |  |  | }else{ | 
| 278 | 1122 |  |  |  |  | 3230 | $self->do_unlock; | 
| 279 |  |  |  |  |  |  | } | 
| 280 | 1155 |  |  |  |  | 4727 | $self->{unlocked} = 1; | 
| 281 | 1155 |  |  |  |  | 3383 | foreach my $signal (@CATCH_SIGS) { | 
| 282 | 2310 | 100 | 66 |  |  | 16101 | if ($SIG{$signal} && | 
| 283 |  |  |  |  |  |  | ($SIG{$signal} eq $graceful_sig)) { | 
| 284 |  |  |  |  |  |  | # Revert handler back to how it used to be. | 
| 285 |  |  |  |  |  |  | # Unfortunately, this will restore the | 
| 286 |  |  |  |  |  |  | # handler back even if there are other | 
| 287 |  |  |  |  |  |  | # locks still in tact, but for most cases, | 
| 288 |  |  |  |  |  |  | # it will still be an improvement. | 
| 289 | 2240 |  |  |  |  | 32439 | delete $SIG{$signal}; | 
| 290 |  |  |  |  |  |  | } | 
| 291 |  |  |  |  |  |  | } | 
| 292 |  |  |  |  |  |  | } | 
| 293 | 1187 |  |  |  |  | 17449 | return 1; | 
| 294 |  |  |  |  |  |  | } | 
| 295 |  |  |  |  |  |  |  | 
| 296 |  |  |  |  |  |  | ###----------------------------------------------------------------### | 
| 297 |  |  |  |  |  |  |  | 
| 298 |  |  |  |  |  |  | # concepts for these routines were taken from Mail::Box which | 
| 299 |  |  |  |  |  |  | # took the concepts from Mail::Folder | 
| 300 |  |  |  |  |  |  |  | 
| 301 |  |  |  |  |  |  |  | 
| 302 |  |  |  |  |  |  | sub rand_file ($) { | 
| 303 | 2322 |  |  | 2322 | 0 | 4179 | my $file = shift; | 
| 304 | 2322 |  |  |  |  | 17810 | "$file.tmp.". time()%10000 .'.'. $$ .'.'. int(rand()*10000); | 
| 305 |  |  |  |  |  |  | } | 
| 306 |  |  |  |  |  |  |  | 
| 307 |  |  |  |  |  |  | sub create_magic ($;$) { | 
| 308 | 1401 |  |  | 1401 | 0 | 2965 | $errstr = undef; | 
| 309 | 1401 |  |  |  |  | 2606 | my $self = shift; | 
| 310 | 1401 |  | 66 |  |  | 7406 | my $append_file = shift || $self->{rand_file}; | 
| 311 | 1401 |  | 66 |  |  | 11611 | $self->{lock_line} ||= "$HOSTNAME $self->{lock_pid} ".time()." ".int(rand()*10000)."\n"; | 
| 312 | 1401 | 50 |  |  |  | 106237 | open (my $fh,'>>', $append_file) or do { $errstr = "Couldn't open \"$append_file\" [$!]"; return undef; }; | 
|  | 0 |  |  |  |  | 0 |  | 
|  | 0 |  |  |  |  | 0 |  | 
| 313 | 1401 |  |  |  |  | 12078 | print $fh $self->{lock_line}; | 
| 314 | 1401 |  |  |  |  | 45362 | close $fh; | 
| 315 | 1401 |  |  |  |  | 10581 | return 1; | 
| 316 |  |  |  |  |  |  | } | 
| 317 |  |  |  |  |  |  |  | 
| 318 |  |  |  |  |  |  | sub do_lock { | 
| 319 | 1344 |  |  | 1344 | 0 | 2645 | $errstr = undef; | 
| 320 | 1344 |  |  |  |  | 2573 | my $self = shift; | 
| 321 | 1344 |  |  |  |  | 2628 | my $lock_file = $self->{lock_file}; | 
| 322 | 1344 |  |  |  |  | 2247 | my $rand_file = $self->{rand_file}; | 
| 323 | 1344 |  |  |  |  | 2125 | my $chmod = 0600; | 
| 324 | 1344 | 50 |  |  |  | 23139 | chmod( $chmod, $rand_file) | 
| 325 |  |  |  |  |  |  | || die "I need ability to chmod files to adequatetly perform locking"; | 
| 326 |  |  |  |  |  |  |  | 
| 327 |  |  |  |  |  |  | ### try a hard link, if it worked | 
| 328 |  |  |  |  |  |  | ### two files are pointing to $rand_file | 
| 329 | 1344 |  | 66 |  |  | 46148 | my $success = link( $rand_file, $lock_file ) | 
| 330 |  |  |  |  |  |  | && -e $rand_file && (stat _)[3] == 2; | 
| 331 | 1344 |  |  |  |  | 38895 | unlink $rand_file; | 
| 332 |  |  |  |  |  |  |  | 
| 333 | 1344 |  |  |  |  | 8905 | return $success; | 
| 334 |  |  |  |  |  |  | } | 
| 335 |  |  |  |  |  |  |  | 
| 336 |  |  |  |  |  |  | sub do_lock_shared { | 
| 337 | 29 |  |  | 29 | 0 | 56 | $errstr = undef; | 
| 338 | 29 |  |  |  |  | 61 | my $self = shift; | 
| 339 | 29 |  |  |  |  | 66 | my $lock_file  = $self->{lock_file}; | 
| 340 | 29 |  |  |  |  | 78 | my $rand_file  = $self->{rand_file}; | 
| 341 |  |  |  |  |  |  |  | 
| 342 |  |  |  |  |  |  | ### chmod local file to make sure we know before | 
| 343 | 29 |  |  |  |  | 46 | my $chmod = 0600; | 
| 344 | 29 |  |  |  |  | 54 | $chmod |= $SHARE_BIT; | 
| 345 | 29 | 50 |  |  |  | 590 | chmod( $chmod, $rand_file) | 
| 346 |  |  |  |  |  |  | || die "I need ability to chmod files to adequatetly perform locking"; | 
| 347 |  |  |  |  |  |  |  | 
| 348 |  |  |  |  |  |  | ### lock the locking process | 
| 349 | 29 |  |  |  |  | 389 | local $LOCK_EXTENSION = ".shared"; | 
| 350 | 29 |  |  |  |  | 658 | my $lock = new File::NFSLock { | 
| 351 |  |  |  |  |  |  | file => $lock_file, | 
| 352 |  |  |  |  |  |  | lock_type => LOCK_EX, | 
| 353 |  |  |  |  |  |  | blocking_timeout => 62, | 
| 354 |  |  |  |  |  |  | stale_lock_timeout => 60, | 
| 355 |  |  |  |  |  |  | }; | 
| 356 |  |  |  |  |  |  | # The ".shared" lock will be released as this status | 
| 357 |  |  |  |  |  |  | # is returned, whether or not the status is successful. | 
| 358 |  |  |  |  |  |  |  | 
| 359 |  |  |  |  |  |  | ### If I didn't have exclusive and the shared bit is not | 
| 360 |  |  |  |  |  |  | ### set, I have failed | 
| 361 |  |  |  |  |  |  |  | 
| 362 |  |  |  |  |  |  | ### Try to create $lock_file from the special | 
| 363 |  |  |  |  |  |  | ### file with the magic $SHARE_BIT set. | 
| 364 | 29 |  |  |  |  | 409 | my $success = link( $rand_file, $lock_file); | 
| 365 | 29 |  |  |  |  | 1201 | unlink $rand_file; | 
| 366 | 29 | 100 | 66 |  |  | 1183 | if ( !$success && | 
|  |  | 100 | 100 |  |  |  |  | 
| 367 |  |  |  |  |  |  | -e $lock_file && | 
| 368 |  |  |  |  |  |  | ((stat _)[2] & $SHARE_BIT) != $SHARE_BIT ){ | 
| 369 |  |  |  |  |  |  |  | 
| 370 | 2 |  |  |  |  | 15 | $errstr = 'Exclusive lock exists.'; | 
| 371 | 2 |  |  |  |  | 17 | return undef; | 
| 372 |  |  |  |  |  |  |  | 
| 373 |  |  |  |  |  |  | } elsif ( !$success ) { | 
| 374 |  |  |  |  |  |  | ### Shared lock exists, append my lock | 
| 375 | 20 |  |  |  |  | 159 | $self->create_magic ($self->{lock_file}); | 
| 376 |  |  |  |  |  |  | } | 
| 377 |  |  |  |  |  |  |  | 
| 378 |  |  |  |  |  |  | # Success | 
| 379 | 27 |  |  |  |  | 212 | return 1; | 
| 380 |  |  |  |  |  |  | } | 
| 381 |  |  |  |  |  |  |  | 
| 382 |  |  |  |  |  |  | sub do_unlock ($) { | 
| 383 | 1122 |  |  | 1122 | 0 | 42681 | return unlink shift->{lock_file}; | 
| 384 |  |  |  |  |  |  | } | 
| 385 |  |  |  |  |  |  |  | 
| 386 |  |  |  |  |  |  | sub do_unlock_shared ($) { | 
| 387 | 33 |  |  | 33 | 0 | 223 | $errstr = undef; | 
| 388 | 33 |  |  |  |  | 175 | my $self = shift; | 
| 389 | 33 |  |  |  |  | 189 | my $lock_file = $self->{lock_file}; | 
| 390 | 33 |  |  |  |  | 201 | my $lock_line = $self->{lock_line}; | 
| 391 |  |  |  |  |  |  |  | 
| 392 |  |  |  |  |  |  | ### lock the locking process | 
| 393 | 33 |  |  |  |  | 630 | local $LOCK_EXTENSION = '.shared'; | 
| 394 | 33 |  |  |  |  | 903 | my $lock = new File::NFSLock ($lock_file,LOCK_EX,62,60); | 
| 395 |  |  |  |  |  |  |  | 
| 396 |  |  |  |  |  |  | ### get the handle on the lock file | 
| 397 | 33 |  |  |  |  | 168 | my $fh; | 
| 398 | 33 | 50 |  |  |  | 1387 | if( ! open ($fh,'+<', $lock_file) ){ | 
| 399 | 0 | 0 |  |  |  | 0 | if( ! -e $lock_file ){ | 
| 400 | 0 |  |  |  |  | 0 | return 1; | 
| 401 |  |  |  |  |  |  | }else{ | 
| 402 | 0 |  |  |  |  | 0 | die "Could not open for writing shared lock file $lock_file ($!)"; | 
| 403 |  |  |  |  |  |  | } | 
| 404 |  |  |  |  |  |  | } | 
| 405 |  |  |  |  |  |  |  | 
| 406 |  |  |  |  |  |  | ### read existing file | 
| 407 | 33 |  |  |  |  | 175 | my $content = ''; | 
| 408 | 33 |  |  |  |  | 823 | while(defined(my $line=<$fh>)){ | 
| 409 | 251 | 100 |  |  |  | 814 | next if $line eq $lock_line; | 
| 410 | 218 |  |  |  |  | 931 | $content .= $line; | 
| 411 |  |  |  |  |  |  | } | 
| 412 |  |  |  |  |  |  |  | 
| 413 |  |  |  |  |  |  | ### other shared locks exist | 
| 414 | 33 | 100 |  |  |  | 296 | if( length($content) ){ | 
| 415 | 28 |  |  |  |  | 281 | seek     $fh, 0, 0; | 
| 416 | 28 |  |  |  |  | 224 | print    $fh $content; | 
| 417 | 28 |  |  |  |  | 1540 | truncate $fh, length($content); | 
| 418 | 28 |  |  |  |  | 905 | close    $fh; | 
| 419 |  |  |  |  |  |  |  | 
| 420 |  |  |  |  |  |  | ### only I exist | 
| 421 |  |  |  |  |  |  | }else{ | 
| 422 | 5 |  |  |  |  | 60 | close $fh; | 
| 423 | 5 |  |  |  |  | 503 | unlink $lock_file; | 
| 424 |  |  |  |  |  |  | } | 
| 425 |  |  |  |  |  |  |  | 
| 426 |  |  |  |  |  |  | } | 
| 427 |  |  |  |  |  |  |  | 
| 428 |  |  |  |  |  |  | sub uncache ($;$) { | 
| 429 |  |  |  |  |  |  | # allow as method call | 
| 430 | 1155 |  |  | 1155 | 1 | 2309 | my $file = pop; | 
| 431 | 1155 | 50 |  |  |  | 4194 | ref $file && ($file = $file->{file}); | 
| 432 | 1155 |  |  |  |  | 2550 | my $rand_file = rand_file( $file ); | 
| 433 |  |  |  |  |  |  |  | 
| 434 |  |  |  |  |  |  | ### hard link to the actual file which will bring it up to date | 
| 435 | 1155 |  | 66 |  |  | 64325 | return ( link( $file, $rand_file) && unlink($rand_file) ); | 
| 436 |  |  |  |  |  |  | } | 
| 437 |  |  |  |  |  |  |  | 
| 438 |  |  |  |  |  |  | sub newpid { | 
| 439 | 12 |  |  | 12 | 1 | 8014559 | my $self = shift; | 
| 440 |  |  |  |  |  |  | # Detect if this is the parent or the child | 
| 441 | 12 | 100 |  |  |  | 506 | if ($self->{lock_pid} == $$) { | 
| 442 |  |  |  |  |  |  | # This is the parent | 
| 443 |  |  |  |  |  |  |  | 
| 444 |  |  |  |  |  |  | # Must wait for child to call newpid before processing. | 
| 445 |  |  |  |  |  |  | # A little patience for the child to call newpid | 
| 446 | 4 |  |  |  |  | 37 | my $patience = time + 10; | 
| 447 | 4 |  |  |  |  | 75 | while (time < $patience) { | 
| 448 | 46 | 100 |  |  |  | 2482 | if (rename("$self->{lock_file}.fork",$self->{rand_file})) { | 
| 449 |  |  |  |  |  |  | # Child finished its newpid call. | 
| 450 |  |  |  |  |  |  | # Wipe the signal file. | 
| 451 | 4 |  |  |  |  | 255 | unlink $self->{rand_file}; | 
| 452 | 4 |  |  |  |  | 71 | last; | 
| 453 |  |  |  |  |  |  | } | 
| 454 |  |  |  |  |  |  | # Brief pause before checking again | 
| 455 |  |  |  |  |  |  | # to avoid intensive IO across NFS. | 
| 456 | 42 |  |  |  |  | 4210402 | select(undef,undef,undef,0.1); | 
| 457 |  |  |  |  |  |  | } | 
| 458 |  |  |  |  |  |  |  | 
| 459 |  |  |  |  |  |  | # Child finished running newpid() and acquired shared lock | 
| 460 |  |  |  |  |  |  | # So now we're safe to continue without risk of | 
| 461 |  |  |  |  |  |  | # blowing away the lock prematurely. | 
| 462 | 4 | 100 |  |  |  | 131 | unless ( $self->{lock_type} & LOCK_SH ) { | 
| 463 |  |  |  |  |  |  | # If it's not already a SHared lock, then | 
| 464 |  |  |  |  |  |  | # just switch it from EXclusive to SHared | 
| 465 |  |  |  |  |  |  | # from this process's point of view. | 
| 466 |  |  |  |  |  |  | # Then the child will still hold the lock | 
| 467 |  |  |  |  |  |  | # if the parent releases it first. | 
| 468 |  |  |  |  |  |  | # (Don't chmod the lock file.) | 
| 469 | 2 |  |  |  |  | 64 | $self->{lock_type} |= LOCK_SH; | 
| 470 |  |  |  |  |  |  | } | 
| 471 |  |  |  |  |  |  | } else { | 
| 472 |  |  |  |  |  |  | # This is the new child | 
| 473 |  |  |  |  |  |  |  | 
| 474 |  |  |  |  |  |  | # Fix lock_pid to the new pid. | 
| 475 | 8 |  |  |  |  | 171 | $self->{lock_pid} = $$; | 
| 476 |  |  |  |  |  |  |  | 
| 477 |  |  |  |  |  |  | # We can leave the old lock_line in the lock_file | 
| 478 |  |  |  |  |  |  | # But we need to add the new lock_line for this pid. | 
| 479 |  |  |  |  |  |  |  | 
| 480 |  |  |  |  |  |  | # Clear lock_line to create a fresh one. | 
| 481 | 8 |  |  |  |  | 434 | delete $self->{lock_line}; | 
| 482 |  |  |  |  |  |  | # Append a new lock_line to the lock_file. | 
| 483 | 8 |  |  |  |  | 317 | $self->create_magic($self->{lock_file}); | 
| 484 |  |  |  |  |  |  |  | 
| 485 | 8 | 100 |  |  |  | 157 | unless ( $self->{lock_type} & LOCK_SH ) { | 
| 486 |  |  |  |  |  |  | # If it's not already a SHared lock, then | 
| 487 |  |  |  |  |  |  | # just switch it from EXclusive to SHared | 
| 488 |  |  |  |  |  |  | # from this process's point of view. | 
| 489 |  |  |  |  |  |  | # Then the parent will still hold the lock | 
| 490 |  |  |  |  |  |  | # if this child releases it first. | 
| 491 |  |  |  |  |  |  | # (Don't chmod the lock file.) | 
| 492 | 4 |  |  |  |  | 56 | $self->{lock_type} |= LOCK_SH; | 
| 493 |  |  |  |  |  |  | } | 
| 494 |  |  |  |  |  |  |  | 
| 495 |  |  |  |  |  |  | # Create signal file to notify parent that | 
| 496 |  |  |  |  |  |  | # the lock_line entry has been delegated. | 
| 497 | 8 |  |  |  |  | 663 | open (my $fh, '>', "$self->{lock_file}.fork"); | 
| 498 | 8 |  |  |  |  | 222 | close($fh); | 
| 499 |  |  |  |  |  |  | } | 
| 500 |  |  |  |  |  |  | } | 
| 501 |  |  |  |  |  |  |  | 
| 502 |  |  |  |  |  |  | sub fork { | 
| 503 | 6 |  |  | 6 | 1 | 1838 | my $self = shift; | 
| 504 |  |  |  |  |  |  | # Store fork response. | 
| 505 | 6 |  |  |  |  | 5297 | my $pid = CORE::fork(); | 
| 506 | 6 | 50 | 33 |  |  | 646 | if (defined $pid and !$self->{unlocked}) { | 
| 507 |  |  |  |  |  |  | # Fork worked and we really have a lock to deal with | 
| 508 |  |  |  |  |  |  | # So upgrade to shared lock across both parent and child | 
| 509 | 6 |  |  |  |  | 192 | $self->newpid; | 
| 510 |  |  |  |  |  |  | } | 
| 511 |  |  |  |  |  |  | # Return original fork response | 
| 512 | 6 |  |  |  |  | 187 | return $pid; | 
| 513 |  |  |  |  |  |  | } | 
| 514 |  |  |  |  |  |  |  | 
| 515 |  |  |  |  |  |  | 1; | 
| 516 |  |  |  |  |  |  |  | 
| 517 |  |  |  |  |  |  |  | 
| 518 |  |  |  |  |  |  | =pod | 
| 519 |  |  |  |  |  |  |  | 
| 520 |  |  |  |  |  |  | =head1 NAME | 
| 521 |  |  |  |  |  |  |  | 
| 522 |  |  |  |  |  |  | File::NFSLock - perl module to do NFS (or not) locking | 
| 523 |  |  |  |  |  |  |  | 
| 524 |  |  |  |  |  |  | =head1 SYNOPSIS | 
| 525 |  |  |  |  |  |  |  | 
| 526 |  |  |  |  |  |  | use File::NFSLock qw(uncache); | 
| 527 |  |  |  |  |  |  | use Fcntl qw(LOCK_EX LOCK_NB); | 
| 528 |  |  |  |  |  |  |  | 
| 529 |  |  |  |  |  |  | my $file = "somefile"; | 
| 530 |  |  |  |  |  |  |  | 
| 531 |  |  |  |  |  |  | ### set up a lock - lasts until object looses scope | 
| 532 |  |  |  |  |  |  | if (my $lock = new File::NFSLock { | 
| 533 |  |  |  |  |  |  | file      => $file, | 
| 534 |  |  |  |  |  |  | lock_type => LOCK_EX|LOCK_NB, | 
| 535 |  |  |  |  |  |  | blocking_timeout   => 10,      # 10 sec | 
| 536 |  |  |  |  |  |  | stale_lock_timeout => 30 * 60, # 30 min | 
| 537 |  |  |  |  |  |  | }) { | 
| 538 |  |  |  |  |  |  |  | 
| 539 |  |  |  |  |  |  | ### OR | 
| 540 |  |  |  |  |  |  | ### my $lock = File::NFSLock->new($file,LOCK_EX|LOCK_NB,10,30*60); | 
| 541 |  |  |  |  |  |  |  | 
| 542 |  |  |  |  |  |  | ### do write protected stuff on $file | 
| 543 |  |  |  |  |  |  | ### at this point $file is uncached from NFS (most recent) | 
| 544 |  |  |  |  |  |  | open(FILE, "+<$file") || die $!; | 
| 545 |  |  |  |  |  |  |  | 
| 546 |  |  |  |  |  |  | ### or open it any way you like | 
| 547 |  |  |  |  |  |  | ### my $fh = IO::File->open( $file, 'w' ) || die $! | 
| 548 |  |  |  |  |  |  |  | 
| 549 |  |  |  |  |  |  | ### update (uncache across NFS) other files | 
| 550 |  |  |  |  |  |  | uncache("someotherfile1"); | 
| 551 |  |  |  |  |  |  | uncache("someotherfile2"); | 
| 552 |  |  |  |  |  |  | # open(FILE2,"someotherfile1"); | 
| 553 |  |  |  |  |  |  |  | 
| 554 |  |  |  |  |  |  | ### unlock it | 
| 555 |  |  |  |  |  |  | $lock->unlock(); | 
| 556 |  |  |  |  |  |  | ### OR | 
| 557 |  |  |  |  |  |  | ### undef $lock; | 
| 558 |  |  |  |  |  |  | ### OR let $lock go out of scope | 
| 559 |  |  |  |  |  |  | }else{ | 
| 560 |  |  |  |  |  |  | die "I couldn't lock the file [$File::NFSLock::errstr]"; | 
| 561 |  |  |  |  |  |  | } | 
| 562 |  |  |  |  |  |  |  | 
| 563 |  |  |  |  |  |  |  | 
| 564 |  |  |  |  |  |  | =head1 DESCRIPTION | 
| 565 |  |  |  |  |  |  |  | 
| 566 |  |  |  |  |  |  | Program based of concept of hard linking of files being atomic across | 
| 567 |  |  |  |  |  |  | NFS.  This concept was mentioned in Mail::Box::Locker (which was | 
| 568 |  |  |  |  |  |  | originally presented in Mail::Folder::Maildir).  Some routine flow is | 
| 569 |  |  |  |  |  |  | taken from there -- particularly the idea of creating a random local | 
| 570 |  |  |  |  |  |  | file, hard linking a common file to the local file, and then checking | 
| 571 |  |  |  |  |  |  | the nlink status.  Some ideologies were not complete (uncache | 
| 572 |  |  |  |  |  |  | mechanism, shared locking) and some coding was even incorrect (wrong | 
| 573 |  |  |  |  |  |  | stat index).  File::NFSLock was written to be light, generic, | 
| 574 |  |  |  |  |  |  | and fast. | 
| 575 |  |  |  |  |  |  |  | 
| 576 |  |  |  |  |  |  |  | 
| 577 |  |  |  |  |  |  | =head1 USAGE | 
| 578 |  |  |  |  |  |  |  | 
| 579 |  |  |  |  |  |  | Locking occurs by creating a File::NFSLock object.  If the object | 
| 580 |  |  |  |  |  |  | is created successfully, a lock is currently in place and remains in | 
| 581 |  |  |  |  |  |  | place until the lock object goes out of scope (or calls the unlock | 
| 582 |  |  |  |  |  |  | method). | 
| 583 |  |  |  |  |  |  |  | 
| 584 |  |  |  |  |  |  | A lock object is created by calling the new method and passing two | 
| 585 |  |  |  |  |  |  | to four parameters in the following manner: | 
| 586 |  |  |  |  |  |  |  | 
| 587 |  |  |  |  |  |  | my $lock = File::NFSLock->new($file, | 
| 588 |  |  |  |  |  |  | $lock_type, | 
| 589 |  |  |  |  |  |  | $blocking_timeout, | 
| 590 |  |  |  |  |  |  | $stale_lock_timeout, | 
| 591 |  |  |  |  |  |  | ); | 
| 592 |  |  |  |  |  |  |  | 
| 593 |  |  |  |  |  |  | Additionally, parameters may be passed as a hashref: | 
| 594 |  |  |  |  |  |  |  | 
| 595 |  |  |  |  |  |  | my $lock = File::NFSLock->new({ | 
| 596 |  |  |  |  |  |  | file               => $file, | 
| 597 |  |  |  |  |  |  | lock_type          => $lock_type, | 
| 598 |  |  |  |  |  |  | blocking_timeout   => $blocking_timeout, | 
| 599 |  |  |  |  |  |  | stale_lock_timeout => $stale_lock_timeout, | 
| 600 |  |  |  |  |  |  | }); | 
| 601 |  |  |  |  |  |  |  | 
| 602 |  |  |  |  |  |  | =head1 PARAMETERS | 
| 603 |  |  |  |  |  |  |  | 
| 604 |  |  |  |  |  |  | =over 4 | 
| 605 |  |  |  |  |  |  |  | 
| 606 |  |  |  |  |  |  | =item Parameter 1: file | 
| 607 |  |  |  |  |  |  |  | 
| 608 |  |  |  |  |  |  | Filename of the file upon which it is anticipated that a write will | 
| 609 |  |  |  |  |  |  | happen to.  Locking will provide the most recent version (uncached) | 
| 610 |  |  |  |  |  |  | of this file upon a successful file lock.  It is not necessary | 
| 611 |  |  |  |  |  |  | for this file to exist. | 
| 612 |  |  |  |  |  |  |  | 
| 613 |  |  |  |  |  |  | =item Parameter 2: lock_type | 
| 614 |  |  |  |  |  |  |  | 
| 615 |  |  |  |  |  |  | Lock type must be one of the following: | 
| 616 |  |  |  |  |  |  |  | 
| 617 |  |  |  |  |  |  | BLOCKING | 
| 618 |  |  |  |  |  |  | BL | 
| 619 |  |  |  |  |  |  | EXCLUSIVE (BLOCKING) | 
| 620 |  |  |  |  |  |  | EX | 
| 621 |  |  |  |  |  |  | NONBLOCKING | 
| 622 |  |  |  |  |  |  | NB | 
| 623 |  |  |  |  |  |  | SHARED | 
| 624 |  |  |  |  |  |  | SH | 
| 625 |  |  |  |  |  |  |  | 
| 626 |  |  |  |  |  |  | Or else one or more of the following joined with '|': | 
| 627 |  |  |  |  |  |  |  | 
| 628 |  |  |  |  |  |  | Fcntl::LOCK_EX() (BLOCKING) | 
| 629 |  |  |  |  |  |  | Fcntl::LOCK_NB() (NONBLOCKING) | 
| 630 |  |  |  |  |  |  | Fcntl::LOCK_SH() (SHARED) | 
| 631 |  |  |  |  |  |  |  | 
| 632 |  |  |  |  |  |  | Lock type determines whether the lock will be blocking, non blocking, | 
| 633 |  |  |  |  |  |  | or shared.  Blocking locks will wait until other locks are removed | 
| 634 |  |  |  |  |  |  | before the process continues.  Non blocking locks will return undef if | 
| 635 |  |  |  |  |  |  | another process currently has the lock.  Shared will allow other | 
| 636 |  |  |  |  |  |  | process to do a shared lock at the same time as long as there is not | 
| 637 |  |  |  |  |  |  | already an exclusive lock obtained. | 
| 638 |  |  |  |  |  |  |  | 
| 639 |  |  |  |  |  |  | =item Parameter 3: blocking_timeout (optional) | 
| 640 |  |  |  |  |  |  |  | 
| 641 |  |  |  |  |  |  | Timeout is used in conjunction with a blocking timeout.  If specified, | 
| 642 |  |  |  |  |  |  | File::NFSLock will block up to the number of seconds specified in | 
| 643 |  |  |  |  |  |  | timeout before returning undef (could not get a lock). | 
| 644 |  |  |  |  |  |  |  | 
| 645 |  |  |  |  |  |  |  | 
| 646 |  |  |  |  |  |  | =item Parameter 4: stale_lock_timeout (optional) | 
| 647 |  |  |  |  |  |  |  | 
| 648 |  |  |  |  |  |  | Timeout is used to see if an existing lock file is older than the stale | 
| 649 |  |  |  |  |  |  | lock timeout.  If do_lock fails to get a lock, the modified time is checked | 
| 650 |  |  |  |  |  |  | and do_lock is attempted again.  If the stale_lock_timeout is set to low, a | 
| 651 |  |  |  |  |  |  | recursion load could exist so do_lock will only recurse 10 times (this is only | 
| 652 |  |  |  |  |  |  | a problem if the stale_lock_timeout is set too low -- on the order of one or two | 
| 653 |  |  |  |  |  |  | seconds). | 
| 654 |  |  |  |  |  |  |  | 
| 655 |  |  |  |  |  |  | =back | 
| 656 |  |  |  |  |  |  |  | 
| 657 |  |  |  |  |  |  | =head1 METHODS | 
| 658 |  |  |  |  |  |  |  | 
| 659 |  |  |  |  |  |  | After the $lock object is instantiated with new, | 
| 660 |  |  |  |  |  |  | as outlined above, some methods may be used for | 
| 661 |  |  |  |  |  |  | additional functionality. | 
| 662 |  |  |  |  |  |  |  | 
| 663 |  |  |  |  |  |  | =head2 unlock | 
| 664 |  |  |  |  |  |  |  | 
| 665 |  |  |  |  |  |  | $lock->unlock; | 
| 666 |  |  |  |  |  |  |  | 
| 667 |  |  |  |  |  |  | This method may be used to explicitly release a lock | 
| 668 |  |  |  |  |  |  | that is acquired.  In most cases, it is not necessary | 
| 669 |  |  |  |  |  |  | to call unlock directly since it will implicitly be | 
| 670 |  |  |  |  |  |  | called when the object leaves whatever scope it is in. | 
| 671 |  |  |  |  |  |  |  | 
| 672 |  |  |  |  |  |  | =head2 uncache | 
| 673 |  |  |  |  |  |  |  | 
| 674 |  |  |  |  |  |  | $lock->uncache; | 
| 675 |  |  |  |  |  |  | $lock->uncache("otherfile1"); | 
| 676 |  |  |  |  |  |  | uncache("otherfile2"); | 
| 677 |  |  |  |  |  |  |  | 
| 678 |  |  |  |  |  |  | This method is used to freshen up the contents of a | 
| 679 |  |  |  |  |  |  | file across NFS, ignoring what is contained in the | 
| 680 |  |  |  |  |  |  | NFS client cache.  It is always called from within | 
| 681 |  |  |  |  |  |  | the new constructor on the file that the lock is | 
| 682 |  |  |  |  |  |  | being attempted.  uncache may be used as either an | 
| 683 |  |  |  |  |  |  | object method or as a stand alone subroutine. | 
| 684 |  |  |  |  |  |  |  | 
| 685 |  |  |  |  |  |  | =head2 fork | 
| 686 |  |  |  |  |  |  |  | 
| 687 |  |  |  |  |  |  | my $pid = $lock->fork; | 
| 688 |  |  |  |  |  |  | if (!defined $pid) { | 
| 689 |  |  |  |  |  |  | # Fork Failed | 
| 690 |  |  |  |  |  |  | } elsif ($pid) { | 
| 691 |  |  |  |  |  |  | # Parent ... | 
| 692 |  |  |  |  |  |  | } else { | 
| 693 |  |  |  |  |  |  | # Child ... | 
| 694 |  |  |  |  |  |  | } | 
| 695 |  |  |  |  |  |  |  | 
| 696 |  |  |  |  |  |  | fork() is a convenience method that acts just like the normal | 
| 697 |  |  |  |  |  |  | CORE::fork() except it safely ensures the lock is retained | 
| 698 |  |  |  |  |  |  | within both parent and child processes. WITHOUT this, then when | 
| 699 |  |  |  |  |  |  | either the parent or child process releases the lock, then the | 
| 700 |  |  |  |  |  |  | entire lock will be lost, allowing external processes to | 
| 701 |  |  |  |  |  |  | re-acquire a lock on the same file, even if the other process | 
| 702 |  |  |  |  |  |  | still has the lock object in scope. This can cause corruption | 
| 703 |  |  |  |  |  |  | since both processes might think they have exclusive access to | 
| 704 |  |  |  |  |  |  | the file. | 
| 705 |  |  |  |  |  |  |  | 
| 706 |  |  |  |  |  |  | =head2 newpid | 
| 707 |  |  |  |  |  |  |  | 
| 708 |  |  |  |  |  |  | my $pid = fork; | 
| 709 |  |  |  |  |  |  | if (!defined $pid) { | 
| 710 |  |  |  |  |  |  | # Fork Failed | 
| 711 |  |  |  |  |  |  | } elsif ($pid) { | 
| 712 |  |  |  |  |  |  | $lock->newpid; | 
| 713 |  |  |  |  |  |  | # Parent ... | 
| 714 |  |  |  |  |  |  | } else { | 
| 715 |  |  |  |  |  |  | $lock->newpid; | 
| 716 |  |  |  |  |  |  | # Child ... | 
| 717 |  |  |  |  |  |  | } | 
| 718 |  |  |  |  |  |  |  | 
| 719 |  |  |  |  |  |  | The newpid() synopsis shown above is equivalent to the | 
| 720 |  |  |  |  |  |  | one used for the fork() method, but it's not intended | 
| 721 |  |  |  |  |  |  | to be called directly. It is called internally by the | 
| 722 |  |  |  |  |  |  | fork() method. To be safe, it is recommended to use | 
| 723 |  |  |  |  |  |  | $lock->fork() from now on. | 
| 724 |  |  |  |  |  |  |  | 
| 725 |  |  |  |  |  |  | =head1 FAILURE | 
| 726 |  |  |  |  |  |  |  | 
| 727 |  |  |  |  |  |  | On failure, a global variable, $File::NFSLock::errstr, should be set and should | 
| 728 |  |  |  |  |  |  | contain the cause for the failure to get a lock.  Useful primarily for debugging. | 
| 729 |  |  |  |  |  |  |  | 
| 730 |  |  |  |  |  |  | =head1 LOCK_EXTENSION | 
| 731 |  |  |  |  |  |  |  | 
| 732 |  |  |  |  |  |  | By default File::NFSLock will use a lock file extension of ".NFSLock".  This is | 
| 733 |  |  |  |  |  |  | in a global variable $File::NFSLock::LOCK_EXTENSION that may be changed to | 
| 734 |  |  |  |  |  |  | suit other purposes (such as compatibility in mail systems). | 
| 735 |  |  |  |  |  |  |  | 
| 736 |  |  |  |  |  |  | =head1 REPO | 
| 737 |  |  |  |  |  |  |  | 
| 738 |  |  |  |  |  |  | The source is now on github: | 
| 739 |  |  |  |  |  |  |  | 
| 740 |  |  |  |  |  |  | git clone https://github.com/hookbot/File-NFSLock | 
| 741 |  |  |  |  |  |  |  | 
| 742 |  |  |  |  |  |  | =head1 BUGS | 
| 743 |  |  |  |  |  |  |  | 
| 744 |  |  |  |  |  |  | If you spot anything, please submit a pull request on | 
| 745 |  |  |  |  |  |  | github and/or submit a ticket with RT: | 
| 746 |  |  |  |  |  |  | https://rt.cpan.org/Dist/Display.html?Queue=File-NFSLock | 
| 747 |  |  |  |  |  |  |  | 
| 748 |  |  |  |  |  |  | =head2 FIFO | 
| 749 |  |  |  |  |  |  |  | 
| 750 |  |  |  |  |  |  | Locks are not necessarily obtained on a first come first serve basis. | 
| 751 |  |  |  |  |  |  | Not only does this not seem fair to new processes trying to obtain a lock, | 
| 752 |  |  |  |  |  |  | but it may cause a process starvation condition on heavily locked files. | 
| 753 |  |  |  |  |  |  |  | 
| 754 |  |  |  |  |  |  | =head2 DIRECTORIES | 
| 755 |  |  |  |  |  |  |  | 
| 756 |  |  |  |  |  |  | Locks cannot be obtained on directory nodes, nor can a directory node be | 
| 757 |  |  |  |  |  |  | uncached with the uncache routine because hard links do not work with | 
| 758 |  |  |  |  |  |  | directory nodes.  Some other algorithm might be used to uncache a | 
| 759 |  |  |  |  |  |  | directory, but I am unaware of the best way to do it.  The biggest use I | 
| 760 |  |  |  |  |  |  | can see would be to avoid NFS cache of directory modified and last accessed | 
| 761 |  |  |  |  |  |  | timestamps. | 
| 762 |  |  |  |  |  |  |  | 
| 763 |  |  |  |  |  |  | =head1 INSTALL | 
| 764 |  |  |  |  |  |  |  | 
| 765 |  |  |  |  |  |  | Download and extract tarball before running | 
| 766 |  |  |  |  |  |  | these commands in its base directory: | 
| 767 |  |  |  |  |  |  |  | 
| 768 |  |  |  |  |  |  | perl Makefile.PL | 
| 769 |  |  |  |  |  |  | make | 
| 770 |  |  |  |  |  |  | make test | 
| 771 |  |  |  |  |  |  | make install | 
| 772 |  |  |  |  |  |  |  | 
| 773 |  |  |  |  |  |  | For RPM installation, download tarball before | 
| 774 |  |  |  |  |  |  | running these commands in your _topdir: | 
| 775 |  |  |  |  |  |  |  | 
| 776 |  |  |  |  |  |  | rpm -ta SOURCES/File-NFSLock-*.tar.gz | 
| 777 |  |  |  |  |  |  | rpm -ih RPMS/noarch/perl-File-NFSLock-*.rpm | 
| 778 |  |  |  |  |  |  |  | 
| 779 |  |  |  |  |  |  | =head1 AUTHORS | 
| 780 |  |  |  |  |  |  |  | 
| 781 |  |  |  |  |  |  | Paul T Seamons (paul@seamons.com) - Performed majority of the | 
| 782 |  |  |  |  |  |  | programming with copious amounts of input from Rob Brown. | 
| 783 |  |  |  |  |  |  |  | 
| 784 |  |  |  |  |  |  | Rob B Brown (bbb@cpan.org) - In addition to helping in the | 
| 785 |  |  |  |  |  |  | programming, Rob Brown provided most of the core testing to make sure | 
| 786 |  |  |  |  |  |  | implementation worked properly.  He is now the current maintainer. | 
| 787 |  |  |  |  |  |  |  | 
| 788 |  |  |  |  |  |  | Also Mark Overmeer (mark@overmeer.net) - Author of Mail::Box::Locker, | 
| 789 |  |  |  |  |  |  | from which some key concepts for File::NFSLock were taken. | 
| 790 |  |  |  |  |  |  |  | 
| 791 |  |  |  |  |  |  | Also Kevin Johnson (kjj@pobox.com) - Author of Mail::Folder::Maildir, | 
| 792 |  |  |  |  |  |  | from which Mark Overmeer based Mail::Box::Locker. | 
| 793 |  |  |  |  |  |  |  | 
| 794 |  |  |  |  |  |  | =head1 COPYRIGHT | 
| 795 |  |  |  |  |  |  |  | 
| 796 |  |  |  |  |  |  | Copyright (C) 2001 | 
| 797 |  |  |  |  |  |  | Paul T Seamons | 
| 798 |  |  |  |  |  |  | paul@seamons.com | 
| 799 |  |  |  |  |  |  | http://seamons.com/ | 
| 800 |  |  |  |  |  |  |  | 
| 801 |  |  |  |  |  |  | Copyright (C) 2002-2018, | 
| 802 |  |  |  |  |  |  | Rob B Brown | 
| 803 |  |  |  |  |  |  | bbb@cpan.org | 
| 804 |  |  |  |  |  |  |  | 
| 805 |  |  |  |  |  |  | This package may be distributed under the terms of either the | 
| 806 |  |  |  |  |  |  | GNU General Public License | 
| 807 |  |  |  |  |  |  | or the | 
| 808 |  |  |  |  |  |  | Perl Artistic License | 
| 809 |  |  |  |  |  |  |  | 
| 810 |  |  |  |  |  |  | All rights reserved. | 
| 811 |  |  |  |  |  |  |  | 
| 812 |  |  |  |  |  |  | =cut |