line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
#!/usr/bin/perl |
2
|
|
|
|
|
|
|
package FCGI::Daemon; |
3
|
|
|
|
|
|
|
our $VERSION = '0.20111121'; |
4
|
1
|
|
|
1
|
|
19854
|
use English '-no_match_vars'; |
|
1
|
|
|
|
|
4221
|
|
|
1
|
|
|
|
|
6
|
|
5
|
1
|
|
|
1
|
|
1141
|
use FCGI 0.71; # on Debian available as libfcgi-perl |
|
1
|
|
|
|
|
1085
|
|
|
1
|
|
|
|
|
24
|
|
6
|
1
|
|
|
1
|
|
814
|
use FCGI::ProcManager 0.18; # on Debian available as libfcgi-procmanager-perl |
|
1
|
|
|
|
|
11637
|
|
|
1
|
|
|
|
|
49
|
|
7
|
1
|
|
|
1
|
|
18
|
use 5.008; |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
31
|
|
8
|
1
|
|
|
1
|
|
5
|
use strict; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
33
|
|
9
|
|
|
|
|
|
|
#use warnings; |
10
|
|
|
|
|
|
|
#use diagnostics -verbose; |
11
|
1
|
|
|
1
|
|
2164
|
use Getopt::Std; |
|
1
|
|
|
|
|
33
|
|
|
1
|
|
|
|
|
51
|
|
12
|
1
|
|
|
1
|
|
1148
|
use autouse 'Pod::Usage'=>qw(pod2usage); |
|
1
|
|
|
|
|
613
|
|
|
1
|
|
|
|
|
4
|
|
13
|
|
|
|
|
|
|
|
14
|
|
|
|
|
|
|
=head1 NAME |
15
|
|
|
|
|
|
|
|
16
|
|
|
|
|
|
|
FCGI::Daemon - Perl-aware Fast CGI daemon for use with nginx web server. |
17
|
|
|
|
|
|
|
|
18
|
|
|
|
|
|
|
=head1 VERSION |
19
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
Version 0.20111121 |
21
|
|
|
|
|
|
|
|
22
|
|
|
|
|
|
|
=begin comment |
23
|
|
|
|
|
|
|
=cut |
24
|
|
|
|
|
|
|
|
25
|
|
|
|
|
|
|
my %o; |
26
|
|
|
|
|
|
|
|
27
|
|
|
|
|
|
|
__PACKAGE__->run() unless caller(); # modulino i.e. executable rather than module |
28
|
|
|
|
|
|
|
|
29
|
|
|
|
|
|
|
=head2 help() |
30
|
|
|
|
|
|
|
print help screen extracted from POD |
31
|
|
|
|
|
|
|
=cut |
32
|
0
|
0
|
|
0
|
1
|
|
sub help { pod2usage(-verbose=>$ARG[0],-noperldoc=>1) and exit; } |
33
|
|
|
|
|
|
|
|
34
|
|
|
|
|
|
|
=head2 run() |
35
|
|
|
|
|
|
|
Modulino-style main routine |
36
|
|
|
|
|
|
|
=cut |
37
|
|
|
|
|
|
|
sub run { |
38
|
0
|
0
|
|
0
|
1
|
|
getopts('hde:f:q:p:s:g:u:m:c:l:w:',\%o) or help(0); |
39
|
0
|
0
|
|
|
|
|
help(2) if $o{'h'}; |
40
|
|
|
|
|
|
|
|
41
|
0
|
|
0
|
|
|
|
$o{sockfile}=$o{'s'}||'/var/run/fcgi-daemon.sock'; |
42
|
0
|
0
|
0
|
|
|
|
$o{pidfile}=$o{'p'}||'/var/run/fcgi-daemon.pid' if $o{'d'}; |
43
|
0
|
0
|
|
|
|
|
$o{prefork}=defined $o{'w'} ? $o{'w'} : 1; |
44
|
0
|
0
|
|
|
|
|
$o{queue}=defined $o{'q'} ? $o{'q'} : 96; |
45
|
0
|
|
0
|
|
|
|
$o{rlimit_vmem}=($o{'m'}||512)*1024*1024; |
46
|
0
|
|
0
|
|
|
|
$o{rlimit_cpu}=$o{'c'}||32; |
47
|
0
|
0
|
|
|
|
|
$o{max_evals}=defined $o{'e'} ? $o{'e'} : 10240; #max evals before exit - paranoid to free memory if leaks |
48
|
0
|
|
0
|
|
|
|
$o{file_pattern}=$o{'f'}||qr{\.pl}; |
49
|
0
|
|
0
|
|
|
|
$o{leak_threshold}=$o{'l'}||1.3; |
50
|
|
|
|
|
|
|
|
51
|
0
|
0
|
0
|
|
|
|
if($REAL_USER_ID==$EFFECTIVE_USER_ID and $EFFECTIVE_USER_ID==0){ # if run as root |
52
|
0
|
|
0
|
|
|
|
$o{gid}=$o{g}||'www-data'; $o{gid_num}=scalar getgrnam($o{gid}); |
|
0
|
|
|
|
|
|
|
53
|
0
|
|
0
|
|
|
|
$o{uid}=$o{u}||'www-data'; $o{uid_num}=scalar getpwnam($o{uid}); |
|
0
|
|
|
|
|
|
|
54
|
|
|
|
|
|
|
} |
55
|
|
|
|
|
|
|
|
56
|
|
|
|
|
|
|
=head2 dieif() |
57
|
|
|
|
|
|
|
exit handler |
58
|
|
|
|
|
|
|
=cut |
59
|
|
|
|
|
|
|
sub dieif { |
60
|
0
|
0
|
|
0
|
1
|
|
if($ARG[0]){ |
61
|
0
|
|
|
|
|
|
my $err=$ARG[1]; |
62
|
0
|
|
|
|
|
|
unlink @o{'pidfile','sockfile'}; |
63
|
0
|
|
|
|
|
|
print "Error - $err:\n",$ARG[0],"\n"; |
64
|
0
|
|
|
|
|
|
exit 1; |
65
|
|
|
|
|
|
|
} |
66
|
|
|
|
|
|
|
} |
67
|
|
|
|
|
|
|
|
68
|
|
|
|
|
|
|
$SIG{INT}=$SIG{TERM}=sub{ # actually FCGI::ProcManager override our TERM handler so .sock and .pid files will be removed only by sysv script... :( |
69
|
0
|
0
|
|
0
|
|
|
$o{fcgi_pm}->pm_remove_pid_file() if $o{fcgi_pm}; |
70
|
0
|
|
|
|
|
|
unlink @o{'sockfile','pidfile'}; |
71
|
0
|
0
|
|
|
|
|
$o{fcgi_pm}->pm_die() if $o{fcgi_pm}; #pm_die() does not return |
72
|
0
|
|
|
|
|
|
exit 0; |
73
|
0
|
|
|
|
|
|
}; |
74
|
|
|
|
|
|
|
|
75
|
|
|
|
|
|
|
# daemonize |
76
|
0
|
0
|
|
|
|
|
if($o{'d'}){ |
77
|
0
|
|
|
|
|
|
chdir '/'; # this is good practice for unmounting |
78
|
0
|
|
|
|
|
|
$PROGRAM_NAME='FCGI::Daemon'; |
79
|
0
|
0
|
|
|
|
|
defined(my $pid=fork) or die "Can't fork: $!"; |
80
|
0
|
0
|
|
|
|
|
exit if $pid; |
81
|
1
|
0
|
|
1
|
|
388
|
eval {use POSIX qw(setsid); POSIX::setsid();} or die q{Can't start a new session: }.$OS_ERROR; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
6
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
82
|
0
|
|
|
|
|
|
open *STDIN,'<','/dev/null'; |
83
|
0
|
|
|
|
|
|
open *STDOUT,'>>','/dev/null'; |
84
|
0
|
|
|
|
|
|
open *STDERR,'>>','/dev/null'; |
85
|
0
|
|
|
|
|
|
umask 022; |
86
|
|
|
|
|
|
|
} |
87
|
|
|
|
|
|
|
|
88
|
0
|
|
|
|
|
|
my %req_env; |
89
|
0
|
|
|
|
|
|
$o{fcgi_pm}=FCGI::ProcManager->new({n_processes=>$o{prefork}, |
90
|
|
|
|
|
|
|
die_timeout=>28, |
91
|
|
|
|
|
|
|
pid_fname=>$o{pidfile} |
92
|
|
|
|
|
|
|
}); |
93
|
0
|
|
|
|
|
|
print "Opening socket $o{sockfile}\n"; |
94
|
0
|
0
|
|
|
|
|
my $rqst=FCGI::Request(\*STDIN,\*STDOUT,\*STDERR,\%req_env, |
95
|
|
|
|
|
|
|
FCGI::OpenSocket($o{sockfile},$o{prefork}*$o{queue}), |
96
|
|
|
|
|
|
|
FCGI::FAIL_ACCEPT_ON_INTR()) |
97
|
|
|
|
|
|
|
or die "Error: Unable to create FCGI::Request..."; |
98
|
|
|
|
|
|
|
|
99
|
0
|
0
|
0
|
|
|
|
if(defined $o{gid_num} and defined $o{uid_num}){ # if run as root |
100
|
0
|
0
|
|
|
|
|
chown $o{uid_num},$o{gid_num},$o{sockfile} # chown SOCKfile |
101
|
|
|
|
|
|
|
or dieif($OS_ERROR,'Unable to chown SOCKfile'); |
102
|
|
|
|
|
|
|
} |
103
|
|
|
|
|
|
|
|
104
|
0
|
|
|
|
|
|
$o{fcgi_pm}->pm_manage(); # from now on we are worker process |
105
|
|
|
|
|
|
|
|
106
|
|
|
|
|
|
|
# drop privileges if run as root |
107
|
0
|
0
|
0
|
|
|
|
if(defined $o{gid_num} and defined $o{uid_num}){ |
108
|
0
|
|
|
|
|
|
$REAL_GROUP_ID=$EFFECTIVE_GROUP_ID=getgrnam($o{gid}); |
109
|
0
|
|
|
|
|
|
dieif($OS_ERROR,'Unable to change group_id to '.$o{gid}); |
110
|
0
|
|
|
|
|
|
$REAL_USER_ID=$EFFECTIVE_USER_ID=getpwnam($o{uid}); |
111
|
0
|
|
|
|
|
|
dieif($OS_ERROR,'Unable to change user_id to '.$o{uid}); |
112
|
|
|
|
|
|
|
} |
113
|
|
|
|
|
|
|
|
114
|
|
|
|
|
|
|
## set rlimit(s) |
115
|
0
|
|
|
|
|
|
require 'syscall.ph'; |
116
|
0
|
|
|
|
|
|
require 'sys/resource.ph'; |
117
|
0
|
|
|
|
|
|
my $rlim=pack 'L!L!',$o{rlimit_vmem},$o{rlimit_vmem}; |
118
|
0
|
0
|
|
|
|
|
dieif($OS_ERROR,"Unable to set RLIMIT_AS: ") if -1==syscall(SYS_setrlimit(),RLIMIT_AS(),$rlim); |
119
|
0
|
|
|
|
|
|
$rlim=pack 'L!L!',$o{rlimit_cpu},$o{rlimit_cpu}; |
120
|
0
|
0
|
|
|
|
|
dieif($OS_ERROR,"Unable to set RLIMIT_CPU: ") if -1==syscall(SYS_setrlimit(),RLIMIT_CPU(),$rlim); |
121
|
|
|
|
|
|
|
|
122
|
|
|
|
|
|
|
REQ_LOOP: # main loop |
123
|
0
|
|
|
|
|
|
while($rqst->Accept()>=0){ |
124
|
|
|
|
|
|
|
|
125
|
0
|
|
|
|
|
|
$req_env{'PATH_INFO'}=$req_env{'SCRIPT_FILENAME'}; |
126
|
0
|
|
|
|
|
|
$req_env{'SCRIPT_FILENAME'}=get_file_from_path($req_env{SCRIPT_FILENAME}); |
127
|
0
|
|
|
|
|
|
$req_env{'PATH_INFO'}=~s/$req_env{'SCRIPT_FILENAME'}//; |
128
|
0
|
|
|
|
|
|
$req_env{'SCRIPT_NAME'}=$req_env{'SCRIPT_FILENAME'}; |
129
|
0
|
|
|
|
|
|
$req_env{'SCRIPT_NAME'}=~s/$req_env{'DOCUMENT_ROOT'}//; |
130
|
|
|
|
|
|
|
|
131
|
|
|
|
|
|
|
# check if script (exacutable, readable, non-zero size) |
132
|
0
|
0
|
|
|
|
|
unless(-x -s -r $req_env{'SCRIPT_FILENAME'}){ |
133
|
0
|
|
|
|
|
|
print "Content-type: text/plain\r\n\r\n"; |
134
|
0
|
|
|
|
|
|
$_="Error: No such CGI app - $req_env{SCRIPT_FILENAME} may not exist or is not executable by this process.\n"; |
135
|
0
|
|
|
|
|
|
print $_; |
136
|
0
|
|
|
|
|
|
print {*STDERR} $_; |
|
0
|
|
|
|
|
|
|
137
|
0
|
|
|
|
|
|
next; |
138
|
|
|
|
|
|
|
} |
139
|
|
|
|
|
|
|
|
140
|
0
|
|
|
|
|
|
local @ENV{keys %req_env}=values %req_env; |
141
|
0
|
0
|
|
|
|
|
chdir $1 if $req_env{'SCRIPT_FILENAME'}=~m{^(.*)\/}; # cd to the script's local directory |
142
|
|
|
|
|
|
|
|
143
|
|
|
|
|
|
|
# Fast Perl-CGI processing |
144
|
0
|
0
|
0
|
|
|
|
if($o{max_evals}>0 and $req_env{'SCRIPT_FILENAME'}=~m{$o{file_pattern}\z}){ # detect if perl script |
145
|
0
|
|
|
|
|
|
my %allvars; |
146
|
0
|
|
|
|
|
|
@allvars{keys %main::}=(); |
147
|
|
|
|
|
|
|
{ |
148
|
0
|
|
|
0
|
|
|
local *CORE::GLOBAL::exit=sub { die 'notr3a11yeXit' }; |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
149
|
0
|
|
|
|
|
|
local $0=$req_env{SCRIPT_FILENAME}; #fixes FindBin (in English $0 means $PROGRAM_NAME) |
150
|
1
|
|
|
1
|
|
575
|
no strict; # default for Perl5 |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
680
|
|
151
|
0
|
|
|
|
|
|
do $0; # do $0; could be enough for strict scripts |
152
|
0
|
0
|
|
|
|
|
if($EVAL_ERROR){ |
153
|
0
|
|
|
|
|
|
$EVAL_ERROR=~s{\n+\z}{}; |
154
|
0
|
0
|
|
|
|
|
print {*STDERR} "$0\n$EVAL_ERROR\n\b" unless $EVAL_ERROR =~ m{^notr3a11yeXit}; |
|
0
|
|
|
|
|
|
|
155
|
|
|
|
|
|
|
} |
156
|
|
|
|
|
|
|
} |
157
|
|
|
|
|
|
|
|
158
|
|
|
|
|
|
|
#untested experimental callback to execute on script exit |
159
|
|
|
|
|
|
|
#$_{$req_env{SCRIPT_FILENAME}}->{SIGTERM}() if defined $_{$req_env{SCRIPT_FILENAME}}->{SIGTERM}; |
160
|
|
|
|
|
|
|
#Perl scripts can cache persistent data in $_{$0}->{mydata} |
161
|
|
|
|
|
|
|
#However if you store too much data it may trigger termination by rlimit |
162
|
|
|
|
|
|
|
#After DO/EVAL $_{$0}->{'SIGTERM'} is being called so termination handler |
163
|
|
|
|
|
|
|
#can be used to close DB connections etc. |
164
|
|
|
|
|
|
|
#$_{$0}->{'SIGTERM'}=sub { print "I closed my handles"; }; |
165
|
|
|
|
|
|
|
|
166
|
0
|
|
|
|
|
|
foreach(keys %main::){ # cleanup garbage after do() |
167
|
0
|
0
|
|
|
|
|
next if exists $allvars{$_}; |
168
|
0
|
0
|
|
|
|
|
next if m{::$}; |
169
|
0
|
0
|
|
|
|
|
next if m{^_}; |
170
|
0
|
|
|
|
|
|
delete $main::{$_}; |
171
|
|
|
|
|
|
|
} |
172
|
|
|
|
|
|
|
|
173
|
0
|
0
|
|
|
|
|
if(open my $STAT,'<',"/proc/$$/status"){ |
174
|
0
|
|
|
|
|
|
my %stat; |
175
|
0
|
|
|
|
|
|
while(my ($k,$v)=split /\:\s+/,<$STAT>){ |
176
|
0
|
|
|
|
|
|
chop $v; |
177
|
0
|
|
|
|
|
|
$stat{$k}=$v; |
178
|
|
|
|
|
|
|
} |
179
|
0
|
|
|
|
|
|
close $STAT; |
180
|
|
|
|
|
|
|
# check if child takes too much resident memory and terminate if necessary |
181
|
0
|
0
|
|
|
|
|
if($stat{VmSize}/$stat{VmRSS}<$o{leak_threshold}){ |
182
|
0
|
|
|
|
|
|
print {*STDERR} 'fcgi-daemon :: terminating child - memory leak? ' |
|
0
|
|
|
|
|
|
|
183
|
|
|
|
|
|
|
."VmSize:$stat{VmSize}; VmRSS:$stat{VmRSS}; Ratio:".$stat{VmSize}/$stat{VmRSS}; |
184
|
0
|
|
|
|
|
|
exit; |
185
|
|
|
|
|
|
|
} |
186
|
|
|
|
|
|
|
} |
187
|
0
|
0
|
|
|
|
|
exit unless --$o{max_evals}; |
188
|
0
|
|
|
|
|
|
next REQ_LOOP; |
189
|
|
|
|
|
|
|
} |
190
|
|
|
|
|
|
|
|
191
|
|
|
|
|
|
|
# Normal CGI processing |
192
|
0
|
|
|
|
|
|
$o{fcgi_pm}->pm_pre_dispatch(); |
193
|
0
|
|
|
|
|
|
local $OUTPUT_AUTOFLUSH=1; # select below is equivalent of: my $oldfh=select($CERR); $|=1; select($oldfh); |
194
|
0
|
|
|
|
|
|
pipe my($PERR),my($CERR); select((select($CERR),$OUTPUT_AUTOFLUSH=1)[0]); #prepare child-to-parent pipe and swith off buffering |
|
0
|
|
|
|
|
|
|
195
|
0
|
|
|
|
|
|
pipe my($CIN),my($PIN); select((select($PIN),$OUTPUT_AUTOFLUSH=1)[0]); #prepare parent-to-child pipe and swith off buffering |
|
0
|
|
|
|
|
|
|
196
|
|
|
|
|
|
|
|
197
|
0
|
|
|
|
|
|
my $pid=open my($COUT),"-|"; ## fork and pipe to us |
198
|
0
|
0
|
|
|
|
|
unless(defined $pid){ |
199
|
0
|
|
|
|
|
|
print "Content-type: text/plain\r\n\r\n" |
200
|
|
|
|
|
|
|
."Error: CGI app returned no output - Executing $req_env{SCRIPT_FILENAME} failed !\n"; |
201
|
0
|
|
|
|
|
|
next; |
202
|
|
|
|
|
|
|
} |
203
|
0
|
|
|
|
|
|
$rqst->Detach(); # needed to restore original STDIN,STDOUT,STDERR |
204
|
|
|
|
|
|
|
|
205
|
0
|
0
|
|
|
|
|
unless($pid){ #### Child #### |
206
|
0
|
|
|
|
|
|
close $PIN; # <--perhaps not really necessary |
207
|
0
|
0
|
|
|
|
|
open *STDIN,'<&=',$CIN or die 'unable to reopen STDIN'; |
208
|
0
|
0
|
|
|
|
|
open *STDERR,'>&=',$CERR or die 'unable to reopen STDERR'; |
209
|
0
|
0
|
|
|
|
|
exec $req_env{'SCRIPT_FILENAME'} or die "exec failed"; # running the cgi app (exec does not return so child terminates here) |
210
|
|
|
|
|
|
|
}else{ #### Parent #### |
211
|
0
|
|
|
|
|
|
close $CIN; # <--perhaps not really necessary |
212
|
0
|
|
|
|
|
|
close $CERR; # *must* close child's file handle to avoid deadlock |
213
|
0
|
|
|
|
|
|
$rqst->Attach(); #reattach FCGI's STDIN,STDOUT,STDERR |
214
|
|
|
|
|
|
|
|
215
|
|
|
|
|
|
|
## send STDIN to child |
216
|
0
|
|
|
|
|
|
my $buffer; |
217
|
|
|
|
|
|
|
#print {$PIN} $buffer while (read *STDIN,$buffer,$ENV{'CONTENT_LENGTH'}); ## longer version below may be safer for very long input. |
218
|
0
|
0
|
0
|
|
|
|
if($req_env{'REQUEST_METHOD'} eq 'POST' and $req_env{'CONTENT_LENGTH'}!=0){ |
219
|
0
|
|
|
|
|
|
my $bytes=0; |
220
|
0
|
|
|
|
|
|
while ($bytes<$req_env{'CONTENT_LENGTH'}){ |
221
|
0
|
|
|
|
|
|
$bytes+=read *STDIN,$buffer,($req_env{'CONTENT_LENGTH'}-$bytes); |
222
|
0
|
0
|
0
|
|
|
|
last if ($bytes==0 or !defined $bytes); |
223
|
0
|
|
|
|
|
|
print {$PIN} $buffer; |
|
0
|
|
|
|
|
|
|
224
|
|
|
|
|
|
|
} } |
225
|
0
|
|
|
|
|
|
close $PIN; |
226
|
|
|
|
|
|
|
|
227
|
1
|
|
|
1
|
|
2024
|
use IO::Select; # non-blocking read from child's redirected STDOUT and STDERR |
|
1
|
|
|
|
|
1988
|
|
|
1
|
|
|
|
|
563
|
|
228
|
0
|
|
|
|
|
|
my $sel = IO::Select->new($COUT,$PERR); |
229
|
0
|
|
|
|
|
|
while(my @ready=$sel->can_read){ |
230
|
0
|
|
|
|
|
|
for my $FH (@ready){ |
231
|
0
|
0
|
|
|
|
|
if(0==sysread $FH,$buffer,4096){ |
232
|
0
|
|
|
|
|
|
$sel->remove($FH); |
233
|
0
|
|
|
|
|
|
close $FH; |
234
|
|
|
|
|
|
|
}else{ |
235
|
0
|
0
|
|
|
|
|
print {$FH==$COUT ? *STDOUT:*STDERR} $buffer; |
|
0
|
|
|
|
|
|
|
236
|
|
|
|
|
|
|
} } } |
237
|
0
|
|
|
|
|
|
waitpid $pid,0; |
238
|
0
|
|
|
|
|
|
$rqst->Finish(); |
239
|
|
|
|
|
|
|
} |
240
|
0
|
|
|
|
|
|
$o{fcgi_pm}->pm_post_dispatch(); |
241
|
|
|
|
|
|
|
} |
242
|
|
|
|
|
|
|
} |
243
|
|
|
|
|
|
|
|
244
|
|
|
|
|
|
|
# overriding process names |
245
|
|
|
|
|
|
|
sub FCGI::ProcManager::pm_change_process_name { |
246
|
0
|
|
|
0
|
1
|
|
my ($self,$name)=@_; |
247
|
0
|
|
|
|
|
|
my %p=( 'perl-fcgi-pm' =>'FCGI::Daemon', |
248
|
|
|
|
|
|
|
'perl-fcgi' =>'FCGI::Daemon-worker', |
249
|
|
|
|
|
|
|
); |
250
|
0
|
0
|
|
|
|
|
$0=$p{$name} if $p{$name} ne ''; |
251
|
|
|
|
|
|
|
} |
252
|
|
|
|
|
|
|
|
253
|
|
|
|
|
|
|
=head2 get_file_from_path() |
254
|
|
|
|
|
|
|
Find first file in path |
255
|
|
|
|
|
|
|
=cut |
256
|
|
|
|
|
|
|
sub get_file_from_path { |
257
|
0
|
|
|
0
|
1
|
|
local $_=shift; |
258
|
0
|
|
|
|
|
|
my $file=''; |
259
|
0
|
|
|
|
|
|
for(split '/',$_){ |
260
|
0
|
0
|
|
|
|
|
next if $_ eq ''; |
261
|
0
|
|
|
|
|
|
$file.='/'.$_; |
262
|
0
|
0
|
|
|
|
|
last if -f -s $file; |
263
|
|
|
|
|
|
|
} |
264
|
0
|
|
|
|
|
|
$file; |
265
|
|
|
|
|
|
|
} |
266
|
|
|
|
|
|
|
|
267
|
|
|
|
|
|
|
1; |
268
|
|
|
|
|
|
|
__END__ |