| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
package File::Path; |
|
2
|
|
|
|
|
|
|
|
|
3
|
|
|
|
|
|
|
use 5.005_04; |
|
4
|
|
|
|
|
|
|
use strict; |
|
5
|
|
|
|
|
|
|
|
|
6
|
|
|
|
|
|
|
use Cwd 'getcwd'; |
|
7
|
|
|
|
|
|
|
use File::Basename (); |
|
8
|
|
|
|
|
|
|
use File::Spec (); |
|
9
|
|
|
|
|
|
|
|
|
10
|
|
|
|
|
|
|
BEGIN { |
|
11
|
|
|
|
|
|
|
if ( $] < 5.006 ) { |
|
12
|
|
|
|
|
|
|
|
|
13
|
|
|
|
|
|
|
# can't say 'opendir my $dh, $dirname' |
|
14
|
|
|
|
|
|
|
# need to initialise $dh |
|
15
|
|
|
|
|
|
|
eval 'use Symbol'; |
|
16
|
|
|
|
|
|
|
} |
|
17
|
|
|
|
|
|
|
} |
|
18
|
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
use Exporter (); |
|
20
|
|
|
|
|
|
|
use vars qw($VERSION @ISA @EXPORT @EXPORT_OK); |
|
21
|
|
|
|
|
|
|
$VERSION = '2.18_001'; |
|
22
|
|
|
|
|
|
|
$VERSION = eval $VERSION; |
|
23
|
|
|
|
|
|
|
@ISA = qw(Exporter); |
|
24
|
|
|
|
|
|
|
@EXPORT = qw(mkpath rmtree); |
|
25
|
|
|
|
|
|
|
@EXPORT_OK = qw(make_path remove_tree); |
|
26
|
|
|
|
|
|
|
|
|
27
|
|
|
|
|
|
|
BEGIN { |
|
28
|
|
|
|
|
|
|
for (qw(VMS MacOS MSWin32 os2)) { |
|
29
|
|
|
|
|
|
|
no strict 'refs'; |
|
30
|
|
|
|
|
|
|
*{"_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
|
|
|
|
|
|
|
) ? 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
|
|
|
|
|
|
|
*_NEED_STAT_CHECK = !(_IS_MSWIN32()) ? sub () { 1 } : sub () { 0 }; |
|
42
|
|
|
|
|
|
|
} |
|
43
|
|
|
|
|
|
|
|
|
44
|
|
|
|
|
|
|
sub _carp { |
|
45
|
0
|
|
|
0
|
|
|
require Carp; |
|
46
|
0
|
|
|
|
|
|
goto &Carp::carp; |
|
47
|
|
|
|
|
|
|
} |
|
48
|
|
|
|
|
|
|
|
|
49
|
|
|
|
|
|
|
sub _croak { |
|
50
|
0
|
|
|
0
|
|
|
require Carp; |
|
51
|
0
|
|
|
|
|
|
goto &Carp::croak; |
|
52
|
|
|
|
|
|
|
} |
|
53
|
|
|
|
|
|
|
|
|
54
|
|
|
|
|
|
|
sub _error { |
|
55
|
0
|
|
|
0
|
|
|
my $arg = shift; |
|
56
|
0
|
|
|
|
|
|
my $message = shift; |
|
57
|
0
|
|
|
|
|
|
my $object = shift; |
|
58
|
|
|
|
|
|
|
|
|
59
|
0
|
0
|
|
|
|
|
if ( $arg->{error} ) { |
|
60
|
0
|
0
|
|
|
|
|
$object = '' unless defined $object; |
|
61
|
0
|
0
|
|
|
|
|
$message .= ": $!" if $!; |
|
62
|
0
|
|
|
|
|
|
push @{ ${ $arg->{error} } }, { $object => $message }; |
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
63
|
|
|
|
|
|
|
} |
|
64
|
|
|
|
|
|
|
else { |
|
65
|
0
|
0
|
|
|
|
|
_carp( defined($object) ? "$message for $object: $!" : "$message: $!" ); |
|
66
|
|
|
|
|
|
|
} |
|
67
|
|
|
|
|
|
|
} |
|
68
|
|
|
|
|
|
|
|
|
69
|
|
|
|
|
|
|
sub __is_arg { |
|
70
|
0
|
|
|
0
|
|
|
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
|
0
|
|
|
|
|
|
return ( ref $arg eq 'HASH' ); |
|
78
|
|
|
|
|
|
|
} |
|
79
|
|
|
|
|
|
|
|
|
80
|
|
|
|
|
|
|
sub make_path { |
|
81
|
0
|
0
|
0
|
0
|
1
|
|
push @_, {} unless @_ and __is_arg( $_[-1] ); |
|
82
|
0
|
|
|
|
|
|
goto &mkpath; |
|
83
|
|
|
|
|
|
|
} |
|
84
|
|
|
|
|
|
|
|
|
85
|
|
|
|
|
|
|
sub mkpath { |
|
86
|
0
|
|
0
|
0
|
1
|
|
my $old_style = !( @_ and __is_arg( $_[-1] ) ); |
|
87
|
|
|
|
|
|
|
|
|
88
|
0
|
|
|
|
|
|
my $data; |
|
89
|
|
|
|
|
|
|
my $paths; |
|
90
|
|
|
|
|
|
|
|
|
91
|
0
|
0
|
|
|
|
|
if ($old_style) { |
|
92
|
0
|
|
|
|
|
|
my ( $verbose, $mode ); |
|
93
|
0
|
|
|
|
|
|
( $paths, $verbose, $mode ) = @_; |
|
94
|
0
|
0
|
|
|
|
|
$paths = [$paths] unless UNIVERSAL::isa( $paths, 'ARRAY' ); |
|
95
|
0
|
|
|
|
|
|
$data->{verbose} = $verbose; |
|
96
|
0
|
0
|
|
|
|
|
$data->{mode} = defined $mode ? $mode : oct '777'; |
|
97
|
|
|
|
|
|
|
} |
|
98
|
|
|
|
|
|
|
else { |
|
99
|
0
|
|
|
|
|
|
my %args_permitted = map { $_ => 1 } ( qw| |
|
|
0
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
chmod |
|
101
|
|
|
|
|
|
|
error |
|
102
|
|
|
|
|
|
|
group |
|
103
|
|
|
|
|
|
|
mask |
|
104
|
|
|
|
|
|
|
mode |
|
105
|
|
|
|
|
|
|
owner |
|
106
|
|
|
|
|
|
|
uid |
|
107
|
|
|
|
|
|
|
user |
|
108
|
|
|
|
|
|
|
verbose |
|
109
|
|
|
|
|
|
|
| ); |
|
110
|
0
|
|
|
|
|
|
my %not_on_win32_args = map { $_ => 1 } ( qw| |
|
|
0
|
|
|
|
|
|
|
|
111
|
|
|
|
|
|
|
group |
|
112
|
|
|
|
|
|
|
owner |
|
113
|
|
|
|
|
|
|
uid |
|
114
|
|
|
|
|
|
|
user |
|
115
|
|
|
|
|
|
|
| ); |
|
116
|
0
|
|
|
|
|
|
my @bad_args = (); |
|
117
|
0
|
|
|
|
|
|
my @win32_implausible_args = (); |
|
118
|
0
|
|
|
|
|
|
my $arg = pop @_; |
|
119
|
0
|
|
|
|
|
|
for my $k (sort keys %{$arg}) { |
|
|
0
|
|
|
|
|
|
|
|
120
|
0
|
0
|
0
|
|
|
|
if (! $args_permitted{$k}) { |
|
|
|
0
|
|
|
|
|
|
|
121
|
0
|
|
|
|
|
|
push @bad_args, $k; |
|
122
|
|
|
|
|
|
|
} |
|
123
|
|
|
|
|
|
|
elsif ($not_on_win32_args{$k} and _IS_MSWIN32) { |
|
124
|
0
|
|
|
|
|
|
push @win32_implausible_args, $k; |
|
125
|
|
|
|
|
|
|
} |
|
126
|
|
|
|
|
|
|
else { |
|
127
|
0
|
|
|
|
|
|
$data->{$k} = $arg->{$k}; |
|
128
|
|
|
|
|
|
|
} |
|
129
|
|
|
|
|
|
|
} |
|
130
|
0
|
0
|
|
|
|
|
_carp("Unrecognized option(s) passed to mkpath() or make_path(): @bad_args") |
|
131
|
|
|
|
|
|
|
if @bad_args; |
|
132
|
0
|
0
|
|
|
|
|
_carp("Option(s) implausible on Win32 passed to mkpath() or make_path(): @win32_implausible_args") |
|
133
|
|
|
|
|
|
|
if @win32_implausible_args; |
|
134
|
0
|
0
|
|
|
|
|
$data->{mode} = delete $data->{mask} if exists $data->{mask}; |
|
135
|
0
|
0
|
|
|
|
|
$data->{mode} = oct '777' unless exists $data->{mode}; |
|
136
|
0
|
0
|
|
|
|
|
${ $data->{error} } = [] if exists $data->{error}; |
|
|
0
|
|
|
|
|
|
|
|
137
|
0
|
0
|
|
|
|
|
unless (@win32_implausible_args) { |
|
138
|
0
|
0
|
|
|
|
|
$data->{owner} = delete $data->{user} if exists $data->{user}; |
|
139
|
0
|
0
|
|
|
|
|
$data->{owner} = delete $data->{uid} if exists $data->{uid}; |
|
140
|
0
|
0
|
0
|
|
|
|
if ( exists $data->{owner} and $data->{owner} =~ /\D/ ) { |
|
141
|
0
|
|
|
|
|
|
my $uid = ( getpwnam $data->{owner} )[2]; |
|
142
|
0
|
0
|
|
|
|
|
if ( defined $uid ) { |
|
143
|
0
|
|
|
|
|
|
$data->{owner} = $uid; |
|
144
|
|
|
|
|
|
|
} |
|
145
|
|
|
|
|
|
|
else { |
|
146
|
0
|
|
|
|
|
|
_error( $data, |
|
147
|
|
|
|
|
|
|
"unable to map $data->{owner} to a uid, ownership not changed" |
|
148
|
|
|
|
|
|
|
); |
|
149
|
0
|
|
|
|
|
|
delete $data->{owner}; |
|
150
|
|
|
|
|
|
|
} |
|
151
|
|
|
|
|
|
|
} |
|
152
|
0
|
0
|
0
|
|
|
|
if ( exists $data->{group} and $data->{group} =~ /\D/ ) { |
|
153
|
0
|
|
|
|
|
|
my $gid = ( getgrnam $data->{group} )[2]; |
|
154
|
0
|
0
|
|
|
|
|
if ( defined $gid ) { |
|
155
|
0
|
|
|
|
|
|
$data->{group} = $gid; |
|
156
|
|
|
|
|
|
|
} |
|
157
|
|
|
|
|
|
|
else { |
|
158
|
0
|
|
|
|
|
|
_error( $data, |
|
159
|
|
|
|
|
|
|
"unable to map $data->{group} to a gid, group ownership not changed" |
|
160
|
|
|
|
|
|
|
); |
|
161
|
0
|
|
|
|
|
|
delete $data->{group}; |
|
162
|
|
|
|
|
|
|
} |
|
163
|
|
|
|
|
|
|
} |
|
164
|
0
|
0
|
0
|
|
|
|
if ( exists $data->{owner} and not exists $data->{group} ) { |
|
165
|
0
|
|
|
|
|
|
$data->{group} = -1; # chown will leave group unchanged |
|
166
|
|
|
|
|
|
|
} |
|
167
|
0
|
0
|
0
|
|
|
|
if ( exists $data->{group} and not exists $data->{owner} ) { |
|
168
|
0
|
|
|
|
|
|
$data->{owner} = -1; # chown will leave owner unchanged |
|
169
|
|
|
|
|
|
|
} |
|
170
|
|
|
|
|
|
|
} |
|
171
|
0
|
|
|
|
|
|
$paths = [@_]; |
|
172
|
|
|
|
|
|
|
} |
|
173
|
0
|
|
|
|
|
|
return _mkpath( $data, $paths ); |
|
174
|
|
|
|
|
|
|
} |
|
175
|
|
|
|
|
|
|
|
|
176
|
|
|
|
|
|
|
sub _mkpath { |
|
177
|
0
|
|
|
0
|
|
|
my $data = shift; |
|
178
|
0
|
|
|
|
|
|
my $paths = shift; |
|
179
|
|
|
|
|
|
|
|
|
180
|
0
|
|
|
|
|
|
my ( @created ); |
|
181
|
0
|
|
|
|
|
|
foreach my $path ( @{$paths} ) { |
|
|
0
|
|
|
|
|
|
|
|
182
|
0
|
0
|
0
|
|
|
|
next unless defined($path) and length($path); |
|
183
|
0
|
|
|
|
|
|
$path .= '/' if _IS_OS2 and $path =~ /^\w:\z/s; # feature of CRT |
|
184
|
|
|
|
|
|
|
|
|
185
|
|
|
|
|
|
|
# Logic wants Unix paths, so go with the flow. |
|
186
|
0
|
|
|
|
|
|
if (_IS_VMS) { |
|
187
|
|
|
|
|
|
|
next if $path eq '/'; |
|
188
|
|
|
|
|
|
|
$path = VMS::Filespec::unixify($path); |
|
189
|
|
|
|
|
|
|
} |
|
190
|
0
|
0
|
|
|
|
|
next if -d $path; |
|
191
|
0
|
|
|
|
|
|
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
|
0
|
0
|
0
|
|
|
|
unless ( -d $parent or $path eq $parent ) { |
|
195
|
0
|
|
|
|
|
|
push( @created, _mkpath( $data, [$parent] ) ); |
|
196
|
|
|
|
|
|
|
} |
|
197
|
0
|
0
|
|
|
|
|
print "mkdir $path\n" if $data->{verbose}; |
|
198
|
0
|
0
|
|
|
|
|
if ( mkdir( $path, $data->{mode} ) ) { |
|
199
|
0
|
|
|
|
|
|
push( @created, $path ); |
|
200
|
0
|
0
|
|
|
|
|
if ( exists $data->{owner} ) { |
|
201
|
|
|
|
|
|
|
|
|
202
|
|
|
|
|
|
|
# NB: $data->{group} guaranteed to be set during initialisation |
|
203
|
0
|
0
|
|
|
|
|
if ( !chown $data->{owner}, $data->{group}, $path ) { |
|
204
|
0
|
|
|
|
|
|
_error( $data, |
|
205
|
|
|
|
|
|
|
"Cannot change ownership of $path to $data->{owner}:$data->{group}" |
|
206
|
|
|
|
|
|
|
); |
|
207
|
|
|
|
|
|
|
} |
|
208
|
|
|
|
|
|
|
} |
|
209
|
0
|
0
|
|
|
|
|
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
|
0
|
0
|
|
|
|
|
if ( !chmod $data->{chmod}, $path ) { |
|
214
|
0
|
|
|
|
|
|
_error( $data, |
|
215
|
|
|
|
|
|
|
"Cannot change permissions of $path to $data->{chmod}" ); |
|
216
|
|
|
|
|
|
|
} |
|
217
|
|
|
|
|
|
|
} |
|
218
|
|
|
|
|
|
|
} |
|
219
|
|
|
|
|
|
|
else { |
|
220
|
0
|
|
|
|
|
|
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
|
0
|
|
|
|
|
|
my ( $e, $e1 ) = ( $save_bang, $^E ); |
|
230
|
0
|
0
|
|
|
|
|
$e .= "; $e1" if $e ne $e1; |
|
231
|
|
|
|
|
|
|
|
|
232
|
|
|
|
|
|
|
# allow for another process to have created it meanwhile |
|
233
|
0
|
0
|
|
|
|
|
if ( ! -d $path ) { |
|
234
|
0
|
|
|
|
|
|
$! = $save_bang; |
|
235
|
0
|
0
|
|
|
|
|
if ( $data->{error} ) { |
|
236
|
0
|
|
|
|
|
|
push @{ ${ $data->{error} } }, { $path => $e }; |
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
237
|
|
|
|
|
|
|
} |
|
238
|
|
|
|
|
|
|
else { |
|
239
|
0
|
|
|
|
|
|
_croak("mkdir $path: $e"); |
|
240
|
|
|
|
|
|
|
} |
|
241
|
|
|
|
|
|
|
} |
|
242
|
|
|
|
|
|
|
} |
|
243
|
|
|
|
|
|
|
} |
|
244
|
0
|
|
|
|
|
|
return @created; |
|
245
|
|
|
|
|
|
|
} |
|
246
|
|
|
|
|
|
|
|
|
247
|
|
|
|
|
|
|
sub remove_tree { |
|
248
|
0
|
0
|
0
|
0
|
1
|
|
push @_, {} unless @_ and __is_arg( $_[-1] ); |
|
249
|
0
|
|
|
|
|
|
goto &rmtree; |
|
250
|
|
|
|
|
|
|
} |
|
251
|
|
|
|
|
|
|
|
|
252
|
|
|
|
|
|
|
sub _is_subdir { |
|
253
|
0
|
|
|
0
|
|
|
my ( $dir, $test ) = @_; |
|
254
|
|
|
|
|
|
|
|
|
255
|
0
|
|
|
|
|
|
my ( $dv, $dd ) = File::Spec->splitpath( $dir, 1 ); |
|
256
|
0
|
|
|
|
|
|
my ( $tv, $td ) = File::Spec->splitpath( $test, 1 ); |
|
257
|
|
|
|
|
|
|
|
|
258
|
|
|
|
|
|
|
# not on same volume |
|
259
|
0
|
0
|
|
|
|
|
return 0 if $dv ne $tv; |
|
260
|
|
|
|
|
|
|
|
|
261
|
0
|
|
|
|
|
|
my @d = File::Spec->splitdir($dd); |
|
262
|
0
|
|
|
|
|
|
my @t = File::Spec->splitdir($td); |
|
263
|
|
|
|
|
|
|
|
|
264
|
|
|
|
|
|
|
# @t can't be a subdir if it's shorter than @d |
|
265
|
0
|
0
|
|
|
|
|
return 0 if @t < @d; |
|
266
|
|
|
|
|
|
|
|
|
267
|
0
|
|
|
|
|
|
return join( '/', @d ) eq join( '/', splice @t, 0, +@d ); |
|
268
|
|
|
|
|
|
|
} |
|
269
|
|
|
|
|
|
|
|
|
270
|
|
|
|
|
|
|
sub rmtree { |
|
271
|
0
|
|
0
|
0
|
1
|
|
my $old_style = !( @_ and __is_arg( $_[-1] ) ); |
|
272
|
|
|
|
|
|
|
|
|
273
|
0
|
|
|
|
|
|
my ($arg, $data, $paths); |
|
274
|
|
|
|
|
|
|
|
|
275
|
0
|
0
|
|
|
|
|
if ($old_style) { |
|
276
|
0
|
|
|
|
|
|
my ( $verbose, $safe ); |
|
277
|
0
|
|
|
|
|
|
( $paths, $verbose, $safe ) = @_; |
|
278
|
0
|
|
|
|
|
|
$data->{verbose} = $verbose; |
|
279
|
0
|
0
|
|
|
|
|
$data->{safe} = defined $safe ? $safe : 0; |
|
280
|
|
|
|
|
|
|
|
|
281
|
0
|
0
|
0
|
|
|
|
if ( defined($paths) and length($paths) ) { |
|
282
|
0
|
0
|
|
|
|
|
$paths = [$paths] unless UNIVERSAL::isa( $paths, 'ARRAY' ); |
|
283
|
|
|
|
|
|
|
} |
|
284
|
|
|
|
|
|
|
else { |
|
285
|
0
|
|
|
|
|
|
_carp("No root path(s) specified\n"); |
|
286
|
0
|
|
|
|
|
|
return 0; |
|
287
|
|
|
|
|
|
|
} |
|
288
|
|
|
|
|
|
|
} |
|
289
|
|
|
|
|
|
|
else { |
|
290
|
0
|
|
|
|
|
|
my %args_permitted = map { $_ => 1 } ( qw| |
|
|
0
|
|
|
|
|
|
|
|
291
|
|
|
|
|
|
|
error |
|
292
|
|
|
|
|
|
|
keep_root |
|
293
|
|
|
|
|
|
|
result |
|
294
|
|
|
|
|
|
|
safe |
|
295
|
|
|
|
|
|
|
verbose |
|
296
|
|
|
|
|
|
|
| ); |
|
297
|
0
|
|
|
|
|
|
my @bad_args = (); |
|
298
|
0
|
|
|
|
|
|
my $arg = pop @_; |
|
299
|
0
|
|
|
|
|
|
for my $k (sort keys %{$arg}) { |
|
|
0
|
|
|
|
|
|
|
|
300
|
0
|
0
|
|
|
|
|
if (! $args_permitted{$k}) { |
|
301
|
0
|
|
|
|
|
|
push @bad_args, $k; |
|
302
|
|
|
|
|
|
|
} |
|
303
|
|
|
|
|
|
|
else { |
|
304
|
0
|
|
|
|
|
|
$data->{$k} = $arg->{$k}; |
|
305
|
|
|
|
|
|
|
} |
|
306
|
|
|
|
|
|
|
} |
|
307
|
0
|
0
|
|
|
|
|
_carp("Unrecognized option(s) passed to remove_tree(): @bad_args") |
|
308
|
|
|
|
|
|
|
if @bad_args; |
|
309
|
0
|
0
|
|
|
|
|
${ $data->{error} } = [] if exists $data->{error}; |
|
|
0
|
|
|
|
|
|
|
|
310
|
0
|
0
|
|
|
|
|
${ $data->{result} } = [] if exists $data->{result}; |
|
|
0
|
|
|
|
|
|
|
|
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
|
0
|
|
|
|
|
|
$paths = [@_]; |
|
322
|
|
|
|
|
|
|
} |
|
323
|
|
|
|
|
|
|
|
|
324
|
0
|
|
|
|
|
|
$data->{prefix} = ''; |
|
325
|
0
|
|
|
|
|
|
$data->{depth} = 0; |
|
326
|
|
|
|
|
|
|
|
|
327
|
0
|
|
|
|
|
|
my @clean_path; |
|
328
|
0
|
0
|
|
|
|
|
$data->{cwd} = getcwd() or do { |
|
329
|
0
|
|
|
|
|
|
_error( $data, "cannot fetch initial working directory" ); |
|
330
|
0
|
|
|
|
|
|
return 0; |
|
331
|
|
|
|
|
|
|
}; |
|
332
|
0
|
|
|
|
|
|
for ( $data->{cwd} ) { /\A(.*)\Z/s; $_ = $1 } # untaint |
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
333
|
|
|
|
|
|
|
|
|
334
|
0
|
|
|
|
|
|
for my $p (@$paths) { |
|
335
|
|
|
|
|
|
|
|
|
336
|
|
|
|
|
|
|
# need to fixup case and map \ to / on Windows |
|
337
|
0
|
|
|
|
|
|
my $ortho_root = _IS_MSWIN32 ? _slash_lc($p) : $p; |
|
338
|
|
|
|
|
|
|
my $ortho_cwd = |
|
339
|
0
|
|
|
|
|
|
_IS_MSWIN32 ? _slash_lc( $data->{cwd} ) : $data->{cwd}; |
|
340
|
0
|
|
|
|
|
|
my $ortho_root_length = length($ortho_root); |
|
341
|
0
|
|
|
|
|
|
$ortho_root_length-- if _IS_VMS; # don't compare '.' with ']' |
|
342
|
0
|
0
|
0
|
|
|
|
if ( $ortho_root_length && _is_subdir( $ortho_root, $ortho_cwd ) ) { |
|
343
|
0
|
|
|
|
|
|
local $! = 0; |
|
344
|
0
|
|
|
|
|
|
_error( $data, "cannot remove path when cwd is $data->{cwd}", $p ); |
|
345
|
0
|
|
|
|
|
|
next; |
|
346
|
|
|
|
|
|
|
} |
|
347
|
|
|
|
|
|
|
|
|
348
|
0
|
|
|
|
|
|
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
|
0
|
|
|
|
|
|
$p =~ s{/\z}{}; |
|
357
|
|
|
|
|
|
|
} |
|
358
|
0
|
|
|
|
|
|
push @clean_path, $p; |
|
359
|
|
|
|
|
|
|
} |
|
360
|
|
|
|
|
|
|
|
|
361
|
0
|
0
|
|
|
|
|
@{$data}{qw(device inode)} = ( lstat $data->{cwd} )[ 0, 1 ] or do { |
|
|
0
|
|
|
|
|
|
|
|
362
|
0
|
|
|
|
|
|
_error( $data, "cannot stat initial working directory", $data->{cwd} ); |
|
363
|
0
|
|
|
|
|
|
return 0; |
|
364
|
|
|
|
|
|
|
}; |
|
365
|
|
|
|
|
|
|
|
|
366
|
0
|
|
|
|
|
|
return _rmtree( $data, \@clean_path ); |
|
367
|
|
|
|
|
|
|
} |
|
368
|
|
|
|
|
|
|
|
|
369
|
|
|
|
|
|
|
sub _rmtree { |
|
370
|
0
|
|
|
0
|
|
|
my $data = shift; |
|
371
|
0
|
|
|
|
|
|
my $paths = shift; |
|
372
|
|
|
|
|
|
|
|
|
373
|
0
|
|
|
|
|
|
my $count = 0; |
|
374
|
0
|
|
|
|
|
|
my $curdir = File::Spec->curdir(); |
|
375
|
0
|
|
|
|
|
|
my $updir = File::Spec->updir(); |
|
376
|
|
|
|
|
|
|
|
|
377
|
0
|
|
|
|
|
|
my ( @files, $root ); |
|
378
|
|
|
|
|
|
|
ROOT_DIR: |
|
379
|
0
|
|
|
|
|
|
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
|
0
|
0
|
|
|
|
|
? File::Spec->catfile( $data->{prefix}, $root ) |
|
390
|
|
|
|
|
|
|
: $root; |
|
391
|
|
|
|
|
|
|
|
|
392
|
0
|
0
|
|
|
|
|
my ( $ldev, $lino, $perm ) = ( lstat $root )[ 0, 1, 2 ] |
|
393
|
|
|
|
|
|
|
or next ROOT_DIR; |
|
394
|
|
|
|
|
|
|
|
|
395
|
0
|
0
|
|
|
|
|
if ( -d _ ) { |
|
396
|
0
|
|
|
|
|
|
$root = VMS::Filespec::vmspath( VMS::Filespec::pathify($root) ) |
|
397
|
|
|
|
|
|
|
if _IS_VMS; |
|
398
|
|
|
|
|
|
|
|
|
399
|
0
|
0
|
|
|
|
|
if ( !chdir($root) ) { |
|
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
|
0
|
|
|
|
|
|
my $root_fh; |
|
406
|
0
|
0
|
|
|
|
|
if (open($root_fh, '<', $root)) { |
|
407
|
0
|
|
|
|
|
|
my ($fh_dev, $fh_inode) = (stat $root_fh )[0,1]; |
|
408
|
0
|
|
|
|
|
|
$perm &= oct '7777'; |
|
409
|
0
|
|
|
|
|
|
my $nperm = $perm | oct '700'; |
|
410
|
0
|
|
|
|
|
|
local $@; |
|
411
|
0
|
0
|
0
|
|
|
|
if ( |
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
412
|
|
|
|
|
|
|
!( |
|
413
|
|
|
|
|
|
|
$data->{safe} |
|
414
|
|
|
|
|
|
|
or $nperm == $perm |
|
415
|
|
|
|
|
|
|
or !-d _ |
|
416
|
|
|
|
|
|
|
or $fh_dev ne $ldev |
|
417
|
|
|
|
|
|
|
or $fh_inode ne $lino |
|
418
|
0
|
|
|
|
|
|
or eval { chmod( $nperm, $root_fh ) } |
|
419
|
|
|
|
|
|
|
) |
|
420
|
|
|
|
|
|
|
) |
|
421
|
|
|
|
|
|
|
{ |
|
422
|
0
|
|
|
|
|
|
_error( $data, |
|
423
|
|
|
|
|
|
|
"cannot make child directory read-write-exec", $canon ); |
|
424
|
0
|
|
|
|
|
|
next ROOT_DIR; |
|
425
|
|
|
|
|
|
|
} |
|
426
|
0
|
|
|
|
|
|
close $root_fh; |
|
427
|
|
|
|
|
|
|
} |
|
428
|
0
|
0
|
|
|
|
|
if ( !chdir($root) ) { |
|
429
|
0
|
|
|
|
|
|
_error( $data, "cannot chdir to child", $canon ); |
|
430
|
0
|
|
|
|
|
|
next ROOT_DIR; |
|
431
|
|
|
|
|
|
|
} |
|
432
|
|
|
|
|
|
|
} |
|
433
|
|
|
|
|
|
|
|
|
434
|
|
|
|
|
|
|
my ( $cur_dev, $cur_inode, $perm ) = ( stat $curdir )[ 0, 1, 2 ] |
|
435
|
0
|
0
|
|
|
|
|
or do { |
|
436
|
0
|
|
|
|
|
|
_error( $data, "cannot stat current working directory", $canon ); |
|
437
|
0
|
|
|
|
|
|
next ROOT_DIR; |
|
438
|
|
|
|
|
|
|
}; |
|
439
|
|
|
|
|
|
|
|
|
440
|
0
|
|
|
|
|
|
if (_NEED_STAT_CHECK) { |
|
441
|
0
|
0
|
0
|
|
|
|
( $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
|
0
|
|
|
|
|
|
$perm &= oct '7777'; # don't forget setuid, setgid, sticky bits |
|
448
|
0
|
|
|
|
|
|
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
|
0
|
0
|
0
|
|
|
|
if ( |
|
|
|
|
0
|
|
|
|
|
|
456
|
|
|
|
|
|
|
!( |
|
457
|
|
|
|
|
|
|
$data->{safe} |
|
458
|
|
|
|
|
|
|
or $nperm == $perm |
|
459
|
|
|
|
|
|
|
or chmod( $nperm, $curdir ) |
|
460
|
|
|
|
|
|
|
) |
|
461
|
|
|
|
|
|
|
) |
|
462
|
|
|
|
|
|
|
{ |
|
463
|
0
|
|
|
|
|
|
_error( $data, "cannot make directory read+writeable", $canon ); |
|
464
|
0
|
|
|
|
|
|
$nperm = $perm; |
|
465
|
|
|
|
|
|
|
} |
|
466
|
|
|
|
|
|
|
|
|
467
|
0
|
|
|
|
|
|
my $d; |
|
468
|
0
|
0
|
|
|
|
|
$d = gensym() if $] < 5.006; |
|
469
|
0
|
0
|
|
|
|
|
if ( !opendir $d, $curdir ) { |
|
470
|
0
|
|
|
|
|
|
_error( $data, "cannot opendir", $canon ); |
|
471
|
0
|
|
|
|
|
|
@files = (); |
|
472
|
|
|
|
|
|
|
} |
|
473
|
|
|
|
|
|
|
else { |
|
474
|
0
|
0
|
0
|
|
|
|
if ( !defined ${^TAINT} or ${^TAINT} ) { |
|
475
|
|
|
|
|
|
|
# Blindly untaint dir names if taint mode is active |
|
476
|
0
|
|
|
|
|
|
@files = map { /\A(.*)\z/s; $1 } readdir $d; |
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
477
|
|
|
|
|
|
|
} |
|
478
|
|
|
|
|
|
|
else { |
|
479
|
0
|
|
|
|
|
|
@files = readdir $d; |
|
480
|
|
|
|
|
|
|
} |
|
481
|
0
|
|
|
|
|
|
closedir $d; |
|
482
|
|
|
|
|
|
|
} |
|
483
|
|
|
|
|
|
|
|
|
484
|
0
|
|
|
|
|
|
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
|
0
|
0
|
|
|
|
|
@files = grep { $_ ne $updir and $_ ne $curdir } @files; |
|
|
0
|
|
|
|
|
|
|
|
493
|
|
|
|
|
|
|
|
|
494
|
0
|
0
|
|
|
|
|
if (@files) { |
|
495
|
|
|
|
|
|
|
|
|
496
|
|
|
|
|
|
|
# remove the contained files before the directory itself |
|
497
|
0
|
|
|
|
|
|
my $narg = {%$data}; |
|
498
|
0
|
|
|
|
|
|
@{$narg}{qw(device inode cwd prefix depth)} = |
|
499
|
0
|
|
|
|
|
|
( $cur_dev, $cur_inode, $updir, $canon, $data->{depth} + 1 ); |
|
500
|
0
|
|
|
|
|
|
$count += _rmtree( $narg, \@files ); |
|
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
|
0
|
0
|
0
|
|
|
|
if ( $nperm != $perm and not chmod( $perm, $curdir ) ) { |
|
507
|
0
|
|
|
|
|
|
_error( $data, "cannot reset chmod", $canon ); |
|
508
|
|
|
|
|
|
|
} |
|
509
|
|
|
|
|
|
|
|
|
510
|
|
|
|
|
|
|
# don't leave the client code in an unexpected directory |
|
511
|
|
|
|
|
|
|
chdir( $data->{cwd} ) |
|
512
|
0
|
0
|
|
|
|
|
or |
|
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
|
0
|
0
|
|
|
|
|
( $cur_dev, $cur_inode ) = ( stat $curdir )[ 0, 1 ] |
|
518
|
|
|
|
|
|
|
or _croak( |
|
519
|
|
|
|
|
|
|
"cannot stat prior working directory $data->{cwd}: $!, aborting." |
|
520
|
|
|
|
|
|
|
); |
|
521
|
|
|
|
|
|
|
|
|
522
|
0
|
|
|
|
|
|
if (_NEED_STAT_CHECK) { |
|
523
|
0
|
0
|
0
|
|
|
|
( $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
|
0
|
0
|
0
|
|
|
|
if ( $data->{depth} or !$data->{keep_root} ) { |
|
532
|
0
|
0
|
0
|
|
|
|
if ( $data->{safe} |
|
533
|
|
|
|
|
|
|
&& ( _IS_VMS |
|
534
|
|
|
|
|
|
|
? !&VMS::Filespec::candelete($root) |
|
535
|
|
|
|
|
|
|
: !-w $root ) ) |
|
536
|
|
|
|
|
|
|
{ |
|
537
|
0
|
0
|
|
|
|
|
print "skipped $root\n" if $data->{verbose}; |
|
538
|
0
|
|
|
|
|
|
next ROOT_DIR; |
|
539
|
|
|
|
|
|
|
} |
|
540
|
0
|
|
|
|
|
|
if ( _FORCE_WRITABLE and !chmod $perm | oct '700', $root ) { |
|
541
|
|
|
|
|
|
|
_error( $data, "cannot make directory writeable", $canon ); |
|
542
|
|
|
|
|
|
|
} |
|
543
|
0
|
0
|
|
|
|
|
print "rmdir $root\n" if $data->{verbose}; |
|
544
|
0
|
0
|
|
|
|
|
if ( rmdir $root ) { |
|
545
|
0
|
0
|
|
|
|
|
push @{ ${ $data->{result} } }, $root if $data->{result}; |
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
546
|
0
|
|
|
|
|
|
++$count; |
|
547
|
|
|
|
|
|
|
} |
|
548
|
|
|
|
|
|
|
else { |
|
549
|
0
|
|
|
|
|
|
_error( $data, "cannot remove directory", $canon ); |
|
550
|
0
|
|
|
|
|
|
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
|
0
|
|
|
|
|
|
$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
|
0
|
0
|
0
|
|
|
|
if ( |
|
|
|
|
0
|
|
|
|
|
|
575
|
|
|
|
|
|
|
$data->{safe} |
|
576
|
|
|
|
|
|
|
&& ( |
|
577
|
|
|
|
|
|
|
_IS_VMS |
|
578
|
|
|
|
|
|
|
? !&VMS::Filespec::candelete($root) |
|
579
|
|
|
|
|
|
|
: !( -l $root || -w $root ) |
|
580
|
|
|
|
|
|
|
) |
|
581
|
|
|
|
|
|
|
) |
|
582
|
|
|
|
|
|
|
{ |
|
583
|
0
|
0
|
|
|
|
|
print "skipped $root\n" if $data->{verbose}; |
|
584
|
0
|
|
|
|
|
|
next ROOT_DIR; |
|
585
|
|
|
|
|
|
|
} |
|
586
|
|
|
|
|
|
|
|
|
587
|
0
|
|
|
|
|
|
my $nperm = $perm & oct '7777' | oct '600'; |
|
588
|
0
|
|
|
|
|
|
if ( _FORCE_WRITABLE |
|
589
|
|
|
|
|
|
|
and $nperm != $perm |
|
590
|
|
|
|
|
|
|
and not chmod $nperm, $root ) |
|
591
|
|
|
|
|
|
|
{ |
|
592
|
|
|
|
|
|
|
_error( $data, "cannot make file writeable", $canon ); |
|
593
|
|
|
|
|
|
|
} |
|
594
|
0
|
0
|
|
|
|
|
print "unlink $canon\n" if $data->{verbose}; |
|
595
|
|
|
|
|
|
|
|
|
596
|
|
|
|
|
|
|
# delete all versions under VMS |
|
597
|
0
|
|
|
|
|
|
for ( ; ; ) { |
|
598
|
0
|
0
|
|
|
|
|
if ( unlink $root ) { |
|
599
|
0
|
0
|
|
|
|
|
push @{ ${ $data->{result} } }, $root if $data->{result}; |
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
600
|
|
|
|
|
|
|
} |
|
601
|
|
|
|
|
|
|
else { |
|
602
|
0
|
|
|
|
|
|
_error( $data, "cannot unlink file", $canon ); |
|
603
|
0
|
|
|
|
|
|
_FORCE_WRITABLE and chmod( $perm, $root ) |
|
604
|
|
|
|
|
|
|
or _error( $data, |
|
605
|
|
|
|
|
|
|
sprintf( "cannot restore permissions to 0%o", $perm ), |
|
606
|
|
|
|
|
|
|
$canon ); |
|
607
|
0
|
|
|
|
|
|
last; |
|
608
|
|
|
|
|
|
|
} |
|
609
|
0
|
|
|
|
|
|
++$count; |
|
610
|
0
|
|
|
|
|
|
last unless _IS_VMS && lstat $root; |
|
611
|
|
|
|
|
|
|
} |
|
612
|
|
|
|
|
|
|
} |
|
613
|
|
|
|
|
|
|
} |
|
614
|
0
|
|
|
|
|
|
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
|
0
|
|
|
0
|
|
|
my $path = shift; |
|
622
|
0
|
|
|
|
|
|
$path =~ tr{\\}{/}; |
|
623
|
0
|
|
|
|
|
|
return lc($path); |
|
624
|
|
|
|
|
|
|
} |
|
625
|
|
|
|
|
|
|
|
|
626
|
|
|
|
|
|
|
1; |
|
627
|
|
|
|
|
|
|
|
|
628
|
|
|
|
|
|
|
__END__ |