App-FuguVM
view release on metacpan or search on metacpan
lib/App/FuguVM/Autoinstall.pm view on Meta::CPAN
# $self->port:
# Return the recorded port, or undef.
sub port ($self)
{
return $self->{store}->get('autoinstall_port');
}
# $self->is_running:
# Report whether the child is alive. The check reaps first, so a
# child that became a zombie reads as stopped.
sub is_running ($self)
{
return $self->{pidfile}->is_running ? 1 : 0;
}
# $self->guest_url:
# Return the response-file URL as the guest reaches it, through
# the QEMU gateway. The gateway routes the request to the
# loopback listener of the host, so no port forward is needed.
sub guest_url ($self)
{
my $port = $self->port;
return if !defined $port;
return
'http://'
. App::FuguVM::Proxy::HOST_GATEWAY()
. ":$port"
. RESPONSE_PATH;
}
# $self->start:
# Take a free port, spawn the child, record the PID and the
# port, and wait until the port answers. Return the port, or
# undef with the reason in error(). A responder that already
# runs returns its port and starts nothing.
sub start ($self)
{
$self->{error} = undef;
return $self->port if $self->is_running;
if ( !defined $self->{file} || !-r $self->{file} ) {
$self->{error} = 'the response file is not readable: '
. ( $self->path // '(none)' );
return;
}
my $port = $self->_find_free_port;
unless ( defined $port ) {
$self->{error} = sprintf 'no free port in %d-%d', @{ +PORTS };
return;
}
my $child = ref $self;
my $result = Fugu::Process->spawn_perl(
code => "use $child; $child->run_child(\@ARGV)",
args => [ $port, $self->{file}, $self->{proxy_url} // '' ],
daemonize => 1,
stdout => $self->{logfile},
stderr => $self->{logfile},
);
unless ( $result->{success} ) {
$self->{error} = "cannot start the responder: $result->{error}";
return;
}
$self->{pidfile}->write_pid( $result->{pid} );
$self->{store}->set( autoinstall_port => $port );
unless ( $self->_wait_ready ) {
$self->{error} = 'the responder did not take connections';
$self->stop;
return;
}
return $port;
}
# $self->stop:
# Stop the child and forget the port. The method returns 1.
sub stop ($self)
{
my $pid = $self->{pidfile}->read_pid;
Fugu::Process->terminate( $pid, grace_period => 5 )
if defined $pid;
$self->{pidfile}->remove;
$self->{store}->delete('autoinstall_port');
return 1;
}
# $class->run_child($port, $file, $proxy_url):
# The entry point of the spawned child. The child reads the
# response file, renders it, and serves it until a SIGTERM. It
# logs each request line, and it never logs the file content.
sub run_child ( $class, $port, $file, $proxy_url )
{
my $log = Fugu::Log->new( mode => 'stderr', level => 'debug' );
my $bytes = Fugu::File->read($file);
die "Cannot read the response file: $file\n" if !defined $bytes;
my $body = $class->render( $bytes, $proxy_url );
my $listener = IO::Socket::INET->new(
LocalAddr => BIND_ADDRESS,
LocalPort => $port,
Proto => 'tcp',
ReuseAddr => 1,
Listen => 5,
) or die 'Cannot listen on ' . BIND_ADDRESS . ":$port: $!\n";
$log->info( 'Responder listening on %s:%d', BIND_ADDRESS, $port );
# A client can disconnect in the middle of an answer
local $SIG{PIPE} = 'IGNORE';
# The self-pipe makes the signal safe: the handler writes one
# byte, and the select loop notices it between requests instead
# of inside one.
require IO::Select;
pipe my $sig_read, my $sig_write or die "pipe: $!";
$sig_read->blocking(0);
$sig_write->blocking(0);
my $running = 1;
local $SIG{TERM} = sub {
$running = 0;
syswrite $sig_write, 'x', 1;
};
my $select = IO::Select->new( $listener, $sig_read );
while ($running) {
my @ready = $select->can_read;
last if !$running;
for my $fh (@ready) {
if ( $fh == $sig_read ) {
sysread $sig_read, my $drain, 100;
next;
}
my $client = $listener->accept or next;
_answer( $client, $body, $log );
close $client;
}
}
$log->info('Responder shutting down');
close $sig_read;
close $sig_write;
close $listener;
return 1;
}
# $class->render($bytes, $proxy_url):
( run in 0.749 second using v1.01-cache-2.11-cpan-5c0b1e786e0 )