serial-console 7.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  1. #!/usr/bin/perl -w
  2. =head1 NAME
  3. serial-console
  4. =head1 SYNOPSIS
  5. serial-console [options]
  6. Options:
  7. -h,--help Display brief help message
  8. -v,--verbose Increase verbosity
  9. -q,--quiet Decrease verbosity
  10. -l,--log FILE Log output to file
  11. -r,--rcfile FILE Modify specified bochsrc file
  12. =head1 DESCRIPTION
  13. C<serial-console> provides a virtual serial console for use with
  14. Bochs. Running C<serial-console> creates a pseudo-tty. The master
  15. side of this pty is made available to the user for interaction; the
  16. slave device is written to the Bochs configuration file
  17. (C<bochsrc.txt>) for use by a subsequent Bochs session.
  18. =head1 EXAMPLES
  19. =over 4
  20. =item C<serial-console>
  21. Create a virtual serial console for Bochs, modify C<bochsrc.txt>
  22. appropriately.
  23. =item C<serial-console -r ../.bochsrc -l serial.log>
  24. Create a virtual serial console for Bochs, modify C<../.bochsrc>
  25. appropriately, log output to C<serial.log>.
  26. =back
  27. =head1 INVOCATION
  28. Before starting Bochs, run C<serial-console> in a different session
  29. (e.g. a different xterm window). When you subsequently start Bochs,
  30. anything that the emulated machine writes to its serial port will
  31. appear in the window running C<serial-console>, and anything typed in
  32. the C<serial-console> window will arrive on the emulated machine's
  33. serial port.
  34. You do B<not> need to rerun C<serial-console> afresh for each Bochs
  35. session.
  36. =head1 OPTIONS
  37. =over 4
  38. =item B<-l,--log FILE>
  39. Log all output (i.e. everything that is printed in the
  40. C<serial-console> window) to the specified file.
  41. =item B<-r,--rcfile FILE>
  42. Modify the specified bochsrc file. The file will be updated to
  43. contain the path to the slave side of the psuedo tty that we create.
  44. The original file will be restored when C<serial-console> exits. The
  45. default is to modify the file C<bochsrc.txt> in the current directory.
  46. To avoid modifying any bochsrc file, use C<--norcfile>.
  47. =back
  48. =cut
  49. use IO::Pty;
  50. use IO::Select;
  51. use File::Spec::Functions qw ( :ALL );
  52. use Getopt::Long;
  53. use Pod::Usage;
  54. use POSIX qw ( :termios_h );
  55. use strict;
  56. use warnings;
  57. my $o;
  58. my $restore_file = {};
  59. my $restore_termios;
  60. use constant BLOCKSIZE => 8192;
  61. ##############################################################################
  62. #
  63. # Parse command line options into options hash ($o)
  64. #
  65. # $o = parse_opts();
  66. sub parse_opts {
  67. # $o is the hash that will hold the options
  68. my $o = {
  69. verbosity => 1,
  70. rcfile => 'bochsrc.txt',
  71. };
  72. # Special handlers for some options
  73. my $opt_handlers = {
  74. verbose => sub { $o->{verbosity}++; },
  75. quiet => sub { $o->{verbosity}--; },
  76. help => sub { pod2usage(1); },
  77. norcfile => sub { delete $o->{rcfile}; },
  78. };
  79. # Merge handlers into main options hash (so that Getopt::Long can find them)
  80. $o->{$_} = $opt_handlers->{$_} foreach keys %$opt_handlers;
  81. # Option specifiers for Getopt::Long
  82. my @optspec = ( 'help|h|?',
  83. 'quiet|q+',
  84. 'verbose|v+',
  85. 'log|l=s',
  86. 'rcfile|r=s',
  87. 'norcfile',
  88. );
  89. # Do option parsing
  90. Getopt::Long::Configure ( 'bundling' );
  91. pod2usage("Error parsing command-line options") unless GetOptions (
  92. $o, @optspec );
  93. # Clean up $o by removing the handlers
  94. delete $o->{$_} foreach keys %$opt_handlers;
  95. return $o;
  96. }
  97. ##############################################################################
  98. #
  99. # Modify bochsrc file
  100. sub patch_bochsrc {
  101. my $active = shift;
  102. my $pty = shift;
  103. # Rename active file to backup file
  104. ( my $vol, my $dir, my $file ) = splitpath ( $active );
  105. $file = '.'.$file.".serial-console";
  106. my $backup = catpath ( $vol, $dir, $file );
  107. rename $active, $backup
  108. or die "Could not back up $active to $backup: $!\n";
  109. # Derive line to be inserted
  110. my $patch = "com1: enabled=1, mode=term, dev=$pty\n";
  111. # Modify file
  112. open my $old, "<$backup" or die "Could not open $backup: $!\n";
  113. open my $new, ">$active" or die "Could not open $active: $!\n";
  114. print $new <<"EOF";
  115. ##################################################
  116. #
  117. # This file has been modified by serial-console.
  118. #
  119. # Do not modify this file; it will be erased when
  120. # serial-console (pid $$) exits and will be
  121. # replaced with the backup copy held in
  122. # $backup.
  123. #
  124. ##################################################
  125. EOF
  126. my $patched;
  127. while ( my $line = <$old> ) {
  128. if ( $line =~ /^\s*\#?\s*com1:\s*\S/ ) {
  129. if ( ! $patched ) {
  130. $line = $patch;
  131. $patched = 1;
  132. } else {
  133. $line = '# '.$line unless $line =~ /^\s*\#/;
  134. }
  135. }
  136. print $new $line;
  137. }
  138. print $new $patch unless $patched;
  139. close $old;
  140. close $new;
  141. return $backup;
  142. }
  143. ##############################################################################
  144. #
  145. # Attach/detach message printing and terminal settings
  146. sub bochs_attached {
  147. print STDERR "Bochs attached.\n\n\n"
  148. if $o->{verbosity} >= 1;
  149. }
  150. sub bochs_detached {
  151. print STDERR "\n\nWaiting for bochs to attach...\n"
  152. if $o->{verbosity} >= 1;
  153. }
  154. ##############################################################################
  155. #
  156. # Main program
  157. $o = parse_opts();
  158. pod2usage(1) if @ARGV;
  159. # Catch signals
  160. my $sigdie = sub { die "Exiting via signal\n"; };
  161. $SIG{INT} = $sigdie;
  162. # Create Pty, close slave side
  163. my $pty = IO::Pty->new();
  164. $pty->close_slave();
  165. $pty->set_raw();
  166. print STDERR "Slave pty is ".$pty->ttyname."\n" if $o->{verbosity} >= 1;
  167. # Open logfile
  168. my $log;
  169. if ( $o->{log} ) {
  170. open $log, ">$o->{log}" or die "Could not open $o->{log}: $!\n";
  171. }
  172. # Set up terminal
  173. my $termios;
  174. if ( -t STDIN ) {
  175. $termios = POSIX::Termios->new;
  176. $restore_termios = POSIX::Termios->new;
  177. $termios->getattr ( fileno(STDIN) );
  178. $restore_termios->getattr ( fileno(STDIN) );
  179. $termios->setlflag ( $termios->getlflag & ~(ICANON) & ~(ECHO) );
  180. $termios->setiflag ( $termios->getiflag & ~(ICRNL) );
  181. $termios->setattr ( fileno(STDIN), TCSANOW );
  182. }
  183. # Modify bochsrc file
  184. $restore_file = { $o->{rcfile} =>
  185. patch_bochsrc ( $o->{rcfile}, $pty->ttyname ) }
  186. if $o->{rcfile};
  187. # Start character shunt
  188. my $attached = 1;
  189. my $select = IO::Select->new ( \*STDIN, $pty );
  190. while ( 1 ) {
  191. my %can_read = map { $_ => 1 }
  192. $select->can_read ( $attached ? undef : 1 );
  193. if ( $can_read{\*STDIN} ) {
  194. sysread ( STDIN, my $data, BLOCKSIZE )
  195. or die "Cannot read from STDIN: $!\n";
  196. $pty->syswrite ( $data );
  197. }
  198. if ( $can_read{$pty} ) {
  199. if ( $pty->sysread ( my $data, BLOCKSIZE ) ) {
  200. # Actual data available
  201. bochs_attached() if $attached == 0;
  202. $attached = 1;
  203. syswrite ( STDOUT, $data );
  204. $log->syswrite ( $data ) if $log;
  205. } else {
  206. # No data available but select() says we can read. This almost
  207. # certainly indicates that nothing is attached to the slave.
  208. bochs_detached() if $attached == 1;
  209. $attached = 0;
  210. sleep ( 1 );
  211. }
  212. } else {
  213. bochs_attached() if $attached == 0;
  214. $attached = 1;
  215. }
  216. }
  217. END {
  218. # Restore bochsrc file if applicable
  219. if ( ( my $orig_file, my $backup_file ) = %$restore_file ) {
  220. unlink $orig_file;
  221. rename $backup_file, $orig_file;
  222. }
  223. # Restore terminal settings if applicable
  224. if ( $restore_termios ) {
  225. $restore_termios->setattr ( fileno(STDIN), TCSANOW );
  226. }
  227. }