Jonathan Nieder | 2d3ca21 | 2010-02-20 02:50:25 -0600 | [diff] [blame] | 1 | #!/usr/bin/perl |
Ævar Arnfjörð Bjarmason | d48b284 | 2010-09-24 20:00:52 +0000 | [diff] [blame] | 2 | use 5.008; |
Jonathan Nieder | 2d3ca21 | 2010-02-20 02:50:25 -0600 | [diff] [blame] | 3 | use strict; |
| 4 | use warnings; |
| 5 | use IO::Pty; |
| 6 | use File::Copy; |
| 7 | |
Paul Tan | 18d8c26 | 2015-08-04 22:08:49 +0800 | [diff] [blame] | 8 | # Run @$argv in the background with stdio redirected to $in, $out and $err. |
Jonathan Nieder | 2d3ca21 | 2010-02-20 02:50:25 -0600 | [diff] [blame] | 9 | sub start_child { |
Paul Tan | 18d8c26 | 2015-08-04 22:08:49 +0800 | [diff] [blame] | 10 | my ($argv, $in, $out, $err) = @_; |
Jonathan Nieder | 2d3ca21 | 2010-02-20 02:50:25 -0600 | [diff] [blame] | 11 | my $pid = fork; |
| 12 | if (not defined $pid) { |
| 13 | die "fork failed: $!" |
| 14 | } elsif ($pid == 0) { |
Paul Tan | 18d8c26 | 2015-08-04 22:08:49 +0800 | [diff] [blame] | 15 | open STDIN, "<&", $in; |
Jonathan Nieder | 2d3ca21 | 2010-02-20 02:50:25 -0600 | [diff] [blame] | 16 | open STDOUT, ">&", $out; |
Jeff King | e23f436 | 2010-10-17 02:36:57 +0800 | [diff] [blame] | 17 | open STDERR, ">&", $err; |
Paul Tan | 18d8c26 | 2015-08-04 22:08:49 +0800 | [diff] [blame] | 18 | close $in; |
Jonathan Nieder | 2d3ca21 | 2010-02-20 02:50:25 -0600 | [diff] [blame] | 19 | close $out; |
| 20 | exec(@$argv) or die "cannot exec '$argv->[0]': $!" |
| 21 | } |
| 22 | return $pid; |
| 23 | } |
| 24 | |
| 25 | # Wait for $pid to finish. |
| 26 | sub finish_child { |
| 27 | # Simplified from wait_or_whine() in run-command.c. |
| 28 | my ($pid) = @_; |
| 29 | |
| 30 | my $waiting = waitpid($pid, 0); |
| 31 | if ($waiting < 0) { |
| 32 | die "waitpid failed: $!"; |
| 33 | } elsif ($? & 127) { |
| 34 | my $code = $? & 127; |
| 35 | warn "died of signal $code"; |
Jeff King | 709ca73 | 2013-01-05 09:49:49 -0500 | [diff] [blame] | 36 | return $code + 128; |
Jonathan Nieder | 2d3ca21 | 2010-02-20 02:50:25 -0600 | [diff] [blame] | 37 | } else { |
| 38 | return $? >> 8; |
| 39 | } |
| 40 | } |
| 41 | |
| 42 | sub xsendfile { |
| 43 | my ($out, $in) = @_; |
| 44 | |
| 45 | # Note: the real sendfile() cannot read from a terminal. |
| 46 | |
| 47 | # It is unspecified by POSIX whether reads |
| 48 | # from a disconnected terminal will return |
| 49 | # EIO (as in AIX 4.x, IRIX, and Linux) or |
| 50 | # end-of-file. Either is fine. |
| 51 | copy($in, $out, 4096) or $!{EIO} or die "cannot copy from child: $!"; |
| 52 | } |
| 53 | |
Paul Tan | 18d8c26 | 2015-08-04 22:08:49 +0800 | [diff] [blame] | 54 | sub copy_stdin { |
| 55 | my ($in) = @_; |
| 56 | my $pid = fork; |
| 57 | if (!$pid) { |
| 58 | xsendfile($in, \*STDIN); |
| 59 | exit 0; |
| 60 | } |
| 61 | close($in); |
| 62 | return $pid; |
| 63 | } |
| 64 | |
Jeff King | e23f436 | 2010-10-17 02:36:57 +0800 | [diff] [blame] | 65 | sub copy_stdio { |
| 66 | my ($out, $err) = @_; |
| 67 | my $pid = fork; |
| 68 | defined $pid or die "fork failed: $!"; |
| 69 | if (!$pid) { |
| 70 | close($out); |
| 71 | xsendfile(\*STDERR, $err); |
| 72 | exit 0; |
| 73 | } |
| 74 | close($err); |
| 75 | xsendfile(\*STDOUT, $out); |
| 76 | finish_child($pid) == 0 |
| 77 | or exit 1; |
| 78 | } |
| 79 | |
Jonathan Nieder | 2d3ca21 | 2010-02-20 02:50:25 -0600 | [diff] [blame] | 80 | if ($#ARGV < 1) { |
| 81 | die "usage: test-terminal program args"; |
| 82 | } |
Jeff King | e433749 | 2017-10-03 09:39:34 -0400 | [diff] [blame] | 83 | $ENV{TERM} = 'vt100'; |
Johannes Schindelin | 659288c | 2020-09-21 22:01:23 +0000 | [diff] [blame] | 84 | my $parent_in = new IO::Pty; |
| 85 | my $parent_out = new IO::Pty; |
| 86 | my $parent_err = new IO::Pty; |
| 87 | $parent_in->set_raw(); |
| 88 | $parent_out->set_raw(); |
| 89 | $parent_err->set_raw(); |
| 90 | $parent_in->slave->set_raw(); |
| 91 | $parent_out->slave->set_raw(); |
| 92 | $parent_err->slave->set_raw(); |
| 93 | my $pid = start_child(\@ARGV, $parent_in->slave, $parent_out->slave, $parent_err->slave); |
| 94 | close $parent_in->slave; |
| 95 | close $parent_out->slave; |
| 96 | close $parent_err->slave; |
| 97 | my $in_pid = copy_stdin($parent_in); |
| 98 | copy_stdio($parent_out, $parent_err); |
Paul Tan | 18d8c26 | 2015-08-04 22:08:49 +0800 | [diff] [blame] | 99 | my $ret = finish_child($pid); |
| 100 | # If the child process terminates before our copy_stdin() process is able to |
Johannes Schindelin | 659288c | 2020-09-21 22:01:23 +0000 | [diff] [blame] | 101 | # write all of its data to $parent_in, the copy_stdin() process could stall. |
Paul Tan | 18d8c26 | 2015-08-04 22:08:49 +0800 | [diff] [blame] | 102 | # Send SIGTERM to it to ensure it terminates. |
| 103 | kill 'TERM', $in_pid; |
| 104 | finish_child($in_pid); |
| 105 | exit($ret); |