File Coverage

lib/File/Valet.pm
Criterion Covered Total %
statement 160 292 54.7
branch 57 150 38.0
condition 19 65 29.2
subroutine 18 23 78.2
pod 9 13 69.2
total 263 543 48.4


line stmt bran cond sub pod time code
1             package File::Valet;
2 20     20   2467605 use 5.010;
  20         268  
3 20     20   100 use strict;
  20         21  
  20         362  
4 20     20   81 use warnings;
  20         56  
  20         438  
5 20     20   63 use Config; # Provides OS-portable means of determining platform type
  20         39  
  20         771  
6 20     20   9706 use POSIX;
  20         131124  
  20         99  
7 20     20   56748 use File::Basename qw(fileparse);
  20         39  
  20         2012  
8 20     20   10502 use File::Copy;
  20         46730  
  20         1201  
9 20     20   156 use vars qw(@EXPORT @EXPORT_OK @ISA $VERSION);
  20         22  
  20         1971  
10              
11             BEGIN {
12 20     20   104 require Exporter;
13 20         568 @ISA = qw(Exporter);
14 20         69 $VERSION = '1.11';
15 20         72063 @EXPORT = @EXPORT_OK = qw(rd_f wr_f ap_f find_first find_home find_temp find_bin lockafile unlockafile unlock_all_the_files);
16             }
17              
18             our $OK = 'OK'; # one of "OK", "WARNING" or "ERROR", reflecting most recently performed operation
19             our $ERROR = ''; # short invariant description of error, or empty string if none
20             our $ERRNO = ''; # variant description of error (such as $!), or empty string if none
21             our $ERRNUM = 0; # numerical variant description of error (such as $!), or empty string if none, undocumented, only used for unit tests
22             our %LOCKS_HASH; # keys on lockfile to bind count, for supporting nested locks.
23              
24             # File::Copy::move() almost doest the right thing, just needs syscopy() as failover instead of copy().
25             # This _rename() function more or less duplicates the needed functionality of File::Copy::_move's logic after the rename,
26             # but uses syscopy() and captures failures in $OK, $ERROR, $ERRNO, $ERRNUM.
27             sub _rename {
28 0     0   0 my ($from, $to) = @_;
29              
30 0         0 ($OK, $ERROR, $ERRNO, $ERRNUM) = ('OK', '', '', 0);
31 0 0       0 return 'OK' if (rename ($from, $to));
32              
33 0         0 my $result = File::Copy::syscopy($from, $to);
34 0 0       0 unless ($result) {
35 0         0 ($OK, $ERROR, $ERRNO, $ERRNUM) = ('ERROR', 'File::Copy::syscopy failed', $!, 0+$!);
36 0         0 return undef;
37             }
38              
39 0         0 my @st = stat($from);
40 0 0       0 unless (@st) {
41 0         0 ($OK, $ERROR, $ERRNO, $ERRNUM) = ('ERROR', 'after-copy stat failed', $!, 0+$!);
42 0         0 return undef;
43             }
44              
45 0         0 my ($atime, $mtime) = (@st)[8,9];
46 0 0       0 unless (utime($atime, $mtime, $to)) {
47 0         0 ($OK, $ERROR, $ERRNO, $ERRNUM) = ('ERROR', 'after-copy utime failed', $!, 0+$!);
48 0         0 return undef;
49             }
50              
51 0 0       0 unless (unlink($from)) {
52 0         0 ($OK, $ERROR, $ERRNO, $ERRNUM) = ('ERROR', 'after-copy unlink failed', $!, 0+$!);
53 0         0 return undef;
54             }
55 0         0 return 'OK';
56             }
57              
58             sub rename_vms {
59 0     0 0 0 my ($fn, $dest) = @_;
60 0 0 0     0 if (!defined($fn) || ($fn eq '')) {
61 0         0 ($OK, $ERROR, $ERRNO, $ERRNUM) = ('ERROR', 'no filename supplied', -1, 0);
62 0         0 return undef;
63             }
64 0 0 0     0 if (!defined($dest) || ($dest eq '')) {
65 0         0 ($OK, $ERROR, $ERRNO, $ERRNUM) = ('ERROR', 'no destination directory supplied', -1, 0);
66 0         0 return undef;
67             }
68 0         0 my $dest_fn = $fn;
69 0 0       0 if (!-d $dest) {
70 0         0 ($dest, $dest_fn) = fileparse($dest);
71             }
72 0 0       0 if (!-e $dest) {
73 0         0 ($OK, $ERROR, $ERRNO, $ERRNUM) = ('ERROR', 'destination directory does not exist', -1, 0);
74 0         0 return undef;
75             }
76 0 0       0 if (!-d _) {
77 0         0 ($OK, $ERROR, $ERRNO, $ERRNUM) = ('ERROR', 'destination directory is not a directory', -1, 0);
78 0         0 return undef;
79             }
80 0 0       0 if (!-w _) {
81 0         0 ($OK, $ERROR, $ERRNO, $ERRNUM) = ('ERROR', 'destination directory is not writable', -1, 0);
82 0         0 return undef;
83             }
84              
85 0 0       0 if (!-e "$dest/$dest_fn") {
86             # degenerate case; just rename it.
87 0         0 return _rename($fn, "$dest/$dest_fn");
88             }
89              
90 0         0 my $i = 1;
91 0         0 $i++ while(-e "$dest/$dest_fn.$i");
92 0         0 return _rename($fn, "$dest/$dest_fn.$i");
93             }
94              
95             sub rd_f {
96 108     108 1 18311 my ($fn) = @_;
97 108         250 my ($fh, $buf);
98 108 100 100     1330 if (!defined($fn) || ($fn eq '')) {
99 2         6 ($OK, $ERROR, $ERRNO, $ERRNUM) = ('ERROR', 'no filename supplied', -1, 0);
100 2         7 return undef;
101             }
102 106         518 $! = 0;
103 106 100       5334 unless (open($fh, '< :raw', $fn)) {
104 23         1095 ($OK, $ERROR, $ERRNO, $ERRNUM) = ('ERROR', "cannot open for reading", $!, 0+$!);
105 23         181 return undef;
106             }
107 83         387 binmode($fh);
108 83         1248 my $file_size = (stat($fn))[7];
109 83 100       464 if ($file_size) {
110 71         1127 my $n_bytes = sysread($fh, $buf, $file_size);
111 71 50       660 if (!defined($n_bytes)) {
    50          
112 0         0 ($OK, $ERROR, $ERRNO, $ERRNUM) = ('ERROR', 'read failed', $!, 0+$!);
113 0         0 return undef;
114             }
115             elsif ($n_bytes != $file_size) {
116 0         0 ($OK, $ERROR, $ERRNO, $ERRNUM) = ('ERROR', 'read underflow', $!, 0+$!);
117 0         0 return undef;
118             }
119             }
120             else {
121 12         653 my $res = sysread($fh, $buf, 0x7FFFFFFF);
122 12 50       238 if (!defined $res) {
123 0         0 ($OK, $ERROR, $ERRNO, $ERRNUM) = ('ERROR', 'read failed', $!, 0+$!);
124 0         0 return undef;
125             }
126             }
127 83         1737 my $res = close($fh);
128 83 50       497 unless ($res) {
129 0         0 ($OK, $ERROR, $ERRNO, $ERRNUM) = ('WARNING', 'close failed', $!, 0+$!);
130 0         0 return undef;
131             }
132 83         864 ($OK, $ERROR, $ERRNO, $ERRNUM) = ('OK', '', '', 0);
133 83         604 return $buf;
134             }
135              
136             sub wr_f {
137 5     5 1 2448 my ($fn, $buf) = @_;
138 5         7 my $fh;
139 5 100 100     23 if (!defined($fn) || ($fn eq '')) {
140 3         6 ($OK, $ERROR, $ERRNO, $ERRNUM) = ('ERROR', 'no filename supplied', -1, 0);
141 3         9 return undef;
142             }
143 2         4 $! = 0;
144 2 50       151 unless (open($fh, '> :raw', $fn)) {
145 0         0 ($OK, $ERROR, $ERRNO, $ERRNUM) = ('ERROR', "cannot open for writing", $!, 0+$!);
146 0         0 return undef;
147             }
148 2         8 binmode($fh);
149 2         48 my $res = syswrite($fh, $buf);
150 2 50       9 unless (defined $res) {
151 0         0 ($OK, $ERROR, $ERRNO, $ERRNUM) = ('ERROR', 'write error', $!, 0+$!);
152 0         0 return undef;
153             }
154 2         115 $res = close($fh);
155 2 50       9 unless ($res) {
156 0         0 ($OK, $ERROR, $ERRNO, $ERRNUM) = ('WARNING', 'close failed', $!, 0+$!);
157 0         0 return undef;
158             }
159 2         6 ($OK, $ERROR, $ERRNO, $ERRNUM) = ('OK', '', '', 0);
160 2         13 return 'OK';
161             }
162              
163             sub ap_f {
164 1604     1604 1 41029 my ($fn, $buf) = @_;
165 1604         2767 my $fh;
166 1604 100 100     9542 if (!defined($fn) || ($fn eq '')) {
167 2         6 ($OK, $ERROR, $ERRNO, $ERRNUM) = ('ERROR', 'no filename supplied', -1, 0);
168 2         6 return undef;
169             }
170 1602         4312 $! = 0;
171 1602 50       78885 unless (open($fh, '>> :raw', $fn)) {
172 0         0 ($OK, $ERROR, $ERRNO, $ERRNUM) = ('ERROR', "cannot open for appending", $!, 0+$!);
173 0         0 return undef;
174             }
175 1602         6321 binmode($fh);
176 1602         63641 my $res = syswrite($fh, $buf);
177 1602 50       6377 unless (defined $res) {
178 0         0 ($OK, $ERROR, $ERRNO, $ERRNUM) = ('ERROR', 'write error', $!, 0+$!);
179 0         0 return undef;
180             }
181 1602         18698 $res = close($fh);
182 1602 50       4533 unless ($res) {
183 0         0 ($OK, $ERROR, $ERRNO, $ERRNUM) = ('WARNING', 'close failed', $!, 0+$!);
184 0         0 return undef;
185             }
186 1602         4664 ($OK, $ERROR, $ERRNO, $ERRNUM) = ('OK', '', '', 0);
187 1602         10996 return 'OK';
188             }
189              
190             sub detect_windows {
191 62 50 33 62 0 1240 return ($^O eq 'MSWin32' || $Config{'osname'} =~ /windows/i || $Config{'osname'} =~ /winserver/i || $Config{'osname'} =~ /microsoft/i) ? 1 : 0;
192             }
193              
194             sub find_first {
195 0     0 0 0 foreach my $d (@_) {
196 0 0       0 next unless (-e $d);
197 0         0 ($OK, $ERROR, $ERRNO, $ERRNUM) = ('OK', '', '', 0);
198 0         0 return $d;
199             }
200 0         0 ($OK, $ERROR, $ERRNO, $ERRNUM) = ('WARNING', 'no file found', '', 0);
201 0         0 return undef;
202             }
203              
204             sub find_home {
205 42     42 1 257 for my $d (@_) {
206 0 0 0     0 return $d if (defined $d && -d $d && -w _);
      0        
207             }
208              
209 42         89 my $is_windows = detect_windows;
210 42         178 ($OK, $ERROR, $ERRNO, $ERRNUM) = ('OK', '', '', 0);
211              
212 42         101 my $env_home = $ENV{HOME};
213 42 50 33     765 return $env_home if (defined $env_home && -d $env_home);
214              
215 0   0     0 my $username = $ENV{USER} // $ENV{USERNAME};
216 0 0       0 if ($is_windows) {
217 0   0     0 my $home_drive = $ENV{HOMEDRIVE} // 'C:';
218 0         0 my $home_path = $ENV{HOMEPATH};
219 0 0       0 if (defined $home_path) {
    0          
220 0         0 $env_home = $home_drive . $home_path;
221             }
222             elsif (defined $username) {
223 0         0 $env_home = $home_drive . '\\Users\\' . $username;
224             }
225 0 0 0     0 return $env_home if (defined $env_home && -d $env_home);
226             } else {
227 0         0 my @row = getpwuid($<);
228 0 0       0 if (@row >= 9) {
229 0         0 my $home_dir = $row[7];
230 0 0 0     0 return $home_dir if (defined $home_dir && -d $home_dir);
231             }
232 0 0 0     0 return '/root' if (-d '/root' && -w '/root');
233             }
234              
235 0         0 ($OK, $ERROR, $ERRNO, $ERRNUM) = ('WARNING', 'cannot find home directory', $is_windows, 1);
236 0         0 return undef;
237             }
238              
239             sub find_temp {
240 20     20 1 2752 my $is_windows = detect_windows;
241 20         78 my $dir_sep_tok = '/';
242 20         308 my $home_dir = find_home;
243              
244 20 50       150 push(@_, $ENV{TEMPDIR}) if (defined($ENV{TEMPDIR}));
245 20 50       115 push(@_, $ENV{TEMP}) if (defined($ENV{TEMP}));
246 20 50       59 push(@_, $ENV{TMP}) if (defined($ENV{TMP})); # set in Windows sometimes
247              
248 20 50       59 if ($is_windows) {
249 0         0 $dir_sep_tok = '\\';
250 0         0 push(@_, 'C:\\Windows\\Temp');
251 0         0 push(@_, 'D:\\Windows\\Temp');
252 0         0 foreach my $vol (qw(C D E F G W X Y Z)) {
253 0         0 push(@_, "$vol:\\Temp");
254             }
255             }
256             # might be CygWin, so adding these regardless of OS:
257 20         59 push(@_, qw (/var/tmp /tmp));
258              
259 20 50       79 push(@_, map {join($dir_sep_tok,("$home_dir",$_))} qw(.tmp .temp tmp temp), $home_dir) if (defined($home_dir));
  100         257  
260 20 50       117 push(@_, map {join($dir_sep_tok,("$ENV{PWD}", $_))} qw(.tmp .temp tmp temp), $ENV{PWD} ) if (defined($ENV{PWD} ));
  100         294  
261 20 50       79 push(@_, '/dev/shm') unless ($is_windows); # Lowest priority, since this is typically a ramdisk.
262 20         54 foreach my $d (@_) {
263 20 50       346 next unless (-d $d);
264 20         97 ($OK, $ERROR, $ERRNO, $ERRNUM) = ('OK', '', '', 0);
265 20 50       254 return $d if (-w _);
266             }
267 0         0 ($OK, $ERROR, $ERRNO, $ERRNUM) = ('WARNING', 'no appropriate temporary directory found', '', 0);
268 0         0 return undef;
269             }
270              
271             sub find_bin {
272 21 50   21 1 2572 return find_bin_win32 (@_) if ($Config::Config{osname} =~ /MSWin/);
273 21         99 my ($bin_name, @bin_dirs) = @_;
274 21         59 my $home_dir = find_home;
275 21 50       359 push(@bin_dirs, split(/\:/, $ENV{PATH})) if (defined($ENV{PATH}));
276 21 50       119 push(@bin_dirs, "$home_dir/bin") if (defined($home_dir));
277 21         63 push(@bin_dirs, ('/usr/local/sbin', '/usr/local/bin', '/sbin', '/bin', '/usr/sbin', '/usr/bin'));
278 21         43 my %been_there = ();
279 21         59 foreach my $d (@bin_dirs) {
280 168 100       425 next if (defined($been_there{$d}));
281 147         266 $been_there{$d} = 1;
282 147         282 my $f = "$d/$bin_name";
283 147 100       3704 next unless (-x $f);
284 21         117 ($OK, $ERROR, $ERRNO, $ERRNUM) = ('OK', '', '', 0);
285 21         193 return $f;
286             }
287 0         0 ($OK, $ERROR, $ERRNO, $ERRNUM) = ('WARNING', 'no executable found', '', 0);
288 0         0 return undef;
289             }
290              
291             sub find_bin_win32 {
292 0     0 0 0 my ($bin_name, @bin_dirs) = @_;
293 0 0       0 push(@bin_dirs, split(/\;/, $ENV{PATH})) if (defined($ENV{PATH}));
294 0         0 push(@bin_dirs, ('C:\\WINDOWS\\system32', 'C:\\WINDOWS'));
295 0         0 my %been_there = ();
296 0         0 foreach my $d (@bin_dirs) {
297 0 0       0 next if (defined($been_there{$d}));
298 0         0 $been_there{$d} = 1;
299 0         0 my $f = "$d\\$bin_name";
300 0 0       0 next unless (-x $f);
301 0         0 ($OK, $ERROR, $ERRNO, $ERRNUM) = ('OK', '', '', 0);
302 0         0 return $f;
303             }
304 0         0 ($OK, $ERROR, $ERRNO, $ERRNUM) = ('WARNING', 'no executable found', '', 0);
305 0         0 return undef;
306             }
307              
308             # returns 1 on great success, 0 on miserable failure
309             sub lockafile {
310 1600     1600 1 16369602 my ($f, %opt) = @_;
311 1600 50 33     12104 $opt{nsec} = 30 unless (defined($opt{nsec}) && int($opt{nsec}) > 0); # Number of seconds we expect to have the file locked. If we hold the lock for longer than this, other processes are welcome to kill us and take the lock themselves.
312 1600 50 33     7129 $opt{msg} = "programmer is lame" unless (defined($opt{msg}) && $opt{msg} ne ''); # Helpful message for the human to understand wtf this lock is about
313 1600 50 33     5629 $opt{limit} = 30 unless (defined($opt{limit}) && int($opt{limit}) > 0); # Number of seconds caller is willing to wait for a lock before failing out.
314 1600 50 33     5055 $opt{sleep_duration} = 0.25 unless (defined($opt{sleep_duration}) && $opt{sleep_duration} > 0.0);
315 1600   33     9563 my $lockfile_name = $opt{lockfile_name} || "$f.lock";
316 1600         2823 my $tm_start = time();
317 1600         2175 my $lockfile_fh;
318              
319             # TODO - This is fast and simple, but fails to handle expired lockfiles and extending lockfile durations.
320 1600 50       4812 if ($LOCKS_HASH{$f}) {
321 0         0 $LOCKS_HASH{$f}++;
322 0         0 return 1;
323             }
324              
325 1600         162493 while (!sysopen($lockfile_fh, $lockfile_name, &O_RDWR | &O_CREAT | &O_EXCL)) {
326 106 100       3196 if (-e $lockfile_name) {
327             # re-scanning after every sleep(), because it could expire while we are sleeping and someone else might grab it while we are sleeping.
328 99         546 my $mtime = (stat(_))[9];
329 99         896 my $txt = File::Valet::rd_f($lockfile_name);
330              
331 99 100       654 if (!defined($txt)) { # handling potential race condition or naughty unreadable lockfile
332 22 50       265 if ((time() - $tm_start) > $opt{limit}) {
333 0         0 ($OK, $ERROR, $ERRNO, $ERRNUM) = ('WARNING', 'lockfile racy or unreadable', $lockfile_name, 0);
334 0         0 return 0;
335             }
336 22         5507742 select(undef, undef, undef, $opt{sleep_duration});
337 22         2597 next;
338             }
339              
340 77         238 chomp($txt);
341 77 50       1554 if ($txt =~ /^\d+\t/) {
342 77         693 my ($pid, $lock_duration, $message, $whence) = split(/\t/, $txt);
343 77 50       257 $lock_duration = 30 unless (defined($lock_duration));
344 77         1621 my $locking_process_still_lives = kill(0, $pid);
345             # TODO - Potential race condition; another process might acquire the expired lock after this second stat() and before unlink().
346             # Perhaps use senate? Slow in filesystem, but could use shm on systems which support SysV shared memory.
347 77 50 33     951 unlink($lockfile_name) if ((time() > $mtime + $lock_duration) || ($locking_process_still_lives < 1));
348             }
349             }
350              
351 84 50       511 if ((time() - $tm_start) > $opt{limit}) {
352 0         0 ($OK, $ERROR, $ERRNO, $ERRNUM) = ('OK', '', '', 0); # Not an error; simply unable to acquire lock within specified duration
353 0         0 return 0;
354             }
355              
356 84         21050881 select(undef, undef, undef, $opt{sleep_duration});
357             }
358 1600         21039 my $msg = sprintf("\%d\t\%d\t%s\t%s\n", $$, $opt{nsec}, $opt{msg}, $0); # populating lockfile with information about locking process
359 1600         55078 syswrite($lockfile_fh, $msg);
360 1600         24431 close($lockfile_fh);
361 1600         5347 $LOCKS_HASH{$f} = 1;
362 1600         6111 ($OK, $ERROR, $ERRNO, $ERRNUM) = ('OK', '', '', 0);
363 1600         15976 return 1;
364             }
365              
366             sub unlockafile {
367 1600     1600 1 18399 my ($f, %opt) = @_;
368 1600         3007 my $lockfile_fh;
369             my $dgram;
370 1600   33     9219 my $lockfile_name = $opt{lockfile_name} || "$f.lock";
371 1600         3641 ($OK, $ERROR, $ERRNO, $ERRNUM) = ('OK', '', '', 0);
372              
373             # TODO - This is fast and simple, but fails to handle expired lockfiles
374 1600 50       4555 if ($LOCKS_HASH{$f}) {
375 1600         2642 $LOCKS_HASH{$f}--;
376 1600 50       3767 return 1 if ($LOCKS_HASH{$f} > 0);
377             }
378 1600         3750 $! = 0;
379 1600 50       63535 unless (sysopen($lockfile_fh, $lockfile_name, &O_RDONLY)) {
380 0         0 ($OK, $ERROR, $ERRNO, $ERRNUM) = ('ERROR', 'open failure', $!, 0+$!);
381 0         0 return 0;
382             }
383 1600 50       23544 unless(my $result = sysread($lockfile_fh, $dgram, 4095)) {
384 0 0       0 if (defined($result)) {
385 0         0 ($OK, $ERROR, $ERRNO, $ERRNUM) = ('ERROR', 'read zero bytes from lockfile', '', 0+$!);
386             } else {
387 0         0 ($OK, $ERROR, $ERRNO, $ERRNUM) = ('ERROR', 'read error', $!, 0+$!);
388             }
389 0         0 return 0;
390             }
391 1600         4369 chomp($dgram);
392 1600         9200 my ($lpid, $nsec, $msg, $whence) = split(/\t/, $dgram);
393 1600         17020 close($lockfile_fh);
394 1600         4710 $LOCKS_HASH{$f} = 0;
395 1600 50 33     9890 if (defined($lpid) && ($lpid ne $$)) {
396             # oops! not ours anymore
397 0         0 ($OK, $ERROR, $ERRNO, $ERRNUM) = ('ERROR', 'lost lock', $dgram, 0+$!);
398 0         0 return 0;
399             }
400 1600         93662 unlink($lockfile_name);
401 1600         15776 return 1;
402             }
403              
404             sub unlock_all_the_files {
405             # Best effort only .. eh, make that "some effort only".
406             # That might still be overly charitable.
407             # Truth be told, you're only using this function if you can't be assed to fix the bugs in your own code,
408             # which relieves me somewhat of any moral imperative.
409 0     0 1   my $n_locks = 0;
410 0           my $n_files = 0;
411 0           my $n_errors = 0;
412 0           foreach my $f (keys %LOCKS_HASH) {
413 0 0         next unless ($LOCKS_HASH{$f});
414 0           $n_locks += $LOCKS_HASH{$f};
415 0           $LOCKS_HASH{$f} = 0;
416 0           unlockafile($f);
417 0 0         $n_errors++ unless ($OK eq 'OK');
418 0           $n_files++;
419             }
420 0           return ($n_errors, $n_locks, $n_files);
421             }
422              
423             1;
424              
425             =head1 NAME
426              
427             File::Valet - Utilities for file slurping, locking, and finding.
428              
429             =head1 SYNOPSIS
430              
431             use File::Valet;
432              
433             # Simple slurp and unslurp with rd_f, wr_f, ap_f:
434              
435             my $text = rd_f('some/file.txt');
436             die "slurp failure: $File::Valet::ERROR ($File::Valet::ERRNO)" unless ($File::Valet::OK eq 'OK');
437             # or, equivalently:
438             die "slurp failure: $File::Valet::ERROR ($File::Valet::ERRNO)" unless (defined($text));
439              
440             # Contents written will be same as that of "some/file.txt",
441             # plus two lines appended at the end:
442              
443             wr_f('another/file.txt', $text);
444             ap_f('another/file.txt', "Oh, and another thing:\n");
445             ap_f('another/file.txt', "STOP BREATHING IN MY CUP\n");
446              
447             # Find a place suited to temporary files:
448             my $tmp = find_temp(); # Likely /var/tmp or /tmp or C:\Windows\Temp
449              
450             # Find the full pathname of an executable:
451             my $shell = find_bin('sh'); # Likely /bin/sh
452              
453             # Use a lockfile for exclusive access to a shared resource:
454             lockafile("$tmp/shared.txt") or die "cannot obtain lock: $File::Valet::ERROR ($File::Valet::ERRNO)";
455             my $text = rd_f("$tmp/shared.txt");
456             unlockafile("$tmp/shared.txt") or die "unlock error: $File::Valet::ERROR ($File::Valet::ERRNO)";
457              
458             # Nested file locking:
459             lockafile("shared.txt") or die "cannot obtain first lock";
460             my $text = rd_f("$tmp/shared.txt");
461             ...
462             lockafile("shared.txt") or die "cannot obtain second lock";
463             ap_f("$tmp/shared.txt", $data);
464             unlockafile("$tmp/shared.txt");
465             ...
466             unlockafile("$tmp/shared.txt");
467              
468             # Your code has bugs, resulting in leaving lockfiles behind, but
469             # instead of debugging you'd rather just remove all your locks:
470             my ($n_errors, $n_locks, $n_files) = unlock_all_the_files();
471              
472             =head1 DESCRIPTION
473              
474             B contains a selection of easy-to-use subroutines for manipulating files and file content. Some effort has been made to establish cross-platform portability, and to make their behavior as unsurprising as possible.
475              
476             =head1 FUNCTIONS
477              
478             The following functions are available through this module for use in other applications. In keeping with the intent of minimizing user keystrokes, all of these functions are exported into the calling namespace.
479              
480             =over 4
481              
482             =item B
483              
484             my $string = rd_f($filename);
485              
486             C is similar to the well-known C, in that it reads the entire contents of the named file and returns it as a string. Its principle differences are a slightly shorter name and the insertion of diagnostic information into C<$File::Valet::OK>, C<$File::Valet::ERROR>, C<$File::Valet::ERRNO> when the operation failed to complete.
487              
488             The return value is either the contents of the file B (an empty string if the file had no contents), or undef on any error.
489              
490             =item B
491              
492             my $success = wr_f($filename, $string);
493              
494             C is conceptually the opposite of C, in that it overwrites the named file's contents with the given B.
495              
496             If the specified file does not exist, C will attempt to create it.
497              
498             Returns 1 on success, or 0 on any failure, and sets C<$File::Valet::OK>, C<$File::Valet::ERROR>, C<$File::Valet::ERRNO> appropriately.
499              
500             =item B
501              
502             my $success = ap_f($filename, $string);
503              
504             C is similar to C, differing in that the specified B is appended to the end of the file, rather than overwriting it.
505              
506             If the specified file does not exist, C will attempt to create it.
507              
508             Returns 1 on success, or 0 on any failure, and sets C<$File::Valet::OK>, C<$File::Valet::ERROR>, C<$File::Valet::ERRNO> appropriately.
509              
510             =item B
511              
512             my $path = find_home;
513             my $path = find_temp("/var/home", "/tmp/home");
514              
515             C performs a best-effort search for the effective user's home, returning a path-string or undef if none is found.
516              
517             If arguments are provided, it will return the first argument for which there is a directory for which the user has write permissions.
518              
519             if C<$ENV{HOME}> is set, C will check there for a writable directory after checking any arguments.
520              
521             Some effort has been made to make it cross-platform.
522              
523             =item B
524              
525             my $path = find_temp();
526             my $path = find_temp("/home/tmp", "/usr/tmp", ...);
527              
528             Intended for easy cross-platform programming, C checks in a number of likely, common filesystem locations for a valid directory for temporary files. It returns the first directory it finds for which the user has write permissions, or undef if none is found.
529              
530             If parameters are passed to C, it will check those locations first.
531              
532             If C<$ENV{TEMPDIR}>, C<$ENV{TEMP}> or C<$ENV{TMP}> are defined, C will check those locations after checking the locations provided as parameters.
533              
534             C is Windows-savvy enough to check such locations as "C:\Windows\Temp", but might try to open locations on network-mounted drives if it is unable to find a local alternative.
535              
536             =item B
537              
538             my $path = find_home();
539             my $path = find_home("/var/home/fred");
540              
541             Another function intended for easy cross-platform programming, C will first check $ENV{HOME} on *nix or $ENV{HOMEDIRVE} and $ENV{HOMEPATH (on Windows) if defined, then in the system's passwd database, and then in a number of other likely locations, for a writable home directory for the effective user.
542              
543             It will return the full absolute path of the home directory on success, or undef on failure.
544              
545             =item B
546              
547             my $pathname = find_bin("ls");
548             my $pathname = find_bin("ls", "/home/ttk/bin", "/opt/bin", ...);
549              
550             Another function intended for easy cross-platform programming, C will first check all of the directories in $ENV{PATH} (if defined), and then in a number of other likely, common locations, for an executable file whose name matches the first parameter. It will return the full absolute pathname of the executable file on success, or undef on failure.
551              
552             C is Windows-savvy, albeit does not search Windows systems as extensively as others.
553              
554             If directory paths are given as additional parameters, C will check those locations first.
555              
556             C is smart enough to only check any given directory once, even if it appears in the parameter list as well as in $ENV{PATH}, or appears multiple times in either.
557              
558             C also sets C<$File::Valet::OK>, C<$File::Valet::ERROR>, C<$File::Valet::ERRNO> appropriately.
559              
560             =item B
561              
562             my $success = lockafile("/tmp/foo", %options);
563             my $success = lockafile("/tmp/foo",
564             limit => 2.0, # keep retrying for 2.0 seconds before giving up
565             msg => 'in-channel update', # helpful message for troubleshooting
566             nsec => 0.5, # we expect to hold the lock for less than 0.5 seconds
567             );
568              
569             C applies an advisory lock on the named file, and attempts to be somewhat clever about it, automatically invalidating existing locks set by processes which no longer exist. or set a very long time ago.
570              
571             If the file is already locked by another process, C will linger and attempt to acquire the lock when the owner of the lock releases the file. This linger time defaults to thirty seconds, and may be overridden with the C parameter.
572              
573             The advisory lock takes the form of a file, which may be manually deleted to remove the lock, or may be inspected to learn something about the process which created the lock. To facilitate this, a message may be embedded in the lock file describing the reason the file is being locked. It defaults to "programmer is lazy", and may be set by passing the C parameter.
574              
575             Advisory locks will be respected by other invocations of C for up to some time before being assumed stale and forceably removed. This period may be increased by passing the C parameter (which becomes embedded in the lockfile).
576              
577             C attempts to manage nested advisory locks via C<%File::Valet::LOCKS_HASH>. C will keep track of which files the caller has locked, and how many times. Thus if the caller locks the same file two or more times, and unlocks it an equal number of times, the lock file will only be created on the first invocation of C, and removed only on the last invocation of C. See B for caveats regarding this.
578              
579             Returns 1 on success, or 0 on any failure, and sets C<$File::Valet::OK>, C<$File::Valet::ERROR>, C<$File::Valet::ERRNO> appropriately.
580              
581             =item B
582              
583             my $success = unlockafile("/tmp/foo", %options);
584              
585             C reverses the action of C, removing an advisory lock on a file (or reducing the count of locks on a multiply-locked file).
586              
587             C will fail if invoked on a file which is not locked, or has been locked by a different process.
588              
589             C returns 1 on success, and 0 on any failure, and sets C<$File::Valet::OK>, C<$File::Valet::ERROR>, C<$File::Valet::ERRNO> appropriately.
590              
591             =item B
592              
593             C is a convenience wrapper for walking C<%File::Valet::LOCKS_HASH> and safely removing all lockfiles.
594              
595             If your code has bugs which cause it to leave lockfiles behind, then calling C before exiting will help prevent that.
596              
597             Really, though, you should fix your bugs.
598              
599             Returns three values: A count of errors returned by C, a count of locks removed, and a count of lock files removed.
600              
601             The number of locks can differ from the number of lock files when locks are nested. A file which is locked twice counts as two locks but has only one lock file.
602              
603             =item B
604              
605             The lockfiles contain useful bits of information which help C figure out if it should override someone else's lock, and is also useful for gaining insight about the system's behavior, for troubleshooting purposes.
606              
607             Its fields are tab-delimited, and the file terminates with a newline. They appear in this order:
608              
609             * Process identifier of the process which created the lockfile (per "$$"),
610             * The number of seconds the lock should be considered valid (per "nsec" parameter),
611             * The helpful message provided by the programmer (per "msg" parameter),
612             * The name of the program which created the lockfile (per "$0")
613              
614             Example:
615              
616             "4873\t2.0\tThe programmer is lame\t/opt/simon/bin/simond\n"
617              
618             These fields may change in future versions of this module.
619              
620             =item B
621              
622             A recursive descent function similar to L is planned, since C is pretty horrible and unusable.
623              
624             The C implementation goes through considerable effort to avoid race conditions, but there is still a very short danger window where an overridden lock might get double-clobbered. If a contended lock expires just when two or more other processes call C on it, it is possible for one process to unlink the lock file, the other process to create a new lock file, and then the first process to overwrite that lock file with its own lock file, leaving both processes under the impression they have acquired the lock. Future implementations may remedy this. In the meantime the possibility can be avoided by setting a sufficiently large "nsec" value when acquiring a lock that it will not expire before the owning process is ready to release it.
625              
626             The nested lock management C and C implement is flawed, in that the lock on the file is only valid for as long specified by the first invocation of C. Thus if a file is locked for 3 seconds, and then subsequently locked for 30 seconds, other processes contending for the locked file will forceably acquire the lock after 3 seconds after the first lock, not 30 seconds after the second lock. Future implementations may overwrite the lockfile to reflect the parameters of subsequent (nested) locks.
627              
628             The file-slurping functions handle data explicitly as B and never as codepoints. This is intentional and unlikely to change. If codepoint handling (utf-8, utf-16, etc) is desired, see L.
629              
630             =item B
631              
632             L is considered the likely successor to C for CPAN's primary file-slurping implementation, with proper handling of multibyte-encoded characters (L in C). If C implemented an appending method, the slurp functions would likely be absent from C. Until then, C's slurping functions provide a simple, robust alternative.
633              
634             L - a different style for file handling, which some people might prefer.
635              
636             L - returns name and handle of a temporary file.
637              
638             L - searches an environment path for a file.
639              
640             L - file locker with an automatic out-of-scope unlocking mechanism.
641              
642             L - file locker with timeout, but no lock expiration.
643              
644             L - file locker with support for nested locks.
645              
646             L - a very easy to use file locker.
647              
648             L - a gaggle of useful functions, including a simple slurp().
649              
650             =back
651              
652             =head1 AUTHOR
653              
654             TTK Ciar
655              
656             =head1 LICENSE
657              
658             You can use and distribute this module under the same terms as Perl itself.
659              
660             =cut