| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278 | 
							- #!/usr/bin/perl -w
 - 
 - =head1 NAME
 - 
 - serial-console
 - 
 - =head1 SYNOPSIS
 - 
 - serial-console [options]
 - 
 - Options:
 - 
 -     -h,--help         Display brief help message
 -     -v,--verbose      Increase verbosity
 -     -q,--quiet        Decrease verbosity
 -     -l,--log FILE     Log output to file
 -     -r,--rcfile	FILE  Modify specified bochsrc file
 - 
 - =head1 DESCRIPTION
 - 
 - C<serial-console> provides a virtual serial console for use with
 - Bochs.  Running C<serial-console> creates a pseudo-tty.  The master
 - side of this pty is made available to the user for interaction; the
 - slave device is written to the Bochs configuration file
 - (C<bochsrc.txt>) for use by a subsequent Bochs session.
 - 
 - =head1 EXAMPLES
 - 
 - =over 4
 - 
 - =item C<serial-console>
 - 
 - Create a virtual serial console for Bochs, modify C<bochsrc.txt>
 - appropriately.
 - 
 - =item C<serial-console -r ../.bochsrc -l serial.log>
 - 
 - Create a virtual serial console for Bochs, modify C<../.bochsrc>
 - appropriately, log output to C<serial.log>.
 - 
 - =back
 - 
 - =head1 INVOCATION
 - 
 - Before starting Bochs, run C<serial-console> in a different session
 - (e.g. a different xterm window).  When you subsequently start Bochs,
 - anything that the emulated machine writes to its serial port will
 - appear in the window running C<serial-console>, and anything typed in
 - the C<serial-console> window will arrive on the emulated machine's
 - serial port.
 - 
 - You do B<not> need to rerun C<serial-console> afresh for each Bochs
 - session.
 - 
 - =head1 OPTIONS
 - 
 - =over 4
 - 
 - =item B<-l,--log FILE>
 - 
 - Log all output (i.e. everything that is printed in the
 - C<serial-console> window) to the specified file.
 - 
 - =item B<-r,--rcfile FILE>
 - 
 - Modify the specified bochsrc file.  The file will be updated to
 - contain the path to the slave side of the psuedo tty that we create.
 - The original file will be restored when C<serial-console> exits.  The
 - default is to modify the file C<bochsrc.txt> in the current directory.
 - 
 - To avoid modifying any bochsrc file, use C<--norcfile>.
 - 
 - =back
 - 
 - =cut
 - 
 - use IO::Pty;
 - use IO::Select;
 - use File::Spec::Functions qw ( :ALL );
 - use Getopt::Long;
 - use Pod::Usage;
 - use POSIX qw ( :termios_h );
 - use strict;
 - use warnings;
 - 
 - my $o;
 - my $restore_file = {};
 - my $restore_termios;
 - use constant BLOCKSIZE => 8192;
 - 
 - ##############################################################################
 - #
 - # Parse command line options into options hash ($o)
 - #
 - # $o = parse_opts();
 - 
 - sub parse_opts {
 -   # $o is the hash that will hold the options
 -   my $o = {
 -     verbosity => 1,
 -     rcfile => 'bochsrc.txt',
 -   };
 -   # Special handlers for some options
 -   my $opt_handlers = {
 -     verbose => sub { $o->{verbosity}++; },
 -     quiet => sub { $o->{verbosity}--; },
 -     help => sub { pod2usage(1); },
 -     norcfile => sub { delete $o->{rcfile}; },
 -   };
 -   # Merge handlers into main options hash (so that Getopt::Long can find them)
 -   $o->{$_} = $opt_handlers->{$_} foreach keys %$opt_handlers;
 -   # Option specifiers for Getopt::Long
 -   my @optspec = ( 'help|h|?',
 -                   'quiet|q+',
 -                   'verbose|v+',
 - 		  'log|l=s',
 - 		  'rcfile|r=s',
 - 		  'norcfile',
 -                   );
 -   # Do option parsing
 -   Getopt::Long::Configure ( 'bundling' );
 -   pod2usage("Error parsing command-line options") unless GetOptions (
 -   $o, @optspec );
 -   # Clean up $o by removing the handlers
 -   delete $o->{$_} foreach keys %$opt_handlers;
 -   return $o;
 - }
 - 
 - ##############################################################################
 - #
 - # Modify bochsrc file
 - 
 - sub patch_bochsrc {
 -   my $active = shift;
 -   my $pty = shift;
 - 
 -   # Rename active file to backup file
 -   ( my $vol, my $dir, my $file ) = splitpath ( $active );
 -   $file = '.'.$file.".serial-console";
 -   my $backup = catpath ( $vol, $dir, $file );
 -   rename $active, $backup
 -       or die "Could not back up $active to $backup: $!\n";
 - 
 -   # Derive line to be inserted
 -   my $patch = "com1: enabled=1, mode=term, dev=$pty\n";
 - 
 -   # Modify file
 -   open my $old, "<$backup" or die "Could not open $backup: $!\n";
 -   open my $new, ">$active" or die "Could not open $active: $!\n";
 -   print $new <<"EOF";
 - ##################################################
 - #
 - # This file has been modified by serial-console.
 - #
 - # Do not modify this file; it will be erased when
 - # serial-console (pid $$) exits and will be
 - # replaced with the backup copy held in
 - # $backup.
 - #
 - ##################################################
 - 
 - 
 - EOF
 -   my $patched;
 -   while ( my $line = <$old> ) {
 -     if ( $line =~ /^\s*\#?\s*com1:\s*\S/ ) {
 -       if ( ! $patched ) {
 - 	$line = $patch;
 - 	$patched = 1;
 -       } else {
 - 	$line = '# '.$line unless $line =~ /^\s*\#/;
 -       }
 -     }
 -     print $new $line;
 -   }
 -   print $new $patch unless $patched;
 -   close $old;
 -   close $new;
 - 
 -   return $backup;
 - }
 - 
 - ##############################################################################
 - #
 - # Attach/detach message printing and terminal settings
 - 
 - sub bochs_attached {
 -   print STDERR "Bochs attached.\n\n\n"
 -       if $o->{verbosity} >= 1;
 - }
 - 
 - sub bochs_detached {
 -   print STDERR "\n\nWaiting for bochs to attach...\n"
 -       if $o->{verbosity} >= 1;
 - }
 - 
 - ##############################################################################
 - #
 - # Main program
 - 
 - $o = parse_opts();
 - pod2usage(1) if @ARGV;
 - 
 - # Catch signals
 - my $sigdie = sub { die "Exiting via signal\n"; };
 - $SIG{INT} = $sigdie;
 - 
 - # Create Pty, close slave side
 - my $pty = IO::Pty->new();
 - $pty->close_slave();
 - $pty->set_raw();
 - print STDERR "Slave pty is ".$pty->ttyname."\n" if $o->{verbosity} >= 1;
 - 
 - # Open logfile
 - my $log;
 - if ( $o->{log} ) {
 -   open $log, ">$o->{log}" or die "Could not open $o->{log}: $!\n";
 - }
 - 
 - # Set up terminal
 - my $termios;
 - if ( -t STDIN ) {
 -   $termios = POSIX::Termios->new;
 -   $restore_termios = POSIX::Termios->new;
 -   $termios->getattr ( fileno(STDIN) );
 -   $restore_termios->getattr ( fileno(STDIN) );
 -   $termios->setlflag ( $termios->getlflag &
 - 		       ~(ICANON) & ~(ECHO) );
 -   $termios->setattr ( fileno(STDIN), TCSANOW );
 - }
 - 
 - # Modify bochsrc file
 - $restore_file = { $o->{rcfile} =>
 - 		  patch_bochsrc ( $o->{rcfile}, $pty->ttyname ) }
 -     if $o->{rcfile};
 - 
 - # Start character shunt
 - my $attached = 1;
 - my $select = IO::Select->new ( \*STDIN, $pty );
 - while ( 1 ) {
 -   my %can_read = map { $_ => 1 }
 -   		     $select->can_read ( $attached ? undef : 1 );
 -   if ( $can_read{\*STDIN} ) {
 -     sysread ( STDIN, my $data, BLOCKSIZE )
 - 	or die "Cannot read from STDIN: $!\n";
 -     $pty->syswrite ( $data );
 -   }
 -   if ( $can_read{$pty} ) {
 -     if ( $pty->sysread ( my $data, BLOCKSIZE ) ) {
 -       # Actual data available
 -       bochs_attached() if $attached == 0;
 -       $attached = 1;
 -       syswrite ( STDOUT, $data );
 -       $log->syswrite ( $data ) if $log;
 -     } else {
 -       # No data available but select() says we can read.  This almost
 -       # certainly indicates that nothing is attached to the slave.
 -       bochs_detached() if $attached == 1;
 -       $attached = 0;
 -       sleep ( 1 );
 -     }
 -   } else {
 -     bochs_attached() if $attached == 0;
 -     $attached = 1;
 -   }
 - }
 - 
 - END {
 -   # Restore bochsrc file if applicable
 -   if ( ( my $orig_file, my $backup_file ) = %$restore_file ) {
 -     unlink $orig_file;
 -     rename $backup_file, $orig_file;
 -   }
 -   # Restore terminal settings if applicable
 -   if ( $restore_termios ) {
 -     $restore_termios->setattr ( fileno(STDIN), TCSANOW );
 -   }
 - }
 
 
  |