1 # Vend::Server - Listen for Interchange CGI requests as a background server
3 # Copyright (C) 2002-2009 Interchange Development Group
4 # Copyright (C) 1996-2002 Red Hat, Inc.
6 # This program was originally based on Vend 0.2 and 0.3
7 # Copyright 1995 by Andrew M. Wilcox <amw@wilcoxsolutions.com>
9 # This program is free software; you can redistribute it and/or modify
10 # it under the terms of the GNU General Public License as published by
11 # the Free Software Foundation; either version 2 of the License, or
12 # (at your option) any later version.
14 # This program is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 # GNU General Public License for more details.
19 # You should have received a copy of the GNU General Public
20 # License along with this program; if not, write to the Free
21 # Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
26 use vars qw($VERSION);
30 use POSIX qw(setsid strftime);
32 use Vend::CharSet qw/ to_internal decode_urlencode default_charset /;
40 no warnings qw(uninitialized);
42 my $ppidsub = sub { return getppid };
45 my ($class, $fh, $env, $entity) = @_;
56 my $msg = errmsg("CGI mapping error: %s", $@);
57 ::logGlobal({ level => 'error' }, $msg);
64 authorization AUTHORIZATION
65 content_length CONTENT_LENGTH
66 content_type CONTENT_TYPE
67 content_encoding HTTP_CONTENT_ENCODING
70 http_x_forwarded_for HTTP_X_FORWARDED_FOR
73 query_string QUERY_STRING
75 remote_addr REMOTE_ADDR
76 remote_host REMOTE_HOST
77 remote_user REMOTE_USER
78 request_method REQUEST_METHOD
79 request_uri REQUEST_URI
80 script_name SCRIPT_NAME
82 server_name SERVER_NAME
84 server_port SERVER_PORT
85 useragent HTTP_USER_AGENT
90 path_info REDIRECT_URL
91 query_string REDIRECT_QUERY_STRING
92 error_notes REDIRECT_ERROR_NOTES
93 redirect_status REDIRECT_STATUS
94 request_method REDIRECT_REQUEST_METHOD
97 ### This is to account for some bad Socket.pm implementations
98 ### which don't set SOMAXCONN, I think SCO is the big one
101 if(defined &SOMAXCONN) {
102 $SOMAXCONN = SOMAXCONN;
114 if($Global::Environment) {
115 for(@{$Global::Environment}) {
116 $ENV{$_} = $cgivar->{$_} if defined $cgivar->{$_};
123 while (($field, $cgi) = splice(@map, 0, 2)) {
124 ${"CGI::$field"} = $cgivar->{$cgi} if defined $cgivar->{$cgi};
125 #::logDebug("CGI::$field=" . ${"CGI::$field"});
128 # try to get originating host's IP address if request was
129 # forwarded through a trusted proxy
133 $CGI::remote_addr =~ $Global::TrustProxy
134 or $CGI::remote_host =~ $Global::TrustProxy
136 and my $forwarded_for = $cgivar->{HTTP_X_FORWARDED_FOR}
138 # multiple source IP addresses may appear in X-Forwarded-For header
139 # in a comma-separated list
140 for my $ip (reverse grep /\S/, split /\s*,\s*/, $forwarded_for) {
141 # do we have a valid-looking IP address?
142 if ($ip !~ /^\d\d?\d?\.\d\d?\d?\.\d\d?\d?\.\d\d?\d?$/) {
143 # if not, log error and ignore X-Forwarded-For header
146 "Unknown X-Forwarded-For header set from trusted proxy %s: %s",
153 # skip any other upstream trusted proxies
154 next if $ip =~ $Global::TrustProxy;
156 # rightmost IP address that's not a trusted proxy is the customer IP
157 # address as far as we're concerned, so keep that and exit loop
158 $CGI::remote_addr = $ip;
159 undef $CGI::remote_host;
166 return unless $Global::Logging > 4;
168 my @parms = split /\s+/,
169 ($Global::Syslog->{http_items} ||
179 my $string = 'access: ';
181 next unless $ref->{env}{$_};
182 $string .= " $_=$ref->{env}{$_}";
184 ::logGlobal( { level => 'info' }, $string);
185 return unless $Global::Logging > 5;
186 my $ent = $ref->{entity};
188 ::logGlobal( { level => 'debug' }, "POST=" . $$ent);
193 $CGI::host = $CGI::remote_host || $CGI::remote_addr;
194 $CGI::user = $CGI::remote_user;
196 my $server_host_without_port = $CGI::server_host;
197 $server_host_without_port =~ s/:.*// if $Global::FullUrlIgnorePort;
198 $CGI::script_path = $CGI::script_name;
199 $CGI::script_name = $server_host_without_port . $CGI::script_path
205 die "REQUEST_METHOD is not defined" unless defined $CGI::request_method
208 map_misc_cgi() if $h;
210 # Initialize since always used elsewhere, never will be 0
211 $CGI::content_type ||= '';
213 my $g = $Global::Selector{$CGI::script_name}
215 my $msg = ::get_locale_message(
217 "Undefined catalog: %s",
220 my $content_type = $msg =~ /<html/i ? 'text/html' : 'text/plain';
221 my $len = length($msg);
222 $Vend::StatusLine = <<EOF;
223 Status: 404 Not found
224 Content-Type: $content_type
232 my @quads = split /\./, $CGI::remote_addr;
233 my $intro = join ".", reverse(@quads), '';
235 for(@{$Global::DNSBL}) {
236 my $addr = gethostbyname($intro . $_)
241 my $msg = ::get_locale_message( 403, "Listed on avoid list.",);
242 my $content_type = $msg =~ /<html/i ? 'text/html' : 'text/plain';
243 my $len = length($msg);
244 $Vend::StatusLine = <<EOF;
245 Status: 403 Forbidden
246 Content-Type: $content_type
254 ($::IV, $::VN, $::SV) = $g->{VarName}
255 ? ($g->{IV}, $g->{VN}, $g->{IgnoreMultiple})
256 : ($Global::IV, $Global::VN, $Global::IgnoreMultiple);
258 # Vend::ModPerl has already handled GET/POST parsing
259 return if $Global::mod_perl;
261 #::logDebug("CGI::query_string=" . $CGI::query_string);
262 #::logDebug("entity=" . ${$h->{entity}});
264 #::logDebug("Check robot UA=$Global::RobotUA IP=$Global::RobotIP");
265 if ($Global::RobotIP and $CGI::remote_addr =~ $Global::RobotIP) {
266 #::logDebug("It is a robot by IP!");
269 elsif ($Global::HostnameLookups && $Global::RobotHost) {
270 if (!$CGI::remote_host && $CGI::remote_addr) {
271 $CGI::remote_host = gethostbyaddr(Socket::inet_aton($CGI::remote_addr),Socket::AF_INET);
272 $CGI::host = $CGI::remote_host || $CGI::remote_addr;
274 if ($CGI::remote_host && $CGI::remote_host =~ $Global::RobotHost) {
275 #::logDebug("It is a robot by host!");
279 unless ($Vend::Robot) {
280 if ($Global::NotRobotUA and $CGI::useragent =~ $Global::NotRobotUA) {
283 elsif ($Global::RobotUA and $CGI::useragent =~ $Global::RobotUA) {
284 #::logDebug("It is a robot by UA!");
289 $CGI::values{mv_tmp_session} ||= 1 if $Vend::Robot;
292 # This is called by parse_multipart
293 # Doesn't do unhexify
295 my ($key, $value) = @_;
298 $Global::DowncaseVarname
299 && $Global::DowncaseVarname =~ /\b\Q$key\E\b/i;
301 $key = $::IV->{$key} if defined $::IV->{$key};
302 if(defined $CGI::values{$key} and ! defined $::SV{$key}) {
303 $CGI::values{$key} = "$CGI::values{$key}\0$value";
304 push ( @{$CGI::values_array{$key}}, $value)
307 $CGI::values{$key} = $value;
308 $CGI::values_array{$key} = [$value];
315 my $request_method = "\U$CGI::request_method";
316 if ($request_method eq 'POST') {
317 #::logDebug("content type header: " . $CGI::content_type);
318 ## check for valid content type
319 if ($CGI::content_type =~ m{^(?:multipart/form-data|application/x-www-form-urlencoded|application/xml|application/json)\b}i) {
320 parse_post(\$CGI::query_string, 1)
321 if $Global::TolerateGet;
322 parse_post($h->{entity});
325 ## invalid content type for POST
326 ## XXX we may want to be a little more forgiving here
327 my $msg = ::get_locale_message(415, "Unsupported Content-Type for POST method");
328 my $content_type = $msg =~ /<html/i ? 'text/html' : 'text/plain';
329 my $len = length($msg);
330 $Vend::StatusLine = <<EOF;
331 Status: 415 Unsupported Media Type
332 Content-Type: $content_type
339 elsif ($request_method eq 'PUT') {
340 #::logDebug("Put operation.");
341 parse_post(\$CGI::query_string);
342 $CGI::put_ref = $h->{entity};
343 #::logDebug("Put contents: $$CGI::put_ref");
344 $$CGI::put_ref =~ s/^\s*--+\s+begin\s+content\s+--+\r?\n//i;
345 $$CGI::put_ref =~ s/^\r?\n--+\s+end\s+content\s+--+\s*$//i;
348 parse_post(\$CGI::query_string);
353 my ($sref, $is_get) = @_;
354 return unless length $$sref;
356 my (@pairs, $pair, $key, $value, $charset);
358 if ($CGI::content_type =~ m/\bcharset=(["']?)([-a-zA-Z0-9]+)\1/i) {
362 $charset = default_charset();
365 $CGI::values{mv_form_charset} = $charset;
367 if ($CGI::content_type =~ m{^multipart/}i && ! $is_get) {
368 return parse_multipart($sref) if $CGI::useragent !~ /MSIE\s+5/i;
369 # try and work around an apparent IE5 bug that sends the content type
370 # of the next POST after a multipart/form POST as multipart also -
371 # even though it's sent as non-multipart data
372 # Contributed by Bill Randle
373 my ($boundary) = $CGI::content_type =~ /\bboundary="?([^";]+)"?/i;
374 $boundary = '--' . quotemeta $boundary;
375 return parse_multipart($sref) if $$sref =~ /^\s*$boundary\s+/;
377 @pairs = split($Global::UrlSplittor, $$sref);
378 if( defined $pairs[0] and $pairs[0] =~ /^ (\w{8,32})? ; /x) {
379 @CGI::values{qw/ mv_session_id mv_arg mv_pc /}
380 = split /;/, $pairs[0], 3;
381 #::logDebug("found session stuff: $CGI::values{mv_session_id} --> $CGI::values{mv_arg} --> $CGI::values{mv_pc} ");
384 elsif (scalar(@pairs) == 1 and $pairs[0] !~ /=/) { # Must be an isindex
385 $CGI::values{ISINDEX} = $pairs[0];
386 $CGI::values_array{ISINDEX} = [ split /\+/, $pairs[0] ];
389 my $request_method = "\U$CGI::request_method";
392 # This loop semi-duplicated in store_cgi_kv
393 foreach $pair (grep length, @pairs) {
394 ($key, $value) = ($pair =~ m/([^=]+)=(.*)/)
396 if ($Global::TolerateGet) {
400 elsif ($request_method eq 'POST') {
401 die ::errmsg("Syntax error in POST input: %s\n%s", $pair, $$sref);
404 die ::errmsg("Syntax error in GET input: %s\n", $pair);
408 #::logDebug("incoming --> $key");
410 $Global::DowncaseVarname
411 && $Global::DowncaseVarname =~ /\b\Q$key\E\b/i;
413 $key = $::IV->{$key} if defined $::IV->{$key};
415 Vend::CharSet::decode_urlencode(\$key, $charset);
417 #::logDebug("mapping --> $key");
419 decode_urlencode(\$value, $charset);
420 # Handle multiple keys
421 if(defined $CGI::values{$key} and ! defined $::SV{$key}) {
422 $CGI::values{$key} = "$CGI::values{$key}\0$value";
423 push @{$CGI::values_array{$key}}, $value;
426 $CGI::values{$key} = $value;
427 $CGI::values_array{$key} = [$value];
431 if (! $redo and $request_method eq 'POST') {
432 @pairs = split $Global::UrlSplittor, $CGI::query_string;
433 if( defined $pairs[0] and $pairs[0] =~ /^ (\w{8,32}) ; /x) {
434 my (@old) = split /;/, $pairs[0], 3;
435 $CGI::values{mv_session_id} = $old[0]
436 if ! defined $CGI::values{mv_session_id};
437 $CGI::values{mv_arg} = $old[1]
438 if ! defined $CGI::values{mv_arg};
439 $CGI::values{mv_pc} = $old[3]
440 if ! defined $CGI::values{mv_pc};
441 #::logDebug("found session stuff: $CGI::values{mv_session_id} --> $CGI::values{mv_arg} --> $CGI::values{mv_pc} ");
449 sub parse_multipart {
452 my ($boundary) = $CGI::content_type =~ /boundary=\"?([^\";]+)\"?/;
453 $boundary = quotemeta $boundary;
455 # Stolen from CGI.pm, thanks Lincoln
456 $boundary = "--$boundary"
457 unless $CGI::useragent =~ /MSIE 3\.0[12]; Mac/i;
459 unless ($$sref =~ s/^\s*$boundary\s+//) {
460 die ::errmsg("multipart/form-data sent incorrectly:\n%s\n", $$sref);
464 @parts = split /\r?\n$boundary/, $$sref;
468 last if ! $_ || ($_ =~ /^--(\r?\n)?$/);
470 my($header, $data) = split /\r?\n\r?\n/, $_, 2;
471 my $token = '[-\w!\#$%&\'*+.^_\`|{}~]';
473 $header =~ s/\r?\n\s+/ /og; # merge continuation lines
474 while ($header=~/($token+):\s+([^\r\n]*)/mgox) {
475 my ($field_name,$field_value) = ($1,$2); # avoid taintedness
476 $field_name =~ s/\b(\w)/uc($1)/eg; #canonicalize
477 $header{$field_name} = $field_value;
480 #::logDebug("Content-Disposition: " . $header{'Content-Disposition'});
481 my($param)= $header{'Content-Disposition'}=~/ name="?([^\";]+)"?/;
483 # Bug: Netscape doesn't escape quotation marks in file names!!!
484 my($filename) = $header{'Content-Disposition'}=~/ filename="?([^\";]*)"?/;
485 #::logDebug("param='$param' filename='$filename'" );
486 if(not defined $param) {
487 ::logGlobal({ level => 'debug' }, "unsupported multipart header: \n%s\n", $header);
491 my ($content_type) = $header{'Content-Type'} =~ /^([^\s;]+)/;
492 my ($charset) = $header{'Content-Type'} =~ / charset="?([-a-zA-Z0-9]+)"?/;
494 $content_type ||= 'text/plain';
495 $charset ||= default_charset();
497 if ($content_type =~ m{^text/}i && ($::Variable->{MV_UTF8} || $Global::Variable->{MV_UTF8})) {
498 Vend::CharSet::to_internal($charset, \$data);
499 # use our character set instead of the client's one
501 $charset = default_charset();
508 $CGI::file{$param} = $data;
509 $CGI::file_encoding{$param} = $charset;
515 store_cgi_kv($param, $data);
523 my($domain,$path) = @_;
525 return '' if $Vend::tmp_session;
528 ($::Instance->{CookieName} || 'MV_SESSION_ID'),
529 defined $::Instance->{ClearCookie} ? '' : $Vend::SessionName,
530 $Vend::Expire || undef,
533 $Vend::Cfg->{SessionCookieSecure} ? $CGI::secure : undef,
535 unless $Vend::CookieID;
536 push @jar, @{$::Instance->{Cookies}}
537 if defined $::Instance->{Cookies};
539 foreach my $cookie (@jar) {
540 my ($name, $value, $expire, $d, $p, $secure) = @$cookie;
541 $d = $domain if ! $d;
543 #::logDebug("create_cookie: name=$name value=$value expire=$expire");
544 $value = Vend::Interpolate::esc($value)
545 if $value !~ /^[-\w:.]+$/;
546 $out .= "Set-Cookie: $name=$value;";
548 $out .= " domain=" . $d . ";" if $d;
549 if (defined $expire or $Vend::Expire) {
552 $expire = $Vend::Expire;
554 elsif($expire =~ /\s\S+\s/) {
555 $expstring = $expire;
557 $expstring = strftime "%a, %d-%b-%Y %H:%M:%S GMT ", gmtime($expire)
559 $expstring = "expires=$expstring" if $expstring !~ /^\s*expires=/i;
560 $expstring =~ s/^\s*/ /;
563 $out .= '; secure' if $secure;
564 $out .= '; HttpOnly' if $::Pragma->{set_httponly};
579 sub get_cache_headers {
582 my $cc = $::Pragma->{cache_control};
583 push @headers, "Cache-Control: $cc" if $cc;
585 push @headers, "Pragma: no-cache" if delete $::Scratch->{mv_no_cache};
590 sub add_cache_headers {
591 return unless my @headers = get_cache_headers();
593 $Vend::StatusLine .= "\r\n" unless $Vend::StatusLine =~ /\n\z/;
594 $Vend::StatusLine .= "$_\r\n" for @headers;
599 # $body is now a reference
601 #show_times("begin response send") if $Global::ShowTimes;
603 # Safe kludge: duplicate Vend::CharSet::default_charset method here
604 # so that $Document->send() will work from within Safe
605 my $c = $Global::Selector{$CGI::script_name};
606 my $response_charset = $c->{Variable}{MV_HTTP_CHARSET} || $Global::Variable->{MV_HTTP_CHARSET};
609 return if $Vend::Sent;
610 if($Vend::StatusLine) {
611 $status = $Vend::StatusLine =~ /(?:^|\n)Status:\s+(.*)/i
616 if($CGI::redirect_status and ! $Vend::StatusLine) {
618 $Vend::StatusLine = "Status: 200 OK\nContent-Type: text/html";
622 if ! $Vend::ResponseMade and $::Pragma->{strip_white};
624 $Vend::StatusLine =~ s/\s*$/\r\n/ if $Vend::StatusLine;
626 # NOTE: if we're supporting arbitrary encodings here in the
627 # response_charset, we should really be setting the binmode to
628 # :encoding($response_charset); if we're considering the case of
629 # UTF-8 vs undeclared, we should set the response charset to UTF-8
630 # iff MV_UTF8 is set, otherwise omit the charset declaration
634 $response_charset =~ /^utf-?8$/i
637 or $Vend::StatusLine =~ m{^Content-Type: text/}i
640 binmode(MESSAGE, ':utf8');
643 if(! $s and $Vend::StatusLine) {
644 if ($Vend::StatusLine !~ /^Content-Type:/im) {
645 $Vend::StatusLine .= "\r\nContent-Type: text/html";
646 if ($response_charset) {
647 $Vend::StatusLine .= "; charset=$response_charset\r\n";
651 $Vend::StatusLine .= "\r\n";
656 $Vend::StatusLine .= "X-Track: " . $Vend::Track->header() . "\r\n"
657 if $Vend::Track and $Vend::Cfg->{UserTrack};
662 print MESSAGE canon_status($Vend::StatusLine);
663 print MESSAGE "\r\n";
664 print MESSAGE $$body;
665 undef $Vend::StatusLine;
666 $Vend::ResponseMade = 1;
667 #show_times("end response send") if $Global::ShowTimes;
674 # Fix for SunOS, Ultrix, Digital UNIX
675 # my($oldfh) = select($fh);
681 if($Vend::write_redirect and ! $rfh) {
683 my $fn = $Vend::Cfg->{RedirectCache} . $CGI::path_info;
684 my $save = umask(022);
687 ::logError("Unable to write redirected page %s: %s", $fn, $!);
688 undef $Vend::write_redirect;
695 if($Vend::ResponseMade || $CGI::values{mv_no_header} ) {
697 print $rfh $$body if $rfh;
698 #show_times("end response send") if $Global::ShowTimes;
702 if (defined $ENV{MOD_PERL} or $CGI::script_name =~ m:/nph-[^/]+$:) {
704 my $save = select $fh;
707 $Vend::StatusLine .= "\r\nX-Track: " . $Vend::Track->header() . "\r\n"
708 if $Vend::Track and $Vend::Cfg->{UserTrack};
713 $status = '200 OK' if ! $status;
714 if(defined $Vend::StatusLine) {
715 $Vend::StatusLine = "HTTP/1.0 $status\r\n$Vend::StatusLine"
716 if $Vend::StatusLine !~ m{^HTTP/};
717 print $fh canon_status($Vend::StatusLine);
718 $Vend::ResponseMade = 1;
719 undef $Vend::StatusLine;
721 else { print $fh "HTTP/1.0 $status\r\n"; }
724 if ( ! $Vend::tmp_session
726 ! $Vend::CookieID && ! $::Instance->{CookiesSet}
727 or defined $Vend::Expire
728 or defined $::Instance->{Cookies}
730 and $Vend::Cfg->{Cookies}
738 if ($Vend::Cfg->{CookieDomain}) {
739 @domains = split /\s+/, $Vend::Cfg->{CookieDomain};
741 elsif($Global::Mall) {
742 my $ref = $Global::Catalog{$Vend::Cat};
743 @paths = ($ref->{script});
744 push (@paths, @{$ref->{alias}}) if defined $ref->{alias};
745 if ($Global::FullUrl) {
746 # remove domain from script
749 s:^[^/]+/:/: or $_ = '/';
752 @paths = keys(%pathhash);
757 foreach $d (@domains) {
758 foreach $p (@paths) {
759 print $fh create_cookie($d, $p);
762 $::Instance->{CookiesSet} = delete $::Instance->{Cookies};
765 if (defined $Vend::StatusLine) {
766 print $fh canon_status($Vend::StatusLine);
768 elsif(! $Vend::ResponseMade) {
769 if ($response_charset) {
770 print $fh canon_status("Content-Type: text/html; charset=$response_charset");
773 print $fh canon_status("Content-Type: text/html");
776 print $fh canon_status("X-Track: " . $Vend::Track->header())
777 if $Vend::Track and $Vend::Cfg->{UserTrack};
781 print $fh canon_status($_) for get_cache_headers();
785 print $rfh $$body if $rfh;
786 #show_times("end response send") if $Global::ShowTimes;
787 $Vend::ResponseMade = 1;
792 $fh = \*MESSAGE if ! $fh;
795 vec($rin,fileno($fh),1) = 1;
798 if (($r = select($rin, undef, undef, $Global::SocketReadTimeout || 1)) > 0) {
799 # read up to an arbitrary 1 MiB at a time for efficiency
800 # (though the operating system may provide far less than that at a time anyway)
801 $r = sysread($fh, $$in, 1_048_576, length($$in));
803 } while ((!defined($r) || $r == -1) && ($!{eintr} || $!{eagain}));
805 die "read: $!" unless defined $r;
806 die "read: closed" unless $r > 0;
810 my ($in, $char) = @_;
813 _read($in) while (($x = index($$in, $char)) == -1);
814 my $before = substr($$in, 0, $x);
815 substr($$in, 0, $x + 1) = '';
821 my $len = _find($in, " ");
822 _read($in) while (length($$in) < $len + 1);
823 my $str = substr($$in, 0, $len);
824 substr($$in, 0, $len + 1) = '';
835 require MIME::Base64;
838 content-length CONTENT_LENGTH
839 content-type CONTENT_TYPE
840 authorization-type AUTH_TYPE
841 authorization AUTHORIZATION
843 client-hostname REMOTE_HOST
844 client-ip-address REMOTE_ADDR
845 client-ident REMOTE_IDENT
846 content-length CONTENT_LENGTH
847 content-type CONTENT_TYPE
852 method REQUEST_METHOD
854 path-translated PATH_TRANSLATED
857 reconfigure RECONFIGURE_MINIVEND
860 server-host SERVER_NAME
861 server-port SERVER_PORT
862 user-agent HTTP_USER_AGENT
863 content-encoding HTTP_CONTENT_ENCODING
864 content-language HTTP_CONTENT_LANGUAGE
865 content-transfer-encoding HTTP_CONTENT_TRANSFER_ENCODING
874 my($status, $env, $request) = @_;
877 # IP, Session, REMOTE_USER (if any) and time
878 push @params, ($$env{REMOTE_HOST} || $$env{REMOTE_ADDR});
879 push @params, ($$env{SERVER_PORT} || '-');
880 push @params, ($$env{REMOTE_USER} || '-');
881 push @params, logtime();
884 push @params, qq{"$request"};
886 push @params, $status;
889 return join " ", @params;
893 my($fh, $env, $entity) = @_;
896 die "Need URI::URL for this functionality.\n"
897 unless defined $HTTP_enabled;
899 my ($real_header, $header, $request, $block);
901 my $status_line = _find(\$in, "\n");
902 #::logDebug("status_line: $status_line");
903 ($$env{REQUEST_METHOD},$request) = split /\s+/, $status_line;
905 $block = _find(\$in, "\n");
906 #::logDebug("read: $block");
911 if ( $block =~ s/^([^:]+):\s*//) {
915 if(defined $CGImap{$header}) {
916 #::logDebug("setting env{$CGImap{$header}} to: $block");
917 $$env{$CGImap{$header}} = $block;
919 $$env{$real_header} = $block;
923 die "HTTP protocol error on '$block':\n$in";
928 if ($$env{CONTENT_LENGTH}) {
929 _read(\$in) while length($in) < $$env{CONTENT_LENGTH};
930 #::logDebug("read entity: $in");
935 #::logDebug("exiting loop");
936 my $url = new URI::URL $request;
938 (undef, $Remote_addr) =
939 sockaddr_in(getpeername($fh));
940 if ($Global::HostnameLookups) {
941 $$env{REMOTE_HOST} = gethostbyaddr($Remote_addr, AF_INET);
943 $Remote_addr = inet_ntoa($Remote_addr);
945 $$env{REMOTE_ADDR} = $Remote_addr;
947 my (@path) = $url->path_components();
952 my $catname = '/'.shift(@path);
953 $$env{SESSION_ID} = shift(@path);
955 #::logDebug("catname is $catname");
957 if($Global::Selector{$catname} and $Global::AllowGlobal->{$catname}) {
958 if ($$env{AUTHORIZATION}) {
960 Vend::Util::check_authorization( delete $$env{AUTHORIZATION} );
962 return undef if ! $$env{REMOTE_USER};
966 if($ref = $Global::Selector{$catname} || $Global::SelectorAlias{$catname}) {
967 #::logDebug("found catalog $catname");
968 $$env{SCRIPT_NAME} = $catname;
973 logData("$Global::VendRoot/etc/access_log",
977 ($$env{REQUEST_METHOD} . " " . $request),
987 my ($argv, $env, $entity) = @_;
988 my ($in, $block, $n, $i, $e, $key, $value);
992 $block = _find(\$in, "\n");
993 if (($n) = ($block =~ m/^env (\d+)$/)) {
994 foreach $i (0 .. $n - 1) {
996 if (($key, $value) = ($e =~ m/^([^=]+)=(.*)$/s)) {
997 $$env{$key} = $value;
1001 elsif ($block =~ m/^end$/) {
1004 elsif ($block =~ m/^entity$/) {
1005 $$entity = _string(\$in);
1007 elsif (($n) = ($block =~ m/^arg (\d+)$/)) {
1009 foreach $i (0 .. $n - 1) {
1010 $$argv[$i] = _string(\$in);
1014 die "Unrecognized block: $block\n";
1022 my $show_in_ps = shift;
1026 set_process_name('connection');
1028 ### This resets all $Vend::variable settings so we start
1029 ### completely initialized. It only affects the Vend package,
1030 ### not any Vend::XXX packages.
1033 if($Global::ShowTimes) {
1034 @Vend::Times = times();
1035 ::logDebug ("begin connection. Summary time set to zero");
1037 read_cgi_data(\@Global::argv, \%env, \$entity)
1039 show_times('end cgi read') if $Global::ShowTimes;
1041 # NOTE to self: this may not be necessary, but not sure of the scoping of MESSAGE
1042 binmode(MESSAGE, ':raw');
1044 my $http = new Vend::Server \*MESSAGE, \%env, \$entity;
1046 # Can log all CGI inputs
1047 log_http_data($http) if $Global::Logging;
1049 set_process_name('dispatch');
1051 show_times("begin dispatch") if $Global::ShowTimes;
1052 ::dispatch($http) if $http;
1053 show_times("end connection") if $Global::ShowTimes;
1054 close $http->{rfh} if $http->{rfh};
1057 my $display = 'done';
1058 $display .= "($show_in_ps)" if $show_in_ps;
1060 set_process_name($display);
1062 Sys::Syslog::closelog(), undef $Vend::SysLogReady
1063 if $Vend::SysLogReady;
1070 my $Signal_Terminate;
1073 my @trapped_signals = qw(INT TERM);
1113 # might also trap: QUIT
1115 my ($Routine_USR1, $Routine_USR2, $Routine_HUP, $Routine_TERM, $Routine_INT);
1116 my ($Sig_inc, $Sig_dec, $Counter);
1118 sub sig_int_or_term {
1119 $Signal_Terminate = 1;
1121 my (%seen, $all_gone);
1124 grep { !$seen{$_}++ }
1125 (keys %Page_pids, keys %Starting_pids);
1128 $all_gone = ! kill TERM => @pids
1130 select (undef, undef, undef, 0.5);
1139 unless ($Global::Windows) {
1140 push @trapped_signals, qw(HUP USR1 USR2);
1141 $Routine_USR1 = sub { $SIG{USR1} = $Routine_USR1; $Num_servers++};
1142 $Routine_USR2 = sub { $SIG{USR2} = $Routine_USR2; $Num_servers--};
1143 $Routine_HUP = sub { $SIG{HUP} = $Routine_HUP; $Signal_Restart = 1};
1146 $Routine_TERM = sub { $SIG{TERM} = $Routine_TERM; $Signal_Terminate = 1 };
1147 $Routine_INT = sub { $SIG{INT} = $Routine_INT; $Signal_Terminate = 1 };
1156 undef %Vend::Table::DBI::DBI_connect_cache;
1157 undef %Vend::Table::DBI::DBI_connect_bad;
1158 undef %Vend::Table::DBI::DBI_connect_count;
1160 #::logDebug("Reset vars");
1163 sub reset_per_fork {
1164 undef %Vend::Table::DBI::DBI_connect_cache;
1165 undef %Vend::Table::DBI::DBI_connect_bad;
1166 undef %Vend::Table::DBI::DBI_connect_count;
1169 sub clean_up_after_fork {
1170 for(values %Vend::Table::DBI::DBI_connect_cache) {
1174 %Vend::Table::DBI::DBI_connect_cache = ();
1175 %Vend::Table::DBI::DBI_connect_bad = ();
1179 @orig_signal{@trapped_signals} =
1180 map(defined $_ ? $_ : 'DEFAULT', @SIG{@trapped_signals});
1181 $Signal_Terminate = '';
1182 $SIG{PIPE} = 'IGNORE';
1183 $SIG{CHLD} = 'IGNORE'
1184 if $Global::PreFork && $Global::PreForkSingleFork;
1186 if ($Global::Windows) {
1187 $SIG{INT} = \&sig_int_or_term;
1188 $SIG{TERM} = \&sig_int_or_term;
1191 $SIG{INT} = \&sig_int_or_term;
1192 $SIG{TERM} = \&sig_int_or_term;
1193 $SIG{HUP} = sub { $Signal_Restart = 1; };
1194 $SIG{USR1} = sub { $Num_servers++; };
1195 $SIG{USR2} = sub { $Num_servers--; };
1198 if(! $Global::MaxServers) {
1199 $Sig_inc = sub { 1 };
1200 $Sig_dec = sub { 1 };
1203 $Sig_inc = sub { kill "USR1", $Vend::MasterProcess || 0; };
1204 $Sig_dec = sub { kill "USR2", $Vend::MasterProcess || 0; };
1208 sub restore_signals {
1209 @SIG{@trapped_signals} = @orig_signal{@trapped_signals};
1212 my $Last_housekeeping = 0;
1214 # Reconfigure any catalogs that have requested it, and
1215 # check to make sure we haven't too many running servers
1217 my ($interval) = @_;
1220 #::logDebug("called housekeeping");
1221 return if defined $interval and ($now - $Last_housekeeping < $interval);
1228 if($Global::HouseKeepingCron) {
1229 ($do, $do_before, $do_after, $cronjobs) = Vend::Cron::housekeeping($now);
1239 #::logDebug("actually doing housekeeping interval=$interval now=$now last=$Last_housekeeping");
1241 $Last_housekeeping = $now;
1243 my ($c, $num,$reconfig, $restart, $jobs, @files, @pidcheck_pids);
1245 if($Global::PreFork) {
1246 my @starting_pids = keys %Starting_pids;
1247 my $starting_count = starting_pids('count');
1249 my @active_pids = keys %Page_pids;
1250 my $active_count = scalar @active_pids;
1251 my $check_time = time();
1252 my $start_max_time = 30;
1254 for my $pid (@starting_pids) {
1255 my $time_taken = $check_time - $Starting_pids{$pid};
1256 if ($time_taken > $start_max_time) {
1257 ::logDebug("pid $pid took $time_taken seconds to start ($start_max_time allowed); scheduling for death");
1258 $bad_pids{$pid} = undef;
1259 delete $Starting_pids{$pid};
1264 while ($active_count > ($Global::StartServers + 1) ) {
1265 #::logDebug("too many pids ($active_count)");
1266 my $bad = shift @active_pids;
1267 #::logDebug("scheduling %s for death", $bad);
1268 $bad_pids{$bad} = undef;
1272 foreach my $pid (@active_pids) {
1273 kill(0, $pid) and next;
1274 #::logDebug("Non-existent server at PID %s", $pid);
1275 delete $Page_pids{$pid};
1279 if ($Global::PIDcheck) {
1280 for my $pid (keys %Page_pids) {
1281 my $pid_stats = $Page_pids{$pid};
1282 my $last_use = $check_time - $pid_stats->[0];
1283 next unless $last_use > $Global::PIDcheck;
1284 #::logDebug('pid %s last used %d seconds ago', $pid, $last_use);
1285 if ($pid_stats->[1]) {
1286 $bad_pids{$pid} = undef;
1287 delete $Page_pids{$pid};
1288 #::logDebug('scheduling %s for death', $pid);
1292 $pid_stats->[0] = time;
1297 if ($active_count + $starting_count < $Global::StartServers) {
1298 my $server_deficit =
1299 $Global::StartServers
1302 ::logGlobal("Spawning %d page server%s to reach %s StartServers", $server_deficit, $server_deficit == 1 ? '' : 's', $Global::StartServers);
1303 start_page(undef, $Global::PreFork, $server_deficit);
1306 for my $pid (@Termed_pids) {
1308 and ::logDebug("Sent $pid a KILL");
1310 ::logGlobal("page server pid %s won't die!", $_)
1311 for grep { kill (0, $_) } @Termed_pids;
1315 #::logDebug("Killing excess, old, or unresponsive servers");
1316 delete @Page_pids{ keys %bad_pids };
1320 { kill (0, $_) or delete $bad_pids{$_} }
1324 kill (TERM => $pid);
1325 ::logDebug("Sent $pid a TERM");
1326 push (@Termed_pids, $pid);
1331 opendir(Vend::Server::CHECKRUN, $Global::RunDir)
1332 or die "opendir $Global::RunDir: $!\n";
1333 @files = readdir Vend::Server::CHECKRUN;
1334 closedir(Vend::Server::CHECKRUN)
1335 or die "closedir $Global::RunDir: $!\n";
1336 ($reconfig) = grep $_ eq 'reconfig', @files
1338 ($restart) = grep $_ eq 'restart', @files
1339 if $Signal_Restart || $Global::Windows;
1340 ($jobs) = grep $_ eq 'jobsqueue', @files
1345 #::logDebug("run before macro $_");
1347 Vend::Dispatch::run_macro($_);
1350 ::logGlobal("cron before macro '%s' failed: %s", $_, $@);
1355 if($Global::PIDcheck) {
1357 @pidcheck_pids = grep /^pid\.\d+$/, @files;
1362 if (defined $restart) {
1363 $Signal_Restart = 0;
1364 open(Vend::Server::RESTART, "+<$Global::RunDir/restart")
1365 or die "open $Global::RunDir/restart: $!\n";
1366 lockfile(\*Vend::Server::RESTART, 1, 1)
1367 or die "lock $Global::RunDir/restart: $!\n";
1368 while(<Vend::Server::RESTART>) {
1370 #::logDebug("restart file reads line '$_'");
1371 my ($directive,$value) = split /\s+/, $_, 2;
1372 if($value =~ /<<(.*)/) {
1374 $value = Vend::Config::read_here(\*Vend::Server::RESTART, $mark);
1375 unless (defined $value) {
1376 ::logGlobal({ level => 'notice'}, <<EOF, $mark);
1377 Global reconfig ERROR
1378 Can't find string terminator "%s" anywhere before EOF.
1383 #::logDebug("restart file reads value '$value'");
1386 if($directive =~ /^\s*(sub)?catalog$/i) {
1387 ::add_catalog("$directive $value");
1390 $directive =~ /^remove$/i and
1391 $value =~ /catalog\s+(\S+)/i
1394 ::remove_catalog($1);
1396 elsif( $directive =~ /^usertag$/i) {
1397 Vend::Config::code_from_file($directive, $value, 'nohup');
1399 elsif( $directive =~ /^codedef$/i) {
1400 ($directive, $value) = split /\s+/, $value, 2;
1401 Vend::Config::code_from_file($directive, $value, 'nohup');
1404 ::change_global_directive("$directive $value");
1408 ::logGlobal({ level => 'notice' }, $@);
1412 unlockfile(\*Vend::Server::RESTART)
1413 or die "unlock $Global::RunDir/restart: $!\n";
1414 close(Vend::Server::RESTART)
1415 or die "close $Global::RunDir/restart: $!\n";
1416 unlink "$Global::RunDir/restart"
1417 or die "unlink $Global::RunDir/restart: $!\n";
1420 if (defined $reconfig) {
1421 open(Vend::Server::RECONFIG, "+<$Global::RunDir/reconfig")
1422 or die "open $Global::RunDir/reconfig: $!\n";
1423 lockfile(\*Vend::Server::RECONFIG, 1, 1)
1424 or die "lock $Global::RunDir/reconfig: $!\n";
1425 while(<Vend::Server::RECONFIG>) {
1427 my ($script_name,$table,$cfile) = split /\s+/, $_, 3;
1428 my $select = $Global::SelectorAlias{$script_name} || $script_name;
1429 my $cat = $Global::Selector{$select};
1430 unless (defined $cat) {
1431 ::logGlobal({ level => 'notice' }, "Bad script name '%s' for reconfig." , $script_name );
1436 $c = Vend::Config::config_named_catalog(
1437 $cat->{CatalogName},
1438 "from running server ($$)",
1445 $Global::Selector{$select} = $c;
1446 for(sort keys %Global::SelectorAlias) {
1447 next unless $Global::SelectorAlias{$_} eq $select;
1448 $Global::Selector{$_} = $c;
1450 ::logGlobal({ level => 'notice' }, "Reconfig of %s successful.", $c->{CatalogName});
1453 ::logGlobal({ level => 'warn' },
1454 "Error reconfiguring catalog %s from running server (%s)\n%s",
1461 unlockfile(\*Vend::Server::RECONFIG)
1462 or die "unlock $Global::RunDir/reconfig: $!\n";
1463 close(Vend::Server::RECONFIG)
1464 or die "close $Global::RunDir/reconfig: $!\n";
1465 unlink "$Global::RunDir/reconfig"
1466 or die "unlink $Global::RunDir/reconfig: $!\n";
1470 if (defined $jobs) {
1471 my (@scheduled_jobs, @queued_jobs);
1472 open(Vend::Server::JOBS, "+<$Global::RunDir/jobsqueue")
1473 or die "open $Global::RunDir/jobsqueue: $!\n";
1474 lockfile(\*Vend::Server::JOBS, 1, 1)
1475 or die "lock $Global::RunDir/jobsqueue: $!\n";
1476 while(<Vend::Server::JOBS>) {
1478 my ($directive,$value) = split /\s+/, $_, 2;
1479 my ($cat, $delay, $jobname, @params) = grep /\S/, split /[\s,\0]+/, $value;
1480 if ($delay && $delay < time()) {
1482 #::logDebug ("Jobs expired ($delay vs $now)\n");
1483 } elsif ($Job_servers++ >= $Global::Jobs->{MaxServers}) {
1486 #::logDebug ("Jobs queued, already %d jobs running/scheduled", $Job_servers);
1487 push(@queued_jobs, "$directive $value");
1489 #::logDebug ("Scheduled job for running");
1492 my ($name, $value) = split /\=/, $_, 2;
1495 push (@scheduled_jobs, [$cat, $jobname, \%p]);
1497 if (@queued_jobs > 20) {
1498 ::logGlobal({ level => 'notice' }, "Excessive size of job queue, stopping");
1503 truncate(Vend::Server::JOBS, 0)
1504 or die "truncate $Global::RunDir/jobsqueue: $!\n";
1505 seek(Vend::Server::JOBS, 0, 0)
1506 or die "seek $Global::RunDir/jobsqueue: $!\n";
1509 #::logDebug("Size of queue $$: %s", scalar(@queued_jobs));
1510 print Vend::Server::JOBS join("\n", @queued_jobs, '');
1511 unlockfile(\*Vend::Server::JOBS)
1512 or die "unlock $Global::RunDir/jobsqueue: $!\n";
1513 close(Vend::Server::JOBS)
1514 or die "close $Global::RunDir/jobsqueue: $!\n";
1516 unlockfile(\*Vend::Server::JOBS)
1517 or die "unlock $Global::RunDir/jobsqueue: $!\n";
1518 close(Vend::Server::JOBS)
1519 or die "close $Global::RunDir/jobsqueue: $!\n";
1520 unlink "$Global::RunDir/jobsqueue"
1521 or die "unlink $Global::RunDir/jobsqueue: $!\n";
1524 # now we run the scheduled jobs
1525 for my $jobref (@scheduled_jobs) {
1527 run_jobs (@$jobref);
1531 ::logGlobal({ level => 'notice' }, $@);
1539 my (@job) = split /[\s,\0]+/, $_;
1545 ::logGlobal({ level => 'notice' }, $@);
1552 #::logDebug("would run after macro $_");
1554 Vend::Dispatch::run_macro($_);
1557 ::logGlobal("cron after macro '%s' failed: %s", $_, $@);
1563 if($Global::PreFork) {
1564 # We need to respawn all the servers to pick up the new config
1565 my @pids = keys %Page_pids;
1568 { level => 'info' },
1569 "respawning page server pid %s to pick up config change",
1572 (kill 'TERM', $_ and delete $Page_pids{$_})
1574 "page server pid %s won't terminate: %s",
1579 start_page(undef, $Global::PreFork, scalar @pids);
1582 # We need to respawn all the SOAP servers to pick up the new config
1583 my @pids = keys %SOAP_pids;
1586 { level => 'info' },
1587 "respawning SOAP server pid %s to pick up config change",
1590 (kill 'TERM', $_ and delete $SOAP_pids{$_})
1592 "SOAP server pid %s won't terminate: %s",
1596 start_soap(undef,1);
1601 for (@pidcheck_pids) {
1603 my $fn = "$Global::RunDir/$_";
1604 ($Num_servers--, next) if ! -f $fn;
1605 my $runtime = $now - (stat(_))[9];
1607 my ($lifetime, $isjob);
1608 if (exists $Lifetime{$_}) {
1609 $lifetime = $Lifetime{$_};
1612 $lifetime = $Global::PIDcheck;
1614 next if $runtime < $lifetime;
1617 # determine catalog name from pid file
1618 if (open (JOBPID, $fn)) {
1619 $catname = <JOBPID>;
1622 delete $Lifetime{$_};
1628 unlink $fn and $Num_servers--;
1630 ::logGlobal({ level => 'error' }, "hammered job PID %s for catalog $catname running %s seconds", $_, $runtime);
1631 flag_job($_, $catname, 'furl');
1633 ::logGlobal({ level => 'error' }, "hammered PID %s running %s seconds", $_, $runtime);
1636 elsif (! kill 0, $_) {
1637 unlink $fn and $Num_servers--;
1638 ::logGlobal({ level => 'error' },
1639 "Spurious PID file for process %s supposedly running %s seconds",
1645 unlink $fn and $Num_servers--;
1646 ::logGlobal({ level => 'crit' },
1647 "PID %s running %s seconds would not die!",
1657 sub server_start_message {
1658 my ($fmt, $reverse) = @_;
1659 $fmt = 'START server (%s) (%s)' unless $fmt;
1661 push (@types, 'INET') if $Global::Inet_Mode;
1662 push (@types, 'UNIX') if $Global::Unix_Mode;
1663 push (@types, 'SOAP') if $Global::SOAP;
1664 push (@types, 'mod_perl') if $Global::mod_perl;
1665 my $server_type = join(" and ", @types);
1666 my $pid = ( $Global::PreFork || $Global::Variable->{MV_BAD_LOCK} )
1669 my @args = $reverse ? ($server_type, $pid) : ($pid, $server_type);
1670 return ::errmsg ($fmt , @args );
1673 sub map_unix_socket {
1674 my ($vec, $vec_map, $fh_map, @files) = @_;
1678 foreach my $sockfn (@files) {
1681 #::logDebug("starting to parse file socket $sockfn, fh created: $fh");
1684 socket($fh, AF_UNIX, SOCK_STREAM, 0) || die "socket: $!";
1686 setsockopt($fh, SOL_SOCKET, SO_REUSEADDR, pack("l", 1));
1688 bind($fh, pack("S", AF_UNIX) . $sockfn . chr(0))
1689 or die "Could not bind (open as a socket) '$sockfn':\n$!\n";
1690 listen($fh,$SOMAXCONN) or die "listen: $!";
1694 ::logGlobal({ level => 'error' },
1695 "Could not bind to UNIX socket file %s: %s",
1702 #::logDebug("made socket $sockfn");
1704 vec($rin, fileno($fh), 1) = 1;
1706 $vec_map->{$sockfn} = fileno($fh);
1707 $fh_map->{$sockfn} = $fh;
1708 push @made, $sockfn;
1713 sub map_inet_socket {
1714 my ($mode, $vec, $vec_map, $fh_map, @ports) = @_;
1716 my $proto = getprotobyname('tcp');
1724 #::logDebug("starting to parse port $_, fh created: $fh");
1725 if (/^([-\w.]+):(\d+)$/) {
1728 $bind_addr = inet_aton($bind_ip);
1731 $bind_ip = '0.0.0.0';
1732 $bind_addr = INADDR_ANY;
1736 ::logGlobal({ level => 'error' },
1737 "Unrecognized port type '%s'",
1742 #::logDebug("Trying to run server on ip=$bind_ip port=$bind_port");
1744 ::logGlobal({ level => 'error' },
1745 "Could not bind to IP address %s on port %s: %s",
1753 socket($fh, PF_INET, SOCK_STREAM, $proto)
1754 || die "socket: $!";
1755 setsockopt($fh, SOL_SOCKET, SO_REUSEADDR, pack("l", 1))
1756 || die "setsockopt: $!";
1757 bind($fh, sockaddr_in($bind_port, $bind_addr))
1759 listen($fh,$SOMAXCONN)
1760 || die "listen: $!";
1764 ::logGlobal({ level => 'error' },
1765 "$mode mode server failed to start on IP address %s, port %s: %s",
1774 vec($rin, fileno($fh), 1) = 1;
1776 my $port_ptr = "$bind_ip:$bind_port";
1777 $vec_map->{$port_ptr} = fileno($fh);
1778 $fh_map->{$port_ptr} = $fh;
1779 push @made, $port_ptr;
1780 #::logDebug( "Made port $bind_ip:$bind_port\n");
1785 sub create_host_pattern {
1787 my @hosts = grep /\S/, split /[,\s\|]+/, $host;
1792 return join "|", @hosts;
1795 sub unlink_sockets {
1799 push @to_unlink, @$_;
1802 push @to_unlink, $_;
1813 "Socket file %s cannot be unlinked: %s",
1821 "Socket file %s exists and is not a socket, possible error",
1830 my ($do_message, $no_fork, $number) = @_;
1831 #::logDebug("entering start_page");
1833 my $current_servers =
1834 starting_pids('count')
1835 + scalar (keys %Page_pids);
1837 my $server_deficit = $Global::StartServers - $current_servers;
1839 # Bail immediately if we already have a slate of
1840 # StartServers servers either pending or serving
1841 return 1 if $server_deficit < 1;
1843 # Shave number down to server_deficit if it's greater
1844 $number = $server_deficit if $server_deficit < $number;
1846 if ($number > 150) {
1848 "Ridiculously large number of StartServers: %s",
1853 my $in_single_fork =
1854 $no_fork && $Global::PreForkSingleFork;
1858 or ! ($dbl_fork_pid = fork)
1862 for (1 .. $number) {
1864 if(! defined ($pid = fork) ) {
1865 my $msg = ::errmsg("Can't fork: %s", $!);
1866 ::logGlobal({ level => 'crit' }, $msg );
1870 $Global::Foreground = 1 if $no_fork;
1872 local $SIG{CHLD} = 'DEFAULT'
1875 local $SIG{INT} = $Routine_INT;
1876 local $SIG{TERM} = $Routine_TERM;
1878 if ($do_message and ! $Vend::Quiet) {
1881 server_start_message(
1882 "Interchange page server started (process id %s)",
1887 send_ipc("register page $$");
1895 $next = server_page($no_fork);
1898 my $msg = ::errmsg("Server spawn error: %s", $@);
1899 ::logGlobal({ level => 'error' }, $msg);
1901 if defined $Vend::Cfg->{ErrorFile};
1904 clean_up_after_fork();
1905 send_ipc("respawn page $$") if $next;
1910 starting_pids('add',$pid)
1913 $in_single_fork or exit(0);
1916 if ($dbl_fork_pid) {
1917 starting_pids('add',undef,$number);
1926 my $do_message = shift;
1928 #::logDebug("starting soap");
1930 $number = $Global::SOAP_StartServers if ! $number;
1931 if ($number > 150) {
1933 "Ridiculously large number of SOAP_StartServers: %s",
1937 for (1 .. $number) {
1939 if(! defined ($pid = fork) ) {
1940 my $msg = ::errmsg("Can't fork: %s", $!);
1941 ::logGlobal({ level => 'crit' }, $msg );
1945 unless( $pid = fork ) {
1948 $Global::Foreground = 1;
1953 server_start_message(
1954 "Interchange SOAP server started (process id %s)",
1956 ) unless $Vend::Quiet;
1959 send_ipc("register soap $$");
1965 $next = server_soap(@_);
1969 ::logGlobal({ level => 'error' }, "Runtime error: %s" , $msg);
1970 logError("Runtime error: %s", $msg)
1971 if defined $Vend::Cfg->{ErrorFile};
1974 clean_up_after_fork();
1975 send_ipc("respawn soap $$") if $next;
1988 my ($action,$pid,$n) = @_;
1991 my $in_single_fork =
1992 $Global::PreFork && $Global::PreForkSingleFork;
1994 if ( $action eq 'count' ) {
1995 return $in_single_fork
1996 ? scalar keys %Starting_pids
2000 elsif ( $action eq 'add' ) {
2002 ? ($Starting_pids{$pid} = time)
2003 : ($Starting_pids += $n)
2006 elsif ( $action eq 'del' ) {
2008 ? delete ($Starting_pids{$pid})
2009 : ($Starting_pids -= $n)
2025 my $start_time = $Global::ChildLife ? time() : 0;
2029 $Global::Foreground ||= $no_fork;
2031 #::logDebug("Start time is $start_time");
2043 $n = select($rout = $rin, undef, undef, $tick);
2044 } while $n == -1 && $!{EINTR} && ! $Signal_Terminate;
2046 #my $pretty_vector = unpack('b*', $rin);
2047 #::logDebug("pid=$$ cycle=$c handled=$handled tick=$tick vector=$pretty_vector n=$n num_servers=$Num_servers");
2049 last if $Signal_Terminate;
2051 $msg = ::errmsg("error '%s' from select, n=$n." , $msg );
2057 my $current_time = time();
2058 next unless $current_time - $start_time > $Global::ChildLife;
2067 while (($p, $v) = each %vec_map) {
2068 #::logDebug("PAGE trying p=$p v=$v vec=" . vec($rout,$v,1) . " pid=$$ c=$c i=" . $i++ );
2069 next unless vec($rout, $v, 1);
2070 #::logDebug("PAGE accepting p=$p v=$v pid=$$ c=$c i=" . $i++);
2071 $Global::TcpPort = $p;
2072 $ok = accept(MESSAGE, $fh_map{$p});
2076 #::logDebug("PAGE port $Global::TcpPort handled=$handled n=$n v=$v error=$! p=$p unix=$unix_socket{$p} ipc=$ipc_socket{$p} pid=$$ c=$c i=" . $i++);
2078 unless (defined $ok) {
2079 #::logDebug("PAGE redo accept on error=$! n=$n v=$v p=$p unix=$unix_socket{$p} pid=$$ c=$c i=" . $i++);
2081 #die ("accept: $! ok=$ok pid=$$ n=$n c=$c i=" . $i++);
2085 last CHECKHOST if $unix_socket{$p};
2087 (undef, $ok) = sockaddr_in($ok);
2088 $connector = inet_ntoa($ok);
2089 last CHECKHOST if $connector =~ /$Global::TcpHost/;
2091 (undef, $dns_name) = gethostbyaddr($ok, AF_INET);
2092 $dns_name = "UNRESOLVED_NAME" if ! $dns_name;
2093 last CHECKHOST if $dns_name =~ /$Global::TcpHost/;
2102 #::logDebug("Died in select, retrying: $msg");
2103 ::logGlobal({ level => 'error' }, "Died in select, retrying: %s", $msg);
2106 #::logDebug ("Past connect, spawn=$spawn");
2110 last SPAWN unless defined $spawn;
2111 #::logDebug ("Spawning connection, " . ($no_fork ? 'no fork, ' : 'forked, ') . scalar localtime());
2113 ### Careful, returns after MaxRequests or terminate signal
2115 #::logDebug("begin non-forked ::connection()");
2116 send_ipc(sprintf ('lastused %s %s 1',$$,time))
2117 if $Global::PIDcheck;
2118 connection(++$handled);
2119 send_ipc(sprintf ('lastused %s %s 0',$$,time))
2120 if $Global::PIDcheck;
2121 #::logDebug("end non-forked ::connection()");
2124 elsif(! defined ($pid = fork) ) {
2125 my $msg = ::errmsg("Can't fork: %s", $!);
2126 ::logGlobal({ level => 'crit' }, $msg );
2131 unless ($pid = fork) {
2132 #::logDebug("forked connection");
2135 touch_pid() if $Global::PIDcheck;
2141 ::logGlobal({ level => 'error' }, "Runtime error: %s" , $msg);
2142 logError("Runtime error: %s", $msg)
2143 if defined $Vend::Cfg->{ErrorFile};
2147 select(undef,undef,undef,0.050) until &$ppidsub == 1;
2148 &$Sig_dec and unlink_pid();
2154 last SPAWN if $no_fork;
2159 # clean up dies during spawn
2162 ::logGlobal({ level => 'error' }, "Died in server spawn: %s", $msg );
2164 Vend::Session::close_session();
2165 $Vend::Cfg = { } if ! $Vend::Cfg;
2168 if($content = ::get_locale_message(500, '', $msg)) {
2169 print MESSAGE canon_status("Content-type: text/html");
2170 print MESSAGE $content;
2177 return if $Signal_Terminate;
2179 next unless $no_fork;
2181 return 1 if $end_of_life;
2183 return 1 if $Global::MaxRequestsPerChild
2184 and $handled >= $Global::MaxRequestsPerChild;
2191 #::logDebug("Entering soap server program");
2197 #my $pretty_vector = unpack('b*', $s_vector);
2198 #::logDebug("SOAP server $$ begun, vector=$pretty_vector servers=$SOAP_servers");
2208 $n = select($rout = $rin, undef, undef, $tick);
2209 } while $n == -1 && $!{EINTR} && ! $Signal_Terminate;
2212 last if $!{EINTR} and $Signal_Terminate;
2214 $msg = ::errmsg("error '%s' from select, n=%s.", $msg, $n );
2218 #soap_housekeeping();
2222 while (($p, $v) = each %s_vec_map) {
2223 next unless vec($rout, $v, 1);
2224 $Global::TcpPort = $p;
2225 $ok = accept(MESSAGE, $s_fh_map{$p});
2231 last if $Signal_Terminate;
2236 #::logDebug("SOAP died in select, retrying: $msg");
2237 ::logGlobal({ level => 'error' }, "SOAP died in select, retrying: %s", $msg);
2240 unless (defined $ok) {
2241 #::logDebug("redo accept on error=$! n=$n p=$p unix=$unix_socket{$p} pid=$$ c=$c");
2251 last CHECKHOST if $unix_socket{$p};
2252 (undef, $ok) = sockaddr_in($ok);
2253 $connector = inet_ntoa($ok);
2254 last CHECKHOST if $connector =~ /$Global::TcpHost/;
2255 (undef, $dns_name) = gethostbyaddr($ok, AF_INET);
2256 $dns_name = $connector if ! $dns_name;
2257 last CHECKHOST if $dns_name =~ /$Global::TcpHost/;
2266 if ($Vend::Cfg = http_soap(\*MESSAGE, \%env, \$entity)) {
2267 $Vend::Cat = $Vend::Cfg->{CatalogName};
2273 #::logDebug("we have no catalog");
2274 $result = Vend::SOAP::Transport::Server
2276 ->make_fault('Client.NotFound','Service not found');
2278 elsif(! $Vend::Cfg->{SOAP}) {
2279 #::logDebug("we have no SOAP enable");
2280 $result = Vend::SOAP::Transport::Server
2282 ->make_fault('Client.NotAvailable','Service not available');
2285 #::logDebug("we have our SOAP enable, entity is $entity");
2287 $::Variable = $Vend::Cfg->{Variable};
2288 $::Pragma = $Vend::Cfg->{Pragma};
2290 ($Vend::SessionID, $CGI::cookiehost) = split /:/, $env{SESSION_ID};
2291 #::logDebug("Received ID=$Vend::SessionID, host='$CGI::cookiehost'");
2292 $Vend::NoInterpolate = 1
2293 unless $Vend::Cfg->{SOAP_Enable}->{interpolate};
2294 $result = Vend::SOAP::Transport::Server
2295 ->new( in => $entity )
2296 ->dispatch_to('', 'Vend::SOAP')
2300 unless ($Vend::StatusLine =~ m{^HTTP/}) {
2301 my $status = $Vend::StatusLine =~ /(?:^|\n)Status:\s+(.*)/i
2303 $Vend::StatusLine = "HTTP/1.0 $status\r\n" . $Vend::StatusLine;
2305 $Vend::StatusLine .= "\r\nContent-Type: text/xml\r\n"
2306 unless $Vend::StatusLine =~ /^Content-Type:/im;
2308 print MESSAGE canon_status($Vend::StatusLine);
2309 print MESSAGE "\r\n";
2310 print MESSAGE $result;
2311 undef $Vend::StatusLine;
2312 $Vend::ResponseMade = 1;
2314 #::logDebug("SOAP port=$p n=$n unix=$unix_socket{$p} pid=$$ c=$c time=" . join '|', times);
2321 #::logDebug("SOAP died in processing: $msg");
2322 ::logGlobal({ level => 'error' }, "SOAP died in processing: %s", $msg);
2326 return if $Signal_Terminate;
2327 return 1 if $handled > ($Global::SOAP_MaxRequests || 10);
2328 ::put_session() if $Vend::HaveSession;
2329 undef $Vend::Session;
2330 undef $Vend::HaveSession;
2337 #::logDebug("pid $$: processing ipc response $fh");
2339 #::logDebug("pid $$: thing is $thing");
2340 if($thing =~ /^\d+$/) {
2344 elsif ($thing =~ /^lastused (\d+) (\d+) ([01])/) {
2345 #::logDebug("Page pid $1 last used at $2");
2346 @{ $Page_pids{$1} } = ($2, $3);
2348 elsif ($thing =~ /^register page (\d+)/) {
2349 $Page_pids{$1} = [ time, 0 ];
2350 starting_pids('del',$1);
2351 #::logDebug("registered Page pid $1");
2354 elsif ($thing =~ /^respawn page (\d+)/) {
2355 delete $Page_pids{$1};
2356 #::logDebug("deleted Page pid $1");
2358 start_page(undef,$Global::PreFork,1);
2360 elsif ($thing =~ /^register soap (\d+)/) {
2362 #::logDebug("registered SOAP pid $1");
2365 elsif ($thing =~ /^respawn soap (\d+)/) {
2366 delete $SOAP_pids{$1};
2367 #::logDebug("deleted SOAP pid $1");
2369 start_soap(undef, 1);
2371 elsif ($thing =~ /^running job (\d+)/) {
2372 #::logDebug("registered job pid $1");
2373 $Lifetime{$1} = $Global::Jobs->{MaxLifetime} || 30;
2375 elsif ($thing =~ /^finishing job (\d+)/) {
2376 #::logDebug("finished job pid $1");
2378 delete $Lifetime{$1};
2380 elsif($thing =~ /^\d+$/) {
2389 socket(SOCK, PF_UNIX, SOCK_STREAM, 0) or die "socket: $!\n";
2394 $ok = connect(SOCK, sockaddr_un($Global::IPCsocket));
2395 } while ( ! defined $ok and ! $!{EINTR});
2398 #::logDebug("pid $$: sent ipc $msg");
2402 sub setup_debug_log {
2403 if ($Global::DebugFile) {
2404 open(Vend::DEBUG, ">>$Global::DebugFile");
2407 print "Start DEBUG at " . localtime() . "\n" unless $Global::SysLog;
2409 elsif (!$Global::DEBUG) {
2410 # May as well turn warnings off, not going anywhere
2412 open (Vend::DEBUG, ">/dev/null") unless $Global::Windows;
2419 open(STDOUT, ">&Vend::DEBUG");
2423 open(STDERR, ">&Vend::DEBUG");
2428 # The servers for both are now combined
2429 # Can have both INET and UNIX on same system
2431 my ($socket_filename) = @_;
2432 my ($n, $rin, $rout, $pid);
2434 ::logGlobal({ level => 'info' }, server_start_message());
2436 $Vend::MasterProcess = $$;
2438 $tick = $Global::HouseKeeping || 60;
2442 #::logDebug("Starting server socket file='$socket_filename'\n");
2446 for (qw/mode.inet mode.unix mode.soap/) {
2447 unlink "$Global::RunDir/$_";
2450 # We always unlink our file-based sockets
2451 unlink_sockets($Global::SocketFile);
2452 if($Global::IPCsocket) {
2453 #::logDebug("Creating IPC socket $Global::IPCsocket");
2454 unlink_sockets($Global::IPCsocket);
2455 ## This is a scalar, not an array like Global::SocketFile
2456 ($ipc) = map_unix_socket(\$vector, \%vec_map, \%fh_map, $Global::IPCsocket );
2457 $ipc_socket{$ipc} = $ipc;
2458 $unix_socket{$ipc} = $ipc;
2459 $ipc_vector = $vector;
2462 # Make UNIX-domain sockets if applicable. The sockets are mapped into the
2463 # vector map and file handle map, socket permissions are set, etc. The
2464 # socket labels are marked with %unix_socket so that INET-specific
2465 # processing like determining IP address are not done.
2466 if($Global::Unix_Mode) {
2468 map_unix_socket(\$vector, \%vec_map, \%fh_map, @$Global::SocketFile);
2470 @unix_socket{@made} = @made;
2471 open(UNIX_MODE_INDICATOR, ">$Global::RunDir/mode.unix")
2472 or die "create $Global::RunDir/mode.unix: $!";
2473 print UNIX_MODE_INDICATOR join " ", @made;
2474 close(UNIX_MODE_INDICATOR);
2475 # So that other apps can read if appropriate
2476 chmod $Global::SocketPerms, "$Global::RunDir/mode.unix";
2478 else { # The error condition
2480 if ($Global::Inet_Mode) {
2481 $msg = errmsg("Failed to make any UNIX sockets, continuing in INET MODE ONLY" );
2482 ::logGlobal({ level => 'warn' }, $msg);
2484 undef $Global::Unix_Mode;
2487 $msg = errmsg( "No sockets -- INTERCHANGE SERVER TERMINATING\a" );
2488 ::logGlobal( {level => 'alert'}, $msg );
2495 chmod $Global::SocketPerms, $_;
2496 if($Global::SocketPerms & 033) {
2499 "ALERT: %s socket permissions are insecure; are you sure you want permissions %o?",
2501 $Global::SocketPerms,
2507 # Make SOAP-IPC sockets if applicable. The sockets are mapped into a
2508 # separate vector map and file handle map. The require of the SOAP
2509 # module is done here so that memory footprint will not be greater
2510 # if SOAP is not used.
2523 "SOAP enabled, but Vend::SOAP failed to load."
2525 print "SOAP enabled, but Vend::SOAP failed to load.\n";
2529 my @unix_soap = grep m{/}, @{$Global::SOAP_Socket};
2530 my @inet_soap = grep $_ !~ m{/}, @{$Global::SOAP_Socket};
2532 unlink_sockets(@unix_soap);
2534 map_unix_socket(\$s_vector, \%s_vec_map, \%s_fh_map, @unix_soap);
2535 chmod $Global::SOAP_Perms, @made;
2536 @unix_socket{@made} = @made;
2540 map_inet_socket('SOAP', \$s_vector, \%s_vec_map, \%s_fh_map, @inet_soap);
2545 # Make INET-domain sockets if applicable. The sockets are added into
2546 # $vector for select(,,,) monitoring, and mapped into the vector map and
2548 if($Global::Inet_Mode) {
2549 $Global::TcpHost = create_host_pattern($Global::TcpHost);
2551 { level => 'info' },
2552 "Accepting connections from %s",
2556 map_inet_socket('TCP', \$vector, \%vec_map, \%fh_map, keys %{$Global::TcpMap});
2557 if (! scalar @made) {
2559 if ($Global::Unix_Mode) {
2560 $msg = errmsg("Continuing in UNIX MODE ONLY" );
2561 ::logGlobal({ level => 'warn' }, $msg);
2563 undef $Global::Inet_Mode;
2566 $msg = errmsg( "No sockets -- INTERCHANGE SERVER TERMINATING\a" );
2567 ::logGlobal( {level => 'alert'}, $msg );
2573 open(INET_MODE_INDICATOR, ">$Global::RunDir/mode.inet")
2574 or die "create $Global::RunDir/mode.inet: $!";
2575 print INET_MODE_INDICATOR join " ", @made;
2576 close(INET_MODE_INDICATOR);
2577 # So that other apps can read if appropriate
2578 chmod $Global::SocketPerms, "$Global::RunDir/mode.inet";
2582 ::logGlobal({ level => 'info' }, server_start_message() );
2584 print server_start_message(
2585 "Interchange server started in %s mode(s) (process id %s)\n",
2587 ) unless $Vend::Quiet;
2590 if($Global::Windows or $Global::DEBUG ) {
2592 $Global::Foreground = 1;
2593 ::logGlobal({ level => 'info' }, "Running in foreground, OS=$^O, debug=$Global::DEBUG\n");
2597 #::logDebug("s_vector=" . unpack('b*', $s_vector));
2604 if($Global::PreFork && $Global::StartServers) {
2606 $p_vector = $vector ^ $ipc_vector;
2607 start_page(1, $Global::PreFork, $Global::StartServers);
2611 my $only_ipc = $master_ipc;
2615 no warnings; ## We will last out of loop
2631 undef $checked_soap;
2633 $n = select($rout = $rin, undef, undef, $cycle);
2634 } while $n == -1 && $!{EINTR} && ! $Signal_Terminate;
2638 #my $pretty_vector = unpack('b*', $rin);
2639 #::logDebug("cycle=$c tick=$cycle vector=$pretty_vector n=$n num_servers=$Num_servers");
2641 last if $Signal_Terminate;
2643 $msg = ::errmsg("error '%s' from select, n=%s." , $msg, $n);
2647 # Do nothing, timed out
2652 while (($p, $v) = each %vec_map) {
2653 #::logDebug("trying p=$p v=$v vec=" . vec($rout,$v,1) . " pid=$$ c=$c i=" . $i++ );
2654 next unless vec($rout, $v, 1);
2655 #::logDebug("accepting p=$p v=$v pid=$$ c=$c i=" . $i++);
2656 $Global::TcpPort = $p;
2657 $ok = accept(MESSAGE, $fh_map{$p});
2661 #::logDebug("port $Global::TcpPort n=$n v=$v error=$! p=$p unix=$unix_socket{$p} ipc=$ipc_socket{$p} pid=$$ c=$c i=" . $i++);
2663 unless (defined $ok) {
2664 #::logDebug("redo accept on error=$! n=$n v=$v p=$p unix=$unix_socket{$p} pid=$$ c=$c i=" . $i++);
2666 #die ("accept: $! ok=$ok pid=$$ n=$n c=$c i=" . $i++);
2669 if ($ipc_socket{$p}) {
2670 process_ipc(\*MESSAGE);
2675 last CHECKHOST if $unix_socket{$p};
2677 (undef, $ok) = sockaddr_in($ok);
2678 $connector = inet_ntoa($ok);
2679 last CHECKHOST if $connector =~ /$Global::TcpHost/;
2681 (undef, $dns_name) = gethostbyaddr($ok, AF_INET);
2682 $dns_name = "UNRESOLVED_NAME" if ! $dns_name;
2683 last CHECKHOST if $dns_name =~ /$Global::TcpHost/;
2685 $spawn = 1 unless $only_ipc;
2692 #::logDebug("Died in select, retrying: $msg");
2693 ::logGlobal({ level => 'error' }, "Died in select, retrying: %s", $msg);
2698 last SPAWN unless defined $spawn;
2699 #::logDebug("Spawning connection, " . ($no_fork ? 'no fork, ' : 'forked, ') . scalar localtime() . "\n");
2700 if(defined $no_fork) {
2705 elsif(! defined ($pid = fork) ) {
2706 my $msg = ::errmsg("Can't fork: %s", $!);
2707 ::logGlobal({ level => 'crit' }, $msg );
2712 unless ($pid = fork) {
2717 touch_pid() if $Global::PIDcheck;
2723 ::logGlobal({ level => 'error' }, "Runtime error: %s" , $msg);
2724 logError("Runtime error: %s", $msg)
2725 if defined $Vend::Cfg->{ErrorFile};
2727 clean_up_after_fork();
2730 select(undef,undef,undef,0.050) until &$ppidsub == 1;
2731 if ($Global::IPCsocket) {
2732 &$Sig_dec and unlink_pid();
2734 elsif ($Global::PIDcheck) {
2735 unlink_pid() and &$Sig_dec;
2745 last SPAWN if $no_fork;
2750 # clean up dies during spawn
2753 ::logGlobal({ level => 'error' }, "Died in server spawn: %s", $msg );
2755 Vend::Session::close_session();
2756 $Vend::Cfg = { } if ! $Vend::Cfg;
2759 if($content = ::get_locale_message(500, '', $msg)) {
2760 print MESSAGE canon_status("Content-type: text/html");
2761 print MESSAGE $content;
2767 last if $Signal_Terminate;
2768 $only_ipc = $master_ipc;
2771 housekeeping($tick);
2772 if ($Global::MaxServers and $Num_servers > $Global::MaxServers) {
2775 if( $rin = $s_vector and select($rin, undef, undef, 0) >= 1 ) {
2777 unless $SOAP_servers > $Global::SOAP_MaxServers;
2780 ::logGlobal({ level => 'crit' }, "Died in housekeeping, retry: %s", $@ ) if $@;
2785 if ($Signal_Terminate) {
2786 ::logGlobal({ level => 'info' }, "STOP server (%s) on signal TERM", $$ );
2787 #::logDebug("SOAP pids: " . ::uneval(\%SOAP_pids));
2788 my @pids = keys %SOAP_pids;
2791 { level => 'info' },
2792 "STOP SOAP servers (%s) on signal TERM",
2793 join ",", keys %SOAP_pids,
2797 @pids = keys %Page_pids;
2800 { level => 'info' },
2801 "STOP page servers (%s) on signal TERM",
2802 join ",", keys %Page_pids,
2806 for(keys %Global::Catalog) {
2807 ::remove_catalog($_);
2815 my $temppid = gensym();
2817 open($temppid, ">>$Global::RunDir/pid.$$")
2818 or die "create PID file $$: $!\n";
2819 lockfile($temppid, 1, 0)
2820 or die "PID $$ conflict: can't lock\n";
2823 $temppid->autoflush(1);
2824 print $temppid $_[0], "\n";
2829 my ($cat, @jobs) = @_;
2832 if (ref($jobs[$#jobs]) eq 'HASH') {
2833 $parms = pop(@jobs);
2836 for my $job (@jobs) {
2837 Vend::Dispatch::run_in_catalog($cat, $job, '', $parms);
2842 my ($pid, $cat, $action, $token) = @_;
2844 if ($action eq 'raise') {
2845 if ($token =~ /^(\d+)$/) {
2846 my $file = "flag.$cat.$1";
2849 unless (open(FLAG, ">>$Global::RunDir/$file")) {
2850 die "unable to create flag file $Global::RunDir/$file: $!\n";
2853 unless (lockfile(\*FLAG, 1, 0)) {
2854 die "unable to lock file $Global::RunDir/$file: $!\n";
2857 unless (chdir($Global::RunDir)) {
2858 die "unable to enter directory $Global::RunDir: $!\n";
2861 unless (symlink($file, "flag.$pid")) {
2863 die "unable to create symlink for $file: $!\n";
2870 } elsif ($action eq 'check') {
2871 return if $token !~ /^(\d+)$/;
2873 if (-f "$Global::RunDir/flag.$cat.$1") {
2878 } elsif ($action eq 'furl') {
2879 my $flagfile = readlink("$Global::RunDir/flag.$pid");
2881 if (defined ($flagfile)) {
2882 if ($flagfile =~ /^flag\.$cat\.(\d+)$/) {
2883 unless (unlink("$Global::RunDir/$flagfile")) {
2884 die "failed to remove flag file: $Global::RunDir/$flagfile: $!\n";
2887 die "invalid flag file $flagfile\n";
2890 unless (unlink("$Global::RunDir/flag.$pid")) {
2891 die "failed to remove link to flag file: $Global::RunDir/flag.$pid: $!\n";
2894 logGlobal({level => 'notice'}, "Readlink failed: $!\n");
2900 my ($cat, @jobs) = @_;
2902 #::logGlobal("Vend::Server::run_jobs: run jobs cat=$cat job=@jobs");
2904 if($Global::Foreground) {
2907 jobs_job($cat, @jobs);
2911 ::logGlobal({ level => 'error' }, "Runtime error: %s" , $msg);
2912 logError("Runtime jobs error: %s", $msg)
2913 if defined $Vend::Cfg->{ErrorFile};
2915 clean_up_after_fork();
2918 elsif(! defined ($pid = fork) ) {
2919 my $msg = ::errmsg("Can't fork: %s", $!);
2920 ::logGlobal({ level => 'crit' }, $msg );
2925 unless ($pid = fork) {
2927 send_ipc("running job $$");
2931 touch_pid($cat) if $Global::PIDcheck;
2933 jobs_job($cat, @jobs);
2937 ::logGlobal({ level => 'error' }, "Runtime error: %s" , $msg);
2938 logError("Runtime jobs error: %s", $msg)
2939 if defined $Vend::Cfg->{ErrorFile};
2941 clean_up_after_fork();
2942 send_ipc("finishing job $$");
2945 select(undef,undef,undef,0.050) until &$ppidsub == 1;
2946 if ($Global::PIDcheck) {
2947 unlink_pid() and &$Sig_dec;
2956 wait unless $Global::Foreground;
2961 unlink("$Global::RunDir/pid.$$");
2968 my $ok = lockfile($fh, 1, 0);
2970 chomp(my $pid = <$fh>);
2975 truncate($fh, 0) or die "Couldn't truncate pid file: $!\n";
2977 print $fh ($Global::mod_perl ? &$ppidsub : $$), "\n";
2982 my $fn = shift || $Global::PIDfile;
2985 or die ::errmsg("Couldn't open '%s': %s\n", $fn, $!);
2987 my $o = select($fh);
2994 my $fn = shift || $Global::PIDfile;
2996 open $fh, "<$fn" or return;
2997 chomp (my $pid = <$fh>);
3004 #::logDebug("trying to run server");
3006 @$Global::SocketFile = "$Global::VendRoot/etc/socket"
3007 unless @$Global::SocketFile and $Global::SocketFile->[0];
3009 if($Global::Variable->{MV_GETPPID_BROKEN}) {
3010 #::logDebug("setting getppid broken");
3011 my $num = $Global::Variable->{MV_GETPPID_BROKEN} > 1
3012 ? $Global::Variable->{MV_GETPPID_BROKEN}
3015 return syscall($num);
3018 my $pidh = open_pid($Global::PIDfile);
3019 #::logDebug("Opened pid file");
3021 if($Global::AcceptRedirect) {
3022 push @Map, @RedirMap
3023 unless grep $_ eq 'REDIRECT_URL', @Map;
3026 if ($Global::mod_perl) {
3027 undef $Global::Unix_Mode;
3028 undef $Global::Inet_Mode;
3029 undef $Global::StartServers;
3030 undef $Global::PreFork;
3031 undef $Global::SOAP;
3032 undef $Global::IPCsocket;
3034 elsif ( $Global::Windows ) {
3035 $Global::Inet_Mode = 1;
3037 elsif (! $Global::Inet_Mode and ! $Global::Unix_Mode) {
3038 $Global::Inet_Mode = $Global::Unix_Mode = 1;
3041 if($Global::mod_perl || $Global::PreFork || $Global::DEBUG || $Global::Windows) {
3043 require Tie::ShadowHash;
3047 if($Global::mod_perl) { $reason = 'under mod_perl' }
3048 elsif($Global::PreFork) { $reason = 'in PreFork mode' }
3049 elsif($Global::DEBUG) { $reason = 'in DEBUG mode' }
3050 elsif($Global::Windows) { $reason = 'under Windows' }
3051 die ::errmsg("Running $reason requires Tie::ShadowHash module.") . "\n";
3055 if ($Global::mod_perl) {
3056 my $running = grab_pid($pidh);
3059 "The Interchange server is already running (process id %s)\n",
3062 undef $Global::mod_perl;
3065 # throw away pidfile -- Apache hasn't forked yet, so pid is wrong
3067 unlink $Global::PIDfile;
3068 print server_start_message("Interchange server started (%s)\n", 1);
3070 { level => 'info' },
3071 Vend::Server::server_start_message('START server (%s)', 1),
3074 # all done; now wait for Apache to call Vend::ModPerl::handler
3078 if ($Global::Windows || $Global::DEBUG) {
3079 my $running = grab_pid($pidh);
3082 "The Interchange server is already running (process id %s)\n",
3088 print server_start_message("Interchange server started (%s) (%s)\n");
3089 $next = server_both();
3093 fcntl($pidh, F_SETFD, 0)
3095 "Can't fcntl close-on-exec flag for '%s': %s\n",
3096 $Global::PIDfile, $!,
3105 elsif (not defined $pid1) {
3107 print "Can't fork: $!\n";
3116 elsif (not defined $pid2) {
3117 print "child 1 can't fork: $!\n";
3122 #::logDebug("getting ready to sleep ...");
3123 sleep 1 until &$ppidsub == 1;
3124 #::logDebug("slept ...");
3126 my $running = grab_pid($pidh);
3129 "The Interchange server is already running (process id %s)\n",
3137 fcntl($pidh, F_SETFD, 1)
3138 or die "Can't fcntl close-on-exec flag for '$Global::PIDfile': $!\n";
3140 $next = server_both();
3143 opendir(RUNDIR, $Global::RunDir)
3144 or die "Couldn't open directory $Global::RunDir: $!\n";
3145 unlink $Global::PIDfile;
3152 # Set the process name ($0) according to MV_DOLLAR_ZERO and a status indicator.
3153 sub set_process_name {
3155 my $base = $Global::Variable->{MV_DOLLAR_ZERO};
3157 # Setting MV_DOLLAR_ZERO to 1 should do the same thing as not setting it for
3158 # backwards compatibility.
3159 $base = 'interchange' if !$base or $base eq '1';
3161 if (defined $status) {
3162 $0 = "$base: $status";
3171 # Disconnect child process from any dangling attachments to parent process.
3172 # Named after similar mod_perl routine.
3173 sub cleanup_for_exec {
3174 # Release any open sockets
3175 %fh_map = %vec_map = %s_vec_map = %s_fh_map = %ipc_socket = %unix_socket
3178 # Close filehandles except for STDERR, used for debug log
3181 open STDIN, '<', '/dev/null';
3182 open STDOUT, '>>', '/dev/null';
3187 sub sever_database {
3188 # Keep connection closings on the client from closing the
3189 # database server, too.
3190 child_process_dbi_prep();
3192 # Clear any cached DBI handles
3195 # Prep new database connections for severed server
3196 Vend::Data::open_database(1);
3197 while (my ($db, $db_ref) = each %Vend::Database) {
3198 delete $Vend::Interpolate::Db{$db};
3199 $db_ref->close_table;
3200 undef $db_ref->[$Vend::Table::DBI::DBI];
3206 sub child_process_dbi_prep {
3207 # Because all clients with a common database connection will share
3208 # the same db server, we want the child process not to destroy the
3209 # database server when it disconnects.
3211 my %d = DBI->installed_drivers;
3212 for my $h (values %d) {
3213 $_->{InactiveDestroy} = 1
3214 for grep { defined } @{ $h->{ChildHandles} };
3219 'WARNING - error setting all DBI handles to InactiveDestroy: %s',