Filename | /Users/timbo/perl5/perlbrew/perls/perl-5.18.2/lib/5.18.2/File/Path.pm |
Statements | Executed 30 statements in 2.41ms |
Calls | P | F | Exclusive Time |
Inclusive Time |
Subroutine |
---|---|---|---|---|---|
1 | 1 | 1 | 21µs | 22µs | BEGIN@27 | File::Path::
1 | 1 | 1 | 14µs | 14µs | BEGIN@3 | File::Path::
1 | 1 | 1 | 10µs | 55µs | BEGIN@6 | File::Path::
1 | 1 | 1 | 10µs | 20µs | BEGIN@29 | File::Path::
1 | 1 | 1 | 7µs | 61µs | BEGIN@20 | File::Path::
1 | 1 | 1 | 6µs | 17µs | BEGIN@4 | File::Path::
1 | 1 | 1 | 4µs | 4µs | BEGIN@10 | File::Path::
1 | 1 | 1 | 3µs | 3µs | BEGIN@7 | File::Path::
1 | 1 | 1 | 3µs | 3µs | BEGIN@19 | File::Path::
1 | 1 | 1 | 2µs | 2µs | BEGIN@8 | File::Path::
1 | 1 | 1 | 900ns | 900ns | __ANON__ (xsub) | File::Path::
0 | 0 | 0 | 0s | 0s | __is_arg | File::Path::
0 | 0 | 0 | 0s | 0s | _carp | File::Path::
0 | 0 | 0 | 0s | 0s | _croak | File::Path::
0 | 0 | 0 | 0s | 0s | _error | File::Path::
0 | 0 | 0 | 0s | 0s | _is_subdir | File::Path::
0 | 0 | 0 | 0s | 0s | _mkpath | File::Path::
0 | 0 | 0 | 0s | 0s | _rmtree | File::Path::
0 | 0 | 0 | 0s | 0s | _slash_lc | File::Path::
0 | 0 | 0 | 0s | 0s | make_path | File::Path::
0 | 0 | 0 | 0s | 0s | mkpath | File::Path::
0 | 0 | 0 | 0s | 0s | remove_tree | File::Path::
0 | 0 | 0 | 0s | 0s | rmtree | File::Path::
Line | State ments |
Time on line |
Calls | Time in subs |
Code |
---|---|---|---|---|---|
1 | package File::Path; | ||||
2 | |||||
3 | 2 | 35µs | 1 | 14µs | # spent 14µs within File::Path::BEGIN@3 which was called:
# once (14µs+0s) by File::Temp::BEGIN@14 at line 3 # spent 14µs making 1 call to File::Path::BEGIN@3 |
4 | 2 | 20µs | 2 | 29µs | # spent 17µs (6+11) within File::Path::BEGIN@4 which was called:
# once (6µs+11µs) by File::Temp::BEGIN@14 at line 4 # spent 17µs making 1 call to File::Path::BEGIN@4
# spent 11µs making 1 call to strict::import |
5 | |||||
6 | 2 | 22µs | 2 | 100µs | # spent 55µs (10+45) within File::Path::BEGIN@6 which was called:
# once (10µs+45µs) by File::Temp::BEGIN@14 at line 6 # spent 55µs making 1 call to File::Path::BEGIN@6
# spent 45µs making 1 call to Exporter::import |
7 | 2 | 15µs | 1 | 3µs | # spent 3µs within File::Path::BEGIN@7 which was called:
# once (3µs+0s) by File::Temp::BEGIN@14 at line 7 # spent 3µs making 1 call to File::Path::BEGIN@7 |
8 | 2 | 31µs | 1 | 2µs | # spent 2µs within File::Path::BEGIN@8 which was called:
# once (2µs+0s) by File::Temp::BEGIN@14 at line 8 # spent 2µs making 1 call to File::Path::BEGIN@8 |
9 | |||||
10 | # spent 4µs within File::Path::BEGIN@10 which was called:
# once (4µs+0s) by File::Temp::BEGIN@14 at line 17 | ||||
11 | 1 | 4µs | if ( $] < 5.006 ) { | ||
12 | |||||
13 | # can't say 'opendir my $dh, $dirname' | ||||
14 | # need to initialise $dh | ||||
15 | eval 'use Symbol'; | ||||
16 | } | ||||
17 | 1 | 12µs | 1 | 4µs | } # spent 4µs making 1 call to File::Path::BEGIN@10 |
18 | |||||
19 | 2 | 22µs | 1 | 3µs | # spent 3µs within File::Path::BEGIN@19 which was called:
# once (3µs+0s) by File::Temp::BEGIN@14 at line 19 # spent 3µs making 1 call to File::Path::BEGIN@19 |
20 | 2 | 53µs | 2 | 115µs | # spent 61µs (7+54) within File::Path::BEGIN@20 which was called:
# once (7µs+54µs) by File::Temp::BEGIN@14 at line 20 # spent 61µs making 1 call to File::Path::BEGIN@20
# spent 54µs making 1 call to vars::import |
21 | 1 | 600ns | $VERSION = '2.11'; | ||
22 | 1 | 10µs | $VERSION = eval $VERSION; # spent 2µs executing statements in string eval | ||
23 | 1 | 6µs | @ISA = qw(Exporter); | ||
24 | 1 | 700ns | @EXPORT = qw(mkpath rmtree); | ||
25 | 1 | 400ns | @EXPORT_OK = qw(make_path remove_tree); | ||
26 | |||||
27 | # spent 22µs (21+900ns) within File::Path::BEGIN@27 which was called:
# once (21µs+900ns) by File::Temp::BEGIN@14 at line 42 | ||||
28 | 1 | 700ns | for (qw(VMS MacOS MSWin32 os2)) { | ||
29 | 2 | 123µs | 2 | 31µs | # spent 20µs (10+11) within File::Path::BEGIN@29 which was called:
# once (10µs+11µs) by File::Temp::BEGIN@14 at line 29 # spent 20µs making 1 call to File::Path::BEGIN@29
# spent 11µs making 1 call to strict::unimport |
30 | 4 | 8µs | *{"_IS_\U$_"} = $^O eq $_ ? sub () { 1 } : sub () { 0 }; | ||
31 | } | ||||
32 | |||||
33 | # These OSes complain if you want to remove a file that you have no | ||||
34 | # write permission to: | ||||
35 | *_FORCE_WRITABLE = ( | ||||
36 | grep { $^O eq $_ } qw(amigaos dos epoc MSWin32 MacOS os2) | ||||
37 | 1 | 2µs | ) ? sub () { 1 } : sub () { 0 }; | ||
38 | |||||
39 | # Unix-like systems need to stat each directory in order to detect | ||||
40 | # race condition. MS-Windows is immune to this particular attack. | ||||
41 | 1 | 9µs | 1 | 900ns | *_NEED_STAT_CHECK = !(_IS_MSWIN32()) ? sub () { 1 } : sub () { 0 }; # spent 900ns making 1 call to File::Path::__ANON__ |
42 | 1 | 2.03ms | 1 | 22µs | } # spent 22µs making 1 call to File::Path::BEGIN@27 |
43 | |||||
44 | sub _carp { | ||||
45 | require Carp; | ||||
46 | goto &Carp::carp; | ||||
47 | } | ||||
48 | |||||
49 | sub _croak { | ||||
50 | require Carp; | ||||
51 | goto &Carp::croak; | ||||
52 | } | ||||
53 | |||||
54 | sub _error { | ||||
55 | my $arg = shift; | ||||
56 | my $message = shift; | ||||
57 | my $object = shift; | ||||
58 | |||||
59 | if ( $arg->{error} ) { | ||||
60 | $object = '' unless defined $object; | ||||
61 | $message .= ": $!" if $!; | ||||
62 | push @{ ${ $arg->{error} } }, { $object => $message }; | ||||
63 | } | ||||
64 | else { | ||||
65 | _carp( defined($object) ? "$message for $object: $!" : "$message: $!" ); | ||||
66 | } | ||||
67 | } | ||||
68 | |||||
69 | sub __is_arg { | ||||
70 | my ($arg) = @_; | ||||
71 | |||||
72 | # If client code blessed an array ref to HASH, this will not work | ||||
73 | # properly. We could have done $arg->isa() wrapped in eval, but | ||||
74 | # that would be expensive. This implementation should suffice. | ||||
75 | # We could have also used Scalar::Util:blessed, but we choose not | ||||
76 | # to add this dependency | ||||
77 | return ( ref $arg eq 'HASH' ); | ||||
78 | } | ||||
79 | |||||
80 | sub make_path { | ||||
81 | push @_, {} unless @_ and __is_arg( $_[-1] ); | ||||
82 | goto &mkpath; | ||||
83 | } | ||||
84 | |||||
85 | sub mkpath { | ||||
86 | my $old_style = !( @_ and __is_arg( $_[-1] ) ); | ||||
87 | |||||
88 | my $arg; | ||||
89 | my $paths; | ||||
90 | |||||
91 | if ($old_style) { | ||||
92 | my ( $verbose, $mode ); | ||||
93 | ( $paths, $verbose, $mode ) = @_; | ||||
94 | $paths = [$paths] unless UNIVERSAL::isa( $paths, 'ARRAY' ); | ||||
95 | $arg->{verbose} = $verbose; | ||||
96 | $arg->{mode} = defined $mode ? $mode : oct '777'; | ||||
97 | } | ||||
98 | else { | ||||
99 | my %args_permitted = map { $_ => 1 } ( qw| | ||||
100 | chmod | ||||
101 | error | ||||
102 | group | ||||
103 | mask | ||||
104 | mode | ||||
105 | owner | ||||
106 | uid | ||||
107 | user | ||||
108 | verbose | ||||
109 | | ); | ||||
110 | my @bad_args = (); | ||||
111 | $arg = pop @_; | ||||
112 | for my $k (sort keys %{$arg}) { | ||||
113 | push @bad_args, $k unless $args_permitted{$k}; | ||||
114 | } | ||||
115 | _carp("Unrecognized option(s) passed to make_path(): @bad_args") | ||||
116 | if @bad_args; | ||||
117 | $arg->{mode} = delete $arg->{mask} if exists $arg->{mask}; | ||||
118 | $arg->{mode} = oct '777' unless exists $arg->{mode}; | ||||
119 | ${ $arg->{error} } = [] if exists $arg->{error}; | ||||
120 | $arg->{owner} = delete $arg->{user} if exists $arg->{user}; | ||||
121 | $arg->{owner} = delete $arg->{uid} if exists $arg->{uid}; | ||||
122 | if ( exists $arg->{owner} and $arg->{owner} =~ /\D/ ) { | ||||
123 | my $uid = ( getpwnam $arg->{owner} )[2]; | ||||
124 | if ( defined $uid ) { | ||||
125 | $arg->{owner} = $uid; | ||||
126 | } | ||||
127 | else { | ||||
128 | _error( $arg, | ||||
129 | "unable to map $arg->{owner} to a uid, ownership not changed" | ||||
130 | ); | ||||
131 | delete $arg->{owner}; | ||||
132 | } | ||||
133 | } | ||||
134 | if ( exists $arg->{group} and $arg->{group} =~ /\D/ ) { | ||||
135 | my $gid = ( getgrnam $arg->{group} )[2]; | ||||
136 | if ( defined $gid ) { | ||||
137 | $arg->{group} = $gid; | ||||
138 | } | ||||
139 | else { | ||||
140 | _error( $arg, | ||||
141 | "unable to map $arg->{group} to a gid, group ownership not changed" | ||||
142 | ); | ||||
143 | delete $arg->{group}; | ||||
144 | } | ||||
145 | } | ||||
146 | if ( exists $arg->{owner} and not exists $arg->{group} ) { | ||||
147 | $arg->{group} = -1; # chown will leave group unchanged | ||||
148 | } | ||||
149 | if ( exists $arg->{group} and not exists $arg->{owner} ) { | ||||
150 | $arg->{owner} = -1; # chown will leave owner unchanged | ||||
151 | } | ||||
152 | $paths = [@_]; | ||||
153 | } | ||||
154 | return _mkpath( $arg, $paths ); | ||||
155 | } | ||||
156 | |||||
157 | sub _mkpath { | ||||
158 | my $arg = shift; | ||||
159 | my $paths = shift; | ||||
160 | |||||
161 | my ( @created ); | ||||
162 | foreach my $path ( @{$paths} ) { | ||||
163 | next unless defined($path) and length($path); | ||||
164 | $path .= '/' if _IS_OS2 and $path =~ /^\w:\z/s; # feature of CRT | ||||
165 | |||||
166 | # Logic wants Unix paths, so go with the flow. | ||||
167 | if (_IS_VMS) { | ||||
168 | next if $path eq '/'; | ||||
169 | $path = VMS::Filespec::unixify($path); | ||||
170 | } | ||||
171 | next if -d $path; | ||||
172 | my $parent = File::Basename::dirname($path); | ||||
173 | unless ( -d $parent or $path eq $parent ) { | ||||
174 | push( @created, _mkpath( $arg, [$parent] ) ); | ||||
175 | } | ||||
176 | print "mkdir $path\n" if $arg->{verbose}; | ||||
177 | if ( mkdir( $path, $arg->{mode} ) ) { | ||||
178 | push( @created, $path ); | ||||
179 | if ( exists $arg->{owner} ) { | ||||
180 | |||||
181 | # NB: $arg->{group} guaranteed to be set during initialisation | ||||
182 | if ( !chown $arg->{owner}, $arg->{group}, $path ) { | ||||
183 | _error( $arg, | ||||
184 | "Cannot change ownership of $path to $arg->{owner}:$arg->{group}" | ||||
185 | ); | ||||
186 | } | ||||
187 | } | ||||
188 | if ( exists $arg->{chmod} ) { | ||||
189 | if ( !chmod $arg->{chmod}, $path ) { | ||||
190 | _error( $arg, | ||||
191 | "Cannot change permissions of $path to $arg->{chmod}" ); | ||||
192 | } | ||||
193 | } | ||||
194 | } | ||||
195 | else { | ||||
196 | my $save_bang = $!; | ||||
197 | my ( $e, $e1 ) = ( $save_bang, $^E ); | ||||
198 | $e .= "; $e1" if $e ne $e1; | ||||
199 | |||||
200 | # allow for another process to have created it meanwhile | ||||
201 | if ( ! -d $path ) { | ||||
202 | $! = $save_bang; | ||||
203 | if ( $arg->{error} ) { | ||||
204 | push @{ ${ $arg->{error} } }, { $path => $e }; | ||||
205 | } | ||||
206 | else { | ||||
207 | _croak("mkdir $path: $e"); | ||||
208 | } | ||||
209 | } | ||||
210 | } | ||||
211 | } | ||||
212 | return @created; | ||||
213 | } | ||||
214 | |||||
215 | sub remove_tree { | ||||
216 | push @_, {} unless @_ and __is_arg( $_[-1] ); | ||||
217 | goto &rmtree; | ||||
218 | } | ||||
219 | |||||
220 | sub _is_subdir { | ||||
221 | my ( $dir, $test ) = @_; | ||||
222 | |||||
223 | my ( $dv, $dd ) = File::Spec->splitpath( $dir, 1 ); | ||||
224 | my ( $tv, $td ) = File::Spec->splitpath( $test, 1 ); | ||||
225 | |||||
226 | # not on same volume | ||||
227 | return 0 if $dv ne $tv; | ||||
228 | |||||
229 | my @d = File::Spec->splitdir($dd); | ||||
230 | my @t = File::Spec->splitdir($td); | ||||
231 | |||||
232 | # @t can't be a subdir if it's shorter than @d | ||||
233 | return 0 if @t < @d; | ||||
234 | |||||
235 | return join( '/', @d ) eq join( '/', splice @t, 0, +@d ); | ||||
236 | } | ||||
237 | |||||
238 | sub rmtree { | ||||
239 | my $old_style = !( @_ and __is_arg( $_[-1] ) ); | ||||
240 | |||||
241 | my $arg; | ||||
242 | my $paths; | ||||
243 | |||||
244 | if ($old_style) { | ||||
245 | my ( $verbose, $safe ); | ||||
246 | ( $paths, $verbose, $safe ) = @_; | ||||
247 | $arg->{verbose} = $verbose; | ||||
248 | $arg->{safe} = defined $safe ? $safe : 0; | ||||
249 | |||||
250 | if ( defined($paths) and length($paths) ) { | ||||
251 | $paths = [$paths] unless UNIVERSAL::isa( $paths, 'ARRAY' ); | ||||
252 | } | ||||
253 | else { | ||||
254 | _carp("No root path(s) specified\n"); | ||||
255 | return 0; | ||||
256 | } | ||||
257 | } | ||||
258 | else { | ||||
259 | my %args_permitted = map { $_ => 1 } ( qw| | ||||
260 | error | ||||
261 | keep_root | ||||
262 | result | ||||
263 | safe | ||||
264 | verbose | ||||
265 | | ); | ||||
266 | my @bad_args = (); | ||||
267 | $arg = pop @_; | ||||
268 | for my $k (sort keys %{$arg}) { | ||||
269 | push @bad_args, $k unless $args_permitted{$k}; | ||||
270 | } | ||||
271 | _carp("Unrecognized option(s) passed to remove_tree(): @bad_args") | ||||
272 | if @bad_args; | ||||
273 | ${ $arg->{error} } = [] if exists $arg->{error}; | ||||
274 | ${ $arg->{result} } = [] if exists $arg->{result}; | ||||
275 | $paths = [@_]; | ||||
276 | } | ||||
277 | |||||
278 | $arg->{prefix} = ''; | ||||
279 | $arg->{depth} = 0; | ||||
280 | |||||
281 | my @clean_path; | ||||
282 | $arg->{cwd} = getcwd() or do { | ||||
283 | _error( $arg, "cannot fetch initial working directory" ); | ||||
284 | return 0; | ||||
285 | }; | ||||
286 | for ( $arg->{cwd} ) { /\A(.*)\Z/s; $_ = $1 } # untaint | ||||
287 | |||||
288 | for my $p (@$paths) { | ||||
289 | |||||
290 | # need to fixup case and map \ to / on Windows | ||||
291 | my $ortho_root = _IS_MSWIN32 ? _slash_lc($p) : $p; | ||||
292 | my $ortho_cwd = | ||||
293 | _IS_MSWIN32 ? _slash_lc( $arg->{cwd} ) : $arg->{cwd}; | ||||
294 | my $ortho_root_length = length($ortho_root); | ||||
295 | $ortho_root_length-- if _IS_VMS; # don't compare '.' with ']' | ||||
296 | if ( $ortho_root_length && _is_subdir( $ortho_root, $ortho_cwd ) ) { | ||||
297 | local $! = 0; | ||||
298 | _error( $arg, "cannot remove path when cwd is $arg->{cwd}", $p ); | ||||
299 | next; | ||||
300 | } | ||||
301 | |||||
302 | if (_IS_MACOS) { | ||||
303 | $p = ":$p" unless $p =~ /:/; | ||||
304 | $p .= ":" unless $p =~ /:\z/; | ||||
305 | } | ||||
306 | elsif ( _IS_MSWIN32 ) { | ||||
307 | $p =~ s{[/\\]\z}{}; | ||||
308 | } | ||||
309 | else { | ||||
310 | $p =~ s{/\z}{}; | ||||
311 | } | ||||
312 | push @clean_path, $p; | ||||
313 | } | ||||
314 | |||||
315 | @{$arg}{qw(device inode perm)} = ( lstat $arg->{cwd} )[ 0, 1 ] or do { | ||||
316 | _error( $arg, "cannot stat initial working directory", $arg->{cwd} ); | ||||
317 | return 0; | ||||
318 | }; | ||||
319 | |||||
320 | return _rmtree( $arg, \@clean_path ); | ||||
321 | } | ||||
322 | |||||
323 | sub _rmtree { | ||||
324 | my $arg = shift; | ||||
325 | my $paths = shift; | ||||
326 | |||||
327 | my $count = 0; | ||||
328 | my $curdir = File::Spec->curdir(); | ||||
329 | my $updir = File::Spec->updir(); | ||||
330 | |||||
331 | my ( @files, $root ); | ||||
332 | ROOT_DIR: | ||||
333 | foreach my $root (@$paths) { | ||||
334 | |||||
335 | # since we chdir into each directory, it may not be obvious | ||||
336 | # to figure out where we are if we generate a message about | ||||
337 | # a file name. We therefore construct a semi-canonical | ||||
338 | # filename, anchored from the directory being unlinked (as | ||||
339 | # opposed to being truly canonical, anchored from the root (/). | ||||
340 | |||||
341 | my $canon = | ||||
342 | $arg->{prefix} | ||||
343 | ? File::Spec->catfile( $arg->{prefix}, $root ) | ||||
344 | : $root; | ||||
345 | |||||
346 | my ( $ldev, $lino, $perm ) = ( lstat $root )[ 0, 1, 2 ] | ||||
347 | or ( _error( $arg, "$root", $root ) and next ROOT_DIR ); | ||||
348 | |||||
349 | if ( -d _ ) { | ||||
350 | $root = VMS::Filespec::vmspath( VMS::Filespec::pathify($root) ) | ||||
351 | if _IS_VMS; | ||||
352 | |||||
353 | if ( !chdir($root) ) { | ||||
354 | |||||
355 | # see if we can escalate privileges to get in | ||||
356 | # (e.g. funny protection mask such as -w- instead of rwx) | ||||
357 | $perm &= oct '7777'; | ||||
358 | my $nperm = $perm | oct '700'; | ||||
359 | if ( | ||||
360 | !( | ||||
361 | $arg->{safe} | ||||
362 | or $nperm == $perm | ||||
363 | or chmod( $nperm, $root ) | ||||
364 | ) | ||||
365 | ) | ||||
366 | { | ||||
367 | _error( $arg, | ||||
368 | "cannot make child directory read-write-exec", $canon ); | ||||
369 | next ROOT_DIR; | ||||
370 | } | ||||
371 | elsif ( !chdir($root) ) { | ||||
372 | _error( $arg, "cannot chdir to child", $canon ); | ||||
373 | next ROOT_DIR; | ||||
374 | } | ||||
375 | } | ||||
376 | |||||
377 | my ( $cur_dev, $cur_inode, $perm ) = ( stat $curdir )[ 0, 1, 2 ] | ||||
378 | or do { | ||||
379 | _error( $arg, "cannot stat current working directory", $canon ); | ||||
380 | next ROOT_DIR; | ||||
381 | }; | ||||
382 | |||||
383 | if (_NEED_STAT_CHECK) { | ||||
384 | ( $ldev eq $cur_dev and $lino eq $cur_inode ) | ||||
385 | or _croak( | ||||
386 | "directory $canon changed before chdir, expected dev=$ldev ino=$lino, actual dev=$cur_dev ino=$cur_inode, aborting." | ||||
387 | ); | ||||
388 | } | ||||
389 | |||||
390 | $perm &= oct '7777'; # don't forget setuid, setgid, sticky bits | ||||
391 | my $nperm = $perm | oct '700'; | ||||
392 | |||||
393 | # notabene: 0700 is for making readable in the first place, | ||||
394 | # it's also intended to change it to writable in case we have | ||||
395 | # to recurse in which case we are better than rm -rf for | ||||
396 | # subtrees with strange permissions | ||||
397 | |||||
398 | if ( | ||||
399 | !( | ||||
400 | $arg->{safe} | ||||
401 | or $nperm == $perm | ||||
402 | or chmod( $nperm, $curdir ) | ||||
403 | ) | ||||
404 | ) | ||||
405 | { | ||||
406 | _error( $arg, "cannot make directory read+writeable", $canon ); | ||||
407 | $nperm = $perm; | ||||
408 | } | ||||
409 | |||||
410 | my $d; | ||||
411 | $d = gensym() if $] < 5.006; | ||||
412 | if ( !opendir $d, $curdir ) { | ||||
413 | _error( $arg, "cannot opendir", $canon ); | ||||
414 | @files = (); | ||||
415 | } | ||||
416 | else { | ||||
417 | if ( !defined ${^TAINT} or ${^TAINT} ) { | ||||
418 | # Blindly untaint dir names if taint mode is active | ||||
419 | @files = map { /\A(.*)\z/s; $1 } readdir $d; | ||||
420 | } | ||||
421 | else { | ||||
422 | @files = readdir $d; | ||||
423 | } | ||||
424 | closedir $d; | ||||
425 | } | ||||
426 | |||||
427 | if (_IS_VMS) { | ||||
428 | |||||
429 | # Deleting large numbers of files from VMS Files-11 | ||||
430 | # filesystems is faster if done in reverse ASCIIbetical order. | ||||
431 | # include '.' to '.;' from blead patch #31775 | ||||
432 | @files = map { $_ eq '.' ? '.;' : $_ } reverse @files; | ||||
433 | } | ||||
434 | |||||
435 | @files = grep { $_ ne $updir and $_ ne $curdir } @files; | ||||
436 | |||||
437 | if (@files) { | ||||
438 | |||||
439 | # remove the contained files before the directory itself | ||||
440 | my $narg = {%$arg}; | ||||
441 | @{$narg}{qw(device inode cwd prefix depth)} = | ||||
442 | ( $cur_dev, $cur_inode, $updir, $canon, $arg->{depth} + 1 ); | ||||
443 | $count += _rmtree( $narg, \@files ); | ||||
444 | } | ||||
445 | |||||
446 | # restore directory permissions of required now (in case the rmdir | ||||
447 | # below fails), while we are still in the directory and may do so | ||||
448 | # without a race via '.' | ||||
449 | if ( $nperm != $perm and not chmod( $perm, $curdir ) ) { | ||||
450 | _error( $arg, "cannot reset chmod", $canon ); | ||||
451 | } | ||||
452 | |||||
453 | # don't leave the client code in an unexpected directory | ||||
454 | chdir( $arg->{cwd} ) | ||||
455 | or | ||||
456 | _croak("cannot chdir to $arg->{cwd} from $canon: $!, aborting."); | ||||
457 | |||||
458 | # ensure that a chdir upwards didn't take us somewhere other | ||||
459 | # than we expected (see CVE-2002-0435) | ||||
460 | ( $cur_dev, $cur_inode ) = ( stat $curdir )[ 0, 1 ] | ||||
461 | or _croak( | ||||
462 | "cannot stat prior working directory $arg->{cwd}: $!, aborting." | ||||
463 | ); | ||||
464 | |||||
465 | if (_NEED_STAT_CHECK) { | ||||
466 | ( $arg->{device} eq $cur_dev and $arg->{inode} eq $cur_inode ) | ||||
467 | or _croak( "previous directory $arg->{cwd} " | ||||
468 | . "changed before entering $canon, " | ||||
469 | . "expected dev=$ldev ino=$lino, " | ||||
470 | . "actual dev=$cur_dev ino=$cur_inode, aborting." | ||||
471 | ); | ||||
472 | } | ||||
473 | |||||
474 | if ( $arg->{depth} or !$arg->{keep_root} ) { | ||||
475 | if ( $arg->{safe} | ||||
476 | && ( _IS_VMS | ||||
477 | ? !&VMS::Filespec::candelete($root) | ||||
478 | : !-w $root ) ) | ||||
479 | { | ||||
480 | print "skipped $root\n" if $arg->{verbose}; | ||||
481 | next ROOT_DIR; | ||||
482 | } | ||||
483 | if ( _FORCE_WRITABLE and !chmod $perm | oct '700', $root ) { | ||||
484 | _error( $arg, "cannot make directory writeable", $canon ); | ||||
485 | } | ||||
486 | print "rmdir $root\n" if $arg->{verbose}; | ||||
487 | if ( rmdir $root ) { | ||||
488 | push @{ ${ $arg->{result} } }, $root if $arg->{result}; | ||||
489 | ++$count; | ||||
490 | } | ||||
491 | else { | ||||
492 | _error( $arg, "cannot remove directory", $canon ); | ||||
493 | if ( | ||||
494 | _FORCE_WRITABLE | ||||
495 | && !chmod( $perm, | ||||
496 | ( _IS_VMS ? VMS::Filespec::fileify($root) : $root ) | ||||
497 | ) | ||||
498 | ) | ||||
499 | { | ||||
500 | _error( | ||||
501 | $arg, | ||||
502 | sprintf( "cannot restore permissions to 0%o", | ||||
503 | $perm ), | ||||
504 | $canon | ||||
505 | ); | ||||
506 | } | ||||
507 | } | ||||
508 | } | ||||
509 | } | ||||
510 | else { | ||||
511 | # not a directory | ||||
512 | $root = VMS::Filespec::vmsify("./$root") | ||||
513 | if _IS_VMS | ||||
514 | && !File::Spec->file_name_is_absolute($root) | ||||
515 | && ( $root !~ m/(?<!\^)[\]>]+/ ); # not already in VMS syntax | ||||
516 | |||||
517 | if ( | ||||
518 | $arg->{safe} | ||||
519 | && ( | ||||
520 | _IS_VMS | ||||
521 | ? !&VMS::Filespec::candelete($root) | ||||
522 | : !( -l $root || -w $root ) | ||||
523 | ) | ||||
524 | ) | ||||
525 | { | ||||
526 | print "skipped $root\n" if $arg->{verbose}; | ||||
527 | next ROOT_DIR; | ||||
528 | } | ||||
529 | |||||
530 | my $nperm = $perm & oct '7777' | oct '600'; | ||||
531 | if ( _FORCE_WRITABLE | ||||
532 | and $nperm != $perm | ||||
533 | and not chmod $nperm, $root ) | ||||
534 | { | ||||
535 | _error( $arg, "cannot make file writeable", $canon ); | ||||
536 | } | ||||
537 | print "unlink $canon\n" if $arg->{verbose}; | ||||
538 | |||||
539 | # delete all versions under VMS | ||||
540 | for ( ; ; ) { | ||||
541 | if ( unlink $root ) { | ||||
542 | push @{ ${ $arg->{result} } }, $root if $arg->{result}; | ||||
543 | } | ||||
544 | else { | ||||
545 | _error( $arg, "cannot unlink file", $canon ); | ||||
546 | _FORCE_WRITABLE and chmod( $perm, $root ) | ||||
547 | or _error( $arg, | ||||
548 | sprintf( "cannot restore permissions to 0%o", $perm ), | ||||
549 | $canon ); | ||||
550 | last; | ||||
551 | } | ||||
552 | ++$count; | ||||
553 | last unless _IS_VMS && lstat $root; | ||||
554 | } | ||||
555 | } | ||||
556 | } | ||||
557 | return $count; | ||||
558 | } | ||||
559 | |||||
560 | sub _slash_lc { | ||||
561 | |||||
562 | # fix up slashes and case on MSWin32 so that we can determine that | ||||
563 | # c:\path\to\dir is underneath C:/Path/To | ||||
564 | my $path = shift; | ||||
565 | $path =~ tr{\\}{/}; | ||||
566 | return lc($path); | ||||
567 | } | ||||
568 | |||||
569 | 1 | 3µs | 1; | ||
570 | |||||
571 | __END__ | ||||
# spent 900ns within File::Path::__ANON__ which was called:
# once (900ns+0s) by File::Path::BEGIN@27 at line 41 |