Profile of File/Path.pm

← Index
NYTProf Performance Profile «block view•line view•sub view»
For xt/tapper-mcp-scheduler-with-db-longrun.t
Run on Tue May 22 17:18:39 2012
Reported on Tue May 22 17:22:38 2012

Filename | /2home/ss5/perl5/perlbrew/perls/perl-5.12.3/lib/5.12.3/File/Path.pm
Statements | Executed 34 statements in 2.16ms

Subroutines
Calls | P | F | Exclusive Time | Inclusive Time | Subroutine
--- | --- | --- | --- | --- | ---
1 | 1 | 1 | 47µs | 69µs | File::Path::::BEGIN@329File::Path::BEGIN@329
1 | 1 | 1 | 21µs | 21µs | File::Path::::BEGIN@3File::Path::BEGIN@3
1 | 1 | 1 | 10µs | 59µs | File::Path::::BEGIN@6File::Path::BEGIN@6
1 | 1 | 1 | 8µs | 70µs | File::Path::::BEGIN@19File::Path::BEGIN@19
1 | 1 | 1 | 7µs | 8µs | File::Path::::BEGIN@4File::Path::BEGIN@4
1 | 1 | 1 | 4µs | 4µs | File::Path::::BEGIN@18File::Path::BEGIN@18
1 | 1 | 1 | 4µs | 4µs | File::Path::::BEGIN@10File::Path::BEGIN@10
1 | 1 | 1 | 4µs | 4µs | File::Path::::BEGIN@7File::Path::BEGIN@7
1 | 1 | 1 | 3µs | 3µs | File::Path::::BEGIN@8File::Path::BEGIN@8
0 | 0 | 0 | 0s | 0s | File::Path::::_carpFile::Path::_carp
0 | 0 | 0 | 0s | 0s | File::Path::::_croakFile::Path::_croak
0 | 0 | 0 | 0s | 0s | File::Path::::_errorFile::Path::_error
0 | 0 | 0 | 0s | 0s | File::Path::::_is_subdirFile::Path::_is_subdir
0 | 0 | 0 | 0s | 0s | File::Path::::_mkpathFile::Path::_mkpath
0 | 0 | 0 | 0s | 0s | File::Path::::_rmtreeFile::Path::_rmtree
0 | 0 | 0 | 0s | 0s | File::Path::::_slash_lcFile::Path::_slash_lc
0 | 0 | 0 | 0s | 0s | File::Path::::make_pathFile::Path::make_path
0 | 0 | 0 | 0s | 0s | File::Path::::mkpathFile::Path::mkpath
0 | 0 | 0 | 0s | 0s | File::Path::::remove_treeFile::Path::remove_tree
0 | 0 | 0 | 0s | 0s | File::Path::::rmtreeFile::Path::rmtree

Call graph for these subroutines as a Graphviz dot language file.

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