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