#!/usr/bin/perl -w =head1 NAME screen-server-backend - TODO =head1 SYNOPSIS screen-server-backend [options] --help TODO --debug --name <screen name> TODO --daemonize (Default) --no-daemonize =head1 DESCRIPTION TODO =cut use strict; # Seemingly socat doesn't seem to transmit STDERR automatically so # re-route it myself. # *STDERR = *STDOUT; # Option handling # use Getopt::Long (); Getopt::Long::GetOptions( help => \&pod2usage, debug => \ my $debug, name => \ my $screen_name, 'daemonize!' => \ my $daemonize, ) or pod2usage(); # Daemonization by default # if ( $daemonize ) { fork && exit; fork && exit; umask 0; chdir '/'; } # Automatically clean up zombie children # $SIG{CHLD} = 'IGNORE'; require File::Temp; my ( $log_fh, $log_fn ) = File::Temp::tempfile(); my $top_pid = $$; my $child_pid = fork; if ( ! defined $child_pid ) { # Oops, failure. Is there a fork bomb going on? # die "Can't fork: $!"; } elsif ( 0 == $child_pid ) { # Double-fork so our parent can reap us immediately and the # exec() below will be reaped by init. # fork && exit; fork && exit; # Wait until either the parent socat has exited or it has logged # the right thing. # require Time::HiRes; Time::HiRes::sleep( 0.05 ) while ! -s $log_fh || kill 0, $top_pid; # Read the PTY from the socat logfile and have /usr/bin/screen # start a window against it. If all goes well, we'll exec() right # out of this loop and never finish it. # while ( my $l = <$log_fh> ) { # socat under `-d -d' flags will print a line like the # following. In the parent, socat is configured to write its # debugging log to a $log_fn which we have a handle to in # $log_fh. # # PTY is /dev/pts/4 # if ( $l =~ m{PTY is (/dev/pts/\d+)} ) { my $pty = $1; # Clean up the log file. We don't need it anymore. socat # itself may keep it open however. This is unfortunate. # truncate $log_fh, 0; close $log_fh; unlink $log_fn; # exec screen to open a new window using the PTY allocated # by socat. # my @screen_opts; if ( $screen_name ) { push @screen_opts, '-S' => $screen_name; } my @cmd = ( 'screen', @screen_opts, '-X' => 'screen', $pty ); if ( $debug ) { print STDERR "exec( @cmd )\n"; } exec @cmd; } } # This is an error condition. # # I was unable to read any "PTY is /dev/pts/#" lines in the # socat log so something has gone wrong. Kill off the socat if # it's still present. # # TODO: check for actual death, wait between signals close $log_fh; unlink $log_fn; kill -2, $top_pid; # SIGINT kill -15, $top_pid; # SIGTERM kill -9, $top_pid; # SIGKILL } elsif ( $child_pid ) { # Tie our input to a new PTY and write the PTY's name to a log # file so the child can pick it up. # # I request two levels of -d debug to get the a message "PTY is # /dev/pts/#" in the log file at -lf. The child process is going # to delete this log file. # my @cmd = ( 'socat', '-d', '-d', "-lf$log_fn", '-' => 'PTY', ); if ( $debug ) { print STDERR "exec( @cmd )\n"; } exec @cmd; } sub pod2usage { require Pod::Usage; goto &Pod::Usage::pod2usage; }