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