File: //lib64/squid/helper-mux
#!/usr/bin/perl
use Getopt::Std;
use Data::Dumper;
use FileHandle;
use IPC::Open2;
use Pod::Usage;
=pod
=head1 NAME
helper-mux - Concurrency protocol multiplexer for Squid helpers
=head1 SYNOPSIS
B<helper-mux> helper-path [helper-options ...]
=head1 DESCRIPTION
B<helper-mux> purpose is to relieve some of the burden
B<squid> has when dealing with slow helpers. It does so by acting as a
middleman between B<squid> and the actual helpers, talking to B<squid> via
the multiplexed variant of the helper protocol and to the helpers
via the non-multiplexed variant.
Helpers are started on demand, and in theory the muxer can handle up to
1k helpers per instance. It is up to B<squid> to decide how many helpers
to start.
The helper can be controlled using various signals:
- SIGHUP: dump the state of all helpers to STDERR
=head1 OPTIONS
=over 8
=item B<helper-path>
Path to the helper being multiplexed.
=item B<helper-options>
Command line options for the helper being multiplexed.
=back
=head1 KNOWN ISSUES
B<helper-mux> knows nothing about the actual messages being passed around,
and as such cannot yet compensate for broken helpers.
It is not yet able to manage dying helpers.
=head1 COPYRIGHT
* Copyright (C) 1996-2021 The Squid Software Foundation and contributors
*
* Squid software is distributed under GPLv2+ license and includes
* contributions from numerous individuals and organizations.
* Please see the COPYING and CONTRIBUTORS files for details.
Copyright (C) Francesco Chemolli <kinkie@squid-cache.org>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
=cut
#mux-ed format: "slot_num non_muxed_request"
# options handling
my %opts=();
$Getopt::Std::STANDARD_HELP_VERSION=1;
getopts('h', \%opts) or die ("unrecognized options");
if (defined $opts{h}) {
HELP_MESSAGE();
exit 0;
}
my $actual_helper_cmd=join(" ",@ARGV);
# variables initialization
my %helpers=();
my $helpers_running = 0;
my $rvec='';
vec($rvec,0,1)=1; #stdin
my $nfound;
my ($rd,$wr,$cl);
# signal handlers
$SIG{'HUP'}=\&dump_state;
$SIG{'CHLD'}=\&reaper;
# TODO: signal handling for child dying
# main loop
$|=1;
while(1) {
print STDERR "selecting\n";
$nfound=select($rd=$rvec,undef,undef,undef);
#$nfound=select($rd=$rvec,undef,$cl=$rvec,undef);
print STDERR "nfound: $nfound\n";
if ($nfound == -1 ) {
print STDERR "error in select: $!\n";
if ($!{ERESTART} || $!{EAGAIN} || $!{EINTR}) {
next;
}
exit 1;
}
#print STDERR "cl: ", unpack("b*", $cl) ,"\n";
print STDERR "rd: ", unpack("b*", $rd) ,"\n";
# stdin is special
#if (vec($cl,0,1)==1) { #stdin was closed
# print STDERR "stdin closed\n";
# exit(0);
#}
if (vec($rd,0,1)==1) { #got stuff from stdin
#TODO: handle leftover buffers? I hope that 40kb are enough..
$nread=sysread(STDIN,$_,40960); # read 40kb
# clear the signal-bit, stdin is special
vec($rd,0,1)=0;
if ($nread==0) {
print STDERR "nothing read from stdin\n";
exit 0;
}
foreach $req (split("\n",$_ )) {
dispatch_request($req);
}
}
# find out if any filedesc was closed
if ($cl != 0) {
#TODO: better handle helper restart
print STDERR "helper crash?";
exit 1;
}
#TODO: is it possible to test the whole bitfield in one go?
# != won't work.
foreach $h (keys %helpers) {
my %hlp=%{$helpers{$h}};
#print STDERR "examining helper slot $h, fileno $hlp{fno}, filemask ", vec($rd,$hlp{fno},1) , "\n";
if (vec($rd,$hlp{fno},1)==1) {
#print STDERR "found\n";
handle_helper_response($h);
}
#no need to clear, it will be reset when iterating
}
}
sub dispatch_request {
my $line=$_[0];
my %h;
#print STDERR "dispatching request $_";
$line =~ /^(\d+) (.*)$/;
my $reqId=$1;
my $req=$2;
undef $h;
# Find a free helper
foreach $slot ( 1 .. ($helpers_running)) {
if (!defined($helpers{$slot}->{lastcmd})) {
$h = $helpers{$slot};
last;
}
}
# If none create one
if (!defined($h)) {
$helpers_running = $helpers_running + 1;
$helpers{$helpers_running}=init_subprocess();
$h = $helpers{$helpers_running};
# print STDERR "Now $helpers_running helpers running\n";
}
$wh=$h->{wh};
$rh=$h->{rh};
$h->{lastcmd}=$req;
$h->{reqId}=$reqId;
print $wh "$req\n";
}
# gets in a slot number having got some response.
# reads the response from the helper and sends it back to squid
# prints the response back
sub handle_helper_response {
my $h=$_[0];
my ($nread,$resp);
$nread=sysread($helpers{$h}->{rh},$resp,40960);
#print STDERR "got $resp from slot $h\n";
print $helpers{$h}->{reqId}, " ", $resp;
delete $helpers{$h}->{lastcmd};
}
# a subprocess is a hash with members:
# pid => $pid
# rh => read handle
# wh => write handle
# fno => file number of the read handle
# lastcmd => the command "in flight"
# a ref to such a hash is returned by this call
sub init_subprocess {
my %rv=();
my ($rh,$wh,$pid);
$pid=open2($rh,$wh,$actual_helper_cmd);
if ($pid == 0) {
die "Failed to fork helper process";
}
select($rh); $|=1;
select($wh); $|=1;
select(STDOUT);
$rv{rh}=$rh;
$rv{wh}=$wh;
$rv{pid}=$pid;
$rv{fno}=fileno($rh);
print STDERR "fileno is $rv{fno}\n";
vec($rvec,$rv{fno},1)=1;
return \%rv;
}
sub HELP_MESSAGE {
print STDERR <<EOF
$0 options:
-h this help message
arguments:
the actual helper executable and its arguments.
it's advisable to prefix it with "--" to avoid confusion
EOF
}
sub dump_state {
$SIG{'HUP'}=\&dump_state;
print STDERR "Helpers state:\n",Dumper(\%helpers),"\n";
}
# finds and returns the slot number of a helper, -1 if not found
# args: - key in helpers
# - value to look for
sub find_helper_slot {
my ($k,$v) = @_;
foreach (keys %helpers) {
return $_ if $helpers{$k}==$v;
}
return -1;
}
sub reaper {
my $child=wait;
print STDERR "child $child died\n";
$SIG{'CHLD'}=\&reaper;
$slot = find_helper_slot('pid',$child);
print STDERR "slot is $slot\n";
#TODO: find the died child, if it was mid-process through a request
# send a "BH" to squid and de-init its data-structs here
exit 1;
}