Filename | /usr/share/perl/5.10/File/Path.pm |
Statements | Executed 34 statements in 2.86ms |
Calls | P | F | Exclusive Time |
Inclusive Time |
Subroutine |
---|---|---|---|---|---|
1 | 1 | 1 | 3.38ms | 3.87ms | BEGIN@6 | File::Path::
1 | 1 | 1 | 66µs | 66µs | BEGIN@3 | File::Path::
1 | 1 | 1 | 16µs | 20µs | BEGIN@4 | File::Path::
1 | 1 | 1 | 13µs | 73µs | BEGIN@19 | File::Path::
1 | 1 | 1 | 11µs | 26µs | BEGIN@295 | File::Path::
1 | 1 | 1 | 8µs | 8µs | BEGIN@8 | File::Path::
1 | 1 | 1 | 5µs | 5µs | BEGIN@7 | File::Path::
1 | 1 | 1 | 5µs | 5µs | BEGIN@10 | File::Path::
1 | 1 | 1 | 4µs | 4µs | BEGIN@18 | 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 | 3 | 82µs | 1 | 66µs | # spent 66µs within File::Path::BEGIN@3 which was called:
# once (66µs+0s) by Template::BEGIN@32 at line 3 # spent 66µs making 1 call to File::Path::BEGIN@3 |
4 | 3 | 29µs | 2 | 24µs | # spent 20µs (16+4) within File::Path::BEGIN@4 which was called:
# once (16µs+4µs) by Template::BEGIN@32 at line 4 # spent 20µs making 1 call to File::Path::BEGIN@4
# spent 4µs making 1 call to strict::import |
5 | |||||
6 | 3 | 127µs | 2 | 3.93ms | # spent 3.87ms (3.38+489µs) within File::Path::BEGIN@6 which was called:
# once (3.38ms+489µs) by Template::BEGIN@32 at line 6 # spent 3.87ms making 1 call to File::Path::BEGIN@6
# spent 60µs making 1 call to Exporter::import |
7 | 3 | 22µs | 1 | 5µs | # spent 5µs within File::Path::BEGIN@7 which was called:
# once (5µs+0s) by Template::BEGIN@32 at line 7 # spent 5µs making 1 call to File::Path::BEGIN@7 |
8 | 3 | 44µs | 1 | 8µs | # spent 8µs within File::Path::BEGIN@8 which was called:
# once (8µs+0s) by Template::BEGIN@32 at line 8 # spent 8µs making 1 call to File::Path::BEGIN@8 |
9 | |||||
10 | # spent 5µs within File::Path::BEGIN@10 which was called:
# once (5µs+0s) by Template::BEGIN@32 at line 16 | ||||
11 | 1 | 5µs | if ($] < 5.006) { | ||
12 | # can't say 'opendir my $dh, $dirname' | ||||
13 | # need to initialise $dh | ||||
14 | eval "use Symbol"; | ||||
15 | } | ||||
16 | 1 | 18µs | 1 | 5µs | } # spent 5µs making 1 call to File::Path::BEGIN@10 |
17 | |||||
18 | 3 | 86µs | 1 | 4µs | # spent 4µs within File::Path::BEGIN@18 which was called:
# once (4µs+0s) by Template::BEGIN@32 at line 18 # spent 4µs making 1 call to File::Path::BEGIN@18 |
19 | 3 | 1.57ms | 2 | 133µs | # spent 73µs (13+60) within File::Path::BEGIN@19 which was called:
# once (13µs+60µs) by Template::BEGIN@32 at line 19 # spent 73µs making 1 call to File::Path::BEGIN@19
# spent 60µs making 1 call to vars::import |
20 | 1 | 700ns | $VERSION = '2.07_03'; | ||
21 | 1 | 9µs | @ISA = qw(Exporter); | ||
22 | 1 | 800ns | @EXPORT = qw(mkpath rmtree); | ||
23 | 1 | 800ns | @EXPORT_OK = qw(make_path remove_tree); | ||
24 | |||||
25 | 1 | 2µs | my $Is_VMS = $^O eq 'VMS'; | ||
26 | 1 | 600ns | my $Is_MacOS = $^O eq 'MacOS'; | ||
27 | |||||
28 | # These OSes complain if you want to remove a file that you have no | ||||
29 | # write permission to: | ||||
30 | 1 | 2µs | my $Force_Writeable = grep {$^O eq $_} qw(amigaos dos epoc MSWin32 MacOS os2); | ||
31 | |||||
32 | # Unix-like systems need to stat each directory in order to detect | ||||
33 | # race condition. MS-Windows is immune to this particular attack. | ||||
34 | 1 | 600ns | my $Need_Stat_Check = !($^O eq 'MSWin32'); | ||
35 | |||||
36 | sub _carp { | ||||
37 | require Carp; | ||||
38 | goto &Carp::carp; | ||||
39 | } | ||||
40 | |||||
41 | sub _croak { | ||||
42 | require Carp; | ||||
43 | goto &Carp::croak; | ||||
44 | } | ||||
45 | |||||
46 | sub _error { | ||||
47 | my $arg = shift; | ||||
48 | my $message = shift; | ||||
49 | my $object = shift; | ||||
50 | |||||
51 | if ($arg->{error}) { | ||||
52 | $object = '' unless defined $object; | ||||
53 | $message .= ": $!" if $!; | ||||
54 | push @{${$arg->{error}}}, {$object => $message}; | ||||
55 | } | ||||
56 | else { | ||||
57 | _carp(defined($object) ? "$message for $object: $!" : "$message: $!"); | ||||
58 | } | ||||
59 | } | ||||
60 | |||||
61 | sub make_path { | ||||
62 | push @_, {} unless @_ and UNIVERSAL::isa($_[-1],'HASH'); | ||||
63 | goto &mkpath; | ||||
64 | } | ||||
65 | |||||
66 | sub mkpath { | ||||
67 | my $old_style = !(@_ and UNIVERSAL::isa($_[-1],'HASH')); | ||||
68 | |||||
69 | my $arg; | ||||
70 | my $paths; | ||||
71 | |||||
72 | if ($old_style) { | ||||
73 | my ($verbose, $mode); | ||||
74 | ($paths, $verbose, $mode) = @_; | ||||
75 | $paths = [$paths] unless UNIVERSAL::isa($paths,'ARRAY'); | ||||
76 | $arg->{verbose} = $verbose; | ||||
77 | $arg->{mode} = defined $mode ? $mode : 0777; | ||||
78 | } | ||||
79 | else { | ||||
80 | $arg = pop @_; | ||||
81 | $arg->{mode} = delete $arg->{mask} if exists $arg->{mask}; | ||||
82 | $arg->{mode} = 0777 unless exists $arg->{mode}; | ||||
83 | ${$arg->{error}} = [] if exists $arg->{error}; | ||||
84 | $paths = [@_]; | ||||
85 | } | ||||
86 | return _mkpath($arg, $paths); | ||||
87 | } | ||||
88 | |||||
89 | sub _mkpath { | ||||
90 | my $arg = shift; | ||||
91 | my $paths = shift; | ||||
92 | |||||
93 | my(@created,$path); | ||||
94 | foreach $path (@$paths) { | ||||
95 | next unless defined($path) and length($path); | ||||
96 | $path .= '/' if $^O eq 'os2' and $path =~ /^\w:\z/s; # feature of CRT | ||||
97 | # Logic wants Unix paths, so go with the flow. | ||||
98 | if ($Is_VMS) { | ||||
99 | next if $path eq '/'; | ||||
100 | $path = VMS::Filespec::unixify($path); | ||||
101 | } | ||||
102 | next if -d $path; | ||||
103 | my $parent = File::Basename::dirname($path); | ||||
104 | unless (-d $parent or $path eq $parent) { | ||||
105 | push(@created,_mkpath($arg, [$parent])); | ||||
106 | } | ||||
107 | print "mkdir $path\n" if $arg->{verbose}; | ||||
108 | if (mkdir($path,$arg->{mode})) { | ||||
109 | push(@created, $path); | ||||
110 | } | ||||
111 | else { | ||||
112 | my $save_bang = $!; | ||||
113 | my ($e, $e1) = ($save_bang, $^E); | ||||
114 | $e .= "; $e1" if $e ne $e1; | ||||
115 | # allow for another process to have created it meanwhile | ||||
116 | if (!-d $path) { | ||||
117 | $! = $save_bang; | ||||
118 | if ($arg->{error}) { | ||||
119 | push @{${$arg->{error}}}, {$path => $e}; | ||||
120 | } | ||||
121 | else { | ||||
122 | _croak("mkdir $path: $e"); | ||||
123 | } | ||||
124 | } | ||||
125 | } | ||||
126 | } | ||||
127 | return @created; | ||||
128 | } | ||||
129 | |||||
130 | sub remove_tree { | ||||
131 | push @_, {} unless @_ and UNIVERSAL::isa($_[-1],'HASH'); | ||||
132 | goto &rmtree; | ||||
133 | } | ||||
134 | |||||
135 | sub _is_subdir { | ||||
136 | my($dir, $test) = @_; | ||||
137 | |||||
138 | my($dv, $dd) = File::Spec->splitpath($dir, 1); | ||||
139 | my($tv, $td) = File::Spec->splitpath($test, 1); | ||||
140 | |||||
141 | # not on same volume | ||||
142 | return 0 if $dv ne $tv; | ||||
143 | |||||
144 | my @d = File::Spec->splitdir($dd); | ||||
145 | my @t = File::Spec->splitdir($td); | ||||
146 | |||||
147 | # @t can't be a subdir if it's shorter than @d | ||||
148 | return 0 if @t < @d; | ||||
149 | |||||
150 | return join('/', @d) eq join('/', splice @t, 0, +@d); | ||||
151 | } | ||||
152 | |||||
153 | sub rmtree { | ||||
154 | my $old_style = !(@_ and UNIVERSAL::isa($_[-1],'HASH')); | ||||
155 | |||||
156 | my $arg; | ||||
157 | my $paths; | ||||
158 | |||||
159 | if ($old_style) { | ||||
160 | my ($verbose, $safe); | ||||
161 | ($paths, $verbose, $safe) = @_; | ||||
162 | $arg->{verbose} = $verbose; | ||||
163 | $arg->{safe} = defined $safe ? $safe : 0; | ||||
164 | |||||
165 | if (defined($paths) and length($paths)) { | ||||
166 | $paths = [$paths] unless UNIVERSAL::isa($paths,'ARRAY'); | ||||
167 | } | ||||
168 | else { | ||||
169 | _carp ("No root path(s) specified\n"); | ||||
170 | return 0; | ||||
171 | } | ||||
172 | } | ||||
173 | else { | ||||
174 | $arg = pop @_; | ||||
175 | ${$arg->{error}} = [] if exists $arg->{error}; | ||||
176 | ${$arg->{result}} = [] if exists $arg->{result}; | ||||
177 | $paths = [@_]; | ||||
178 | } | ||||
179 | |||||
180 | $arg->{prefix} = ''; | ||||
181 | $arg->{depth} = 0; | ||||
182 | |||||
183 | my @clean_path; | ||||
184 | $arg->{cwd} = getcwd() or do { | ||||
185 | _error($arg, "cannot fetch initial working directory"); | ||||
186 | return 0; | ||||
187 | }; | ||||
188 | for ($arg->{cwd}) { /\A(.*)\Z/; $_ = $1 } # untaint | ||||
189 | |||||
190 | for my $p (@$paths) { | ||||
191 | # need to fixup case and map \ to / on Windows | ||||
192 | my $ortho_root = $^O eq 'MSWin32' ? _slash_lc($p) : $p; | ||||
193 | my $ortho_cwd = $^O eq 'MSWin32' ? _slash_lc($arg->{cwd}) : $arg->{cwd}; | ||||
194 | my $ortho_root_length = length($ortho_root); | ||||
195 | $ortho_root_length-- if $^O eq 'VMS'; # don't compare '.' with ']' | ||||
196 | if ($ortho_root_length && _is_subdir($ortho_root, $ortho_cwd)) { | ||||
197 | local $! = 0; | ||||
198 | _error($arg, "cannot remove path when cwd is $arg->{cwd}", $p); | ||||
199 | next; | ||||
200 | } | ||||
201 | |||||
202 | if ($Is_MacOS) { | ||||
203 | $p = ":$p" unless $p =~ /:/; | ||||
204 | $p .= ":" unless $p =~ /:\z/; | ||||
205 | } | ||||
206 | elsif ($^O eq 'MSWin32') { | ||||
207 | $p =~ s{[/\\]\z}{}; | ||||
208 | } | ||||
209 | else { | ||||
210 | $p =~ s{/\z}{}; | ||||
211 | } | ||||
212 | push @clean_path, $p; | ||||
213 | } | ||||
214 | |||||
215 | @{$arg}{qw(device inode perm)} = (lstat $arg->{cwd})[0,1] or do { | ||||
216 | _error($arg, "cannot stat initial working directory", $arg->{cwd}); | ||||
217 | return 0; | ||||
218 | }; | ||||
219 | |||||
220 | return _rmtree($arg, \@clean_path); | ||||
221 | } | ||||
222 | |||||
223 | sub _rmtree { | ||||
224 | my $arg = shift; | ||||
225 | my $paths = shift; | ||||
226 | |||||
227 | my $count = 0; | ||||
228 | my $curdir = File::Spec->curdir(); | ||||
229 | my $updir = File::Spec->updir(); | ||||
230 | |||||
231 | my (@files, $root); | ||||
232 | ROOT_DIR: | ||||
233 | foreach $root (@$paths) { | ||||
234 | # since we chdir into each directory, it may not be obvious | ||||
235 | # to figure out where we are if we generate a message about | ||||
236 | # a file name. We therefore construct a semi-canonical | ||||
237 | # filename, anchored from the directory being unlinked (as | ||||
238 | # opposed to being truly canonical, anchored from the root (/). | ||||
239 | |||||
240 | my $canon = $arg->{prefix} | ||||
241 | ? File::Spec->catfile($arg->{prefix}, $root) | ||||
242 | : $root | ||||
243 | ; | ||||
244 | |||||
245 | my ($ldev, $lino, $perm) = (lstat $root)[0,1,2] or next ROOT_DIR; | ||||
246 | |||||
247 | if ( -d _ ) { | ||||
248 | $root = VMS::Filespec::pathify($root) if $Is_VMS; | ||||
249 | |||||
250 | if (!chdir($root)) { | ||||
251 | # see if we can escalate privileges to get in | ||||
252 | # (e.g. funny protection mask such as -w- instead of rwx) | ||||
253 | $perm &= 07777; | ||||
254 | my $nperm = $perm | 0700; | ||||
255 | if (!($arg->{safe} or $nperm == $perm or chmod($nperm, $root))) { | ||||
256 | _error($arg, "cannot make child directory read-write-exec", $canon); | ||||
257 | next ROOT_DIR; | ||||
258 | } | ||||
259 | elsif (!chdir($root)) { | ||||
260 | _error($arg, "cannot chdir to child", $canon); | ||||
261 | next ROOT_DIR; | ||||
262 | } | ||||
263 | } | ||||
264 | |||||
265 | my ($cur_dev, $cur_inode, $perm) = (stat $curdir)[0,1,2] or do { | ||||
266 | _error($arg, "cannot stat current working directory", $canon); | ||||
267 | next ROOT_DIR; | ||||
268 | }; | ||||
269 | |||||
270 | if ($Need_Stat_Check) { | ||||
271 | ($ldev eq $cur_dev and $lino eq $cur_inode) | ||||
272 | or _croak("directory $canon changed before chdir, expected dev=$ldev ino=$lino, actual dev=$cur_dev ino=$cur_inode, aborting."); | ||||
273 | } | ||||
274 | |||||
275 | $perm &= 07777; # don't forget setuid, setgid, sticky bits | ||||
276 | my $nperm = $perm | 0700; | ||||
277 | |||||
278 | # notabene: 0700 is for making readable in the first place, | ||||
279 | # it's also intended to change it to writable in case we have | ||||
280 | # to recurse in which case we are better than rm -rf for | ||||
281 | # subtrees with strange permissions | ||||
282 | |||||
283 | if (!($arg->{safe} or $nperm == $perm or chmod($nperm, $curdir))) { | ||||
284 | _error($arg, "cannot make directory read+writeable", $canon); | ||||
285 | $nperm = $perm; | ||||
286 | } | ||||
287 | |||||
288 | my $d; | ||||
289 | $d = gensym() if $] < 5.006; | ||||
290 | if (!opendir $d, $curdir) { | ||||
291 | _error($arg, "cannot opendir", $canon); | ||||
292 | @files = (); | ||||
293 | } | ||||
294 | else { | ||||
295 | 3 | 849µs | 2 | 42µs | # spent 26µs (11+16) within File::Path::BEGIN@295 which was called:
# once (11µs+16µs) by Template::BEGIN@32 at line 295 # spent 26µs making 1 call to File::Path::BEGIN@295
# spent 15µs making 1 call to strict::unimport |
296 | if (!defined ${"\cTAINT"} or ${"\cTAINT"}) { | ||||
297 | # Blindly untaint dir names if taint mode is | ||||
298 | # active, or any perl < 5.006 | ||||
299 | @files = map { /\A(.*)\z/s; $1 } readdir $d; | ||||
300 | } | ||||
301 | else { | ||||
302 | @files = readdir $d; | ||||
303 | } | ||||
304 | closedir $d; | ||||
305 | } | ||||
306 | |||||
307 | if ($Is_VMS) { | ||||
308 | # Deleting large numbers of files from VMS Files-11 | ||||
309 | # filesystems is faster if done in reverse ASCIIbetical order. | ||||
310 | # include '.' to '.;' from blead patch #31775 | ||||
311 | @files = map {$_ eq '.' ? '.;' : $_} reverse @files; | ||||
312 | ($root = VMS::Filespec::unixify($root)) =~ s/\.dir\z//; | ||||
313 | } | ||||
314 | |||||
315 | @files = grep {$_ ne $updir and $_ ne $curdir} @files; | ||||
316 | |||||
317 | if (@files) { | ||||
318 | # remove the contained files before the directory itself | ||||
319 | my $narg = {%$arg}; | ||||
320 | @{$narg}{qw(device inode cwd prefix depth)} | ||||
321 | = ($cur_dev, $cur_inode, $updir, $canon, $arg->{depth}+1); | ||||
322 | $count += _rmtree($narg, \@files); | ||||
323 | } | ||||
324 | |||||
325 | # restore directory permissions of required now (in case the rmdir | ||||
326 | # below fails), while we are still in the directory and may do so | ||||
327 | # without a race via '.' | ||||
328 | if ($nperm != $perm and not chmod($perm, $curdir)) { | ||||
329 | _error($arg, "cannot reset chmod", $canon); | ||||
330 | } | ||||
331 | |||||
332 | # don't leave the client code in an unexpected directory | ||||
333 | chdir($arg->{cwd}) | ||||
334 | or _croak("cannot chdir to $arg->{cwd} from $canon: $!, aborting."); | ||||
335 | |||||
336 | # ensure that a chdir upwards didn't take us somewhere other | ||||
337 | # than we expected (see CVE-2002-0435) | ||||
338 | ($cur_dev, $cur_inode) = (stat $curdir)[0,1] | ||||
339 | or _croak("cannot stat prior working directory $arg->{cwd}: $!, aborting."); | ||||
340 | |||||
341 | if ($Need_Stat_Check) { | ||||
342 | ($arg->{device} eq $cur_dev and $arg->{inode} eq $cur_inode) | ||||
343 | or _croak("previous directory $arg->{cwd} changed before entering $canon, expected dev=$ldev ino=$lino, actual dev=$cur_dev ino=$cur_inode, aborting."); | ||||
344 | } | ||||
345 | |||||
346 | if ($arg->{depth} or !$arg->{keep_root}) { | ||||
347 | if ($arg->{safe} && | ||||
348 | ($Is_VMS ? !&VMS::Filespec::candelete($root) : !-w $root)) { | ||||
349 | print "skipped $root\n" if $arg->{verbose}; | ||||
350 | next ROOT_DIR; | ||||
351 | } | ||||
352 | if ($Force_Writeable and !chmod $perm | 0700, $root) { | ||||
353 | _error($arg, "cannot make directory writeable", $canon); | ||||
354 | } | ||||
355 | print "rmdir $root\n" if $arg->{verbose}; | ||||
356 | if (rmdir $root) { | ||||
357 | push @{${$arg->{result}}}, $root if $arg->{result}; | ||||
358 | ++$count; | ||||
359 | } | ||||
360 | else { | ||||
361 | _error($arg, "cannot remove directory", $canon); | ||||
362 | if ($Force_Writeable && !chmod($perm, ($Is_VMS ? VMS::Filespec::fileify($root) : $root)) | ||||
363 | ) { | ||||
364 | _error($arg, sprintf("cannot restore permissions to 0%o",$perm), $canon); | ||||
365 | } | ||||
366 | } | ||||
367 | } | ||||
368 | } | ||||
369 | else { | ||||
370 | # not a directory | ||||
371 | $root = VMS::Filespec::vmsify("./$root") | ||||
372 | if $Is_VMS | ||||
373 | && !File::Spec->file_name_is_absolute($root) | ||||
374 | && ($root !~ m/(?<!\^)[\]>]+/); # not already in VMS syntax | ||||
375 | |||||
376 | if ($arg->{safe} && | ||||
377 | ($Is_VMS ? !&VMS::Filespec::candelete($root) | ||||
378 | : !(-l $root || -w $root))) | ||||
379 | { | ||||
380 | print "skipped $root\n" if $arg->{verbose}; | ||||
381 | next ROOT_DIR; | ||||
382 | } | ||||
383 | |||||
384 | my $nperm = $perm & 07777 | 0600; | ||||
385 | if ($Force_Writeable and $nperm != $perm and not chmod $nperm, $root) { | ||||
386 | _error($arg, "cannot make file writeable", $canon); | ||||
387 | } | ||||
388 | print "unlink $canon\n" if $arg->{verbose}; | ||||
389 | # delete all versions under VMS | ||||
390 | for (;;) { | ||||
391 | if (unlink $root) { | ||||
392 | push @{${$arg->{result}}}, $root if $arg->{result}; | ||||
393 | } | ||||
394 | else { | ||||
395 | _error($arg, "cannot unlink file", $canon); | ||||
396 | $Force_Writeable and chmod($perm, $root) or | ||||
397 | _error($arg, sprintf("cannot restore permissions to 0%o",$perm), $canon); | ||||
398 | last; | ||||
399 | } | ||||
400 | ++$count; | ||||
401 | last unless $Is_VMS && lstat $root; | ||||
402 | } | ||||
403 | } | ||||
404 | } | ||||
405 | return $count; | ||||
406 | } | ||||
407 | |||||
408 | sub _slash_lc { | ||||
409 | # fix up slashes and case on MSWin32 so that we can determine that | ||||
410 | # c:\path\to\dir is underneath C:/Path/To | ||||
411 | my $path = shift; | ||||
412 | $path =~ tr{\\}{/}; | ||||
413 | return lc($path); | ||||
414 | } | ||||
415 | |||||
416 | 1 | 8µs | 1; | ||
417 | __END__ |