| 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 | File::Path::BEGIN@27 |
| 1 | 1 | 1 | 14µs | 14µs | File::Path::BEGIN@3 |
| 1 | 1 | 1 | 10µs | 55µs | File::Path::BEGIN@6 |
| 1 | 1 | 1 | 10µs | 20µs | File::Path::BEGIN@29 |
| 1 | 1 | 1 | 7µs | 61µs | File::Path::BEGIN@20 |
| 1 | 1 | 1 | 6µs | 17µs | File::Path::BEGIN@4 |
| 1 | 1 | 1 | 4µs | 4µs | File::Path::BEGIN@10 |
| 1 | 1 | 1 | 3µs | 3µs | File::Path::BEGIN@7 |
| 1 | 1 | 1 | 3µs | 3µs | File::Path::BEGIN@19 |
| 1 | 1 | 1 | 2µs | 2µs | File::Path::BEGIN@8 |
| 1 | 1 | 1 | 900ns | 900ns | File::Path::__ANON__ (xsub) |
| 0 | 0 | 0 | 0s | 0s | File::Path::__is_arg |
| 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::_is_subdir |
| 0 | 0 | 0 | 0s | 0s | File::Path::_mkpath |
| 0 | 0 | 0 | 0s | 0s | File::Path::_rmtree |
| 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 |
| 0 | 0 | 0 | 0s | 0s | File::Path::rmtree |
| 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 |