| Filename | /usr/libdata/perl5/File/Path.pm |
| Statements | Executed 5790 statements in 42.3ms |
| Calls | P | F | Exclusive Time |
Inclusive Time |
Subroutine |
|---|---|---|---|---|---|
| 120 | 2 | 1 | 10.1ms | 28.0ms | File::Path::_rmtree (recurses: max depth 1, inclusive time 13.2ms) |
| 80 | 1 | 1 | 6.56ms | 42.6ms | File::Path::rmtree |
| 1 | 1 | 1 | 3.48ms | 4.84ms | File::Path::BEGIN@6 |
| 80 | 1 | 1 | 3.46ms | 5.21ms | File::Path::_is_subdir |
| 40 | 1 | 1 | 3.28ms | 3.28ms | File::Path::CORE:readdir (opcode) |
| 1 | 1 | 1 | 2.43ms | 2.95ms | File::Path::BEGIN@8 |
| 80 | 1 | 1 | 2.27ms | 2.27ms | File::Path::CORE:unlink (opcode) |
| 240 | 2 | 1 | 1.77ms | 1.77ms | File::Path::CORE:lstat (opcode) |
| 40 | 1 | 1 | 1.38ms | 1.38ms | File::Path::CORE:rmdir (opcode) |
| 80 | 2 | 1 | 686µs | 686µs | File::Path::CORE:chdir (opcode) |
| 40 | 1 | 1 | 678µs | 678µs | File::Path::CORE:closedir (opcode) |
| 80 | 1 | 1 | 650µs | 650µs | File::Path::CORE:match (opcode) |
| 80 | 1 | 1 | 538µs | 538µs | File::Path::__is_arg |
| 40 | 1 | 1 | 480µs | 480µs | File::Path::CORE:open_dir (opcode) |
| 80 | 1 | 1 | 401µs | 401µs | File::Path::CORE:subst (opcode) |
| 80 | 2 | 1 | 358µs | 358µs | File::Path::CORE:stat (opcode) |
| 120 | 1 | 1 | 129µs | 129µs | File::Path::CORE:ftdir (opcode) |
| 1 | 1 | 1 | 32µs | 33µs | File::Path::BEGIN@27 |
| 1 | 1 | 1 | 25µs | 25µs | File::Path::BEGIN@3 |
| 1 | 1 | 1 | 14µs | 20µs | File::Path::BEGIN@4 |
| 1 | 1 | 1 | 9µs | 81µs | File::Path::BEGIN@20 |
| 1 | 1 | 1 | 9µs | 27µs | File::Path::BEGIN@29 |
| 1 | 1 | 1 | 6µs | 6µs | File::Path::BEGIN@19 |
| 1 | 1 | 1 | 6µs | 6µs | File::Path::BEGIN@10 |
| 1 | 1 | 1 | 6µs | 6µs | File::Path::BEGIN@7 |
| 1 | 1 | 1 | 1µs | 1µs | File::Path::__ANON__ (xsub) |
| 0 | 0 | 0 | 0s | 0s | File::Path::_carp |
| 0 | 0 | 0 | 0s | 0s | File::Path::_croak |
| 0 | 0 | 0 | 0s | 0s | File::Path::_error |
| 0 | 0 | 0 | 0s | 0s | File::Path::_mkpath |
| 0 | 0 | 0 | 0s | 0s | File::Path::_slash_lc |
| 0 | 0 | 0 | 0s | 0s | File::Path::make_path |
| 0 | 0 | 0 | 0s | 0s | File::Path::mkpath |
| 0 | 0 | 0 | 0s | 0s | File::Path::remove_tree |
| Line | State ments |
Time on line |
Calls | Time in subs |
Code |
|---|---|---|---|---|---|
| 1 | package File::Path; | ||||
| 2 | |||||
| 3 | 2 | 57µs | 1 | 25µs | # spent 25µs within File::Path::BEGIN@3 which was called:
# once (25µs+0s) by OpenBSD::PackageRepository::wipe_info at line 3 # spent 25µs making 1 call to File::Path::BEGIN@3 |
| 4 | 2 | 34µs | 2 | 26µs | # spent 20µs (14+6) within File::Path::BEGIN@4 which was called:
# once (14µs+6µs) by OpenBSD::PackageRepository::wipe_info at line 4 # spent 20µs making 1 call to File::Path::BEGIN@4
# spent 6µs making 1 call to strict::import |
| 5 | |||||
| 6 | 2 | 122µs | 2 | 4.91ms | # spent 4.84ms (3.48+1.36) within File::Path::BEGIN@6 which was called:
# once (3.48ms+1.36ms) by OpenBSD::PackageRepository::wipe_info at line 6 # spent 4.84ms making 1 call to File::Path::BEGIN@6
# spent 73µs making 1 call to Exporter::import |
| 7 | 2 | 24µs | 1 | 6µs | # spent 6µs within File::Path::BEGIN@7 which was called:
# once (6µs+0s) by OpenBSD::PackageRepository::wipe_info at line 7 # spent 6µs making 1 call to File::Path::BEGIN@7 |
| 8 | 2 | 134µs | 1 | 2.95ms | # spent 2.95ms (2.43+516µs) within File::Path::BEGIN@8 which was called:
# once (2.43ms+516µs) by OpenBSD::PackageRepository::wipe_info at line 8 # spent 2.95ms making 1 call to File::Path::BEGIN@8 |
| 9 | |||||
| 10 | # spent 6µs within File::Path::BEGIN@10 which was called:
# once (6µs+0s) by OpenBSD::PackageRepository::wipe_info at line 17 | ||||
| 11 | 1 | 6µ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 | 20µs | 1 | 6µs | } # spent 6µs making 1 call to File::Path::BEGIN@10 |
| 18 | |||||
| 19 | 2 | 27µs | 1 | 6µs | # spent 6µs within File::Path::BEGIN@19 which was called:
# once (6µs+0s) by OpenBSD::PackageRepository::wipe_info at line 19 # spent 6µs making 1 call to File::Path::BEGIN@19 |
| 20 | 2 | 58µs | 2 | 152µs | # spent 81µs (9+72) within File::Path::BEGIN@20 which was called:
# once (9µs+72µs) by OpenBSD::PackageRepository::wipe_info at line 20 # spent 81µs making 1 call to File::Path::BEGIN@20
# spent 72µs making 1 call to vars::import |
| 21 | 1 | 800ns | $VERSION = '2.13'; | ||
| 22 | 1 | 16µs | $VERSION = eval $VERSION; # spent 3µs executing statements in string eval | ||
| 23 | 1 | 6µs | @ISA = qw(Exporter); | ||
| 24 | 1 | 1µs | @EXPORT = qw(mkpath rmtree); | ||
| 25 | 1 | 1µs | @EXPORT_OK = qw(make_path remove_tree); | ||
| 26 | |||||
| 27 | # spent 33µs (32+1) within File::Path::BEGIN@27 which was called:
# once (32µs+1µs) by OpenBSD::PackageRepository::wipe_info at line 42 | ||||
| 28 | 1 | 2µs | for (qw(VMS MacOS MSWin32 os2)) { | ||
| 29 | 2 | 124µs | 2 | 45µs | # spent 27µs (9+18) within File::Path::BEGIN@29 which was called:
# once (9µs+18µs) by OpenBSD::PackageRepository::wipe_info at line 29 # spent 27µs making 1 call to File::Path::BEGIN@29
# spent 18µs making 1 call to strict::unimport |
| 30 | 4 | 13µ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 | 3µ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 | 14µs | 1 | 1µs | *_NEED_STAT_CHECK = !(_IS_MSWIN32()) ? sub () { 1 } : sub () { 0 }; # spent 1µs making 1 call to File::Path::__ANON__ |
| 42 | 1 | 2.44ms | 1 | 33µs | } # spent 33µ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 | # spent 538µs within File::Path::__is_arg which was called 80 times, avg 7µs/call:
# 80 times (538µs+0s) by File::Path::rmtree at line 271, avg 7µs/call | ||||
| 70 | 80 | 85µs | 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 | 80 | 337µs | 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 $data; | ||||
| 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 | $data->{verbose} = $verbose; | ||||
| 96 | $data->{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 %not_on_win32_args = map { $_ => 1 } ( qw| | ||||
| 111 | group | ||||
| 112 | owner | ||||
| 113 | uid | ||||
| 114 | user | ||||
| 115 | | ); | ||||
| 116 | my @bad_args = (); | ||||
| 117 | my @win32_implausible_args = (); | ||||
| 118 | my $arg = pop @_; | ||||
| 119 | for my $k (sort keys %{$arg}) { | ||||
| 120 | if (! $args_permitted{$k}) { | ||||
| 121 | push @bad_args, $k; | ||||
| 122 | } | ||||
| 123 | elsif ($not_on_win32_args{$k} and _IS_MSWIN32) { | ||||
| 124 | push @win32_implausible_args, $k; | ||||
| 125 | } | ||||
| 126 | else { | ||||
| 127 | $data->{$k} = $arg->{$k}; | ||||
| 128 | } | ||||
| 129 | } | ||||
| 130 | _carp("Unrecognized option(s) passed to mkpath() or make_path(): @bad_args") | ||||
| 131 | if @bad_args; | ||||
| 132 | _carp("Option(s) implausible on Win32 passed to mkpath() or make_path(): @win32_implausible_args") | ||||
| 133 | if @win32_implausible_args; | ||||
| 134 | $data->{mode} = delete $data->{mask} if exists $data->{mask}; | ||||
| 135 | $data->{mode} = oct '777' unless exists $data->{mode}; | ||||
| 136 | ${ $data->{error} } = [] if exists $data->{error}; | ||||
| 137 | unless (@win32_implausible_args) { | ||||
| 138 | $data->{owner} = delete $data->{user} if exists $data->{user}; | ||||
| 139 | $data->{owner} = delete $data->{uid} if exists $data->{uid}; | ||||
| 140 | if ( exists $data->{owner} and $data->{owner} =~ /\D/ ) { | ||||
| 141 | my $uid = ( getpwnam $data->{owner} )[2]; | ||||
| 142 | if ( defined $uid ) { | ||||
| 143 | $data->{owner} = $uid; | ||||
| 144 | } | ||||
| 145 | else { | ||||
| 146 | _error( $data, | ||||
| 147 | "unable to map $data->{owner} to a uid, ownership not changed" | ||||
| 148 | ); | ||||
| 149 | delete $data->{owner}; | ||||
| 150 | } | ||||
| 151 | } | ||||
| 152 | if ( exists $data->{group} and $data->{group} =~ /\D/ ) { | ||||
| 153 | my $gid = ( getgrnam $data->{group} )[2]; | ||||
| 154 | if ( defined $gid ) { | ||||
| 155 | $data->{group} = $gid; | ||||
| 156 | } | ||||
| 157 | else { | ||||
| 158 | _error( $data, | ||||
| 159 | "unable to map $data->{group} to a gid, group ownership not changed" | ||||
| 160 | ); | ||||
| 161 | delete $data->{group}; | ||||
| 162 | } | ||||
| 163 | } | ||||
| 164 | if ( exists $data->{owner} and not exists $data->{group} ) { | ||||
| 165 | $data->{group} = -1; # chown will leave group unchanged | ||||
| 166 | } | ||||
| 167 | if ( exists $data->{group} and not exists $data->{owner} ) { | ||||
| 168 | $data->{owner} = -1; # chown will leave owner unchanged | ||||
| 169 | } | ||||
| 170 | } | ||||
| 171 | $paths = [@_]; | ||||
| 172 | } | ||||
| 173 | return _mkpath( $data, $paths ); | ||||
| 174 | } | ||||
| 175 | |||||
| 176 | sub _mkpath { | ||||
| 177 | my $data = shift; | ||||
| 178 | my $paths = shift; | ||||
| 179 | |||||
| 180 | my ( @created ); | ||||
| 181 | foreach my $path ( @{$paths} ) { | ||||
| 182 | next unless defined($path) and length($path); | ||||
| 183 | $path .= '/' if _IS_OS2 and $path =~ /^\w:\z/s; # feature of CRT | ||||
| 184 | |||||
| 185 | # Logic wants Unix paths, so go with the flow. | ||||
| 186 | if (_IS_VMS) { | ||||
| 187 | next if $path eq '/'; | ||||
| 188 | $path = VMS::Filespec::unixify($path); | ||||
| 189 | } | ||||
| 190 | next if -d $path; | ||||
| 191 | my $parent = File::Basename::dirname($path); | ||||
| 192 | # Coverage note: It's not clear how we would test the condition: | ||||
| 193 | # '-d $parent or $path eq $parent' | ||||
| 194 | unless ( -d $parent or $path eq $parent ) { | ||||
| 195 | push( @created, _mkpath( $data, [$parent] ) ); | ||||
| 196 | } | ||||
| 197 | print "mkdir $path\n" if $data->{verbose}; | ||||
| 198 | if ( mkdir( $path, $data->{mode} ) ) { | ||||
| 199 | push( @created, $path ); | ||||
| 200 | if ( exists $data->{owner} ) { | ||||
| 201 | |||||
| 202 | # NB: $data->{group} guaranteed to be set during initialisation | ||||
| 203 | if ( !chown $data->{owner}, $data->{group}, $path ) { | ||||
| 204 | _error( $data, | ||||
| 205 | "Cannot change ownership of $path to $data->{owner}:$data->{group}" | ||||
| 206 | ); | ||||
| 207 | } | ||||
| 208 | } | ||||
| 209 | if ( exists $data->{chmod} ) { | ||||
| 210 | # Coverage note: It's not clear how we would trigger the next | ||||
| 211 | # 'if' block. Failure of 'chmod' might first result in a | ||||
| 212 | # system error: "Permission denied". | ||||
| 213 | if ( !chmod $data->{chmod}, $path ) { | ||||
| 214 | _error( $data, | ||||
| 215 | "Cannot change permissions of $path to $data->{chmod}" ); | ||||
| 216 | } | ||||
| 217 | } | ||||
| 218 | } | ||||
| 219 | else { | ||||
| 220 | my $save_bang = $!; | ||||
| 221 | |||||
| 222 | # From 'perldoc perlvar': $EXTENDED_OS_ERROR ($^E) is documented | ||||
| 223 | # as: | ||||
| 224 | # Error information specific to the current operating system. At the | ||||
| 225 | # moment, this differs from "$!" under only VMS, OS/2, and Win32 | ||||
| 226 | # (and for MacPerl). On all other platforms, $^E is always just the | ||||
| 227 | # same as $!. | ||||
| 228 | |||||
| 229 | my ( $e, $e1 ) = ( $save_bang, $^E ); | ||||
| 230 | $e .= "; $e1" if $e ne $e1; | ||||
| 231 | |||||
| 232 | # allow for another process to have created it meanwhile | ||||
| 233 | if ( ! -d $path ) { | ||||
| 234 | $! = $save_bang; | ||||
| 235 | if ( $data->{error} ) { | ||||
| 236 | push @{ ${ $data->{error} } }, { $path => $e }; | ||||
| 237 | } | ||||
| 238 | else { | ||||
| 239 | _croak("mkdir $path: $e"); | ||||
| 240 | } | ||||
| 241 | } | ||||
| 242 | } | ||||
| 243 | } | ||||
| 244 | return @created; | ||||
| 245 | } | ||||
| 246 | |||||
| 247 | sub remove_tree { | ||||
| 248 | push @_, {} unless @_ and __is_arg( $_[-1] ); | ||||
| 249 | goto &rmtree; | ||||
| 250 | } | ||||
| 251 | |||||
| 252 | # spent 5.21ms (3.46+1.74) within File::Path::_is_subdir which was called 80 times, avg 65µs/call:
# 80 times (3.46ms+1.74ms) by File::Path::rmtree at line 342, avg 65µs/call | ||||
| 253 | 80 | 86µs | my ( $dir, $test ) = @_; | ||
| 254 | |||||
| 255 | 80 | 1.11ms | 80 | 802µs | my ( $dv, $dd ) = File::Spec->splitpath( $dir, 1 ); # spent 802µs making 80 calls to File::Spec::Unix::splitpath, avg 10µs/call |
| 256 | 80 | 251µs | 80 | 366µs | my ( $tv, $td ) = File::Spec->splitpath( $test, 1 ); # spent 366µs making 80 calls to File::Spec::Unix::splitpath, avg 5µs/call |
| 257 | |||||
| 258 | # not on same volume | ||||
| 259 | 80 | 80µs | return 0 if $dv ne $tv; | ||
| 260 | |||||
| 261 | 80 | 692µs | 80 | 365µs | my @d = File::Spec->splitdir($dd); # spent 365µs making 80 calls to File::Spec::Unix::splitdir, avg 5µs/call |
| 262 | 80 | 367µs | 80 | 212µs | my @t = File::Spec->splitdir($td); # spent 212µs making 80 calls to File::Spec::Unix::splitdir, avg 3µs/call |
| 263 | |||||
| 264 | # @t can't be a subdir if it's shorter than @d | ||||
| 265 | 80 | 471µs | return 0 if @t < @d; | ||
| 266 | |||||
| 267 | return join( '/', @d ) eq join( '/', splice @t, 0, +@d ); | ||||
| 268 | } | ||||
| 269 | |||||
| 270 | # spent 42.6ms (6.56+36.0) within File::Path::rmtree which was called 80 times, avg 532µs/call:
# 80 times (6.56ms+36.0ms) by OpenBSD::Error::rmtree at line 210 of OpenBSD/Error.pm, avg 532µs/call | ||||
| 271 | 80 | 538µs | 80 | 538µs | my $old_style = !( @_ and __is_arg( $_[-1] ) ); # spent 538µs making 80 calls to File::Path::__is_arg, avg 7µs/call |
| 272 | |||||
| 273 | 80 | 63µs | my ($arg, $data, $paths); | ||
| 274 | |||||
| 275 | 80 | 129µs | if ($old_style) { | ||
| 276 | 80 | 62µs | my ( $verbose, $safe ); | ||
| 277 | 80 | 77µs | ( $paths, $verbose, $safe ) = @_; | ||
| 278 | 80 | 271µs | $data->{verbose} = $verbose; | ||
| 279 | 80 | 246µs | $data->{safe} = defined $safe ? $safe : 0; | ||
| 280 | |||||
| 281 | 80 | 840µs | 80 | 413µs | if ( defined($paths) and length($paths) ) { # spent 413µs making 80 calls to UNIVERSAL::isa, avg 5µs/call |
| 282 | $paths = [$paths] unless UNIVERSAL::isa( $paths, 'ARRAY' ); | ||||
| 283 | } | ||||
| 284 | else { | ||||
| 285 | _carp("No root path(s) specified\n"); | ||||
| 286 | return 0; | ||||
| 287 | } | ||||
| 288 | } | ||||
| 289 | else { | ||||
| 290 | my %args_permitted = map { $_ => 1 } ( qw| | ||||
| 291 | error | ||||
| 292 | keep_root | ||||
| 293 | result | ||||
| 294 | safe | ||||
| 295 | verbose | ||||
| 296 | | ); | ||||
| 297 | my @bad_args = (); | ||||
| 298 | my $arg = pop @_; | ||||
| 299 | for my $k (sort keys %{$arg}) { | ||||
| 300 | if (! $args_permitted{$k}) { | ||||
| 301 | push @bad_args, $k; | ||||
| 302 | } | ||||
| 303 | else { | ||||
| 304 | $data->{$k} = $arg->{$k}; | ||||
| 305 | } | ||||
| 306 | } | ||||
| 307 | _carp("Unrecognized option(s) passed to remove_tree(): @bad_args") | ||||
| 308 | if @bad_args; | ||||
| 309 | ${ $data->{error} } = [] if exists $data->{error}; | ||||
| 310 | ${ $data->{result} } = [] if exists $data->{result}; | ||||
| 311 | |||||
| 312 | # Wouldn't it make sense to do some validation on @_ before assigning | ||||
| 313 | # to $paths here? | ||||
| 314 | # In the $old_style case we guarantee that each path is both defined | ||||
| 315 | # and non-empty. We don't check that here, which means we have to | ||||
| 316 | # check it later in the first condition in this line: | ||||
| 317 | # if ( $ortho_root_length && _is_subdir( $ortho_root, $ortho_cwd ) ) { | ||||
| 318 | # Granted, that would be a change in behavior for the two | ||||
| 319 | # non-old-style interfaces. | ||||
| 320 | |||||
| 321 | $paths = [@_]; | ||||
| 322 | } | ||||
| 323 | |||||
| 324 | 80 | 228µs | $data->{prefix} = ''; | ||
| 325 | 80 | 222µs | $data->{depth} = 0; | ||
| 326 | |||||
| 327 | 80 | 67µs | my @clean_path; | ||
| 328 | 80 | 884µs | 80 | 269µs | $data->{cwd} = getcwd() or do { # spent 269µs making 80 calls to Cwd::getcwd, avg 3µs/call |
| 329 | _error( $data, "cannot fetch initial working directory" ); | ||||
| 330 | return 0; | ||||
| 331 | }; | ||||
| 332 | 240 | 1.39ms | 80 | 650µs | for ( $data->{cwd} ) { /\A(.*)\Z/s; $_ = $1 } # untaint # spent 650µs making 80 calls to File::Path::CORE:match, avg 8µs/call |
| 333 | |||||
| 334 | 80 | 283µs | for my $p (@$paths) { | ||
| 335 | |||||
| 336 | # need to fixup case and map \ to / on Windows | ||||
| 337 | 80 | 71µs | my $ortho_root = _IS_MSWIN32 ? _slash_lc($p) : $p; | ||
| 338 | my $ortho_cwd = | ||||
| 339 | 80 | 93µs | _IS_MSWIN32 ? _slash_lc( $data->{cwd} ) : $data->{cwd}; | ||
| 340 | 80 | 70µs | my $ortho_root_length = length($ortho_root); | ||
| 341 | $ortho_root_length-- if _IS_VMS; # don't compare '.' with ']' | ||||
| 342 | 80 | 381µs | 80 | 5.21ms | if ( $ortho_root_length && _is_subdir( $ortho_root, $ortho_cwd ) ) { # spent 5.21ms making 80 calls to File::Path::_is_subdir, avg 65µs/call |
| 343 | local $! = 0; | ||||
| 344 | _error( $data, "cannot remove path when cwd is $data->{cwd}", $p ); | ||||
| 345 | next; | ||||
| 346 | } | ||||
| 347 | |||||
| 348 | 80 | 118µs | if (_IS_MACOS) { | ||
| 349 | $p = ":$p" unless $p =~ /:/; | ||||
| 350 | $p .= ":" unless $p =~ /:\z/; | ||||
| 351 | } | ||||
| 352 | elsif ( _IS_MSWIN32 ) { | ||||
| 353 | $p =~ s{[/\\]\z}{}; | ||||
| 354 | } | ||||
| 355 | else { | ||||
| 356 | 80 | 670µs | 80 | 401µs | $p =~ s{/\z}{}; # spent 401µs making 80 calls to File::Path::CORE:subst, avg 5µs/call |
| 357 | } | ||||
| 358 | 80 | 189µs | push @clean_path, $p; | ||
| 359 | } | ||||
| 360 | |||||
| 361 | 80 | 934µs | 80 | 484µs | @{$data}{qw(device inode)} = ( lstat $data->{cwd} )[ 0, 1 ] or do { # spent 484µs making 80 calls to File::Path::CORE:lstat, avg 6µs/call |
| 362 | _error( $data, "cannot stat initial working directory", $data->{cwd} ); | ||||
| 363 | return 0; | ||||
| 364 | }; | ||||
| 365 | |||||
| 366 | 80 | 632µs | 80 | 28.0ms | return _rmtree( $data, \@clean_path ); # spent 28.0ms making 80 calls to File::Path::_rmtree, avg 351µs/call |
| 367 | } | ||||
| 368 | |||||
| 369 | sub _rmtree { | ||||
| 370 | 120 | 109µs | my $data = shift; | ||
| 371 | 120 | 93µs | my $paths = shift; | ||
| 372 | |||||
| 373 | 120 | 99µs | my $count = 0; | ||
| 374 | 120 | 967µs | 120 | 569µs | my $curdir = File::Spec->curdir(); # spent 569µs making 120 calls to File::Spec::Unix::curdir, avg 5µs/call |
| 375 | 120 | 403µs | 120 | 266µs | my $updir = File::Spec->updir(); # spent 266µs making 120 calls to File::Spec::Unix::updir, avg 2µs/call |
| 376 | |||||
| 377 | 120 | 100µs | my ( @files, $root ); | ||
| 378 | ROOT_DIR: | ||||
| 379 | 120 | 487µs | foreach my $root (@$paths) { | ||
| 380 | |||||
| 381 | # since we chdir into each directory, it may not be obvious | ||||
| 382 | # to figure out where we are if we generate a message about | ||||
| 383 | # a file name. We therefore construct a semi-canonical | ||||
| 384 | # filename, anchored from the directory being unlinked (as | ||||
| 385 | # opposed to being truly canonical, anchored from the root (/). | ||||
| 386 | |||||
| 387 | my $canon = | ||||
| 388 | $data->{prefix} | ||||
| 389 | 160 | 7.17ms | 320 | 7.40ms | ? File::Spec->catfile( $data->{prefix}, $root ) # spent 6.53ms making 80 calls to File::Spec::Unix::catfile, avg 82µs/call
# spent 563µs making 80 calls to File::Spec::Unix::catdir, avg 7µs/call
# spent 304µs making 160 calls to File::Spec::Unix::canonpath, avg 2µs/call |
| 390 | : $root; | ||||
| 391 | |||||
| 392 | 160 | 1.99ms | 160 | 1.28ms | my ( $ldev, $lino, $perm ) = ( lstat $root )[ 0, 1, 2 ] # spent 1.28ms making 160 calls to File::Path::CORE:lstat, avg 8µs/call |
| 393 | or next ROOT_DIR; | ||||
| 394 | |||||
| 395 | 120 | 743µs | 120 | 129µs | if ( -d _ ) { # spent 129µs making 120 calls to File::Path::CORE:ftdir, avg 1µs/call |
| 396 | $root = VMS::Filespec::vmspath( VMS::Filespec::pathify($root) ) | ||||
| 397 | if _IS_VMS; | ||||
| 398 | |||||
| 399 | 40 | 539µs | 40 | 259µs | if ( !chdir($root) ) { # spent 259µs making 40 calls to File::Path::CORE:chdir, avg 6µs/call |
| 400 | |||||
| 401 | # see if we can escalate privileges to get in | ||||
| 402 | # (e.g. funny protection mask such as -w- instead of rwx) | ||||
| 403 | # This uses fchmod to avoid traversing outside of the proper | ||||
| 404 | # location (CVE-2017-6512) | ||||
| 405 | my $root_fh; | ||||
| 406 | if (open($root_fh, '<', $root)) { | ||||
| 407 | my ($fh_dev, $fh_inode) = (stat $root_fh )[0,1]; | ||||
| 408 | $perm &= oct '7777'; | ||||
| 409 | my $nperm = $perm | oct '700'; | ||||
| 410 | local $@; | ||||
| 411 | if ( | ||||
| 412 | !( | ||||
| 413 | $data->{safe} | ||||
| 414 | or $nperm == $perm | ||||
| 415 | or !-d _ | ||||
| 416 | or $fh_dev ne $ldev | ||||
| 417 | or $fh_inode ne $lino | ||||
| 418 | or eval { chmod( $nperm, $root_fh ) } | ||||
| 419 | ) | ||||
| 420 | ) | ||||
| 421 | { | ||||
| 422 | _error( $data, | ||||
| 423 | "cannot make child directory read-write-exec", $canon ); | ||||
| 424 | next ROOT_DIR; | ||||
| 425 | } | ||||
| 426 | close $root_fh; | ||||
| 427 | } | ||||
| 428 | if ( !chdir($root) ) { | ||||
| 429 | _error( $data, "cannot chdir to child", $canon ); | ||||
| 430 | next ROOT_DIR; | ||||
| 431 | } | ||||
| 432 | } | ||||
| 433 | |||||
| 434 | my ( $cur_dev, $cur_inode, $perm ) = ( stat $curdir )[ 0, 1, 2 ] | ||||
| 435 | 40 | 425µs | 40 | 207µs | or do { # spent 207µs making 40 calls to File::Path::CORE:stat, avg 5µs/call |
| 436 | _error( $data, "cannot stat current working directory", $canon ); | ||||
| 437 | next ROOT_DIR; | ||||
| 438 | }; | ||||
| 439 | |||||
| 440 | 40 | 91µs | if (_NEED_STAT_CHECK) { | ||
| 441 | ( $ldev eq $cur_dev and $lino eq $cur_inode ) | ||||
| 442 | or _croak( | ||||
| 443 | "directory $canon changed before chdir, expected dev=$ldev ino=$lino, actual dev=$cur_dev ino=$cur_inode, aborting." | ||||
| 444 | ); | ||||
| 445 | } | ||||
| 446 | |||||
| 447 | 40 | 40µs | $perm &= oct '7777'; # don't forget setuid, setgid, sticky bits | ||
| 448 | 40 | 57µs | my $nperm = $perm | oct '700'; | ||
| 449 | |||||
| 450 | # notabene: 0700 is for making readable in the first place, | ||||
| 451 | # it's also intended to change it to writable in case we have | ||||
| 452 | # to recurse in which case we are better than rm -rf for | ||||
| 453 | # subtrees with strange permissions | ||||
| 454 | |||||
| 455 | 40 | 60µs | if ( | ||
| 456 | !( | ||||
| 457 | $data->{safe} | ||||
| 458 | or $nperm == $perm | ||||
| 459 | or chmod( $nperm, $curdir ) | ||||
| 460 | ) | ||||
| 461 | ) | ||||
| 462 | { | ||||
| 463 | _error( $data, "cannot make directory read+writeable", $canon ); | ||||
| 464 | $nperm = $perm; | ||||
| 465 | } | ||||
| 466 | |||||
| 467 | 40 | 31µs | my $d; | ||
| 468 | 40 | 51µs | $d = gensym() if $] < 5.006; | ||
| 469 | 40 | 1.14ms | 40 | 480µs | if ( !opendir $d, $curdir ) { # spent 480µs making 40 calls to File::Path::CORE:open_dir, avg 12µs/call |
| 470 | _error( $data, "cannot opendir", $canon ); | ||||
| 471 | @files = (); | ||||
| 472 | } | ||||
| 473 | else { | ||||
| 474 | 40 | 338µs | if ( !defined ${^TAINT} or ${^TAINT} ) { | ||
| 475 | # Blindly untaint dir names if taint mode is active | ||||
| 476 | @files = map { /\A(.*)\z/s; $1 } readdir $d; | ||||
| 477 | } | ||||
| 478 | else { | ||||
| 479 | 40 | 3.56ms | 40 | 3.28ms | @files = readdir $d; # spent 3.28ms making 40 calls to File::Path::CORE:readdir, avg 82µs/call |
| 480 | } | ||||
| 481 | 40 | 836µs | 40 | 678µs | closedir $d; # spent 678µs making 40 calls to File::Path::CORE:closedir, avg 17µs/call |
| 482 | } | ||||
| 483 | |||||
| 484 | if (_IS_VMS) { | ||||
| 485 | |||||
| 486 | # Deleting large numbers of files from VMS Files-11 | ||||
| 487 | # filesystems is faster if done in reverse ASCIIbetical order. | ||||
| 488 | # include '.' to '.;' from blead patch #31775 | ||||
| 489 | @files = map { $_ eq '.' ? '.;' : $_ } reverse @files; | ||||
| 490 | } | ||||
| 491 | |||||
| 492 | 40 | 254µs | @files = grep { $_ ne $updir and $_ ne $curdir } @files; | ||
| 493 | |||||
| 494 | 40 | 85µs | if (@files) { | ||
| 495 | |||||
| 496 | # remove the contained files before the directory itself | ||||
| 497 | 40 | 296µs | my $narg = {%$data}; | ||
| 498 | @{$narg}{qw(device inode cwd prefix depth)} = | ||||
| 499 | 40 | 184µs | ( $cur_dev, $cur_inode, $updir, $canon, $data->{depth} + 1 ); | ||
| 500 | 40 | 399µs | 40 | 0s | $count += _rmtree( $narg, \@files ); # spent 13.2ms making 40 calls to File::Path::_rmtree, avg 329µs/call, recursion: max depth 1, sum of overlapping time 13.2ms |
| 501 | } | ||||
| 502 | |||||
| 503 | # restore directory permissions of required now (in case the rmdir | ||||
| 504 | # below fails), while we are still in the directory and may do so | ||||
| 505 | # without a race via '.' | ||||
| 506 | 40 | 50µs | if ( $nperm != $perm and not chmod( $perm, $curdir ) ) { | ||
| 507 | _error( $data, "cannot reset chmod", $canon ); | ||||
| 508 | } | ||||
| 509 | |||||
| 510 | # don't leave the client code in an unexpected directory | ||||
| 511 | chdir( $data->{cwd} ) | ||||
| 512 | 40 | 598µs | 40 | 428µs | or # spent 428µs making 40 calls to File::Path::CORE:chdir, avg 11µs/call |
| 513 | _croak("cannot chdir to $data->{cwd} from $canon: $!, aborting."); | ||||
| 514 | |||||
| 515 | # ensure that a chdir upwards didn't take us somewhere other | ||||
| 516 | # than we expected (see CVE-2002-0435) | ||||
| 517 | 40 | 354µs | 40 | 151µs | ( $cur_dev, $cur_inode ) = ( stat $curdir )[ 0, 1 ] # spent 151µs making 40 calls to File::Path::CORE:stat, avg 4µs/call |
| 518 | or _croak( | ||||
| 519 | "cannot stat prior working directory $data->{cwd}: $!, aborting." | ||||
| 520 | ); | ||||
| 521 | |||||
| 522 | 40 | 132µs | if (_NEED_STAT_CHECK) { | ||
| 523 | ( $data->{device} eq $cur_dev and $data->{inode} eq $cur_inode ) | ||||
| 524 | or _croak( "previous directory $data->{cwd} " | ||||
| 525 | . "changed before entering $canon, " | ||||
| 526 | . "expected dev=$ldev ino=$lino, " | ||||
| 527 | . "actual dev=$cur_dev ino=$cur_inode, aborting." | ||||
| 528 | ); | ||||
| 529 | } | ||||
| 530 | |||||
| 531 | 40 | 193µs | if ( $data->{depth} or !$data->{keep_root} ) { | ||
| 532 | 40 | 39µs | if ( $data->{safe} | ||
| 533 | && ( _IS_VMS | ||||
| 534 | ? !&VMS::Filespec::candelete($root) | ||||
| 535 | : !-w $root ) ) | ||||
| 536 | { | ||||
| 537 | print "skipped $root\n" if $data->{verbose}; | ||||
| 538 | next ROOT_DIR; | ||||
| 539 | } | ||||
| 540 | if ( _FORCE_WRITABLE and !chmod $perm | oct '700', $root ) { | ||||
| 541 | _error( $data, "cannot make directory writeable", $canon ); | ||||
| 542 | } | ||||
| 543 | 40 | 41µs | print "rmdir $root\n" if $data->{verbose}; | ||
| 544 | 40 | 1.60ms | 40 | 1.38ms | if ( rmdir $root ) { # spent 1.38ms making 40 calls to File::Path::CORE:rmdir, avg 35µs/call |
| 545 | 40 | 38µs | push @{ ${ $data->{result} } }, $root if $data->{result}; | ||
| 546 | 40 | 36µs | ++$count; | ||
| 547 | } | ||||
| 548 | else { | ||||
| 549 | _error( $data, "cannot remove directory", $canon ); | ||||
| 550 | if ( | ||||
| 551 | _FORCE_WRITABLE | ||||
| 552 | && !chmod( $perm, | ||||
| 553 | ( _IS_VMS ? VMS::Filespec::fileify($root) : $root ) | ||||
| 554 | ) | ||||
| 555 | ) | ||||
| 556 | { | ||||
| 557 | _error( | ||||
| 558 | $data, | ||||
| 559 | sprintf( "cannot restore permissions to 0%o", | ||||
| 560 | $perm ), | ||||
| 561 | $canon | ||||
| 562 | ); | ||||
| 563 | } | ||||
| 564 | } | ||||
| 565 | } | ||||
| 566 | } | ||||
| 567 | else { | ||||
| 568 | # not a directory | ||||
| 569 | $root = VMS::Filespec::vmsify("./$root") | ||||
| 570 | if _IS_VMS | ||||
| 571 | && !File::Spec->file_name_is_absolute($root) | ||||
| 572 | && ( $root !~ m/(?<!\^)[\]>]+/ ); # not already in VMS syntax | ||||
| 573 | |||||
| 574 | 80 | 78µs | if ( | ||
| 575 | $data->{safe} | ||||
| 576 | && ( | ||||
| 577 | _IS_VMS | ||||
| 578 | ? !&VMS::Filespec::candelete($root) | ||||
| 579 | : !( -l $root || -w $root ) | ||||
| 580 | ) | ||||
| 581 | ) | ||||
| 582 | { | ||||
| 583 | print "skipped $root\n" if $data->{verbose}; | ||||
| 584 | next ROOT_DIR; | ||||
| 585 | } | ||||
| 586 | |||||
| 587 | 80 | 102µs | my $nperm = $perm & oct '7777' | oct '600'; | ||
| 588 | if ( _FORCE_WRITABLE | ||||
| 589 | and $nperm != $perm | ||||
| 590 | and not chmod $nperm, $root ) | ||||
| 591 | { | ||||
| 592 | _error( $data, "cannot make file writeable", $canon ); | ||||
| 593 | } | ||||
| 594 | 80 | 85µs | print "unlink $canon\n" if $data->{verbose}; | ||
| 595 | |||||
| 596 | # delete all versions under VMS | ||||
| 597 | 80 | 66µs | for ( ; ; ) { | ||
| 598 | 80 | 2.62ms | 80 | 2.27ms | if ( unlink $root ) { # spent 2.27ms making 80 calls to File::Path::CORE:unlink, avg 28µs/call |
| 599 | push @{ ${ $data->{result} } }, $root if $data->{result}; | ||||
| 600 | } | ||||
| 601 | else { | ||||
| 602 | _error( $data, "cannot unlink file", $canon ); | ||||
| 603 | _FORCE_WRITABLE and chmod( $perm, $root ) | ||||
| 604 | or _error( $data, | ||||
| 605 | sprintf( "cannot restore permissions to 0%o", $perm ), | ||||
| 606 | $canon ); | ||||
| 607 | last; | ||||
| 608 | } | ||||
| 609 | 80 | 69µs | ++$count; | ||
| 610 | 80 | 110µs | last unless _IS_VMS && lstat $root; | ||
| 611 | } | ||||
| 612 | } | ||||
| 613 | } | ||||
| 614 | 120 | 492µs | return $count; | ||
| 615 | } | ||||
| 616 | |||||
| 617 | sub _slash_lc { | ||||
| 618 | |||||
| 619 | # fix up slashes and case on MSWin32 so that we can determine that | ||||
| 620 | # c:\path\to\dir is underneath C:/Path/To | ||||
| 621 | my $path = shift; | ||||
| 622 | $path =~ tr{\\}{/}; | ||||
| 623 | return lc($path); | ||||
| 624 | } | ||||
| 625 | |||||
| 626 | 1 | 5µs | 1; | ||
| 627 | |||||
| 628 | __END__ | ||||
sub File::Path::CORE:chdir; # opcode | |||||
# spent 678µs within File::Path::CORE:closedir which was called 40 times, avg 17µs/call:
# 40 times (678µs+0s) by File::Path::_rmtree at line 481, avg 17µs/call | |||||
# spent 129µs within File::Path::CORE:ftdir which was called 120 times, avg 1µs/call:
# 120 times (129µs+0s) by File::Path::_rmtree at line 395, avg 1µs/call | |||||
sub File::Path::CORE:lstat; # opcode | |||||
# spent 650µs within File::Path::CORE:match which was called 80 times, avg 8µs/call:
# 80 times (650µs+0s) by File::Path::rmtree at line 332, avg 8µs/call | |||||
# spent 480µs within File::Path::CORE:open_dir which was called 40 times, avg 12µs/call:
# 40 times (480µs+0s) by File::Path::_rmtree at line 469, avg 12µs/call | |||||
# spent 3.28ms within File::Path::CORE:readdir which was called 40 times, avg 82µs/call:
# 40 times (3.28ms+0s) by File::Path::_rmtree at line 479, avg 82µs/call | |||||
# spent 1.38ms within File::Path::CORE:rmdir which was called 40 times, avg 35µs/call:
# 40 times (1.38ms+0s) by File::Path::_rmtree at line 544, avg 35µs/call | |||||
sub File::Path::CORE:stat; # opcode | |||||
# spent 401µs within File::Path::CORE:subst which was called 80 times, avg 5µs/call:
# 80 times (401µs+0s) by File::Path::rmtree at line 356, avg 5µs/call | |||||
# spent 2.27ms within File::Path::CORE:unlink which was called 80 times, avg 28µs/call:
# 80 times (2.27ms+0s) by File::Path::_rmtree at line 598, avg 28µs/call | |||||
# spent 1µs within File::Path::__ANON__ which was called:
# once (1µs+0s) by File::Path::BEGIN@27 at line 41 |