File Coverage

blib/lib/Mail/Box/Locker/NFS.pm
Criterion Covered Total %
statement 50 63 79.3
branch 10 26 38.4
condition 3 6 50.0
subroutine 12 13 92.3
pod 3 3 100.0
total 78 111 70.2


line stmt bran cond sub pod time code
1             # This code is part of Perl distribution Mail-Box version 4.01.
2             # The POD got stripped from this file by OODoc version 3.05.
3             # For contributors see file ChangeLog.
4              
5             # This software is copyright (c) 2001-2025 by Mark Overmeer.
6              
7             # This is free software; you can redistribute it and/or modify it under
8             # the same terms as the Perl 5 programming language system itself.
9             # SPDX-License-Identifier: Artistic-1.0-Perl OR GPL-1.0-or-later
10              
11              
12             package Mail::Box::Locker::NFS;{
13             our $VERSION = '4.01';
14             }
15              
16 3     3   2399 use parent 'Mail::Box::Locker';
  3         6  
  3         34  
17              
18 3     3   253 use strict;
  3         13  
  3         97  
19 3     3   68 use warnings;
  3         8  
  3         232  
20              
21 3     3   18 use Log::Report 'mail-box', import => [ qw/__x fault warning/ ];
  3         6  
  3         23  
22              
23 3     3   741 use Sys::Hostname qw/hostname/;
  3         5  
  3         206  
24 3     3   18 use Fcntl qw/O_CREAT O_WRONLY/;
  3         6  
  3         2578  
25              
26             #--------------------
27              
28             sub name() { 'NFS' }
29              
30             #--------------------
31              
32             # METHOD nfs
33             # This hack is copied from the Mail::Folder packages, as written
34             # by Kevin Jones. Cited from his code:
35             # Whhheeeee!!!!!
36             # In NFS, the O_CREAT|O_EXCL isn't guaranteed to be atomic.
37             # So we create a temp file that is probably unique in space
38             # and time ($folder.lock.$time.$pid.$host).
39             # Then we use link to create the real lock file. Since link
40             # is atomic across nfs, this works.
41             # It loses if it's on a filesystem that doesn't do long filenames.
42              
43             my $hostname = hostname;
44              
45             sub _tmpfilename()
46 4     4   8 { my $self = shift;
47 4   66     29 $self->{MBLN_tmp} ||= $self->filename . $$;
48             }
49              
50             sub _construct_tmpfile()
51 2     2   5 { my $self = shift;
52 2         8 my $tmpfile = $self->_tmpfilename;
53              
54 2 50       518 sysopen my $fh, $tmpfile, O_CREAT|O_WRONLY, 0600
55             or return undef;
56              
57 2         59 $fh->close;
58 2         65 $tmpfile;
59             }
60              
61             sub _try_lock($$)
62 2     2   7 { my ($self, $tmpfile, $lockfile) = @_;
63              
64 2 50       206 link $tmpfile, $lockfile
65             or return undef;
66              
67 2         42 my $linkcount = (stat $tmpfile)[3];
68              
69 2         170 unlink $tmpfile;
70 2         38 $linkcount == 2;
71             }
72              
73              
74             sub lock()
75 3     3 1 2498 { my $self = shift;
76 3         13 my $folder = $self->folder;
77              
78 3 100       18 $self->hasLock
79             and warning(__x"folder {name} already locked over NFS.", name => $folder), return 1;
80              
81 2         19 my $lockfile = $self->filename;
82 2 50       7 my $tmpfile = $self->_construct_tmpfile or return;
83 2         89 my $timeout = $self->timeout;
84 2 50       11 my $end = $timeout eq 'NOTIMEOUT' ? -1 : $timeout;
85 2         15 my $expires = $self->expires / 86400; # in days for -A
86              
87 2 50 33     38 if(-e $lockfile && -A $lockfile > $expires)
88 0 0       0 { unlink $lockfile
89             or fault __x"Unable to remove expired lockfile {file}", file => $lockfile;
90              
91 0         0 warning __x"removed expired lockfile {file}.", file => $lockfile;
92             }
93              
94 2         6 while(1)
95 2 50       8 { return $self->SUPER::lock
96             if $self->_try_lock($tmpfile, $lockfile);
97              
98 0 0       0 --$end or last;
99 0         0 sleep 1;
100             }
101              
102 0         0 return 0;
103             }
104              
105             sub isLocked()
106 0     0 1 0 { my $self = shift;
107 0 0       0 my $tmpfile = $self->_construct_tmpfile or return 0;
108 0         0 my $lockfile = $self->filename;
109              
110 0 0       0 my $fh = $self->_try_lock($tmpfile, $lockfile) or return 0;
111 0         0 close $fh;
112              
113 0         0 $self->_unlock($tmpfile, $lockfile);
114 0         0 $self->SUPER::unlock;
115              
116 0         0 1;
117             }
118              
119              
120             sub _unlock($$)
121 2     2   7 { my ($self, $tmpfile, $lockfile) = @_;
122              
123 2 50       238 unlink $lockfile
124             or fault __x"couldn't remove lockfile {file}", file => $lockfile;
125              
126 2         60 unlink $tmpfile;
127 2         9 $self;
128             }
129              
130             sub unlock($)
131 2     2 1 1005 { my $self = shift;
132 2 50       9 $self->hasLock or return $self;
133              
134 2         9 $self->_unlock($self->_tmpfilename, $self->filename);
135 2         24 $self->SUPER::unlock;
136 2         10 $self;
137             }
138              
139             1;