File Coverage

blib/lib/File/Remove.pm
Criterion Covered Total %
statement 107 133 80.4
branch 44 70 62.8
condition 1 3 33.3
subroutine 20 24 83.3
pod 6 6 100.0
total 178 236 75.4


line stmt bran cond sub pod time code
1             package File::Remove;
2             $File::Remove::VERSION = '1.60';
3 11     11   737492 use 5.008;
  11         134  
4 11     11   59 use strict;
  11         21  
  11         244  
5 11     11   53 use warnings;
  11         20  
  11         318  
6              
7 11     11   71 use vars qw{ @ISA @EXPORT_OK };
  11         21  
  11         716  
8 11     11   76 use vars qw{ $DEBUG $unlink $rmdir };
  11         33  
  11         754  
9              
10             BEGIN
11             {
12 11     11   197 @ISA = qw{ Exporter };
13 11         307 @EXPORT_OK = qw{ remove rm clear trash };
14             }
15              
16 11     11   76 use File::Path ();
  11         26  
  11         172  
17 11     11   58 use File::Glob ();
  11         20  
  11         332  
18 11     11   65 use File::Spec 3.29 ();
  11         288  
  11         323  
19 11     11   77 use Cwd 3.29 ();
  11         172  
  11         344  
20              
21             # $debug variable must be set before loading File::Remove.
22             # Convert to a constant to allow debugging code to be pruned out.
23 11     11   76 use constant DEBUG => !!$DEBUG;
  11         18  
  11         1293  
24              
25             # Are we on VMS?
26             # If so copy File::Path and assume VMS::Filespec is loaded
27 11     11   83 use constant IS_VMS => !!( $^O eq 'VMS' );
  11         19  
  11         741  
28              
29             # Are we on Mac?
30             # If so we'll need to do some special trash work
31 11     11   71 use constant IS_MAC => !!( $^O eq 'darwin' );
  11         37  
  11         1086  
32              
33             # Are we on Win32?
34             # If so write permissions does not imply deletion permissions
35 11   33 11   79 use constant IS_WIN32 => !!( $^O =~ /^MSWin/ or $^O eq 'cygwin' );
  11         17  
  11         16925  
36              
37             # If we ever need a Mac::Glue object we will want to cache it.
38             my $glue;
39              
40             #####################################################################
41             # Main Functions
42              
43             my @CLEANUP = ();
44              
45             ## no critic
46             sub clear (@)
47             {
48 4     4 1 6879 my @files = expand(@_);
49              
50             # Do the initial deletion
51 4         36 foreach my $file (@files)
52             {
53 4 50       60 next unless -e $file;
54 0         0 remove( \1, $file );
55             }
56              
57             # Delete again at END-time.
58             # Save the current PID so that forked children
59             # won't delete things that the parent expects to
60             # live until their end-time.
61 4         22 push @CLEANUP, map { [ $$, $_ ] } @files;
  4         81  
62             }
63             ## use critic
64              
65             END
66             {
67 10     10   5238955 foreach my $file (@CLEANUP)
68             {
69 4 100       89 next unless $file->[0] == $$;
70 3 50       165 next unless -e $file->[1];
71 3         69 remove( \1, $file->[1] );
72             }
73             }
74              
75             # Acts like unlink would until given a directory as an argument, then
76             # it acts like rm -rf ;) unless the recursive arg is zero which it is by
77             # default
78             ## no critic
79             sub remove (@)
80             {
81             ## use critic
82 29 100   29 1 63613 my $recursive = ( ref $_[0] eq 'SCALAR' ) ? shift : \0;
83 29 100       285 my $opts = ( ref $_[0] eq 'HASH' ) ? shift : { glob => 1 };
84 29         199 my @files = _expand_with_opts( $opts, @_ );
85              
86             # Iterate over the files
87 29         85 my @removes;
88 29         113 foreach my $path (@files)
89             {
90             # need to check for symlink first
91             # could be pointing to nonexisting/non-readable destination
92 30 100       410 if ( -l $path )
93             {
94 2         4 print "link: $path\n" if DEBUG;
95 2 50       115 if ( $unlink ? $unlink->($path) : unlink($path) )
    50          
96             {
97 2         8 push @removes, $path;
98             }
99 2         6 next;
100             }
101 28 100       384 unless ( -e $path )
102             {
103 3         6 print "missing: $path\n" if DEBUG;
104 3         9 push @removes, $path; # Say we deleted it
105 3         9 next;
106             }
107 25         84 my $can_delete;
108 25 50       379 if (IS_VMS)
    0          
    0          
109             {
110             $can_delete = VMS::Filespec::candelete($path);
111             }
112             elsif (IS_WIN32)
113             {
114             # Assume we can delete it for the moment
115             $can_delete = 1;
116             }
117 0         0 elsif ( -w $path )
118             {
119             # We have write permissions already
120 25         87 $can_delete = 1;
121             }
122             elsif ( $< == 0 )
123             {
124             # Unixy and root
125 0         0 $can_delete = 1;
126             }
127             elsif ( ( lstat($path) )[4] == $< )
128             {
129             # I own the file
130 0         0 $can_delete = 1;
131             }
132             else
133             {
134             # I don't think we can delete it
135 0         0 $can_delete = 0;
136             }
137 25 50       134 unless ($can_delete)
138             {
139 0         0 print "nowrite: $path\n" if DEBUG;
140 0         0 next;
141             }
142              
143 25 100       561 if ( -f $path )
    50          
144             {
145 1         3 print "file: $path\n" if DEBUG;
146 1 50       14 unless ( -w $path )
147             {
148             # Make the file writable (implementation from File::Path)
149 0 0       0 ( undef, undef, my $rp ) = lstat $path or next;
150 0         0 $rp &= 07777; # Don't forget setuid, setgid, sticky bits
151 0         0 $rp |= 0600; # Turn on user read/write
152 0         0 chmod $rp, $path;
153             }
154 1 50       69 if ( $unlink ? $unlink->($path) : unlink($path) )
    50          
155             {
156             # Failed to delete the file
157 1 50       22 next if -e $path;
158 1         5 push @removes, $path;
159             }
160              
161             }
162             elsif ( -d $path )
163             {
164 24         59 print "dir: $path\n" if DEBUG;
165 24         370 my $dir = File::Spec->canonpath($path);
166              
167             # Do we need to move our cwd out of the location
168             # we are planning to delete?
169 24         116 my $chdir = _moveto($dir);
170 24 100       229 if ( length $chdir )
171             {
172 1 50       32 chdir($chdir) or next;
173             }
174              
175 24 100       129 if ($$recursive)
176             {
177 16 50       9620 if ( File::Path::rmtree( [$dir], DEBUG, 0 ) )
178             {
179             # Failed to delete the directory
180 16 50       390 next if -e $path;
181 16         303 push @removes, $path;
182             }
183              
184             }
185             else
186             {
187 8         144 my ($save_mode) = ( stat $dir )[2];
188 8         187 chmod $save_mode & 0777,
189             $dir; # just in case we cannot remove it.
190 8 50       386 if ( $rmdir ? $rmdir->($dir) : rmdir($dir) )
    50          
191             {
192             # Failed to delete the directory
193 8 50       152 next if -e $path;
194 8         102 push @removes, $path;
195             }
196             }
197              
198             }
199             else
200             {
201 0         0 print "???: $path\n" if DEBUG;
202             }
203             }
204              
205 29         1474 return @removes;
206             }
207              
208             sub rm (@)
209             {
210 0     0 1 0 goto &remove;
211             }
212              
213             sub trash (@)
214             {
215 0     0 1 0 local $unlink = $unlink;
216 0         0 local $rmdir = $rmdir;
217              
218 0 0       0 if ( ref $_[0] eq 'HASH' )
219             {
220 0         0 my %options = %{ +shift @_ };
  0         0  
221 0         0 $unlink = $options{unlink};
222 0         0 $rmdir = $options{rmdir};
223              
224             }
225             elsif (IS_WIN32)
226             {
227             local $@;
228             eval 'use Win32::FileOp ();';
229             die "Can't load Win32::FileOp to support the Recycle Bin: \$@ = $@"
230             if length $@;
231             $unlink = \&Win32::FileOp::Recycle;
232             $rmdir = \&Win32::FileOp::Recycle;
233              
234             }
235             elsif (IS_MAC)
236             {
237             unless ($glue)
238             {
239             local $@;
240             eval 'use Mac::Glue ();';
241             die
242             "Can't load Mac::Glue::Finder to support the Trash Can: \$@ = $@"
243             if length $@;
244             $glue = Mac::Glue->new('Finder');
245             }
246             my $code = sub {
247             my @files =
248 0     0   0 map { Mac::Glue::param_type( Mac::Glue::typeAlias() => $_ ) }
  0         0  
249             @_;
250 0         0 $glue->delete( \@files );
251             };
252             $unlink = $code;
253             $rmdir = $code;
254             }
255             else
256             {
257 0         0 die
258             "Support for trash() on platform '$^O' not available at this time.\n";
259             }
260              
261 0         0 remove(@_);
262             }
263              
264             sub undelete (@)
265             {
266 0     0 1 0 goto &trash;
267             }
268              
269             ######################################################################
270             # Support Functions
271              
272             sub _expand_with_opts
273             {
274 29     29   131 my $opts = shift;
275 29 100       176 return ( $opts->{glob} ? expand(@_) : @_ );
276             }
277              
278             sub expand (@)
279             {
280 33 100   33 1 18663 map { -e $_ ? $_ : File::Glob::bsd_glob($_) } @_;
  34         1299  
281             }
282              
283             # Do we need to move to a different directory to delete a directory,
284             # and if so which.
285             sub _moveto
286             {
287 28     28   5764 my $remove = File::Spec->rel2abs(shift);
288 28 100       60355 my $cwd = @_ ? shift : Cwd::cwd();
289              
290             # Do everything in absolute terms
291 28         2437 $remove = Cwd::abs_path($remove);
292 28         1215 $cwd = Cwd::abs_path($cwd);
293              
294             # If we are on a different volume we don't need to move
295 28         1300 my ( $cv, $cd ) = File::Spec->splitpath( $cwd, 1 );
296 28         326 my ( $rv, $rd ) = File::Spec->splitpath( $remove, 1 );
297 28 50       231 return '' unless $cv eq $rv;
298              
299             # If we have to move, it's to one level above the deletion
300 28         521 my @cd = File::Spec->splitdir($cd);
301 28         380 my @rd = File::Spec->splitdir($rd);
302              
303             # Is the current directory the same as or inside the remove directory?
304 28 100       176 unless ( @cd >= @rd )
305             {
306 24         485 return '';
307             }
308 4         29 foreach ( 0 .. $#rd )
309             {
310 26 100       79 $cd[$_] eq $rd[$_] or return '';
311             }
312              
313             # Confirmed, the current working dir is in the removal dir
314 3         7 pop @rd;
315 3         83 return File::Spec->catpath( $rv, File::Spec->catdir(@rd), '' );
316             }
317              
318             1;
319              
320             __END__