Initial commit of OpenSPARC T2 design and verification files.
[OpenSPARC-T2-DV] / tools / perl-5.8.0 / lib / site_perl / 5.8.0 / CommandTerm / ProgPty.pm
CommitLineData
86530b38
AT
1package CommandTerm::ProgPty;
2
3require 5.004;
4use strict;
5use Getopt::Long;
6use IO::Pty;
7use POSIX;
8
9use vars qw(
10 @ISA
11 $VERSION
12 $Verbose
13 $PrintFunc
14 $AUTOLOAD
15);
16
17$VERSION = '1.00';
18$Verbose = 3;
19$PrintFunc = undef;
20
21sub new {
22 my ($class, $progname, @opts) = @_;
23 my $this = {};
24
25 bless $this, $class;
26
27 $this->{'read_buf'} = '';
28 $this->{'alive'} = 1;
29 $this->{'prompt'} = 'rivet> ';
30 $this->{'response_pending'} = 0;
31
32 if( defined($PrintFunc) ) {
33 $this->{'printfunc'} = $PrintFunc;
34 } else {
35 $| = 1;
36 $this->{'printfunc'} = sub { $this->default_print(@_); };
37 }
38
39 $this->verbose($Verbose);
40 $this->print_status(8, "SETTING VERBOSE LEVEL TO $Verbose\n");
41
42 my $resolved_progname = which($progname);
43
44 unless( $resolved_progname ) {
45 $this->print_error(0, "Can't find '$progname', check your path.\n");
46 return undef;
47 }
48
49 my @prog_exec_opts = ($resolved_progname, @opts);
50
51 $this->install_error_handler(\&default_error_handler);
52 $this->reset_data_handler;
53
54 $this->{'pty'} = new IO::Pty;
55 $this->pty->autoflush(1);
56
57 if( $this->pty->IO::Pty::ttyname ) {
58 $this->print_status(8, "OPENED A PTY NAMED '", $this->pty->IO::Pty::ttyname, "'\n");
59 } else {
60 $this->print_error(0, "Couldn't get a ttyname for IO::Pty. Proceeding anyway\n");
61 }
62
63 # Parent
64 if( $this->{'pid'} = fork ) {
65 $this->{'fileno'} = $this->pty->fileno;
66 $this->print_status(8, "FORKED '$prog_exec_opts[0]'\n",
67 " PID ", $this->pid, "\n",
68 " FILENO ", $this->fileno, "\n");
69
70 # read data until a prompt
71 my $response = $this->wait_response;
72
73 return undef unless defined($response);
74
75 # Normal return point
76 return $this;
77
78 # Child
79 } elsif( defined($this->pid) ) {
80 POSIX::setsid or die;
81 my $tty = $this->pty->slave;
82
83 # Below are 6 lines of terminal wizardry turn out to really save the
84 # day for this whole module. If you set the terminal correctly, many
85 # problems simply disappear and things start working great. So what do
86 # these lines do? Here we go:
87 #
88 # 1) The part that says ~(ECHO | ECHOE | ECHOK | ECHONL) turns off all local
89 # echoing. Imagine your sitting at a keyboard and you type the command
90 # "cat -". The cat command reads your keystrokes and echoes them when
91 # you press return. But notice how you can see what you're typing as
92 # hit the keys. After you hit return, the cat echoes again everything
93 # you just entered and you see what you typed twice. The moral of the
94 # story is that cat relies on the terminal being in "ECHO" mode to allow
95 # the user to see what they're typing as they type it. Now imagine
96 # you're a process writing to the terminal of another process *AND* your
97 # stdin and stdout are tied to the same file handle. If the terminal
98 # were in echo mode, the first thing you'd ever read from the terminal
99 # is the exact thing that you sent to it. By turning off ECHO, we save
100 # ourselves the trouble of removing what we sent from the return data
101 # stream. If you ever looked at the original dc_perl by Steve Golson,
102 # you saw how he went to great effort to remove the first line he got
103 # back.
104 #
105 # 2) The part that says ~(ICANON) turns off canonical mode processing.
106 # When canonical mode is enabled, the terminal assembles the input
107 # characters into lines and also has a fixed line length. This presents
108 # two problems for Synopsys. The most obvious one is that our line
109 # length is limited by the length of the canonical mode input queue. For
110 # my box (Sun Solaris 2.6) that is 256 characters. The second
111 # reason is a little more esoteric. One of the features of Synopsys
112 # is that it only ties STDOUT back to itself. STDERR is left alone to
113 # allow users to print to it from their Tcl scripts, bypassing
114 # Synopsys. With this setup, if native Synopsys paging were enabled
115 # and activated (a paging command were issued), the terminal would hang.
116 # I don't really have a good explanation of why, but obviously something
117 # in the canonical processing is causing the terminal to hang. Even more
118 # mysterious is the fact that when STDERR is also tied back to
119 # $tty->fileno, the problem goes away, but the user loses the ability to
120 # bypass Synopsys by printing to STDERR. This is not a good trade.
121 #
122 # Well, almost no worries. When you disable canonical mode processing,
123 # the terminal won't deliver data to the reader unless a certain number
124 # of bytes have been received, EVEN if one of those bytes is a newline
125 # (since a newline loses it's meaning in non-canonical mode). Actually,
126 # I'm lying again because non-canonical mode really has 4 modes of
127 # operation and I'm just speaking about two of them. Anyway, on my box
128 # (Solaris 2.6) the default minimum number of bytes to receive before
129 # delivery is 4 (even that's a long story). That's no good. I want
130 # Synosys to read byte if I send a byte. Of course, I can setup the
131 # terminal anyway that I want. So I set the magical VMIN to 1 and the
132 # VTIME to 0 which tells the terminal to deliver a mininum of 1 bytes to
133 # Synopsys every time it tries to read. If nothing is available to read,
134 # the Synopsys will block. I'm assuming that Synopsys was written to
135 # block on a read when nothing is available so that's ok.
136 #
137 # So life is good. I don't have to re-extract the data I sent. I don't have
138 # line length limit. I can leave STDERR alone and still not care whether
139 # the user enables native paging. Actually, It's still recommended that
140 # native paging be disabled because I still see the occasional hiccup that
141 # I can't explain.
142 my $termios = POSIX::Termios->new;
143 $termios->getattr($tty->fileno) or die;
144 $termios->setlflag($termios->getlflag & ~(ECHO | ECHOE | ECHOK | ECHONL | ICANON));
145 $termios->setcc(VMIN, 1);
146 $termios->setcc(VTIME, 0);
147 $termios->setattr($tty->fileno, TCSANOW) or die;
148
149 close $this->pty;
150 close STDIN;
151 close STDOUT;
152 open(STDIN, "<&" . $tty->fileno) or die "Child Error: Couldn't re-open STDIN: $!";
153 open(STDOUT, ">&" . $tty->fileno) or die "Child Error: Couldn't re-open STDOUT: $!";
154
155 exec(@prog_exec_opts);
156
157 # Error
158 } else {
159 $this->print_error(0, "FORK FAILED: $!\n");
160 return undef;
161 }
162}
163
164sub DESTROY {}
165
166sub AUTOLOAD {
167 my ($this, @opts) = @_;
168 my $method = $AUTOLOAD;
169
170 $method =~ s/.*:://;
171
172 return $this->print_error(1, "$method @opts\n");
173}
174
175sub default_error_handler {
176 my ($this, $error_type) = @_;
177
178 if( $error_type == 1 ) {
179 if( $this->verbose < 3 ) {
180 $this->print_error(1, "Error with the command '",
181 $this->current_command, "'\n\n",
182 $this->output_string);
183 }
184 } elsif( $error_type == -1 ) {
185 $this->print_error(0, "Fatal error, exiting.\n");
186 exit;
187 }
188}
189
190sub perl_list2tcl_list {
191 my $this = shift;
192
193 return (not defined($_[0]) or @_ < 1) ? '""' : return "[list @_]";
194}
195
196sub get_options {
197 my ($this, $argv_ref, $config_ref, @opts) = (@_);
198
199 local (@ARGV) = @$argv_ref;
200 Getopt::Long::config(@$config_ref) if @$config_ref > 0;
201 my $ret = GetOptions(@opts);
202 Getopt::Long::config('default');
203 @$argv_ref = @ARGV;
204 return $ret;
205}
206
207sub alive {
208 my $this = shift;
209
210 return $this->{'alive'};
211}
212
213sub verbose {
214 my ($this, $verbose) = @_;
215
216 my $old_verbose = $this->{'verbose'};
217
218 $this->{'verbose'} = $verbose if defined($verbose);
219
220 return $old_verbose;
221}
222
223sub print_data {
224 my ($this, $verbose, @what) = @_;
225
226 &{ $this->{'printfunc'} }('data', @what) if $this->verbose >= $verbose;
227}
228
229sub print_status {
230 my ($this, $verbose, @what) = @_;
231
232 &{ $this->{'printfunc'} }('status', @what) if $this->verbose >= $verbose;
233}
234
235sub print_error {
236 my ($this, $verbose, @what) = @_;
237
238 &{ $this->{'printfunc'} }('error', @what) if $this->verbose >= $verbose;
239}
240
241sub default_print {
242 my ($this, $type, @what) = @_;
243
244 print STDOUT @what;
245}
246
247sub reset_data_handler {
248 my ($this) = @_;
249
250 $this->{'data_handler'} = undef;
251}
252
253sub install_print_handler {
254 my ($this, $handler) = @_;
255
256 unless( defined($handler) and ref($handler) eq 'CODE' ) {
257 $this->print_error(1, "Argument to install_print_handler must be a CODE ref.\n");
258 return;
259 }
260
261 $this->{'printfunc'} = $handler;
262}
263
264sub install_data_handler {
265 my ($this, $handler) = @_;
266
267 unless( defined($handler) and ref($handler) eq 'CODE' ) {
268 $this->print_error(1, "Argument to install_data_handler must be a CODE ref.\n");
269 return;
270 }
271
272 $this->{'data_handler'} = $handler;
273}
274
275sub install_error_handler {
276 my ($this, $handler) = @_;
277
278 unless( defined($handler) and ref($handler) eq 'CODE' ) {
279 $this->print_error(1, "Argument to install_error_handler must be a CODE ref.\n");
280 return;
281 }
282
283 $this->{'error_handler'} = $handler;
284}
285
286sub current_command {
287 my $this = shift;
288
289 return $this->{'current_command'};
290}
291
292sub output_lines {
293 my $this = shift;
294
295 return @{ $this->{'output_lines'} };
296}
297
298sub output_lastline {
299 my $this = shift;
300
301 return $this->{'output_lastline'};
302}
303
304sub output_string {
305 my $this = shift;
306
307 return $this->{'output_string'};
308}
309
310sub run {
311 my ($this, $command, $verbose, $update) = @_;
312
313 unless( defined($command) ) {
314 $this->print_error(1, "Argument to run ommitted.\n");
315 return undef;
316 }
317
318 $this->{'current_command'} = $command
319 unless( defined($update) and $update == 0 );
320
321 my $old_verbose = $this->verbose;
322
323 $this->verbose($verbose)
324 if defined($verbose) and ($this->verbose < 4 or $verbose == 0);
325
326 $this->write_data("$command\n");
327
328 my $retval = $this->wait_response($update);
329
330 # It doesn't hurt to restore, even if we didn't change it.
331 $this->verbose($old_verbose);
332
333 return $retval;
334}
335
336sub _output_lines {
337 my $this = shift;
338
339 return split /\n/, $this->{'current_response'};
340}
341
342sub _output_lastline {
343 my $this = shift;
344
345 my @output_lines = split /\n/, $this->{'current_response'};
346 return $output_lines[ $#output_lines ];
347}
348
349sub fileno {
350 my $this = shift;
351
352 return $this->{'fileno'};
353}
354
355sub pid {
356 my $this = shift;
357
358 return $this->{'pid'};
359}
360
361sub pty {
362 my $this = shift;
363
364 return $this->{'pty'};
365}
366
367sub write_data {
368 my ($this, $data) = @_;
369
370 return unless defined($data) and length($data) > 0;
371
372 $this->print_status(4, "$data");
373 $this->print_status(8, "WRITING ", length($data), " BYTES TO PTY\n");
374 $this->print_status(10, "WRITE DATA: '", _make_readable($data), "'\n");
375
376 print { $this->pty } $data;
377}
378
379sub readable {
380 my $this = shift;
381 my $fd_set;
382
383 vec($fd_set, $this->fileno, 1) = 1;
384
385 return select($fd_set, undef, undef, 0);
386}
387
388sub wait_response {
389 my ($this, $update) = @_;
390
391 if( $this->{'response_pending'} == 0 ) {
392
393 $this->{'current_response'} = '';
394 $this->{'partial_response'} = '';
395
396 my ($fd_set, $global_fd) = ('', '');
397
398 vec($global_fd, $this->fileno, 1) = 1;
399
400 my $done = 0;
401
402 while( not $done ) {
403
404 $this->print_status(8, "BLOCKING ON SELECT\n");
405
406 my $s = select($fd_set = $global_fd, undef, undef, undef);
407
408 $this->print_status(8, "SELECT RETURNS ", defined($s) ? $s : "UNDEFINED", "\n");
409
410 if( $s < 0 ) {
411 $this->print_error(8, "SELECT ERROR: $!\n");
412 &{ $this->{'error_handler'} }($this, 0);
413 return 0;
414 }
415
416 if( vec($fd_set, $this->fileno, 1) ) {
417 $done = $this->read_data;
418 return undef unless defined($done);
419 }
420 }
421 }
422
423 $this->print_status(10, "RESPONSE: '", _make_readable($this->{'current_response'}), "'\n");
424
425 $update = 1 unless defined($update);
426
427 if( $update ) {
428 $this->{'output_string'} = $this->{'current_response'};
429 $this->{'output_lines'} = [ split /\n/, $this->{'current_response'} ];
430 $this->{'output_lastline'} =
431 ${ $this->{'output_lines'} }[ $#{ $this->{'output_lines'} } ];
432 }
433
434 $this->{'response_pending'} = 0;
435
436 my $retval = ($this->{'current_response'} =~ /^Error: /m) ? 0 : 1;
437
438 &{ $this->{'error_handler'} }($this, 1) if $retval == 0;
439
440 return $retval;
441}
442
443sub read_data {
444 my $this = shift;
445
446 $this->print_status(8, "BLOCKING ON SYSREAD\n");
447
448 my $nbytes =
449 sysread($this->pty, $this->{'read_buf'}, 2048, length($this->{'read_buf'}));
450
451 $this->print_status(8, "SYSREAD RETURNS: ", defined($nbytes) ? $nbytes : "UNDEFINED", "\n");
452
453 # Error from sysread
454 if( not defined($nbytes) ) {
455 # check to see if prog is still alive
456 my $pid = waitpid($this->pid, WNOHANG);
457
458 # Normal termination.
459 if( $pid = $this->pid ) {
460 $this->{'alive'} = 0;
461 $this->print_status(8, "WAITPID RETURNS. PID: $pid, NORMAL EXIT STATUS.\n");
462 return undef;
463
464 # Still alive!
465 } elsif( $pid == 0 ) {
466 return 0;
467
468 # Yikes! Unexpected error.
469 } else {
470 $this->print_error(0, "waitpid on pid($this->{'pid'}) returned $pid: $!.\n");
471 $this->{'alive'} = 0;
472 &{ $this->{'error_handler'} }($this, -1);
473 return undef;
474 }
475
476 # EOF from sysread
477 } elsif( $nbytes == 0 ) {
478 $this->print_status(8, "GOT EOF FROM SYSREAD.\n");
479 $this->{'alive'} = 0;
480 return undef;
481
482 # Data from sysread
483 } else {
484 $this->print_status(10, "RAW DATA: '", _make_readable($this->{'read_buf'}), "'\n");
485
486 if( defined($this->{'data_handler'}) ) {
487 $this->{'partial_response'} .= $this->{'read_buf'};
488 $this->{'partial_response'} =~ s/(\r|\e.{0,2})//mg;
489 my @partial_lines = split /\n/, $this->{'partial_response'};
490 if( $this->{'partial_response'} =~ /\n$/ ) {
491 $this->{'partial_response'} = '';
492 } else {
493 $this->{'partial_response'} = pop @partial_lines;
494 }
495 &{ $this->{'data_handler'} }($this, @partial_lines);
496 }
497
498 my $before;
499
500 # The data read contains a prompt. Process all the prompts. Multiple prompts
501 # will be in the data when the user hits Ctrl-C.
502 if( $this->{'read_buf'} =~ /^$this->{'prompt'}/m ) {
503 while( $this->{'read_buf'} =~ /^$this->{'prompt'}/m ) {
504 $this->print_status(8, "MATCHED\n",
505 "BEFORE: '", _make_readable($`), "'\n",
506 "PROMPT: '", _make_readable($&), "'\n",
507 "AFTER: '", _make_readable($'), "'\n");
508 $this->{'read_buf'} = $';
509 $before = $`;
510 $before =~ s/(\r|\e.{0,2})//mg;
511 $this->print_data(3, $before);
512 $this->{'current_response'} .= $before;
513 }
514 $this->{'response_pending'} = 1;
515 return 1;
516
517 # The pager string is in the data. If the termios stuff is functioning
518 # properly, I should be able to send a space to advance the pager.
519 } elsif( $this->{'read_buf'} =~ /\e\[7m--More--\e\[m/ ) {
520 $this->print_status(8, "MATCHED THE PAGER STRING\n",
521 "SENDING A SPACE TO ADVANCE THE PAGER.\n",
522 "BEFORE: '", _make_readable($`), "'\n",
523 "PROMPT: '", _make_readable($&), "'\n",
524 "AFTER: '", _make_readable($'), "'\n");
525 $before = $`;
526 $before =~ s/(\r|\e.{0,2})//mg;
527 $this->print_data(3, $before);
528 $this->{'$current_response'} .= $before;
529 $this->write_data(' ');
530 $this->{'read_buf'} = '';
531 return 0;
532
533 # Multiline command detected - this is by no means full proof.
534 # People shouldn't be entering mlc, just return so they don't hang.
535 } elsif( $this->{'read_buf'} =~ /^(\? )+/ and $this->{'current_response'} eq '' ) {
536 $this->print_status(3, "Multiline command detected.\n");
537 $this->{'read_buf'} = '';
538 return 1;
539
540 # No prompt, just data.
541 } else {
542 $this->print_status(9, "NOMATCH\n'", _make_readable($this->{'read_buf'}), "'\n");
543 $before = $this->{'read_buf'};
544 $before =~ s/(\r|\e.{0,2})//mg;
545 $this->print_data(3, $before);
546 $this->{'current_response'} .= $before;
547 $this->{'read_buf'} = '';
548 return 0;
549 }
550 }
551}
552
553#
554# Private local subroutines that aren't methods are below. Only used by this
555# module.
556#
557
558sub _make_readable {
559 local $_ = shift;
560 return '' unless defined $_;
561 s/\\/\\\\/g;
562 s/\n/\\n/g;
563 s/\r/\\r/g;
564 s/\e/\\e/g;
565 s/\t/\\t/g;
566 s/\0/\\0/g;
567 s/\f/\\f/g;
568 s/([\000-\037\177])/'^' . chr(ord($1)^64)/eg;
569 s/([\200-\377])/sprintf("\\%3o", ord($1))/eg;
570 return $_;
571}
572
573sub which {
574 my $file = shift;
575 my @dirs;
576
577 if( $file =~ m(^/) ) {
578 if( -f $file and -x _ ) {
579 return $file;
580 } else {
581 return undef;
582 }
583 } elsif( $file =~ m(/) ) {
584 @dirs = (POSIX::getcwd);
585 } else {
586 @dirs = (split ':', $ENV{'PATH'});
587 }
588
589 foreach my $dir (@dirs) {
590 my $path = "$dir/$file";
591 return $path if( -f $path and -x _ );
592 }
593
594 return undef;
595}
596
5971;